WPF DataGrid中的单击编辑

100
我希望用户能够单击一次将单元格置于编辑模式并突出显示包含单元格的行。默认情况下,需要双击。
如何覆盖或实现这个功能?

你是否正在使用WPF Toolkit中的DataGrid? - myermian
4
请问您是否能提供更多关于您尝试过什么以及它为何无法正常工作的信息? - Zach Johnson
13个回答

83

这是我解决此问题的方法:

<DataGrid DataGridCell.Selected="DataGridCell_Selected" 
          ItemsSource="{Binding Source={StaticResource itemView}}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Nom" Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"/>
    </DataGrid.Columns>
</DataGrid>

这个DataGrid绑定了一个CollectionViewSource(包含虚拟的Person对象)。

实现方法为:DataGridCell.Selected="DataGridCell_Selected"

我只是简单地挂接DataGrid单元格的Selected事件,并在DataGrid上调用BeginEdit()。

以下是事件处理程序的代码:

private void DataGridCell_Selected(object sender, RoutedEventArgs e)
{
    // Lookup for the source to be DataGridCell
    if (e.OriginalSource.GetType() == typeof(DataGridCell))
    {
        // Starts the Edit on the row;
        DataGrid grd = (DataGrid)sender;
        grd.BeginEdit(e);
    }
}

8
将数据表(DataGrid)的SelectionUnit属性设置为Cell,就可以解决已选择行的问题。 - Matt Winckler
假设我在DataGridCell中有一个TextBox。 在调用grd.BeginEdit(e)之后,我希望该单元格中的TextBox获得焦点。 我该怎么做? 我尝试在DataGridCell和DataGrid上调用FindName(“txtBox”),但它对我返回null。 - user1214135
GotFocus="DataGrid_GotFocus" 似乎遗失了? - synergetic
4
这个方法运行良好,但我不建议这样做。我在我的项目中使用过它,但决定回滚到标准DG行为。将来,当您的DG变得更加复杂时,您可能会遇到验证、添加新行和其他奇怪的行为问题。 - white.zaz
1
@white.zaz,您在将回滚到标准DG行为后,客户是否感到满意?因为提出这个问题的主要原因是标准DG功能的编辑不够用户友好,需要点击多次才能进入编辑模式。 - AEMLoviji
显示剩余3条评论

45
Micael Bergeron的回答为我提供了一个良好的起点,以便找到适合我的解决方案。为了允许在已处于编辑模式的同一行中进行单击编辑,我不得不进行一些调整。对于我来说,使用SelectionUnit Cell不是一个选项。
我使用了DataGridCell.GotFocus事件,而不是仅在第一次单击行单元格时触发的DataGridCell.Selected事件。
<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

如果这样做,您将始终拥有正确的单元格聚焦并处于编辑模式,但是不会聚焦在单元格中的任何控件。 解决方法如下:
private void DataGrid_CellGotFocus(object sender, RoutedEventArgs e)
{
    // Lookup for the source to be DataGridCell
    if (e.OriginalSource.GetType() == typeof(DataGridCell))
    {
        // Starts the Edit on the row;
        DataGrid grd = (DataGrid)sender;
        grd.BeginEdit(e);

        Control control = GetFirstChildByType<Control>(e.OriginalSource as DataGridCell);
        if (control != null)
        {
            control.Focus();
        }
    }
}

private T GetFirstChildByType<T>(DependencyObject prop) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(prop); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild((prop), i) as DependencyObject;
        if (child == null)
            continue;

        T castedProp = child as T;
        if (castedProp != null)
            return castedProp;

        castedProp = GetFirstChildByType<T>(child);

        if (castedProp != null)
            return castedProp;
    }
    return null;
}

3
复选框似乎对我无效?我仍然必须双击它们。 - Thomas Klammer
这对我帮助很大。原始数据网格确实很难处理。 - ceds

9

来源:http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing

XAML:

<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type dg:DataGridCell}">
    <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
</Style>

代码后台:

//
// SINGLE CLICK EDITING
//
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    DataGridCell cell = sender as DataGridCell;
    if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
    {
        if (!cell.IsFocused)
        {
            cell.Focus();
        }
        DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
        if (dataGrid != null)
        {
            if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
            {
                if (!cell.IsSelected)
                    cell.IsSelected = true;
            }
            else
            {
                DataGridRow row = FindVisualParent<DataGridRow>(cell);
                if (row != null && !row.IsSelected)
                {
                    row.IsSelected = true;
                }
            }
        }
    }
}

static T FindVisualParent<T>(UIElement element) where T : UIElement
{
    UIElement parent = element;
    while (parent != null)
    {
        T correctlyTyped = parent as T;
        if (correctlyTyped != null)
        {
            return correctlyTyped;
        }

        parent = VisualTreeHelper.GetParent(parent) as UIElement;
    }

    return null;
}

1
这在某些情况下不起作用,而且比Micael Bergeron的解决方案更复杂。 - SwissCoder
对我来说,这几乎就是解决方案了。我需要添加一个“PreviewMouseLeftButtonUp”事件处理程序,并在其中放置完全相同的代码。 - Néstor Sánchez A.
一旦您有了ComboBox,这个方法就不再起作用了。预览点击会看到对ComboBox弹出窗口的点击,然后cell.focus调用会把一切都搞砸。最简单的解决方法是添加一个部分,查看鼠标事件的原始来源,使用FindVisualParent来查看它是否在DataGrid内部。如果不是,则不执行任何其他操作。 - John Gardner

7

来自http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing的解决方案对我非常有用,但我使用了在ResourceDictionary中定义的Style为每个DataGrid启用它。要在资源字典中使用处理程序,您需要向其中添加一个代码后台文件。以下是操作步骤:

这是一个名为DataGridStyles.xaml的资源字典:

    <ResourceDictionary x:Class="YourNamespace.DataGridStyles"
                x:ClassModifier="public"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Style TargetType="DataGrid">
            <!-- Your DataGrid style definition goes here -->

            <!-- Cell style -->
            <Setter Property="CellStyle">
                <Setter.Value>
                    <Style TargetType="DataGridCell">                    
                        <!-- Your DataGrid Cell style definition goes here -->
                        <!-- Single Click Editing -->
                        <EventSetter Event="PreviewMouseLeftButtonDown"
                                 Handler="DataGridCell_PreviewMouseLeftButtonDown" />
                    </Style>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>


请注意根元素中的x:Class属性。创建一个类文件,在这个例子中应该是DataGridStyles.xaml.cs。将以下代码放入其中:
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;

namespace YourNamespace
{
    partial class DataGridStyles : ResourceDictionary
    {

        public DataGridStyles()
        {
          InitializeComponent();
        }

     // The code from the myermian's answer goes here.
}

1
链接失效。 - Welcor

6

我通过添加一个触发器来解决了这个问题,当鼠标悬停在DataGridCell上时,它会将IsEditing属性设置为True。这解决了我的大部分问题,它也适用于组合框。

<Style TargetType="DataGridCell">
     <Style.Triggers>
         <Trigger Property="IsMouseOver" Value="True">
             <Setter Property="IsEditing" Value="True" />
         </Trigger>
     </Style.Triggers>
 </Style>

1
不起作用...一旦鼠标离开单元格,它就会丢失编辑。所以你需要:1)左键单击要编辑的单元格。2)将鼠标移开。3)开始输入。但由于单元格不再处于编辑模式,因此你的输入无效。 - Skarsnik
1
对我也不起作用。它阻止了我编辑文本框。 - Welcor
但是这种方法存在一个问题, 我已经将第1列锁定以进行编辑,使用这种方法会使第1列也变为可编辑! - Chandraprakash

5
我喜欢这种方式,基于Dušan Knežević的建议。你只需点击即可完成))
<DataGrid.Resources>

    <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
        <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsMouseOver"
                                   Value="True" />
                        <Condition Property="IsReadOnly"
                                   Value="False" />
                    </MultiTrigger.Conditions>
                    <MultiTrigger.Setters>
                        <Setter Property="IsEditing"
                                Value="True" />
                    </MultiTrigger.Setters>
                </MultiTrigger>
        </Style.Triggers>
    </Style>

</DataGrid.Resources>

如果使用组合框作为编辑模板,则此方法无效,我认为其他捕获鼠标事件的控件,如复选框,也会出现问题。 - Steve
对于我来说,这在组合框列中有效,但是“新项目行”的文本框(最后一行)有奇怪的行为:第一次单击时,我获得输入焦点并可以输入内容。当我将鼠标移出单元格时,文本框的值消失了。继续输入时,新输入的文本被正确保存(它创建了一个新条目,如所需)。即使使用ComboboxColumn,这种情况也会发生。 - FrankM
最初看起来工作正常,但完全搞乱了我的数据网格,当我尝试排序时,所有这些值都消失了,没有这段代码,排序一切正常。 - Chandraprakash
行验证也不再触发。 - hakamairi

3
在MVVM中,我希望能够通过单击编辑单元格来实现编辑,并且这是另一种实现方式。
  1. Adding behavior in xaml

    <UserControl xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:myBehavior="clr-namespace:My.Namespace.To.Behavior">
    
        <DataGrid>
            <i:Interaction.Behaviors>
                <myBehavior:EditCellOnSingleClickBehavior/>
            </i:Interaction.Behaviors>
        </DataGrid>
    </UserControl>
    
  2. The EditCellOnSingleClickBehavior class extend System.Windows.Interactivity.Behavior;

    public class EditCellOnSingleClick : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.LoadingRow += this.OnLoadingRow;
            this.AssociatedObject.UnloadingRow += this.OnUnloading;
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.LoadingRow -= this.OnLoadingRow;
            this.AssociatedObject.UnloadingRow -= this.OnUnloading;
        }
    
        private void OnLoadingRow(object sender, DataGridRowEventArgs e)
        {
            e.Row.GotFocus += this.OnGotFocus;
        }
    
        private void OnUnloading(object sender, DataGridRowEventArgs e)
        {
            e.Row.GotFocus -= this.OnGotFocus;
        }
    
        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            this.AssociatedObject.BeginEdit(e);
        }
    }
    
Voila!

这是一个非常适合我的用例的好解决方案,但我修改了 OnGotFocus 方法,否则,在单元格上点击 Enter 键会再次触发此方法而不是提交编辑:var row = sender as Row; if (!row.IsEditing) this.AssociatedObject.BeginEdit(e); - lavantgarde

2

这些答案中的几个启发了我,还有this blog post,但每个答案都有所欠缺。我将它们中最好的部分结合起来,想出了这个相当优雅的解决方案,似乎完美地实现了用户体验。

这使用了一些C# 9语法,在任何地方都可以正常工作,但您可能需要在项目文件中进行设置:

<LangVersion>9</LangVersion>

首先,我们可以通过以下方法将点击次数从3减少到2:

将以下类添加到您的项目中:

public static class WpfHelpers
{
    internal static void DataGridPreviewMouseLeftButtonDownEvent(object sender, RoutedEventArgs e)
    {
        // The original source for this was inspired by https://softwaremechanik.wordpress.com/2013/10/02/how-to-make-all-wpf-datagrid-cells-have-a-single-click-to-edit/
        DataGridCell? cell = e is MouseButtonEventArgs { OriginalSource: UIElement clickTarget } ? FindVisualParent<DataGridCell>(clickTarget) : null;
        if (cell is { IsEditing: false, IsReadOnly: false })
        {
            if (!cell.IsFocused)
            {
                cell.Focus();
            }

            if (FindVisualParent<DataGrid>(cell) is DataGrid dataGrid)
            {
                if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                {
                    if (!cell.IsSelected)
                    {
                        cell.IsSelected = true;
                    }
                }
                else
                {
                    if (FindVisualParent<DataGridRow>(cell) is DataGridRow { IsSelected: false } row)
                    {
                        row.IsSelected = true;
                    }
                }
            }
        }
    }

    internal static T? GetFirstChildByType<T>(DependencyObject prop)
        where T : DependencyObject
    {
        int count = VisualTreeHelper.GetChildrenCount(prop);
        for (int i = 0; i < count; i++)
        {
            if (VisualTreeHelper.GetChild(prop, i) is DependencyObject child)
            {
                T? typedChild = child as T ?? GetFirstChildByType<T>(child);
                if (typedChild is object)
                {
                    return typedChild;
                }
            }
        }

        return null;
    }

    private static T? FindVisualParent<T>(UIElement element)
        where T : UIElement
    {
        UIElement? parent = element;
        while (parent is object)
        {
            if (parent is T correctlyTyped)
            {
                return correctlyTyped;
            }

            parent = VisualTreeHelper.GetParent(parent) as UIElement;
        }

        return null;
    }
}

将以下内容添加到您的 App.xaml.cs 文件中:
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    EventManager.RegisterClassHandler(
        typeof(DataGrid),
        DataGrid.PreviewMouseLeftButtonDownEvent,
        new RoutedEventHandler(WpfHelpers.DataGridPreviewMouseLeftButtonDownEvent));
}

接着我们用以下代码从2到1:

将以下代码添加到包含DataGrid的页面的后台代码中:

private void TransactionDataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
    WpfHelpers.GetFirstChildByType<Control>(e.EditingElement)?.Focus();
}

并将其链接起来(在XAML中):PreparingCellForEdit="TransactionDataGrid_PreparingCellForEdit"


这种方法在遇到模板列之前运行得很好。但是它的行为会被破坏。在聚焦到另一行之前,它不会取消其他行上的高亮显示。不幸的是,我不够聪明,无法理解其中的原因。但我认为我已经缩小了范围,得出结论:这种方法在某种程度上干扰了DataGrid处理聚焦的方式。因为如果我不让它聚焦到模板列,那么它就可以正常工作。 - peanut
我几乎完全使用模板列,并且没有遇到您所描述的任何问题。 - Andrew Arnott
这很奇怪。我希望是我的操作有误。因为这个解决方案非常优雅。我创建了一个小项目来重新制造这个问题。我希望你能告诉我哪里出了问题。这是一个简单的DataGrid,有两列。一列是文本列,另一列是包含3个按钮的模板列。当程序启动时,请尝试将焦点放在第二行的文本列上,然后单击第一行的按钮。你会发现它会同时突出显示两行。 https://drive.google.com/file/d/1YLdK_Rq5hRrd-hv00AQivf2gyuysIOMH/view?usp=sharing - peanut
嗯...我从未在我的模板列中测试过 _按钮_。我下载了你的代码,看起来你已经按照我的步骤做了一切,我看到你的网格在第一列中表现得像预期一样。只是按钮会导致网格无法正常工作。:( - Andrew Arnott

2
我稍微修改了Dušan Knežević的解决方案。
<DataGrid.Resources>
   <Style x:Key="ddlStyle" TargetType="DataGridCell">
      <Style.Triggers>
         <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="IsEditing" Value="True" />
         </Trigger>
      </Style.Triggers>
    </Style>
</DataGrid.Resources>

并将样式应用于我想要的列

<DataGridComboBoxColumn CellStyle="{StaticResource ddlStyle}">

1

user2134678的答案存在两个问题。其中一个非常小,没有实际影响。另一个则比较重要。

第一个问题是在实践中,GotFocus实际上是针对DataGrid而不是DataGridCell进行调用的。XAML中的DataGridCell限定符是多余的。

我发现该答案的主要问题是Enter键的行为有问题。在正常的DataGrid行为中,按Enter应该将您移动到当前单元格下方的下一个单元格。然而,在幕后实际发生的是GotFocus事件将被调用两次。一次在当前单元格失去焦点时,另一次在新单元格获取焦点时。但只要在第一个单元格上调用BeginEdit,下一个单元格就永远不会被激活。结果是,您可以进行一键编辑,但任何没有真正点击网格的人都会感到不便,用户界面设计师不应该假设所有用户都在使用鼠标。(键盘用户可以通过使用Tab来解决这个问题,但仍然意味着他们需要跳过不必要的步骤。)

那么解决这个问题的方法是什么呢?为单元格处理KeyDown事件,如果按键是Enter键,则设置一个标志,阻止在第一个单元格上触发BeginEdit。现在,Enter键的行为就正常了。

首先,将以下样式添加到您的DataGrid中:
<DataGrid.Resources>
    <Style TargetType="{x:Type DataGridCell}" x:Key="SingleClickEditingCellStyle">
        <EventSetter Event="KeyDown" Handler="DataGridCell_KeyDown" />
    </Style>
</DataGrid.Resources>

将这种样式应用到“CellStyle”属性上,以启用单击的列。
然后,在代码后台中,您在GotFocus处理程序中有以下内容(请注意,我在此处使用VB,因为这是我们的“单击数据网格请求”客户端所需的开发语言):
Private _endEditing As Boolean = False

Private Sub DataGrid_GotFocus(ByVal sender As Object, ByVal e As RoutedEventArgs)
    If Me._endEditing Then
        Me._endEditing = False
        Return
    End If

    Dim cell = TryCast(e.OriginalSource, DataGridCell)

    If cell Is Nothing Then
        Return
    End If

    If cell.IsReadOnly Then
        Return
    End If

    DirectCast(sender, DataGrid).BeginEdit(e)
    .
    .
    .

然后您添加KeyDown事件的处理程序:

Private Sub DataGridCell_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    If e.Key = Key.Enter Then
        Me._endEditing = True
    End If
End Sub

现在你拥有了一个数据网格,它没有改变原始实现的任何基本行为,但支持单击编辑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接