如何在WPF DataGrid中实现单击复选框选择?

161

我有一个DataGrid,第一列是文本列,第二列是复选框列。我的目标是,如果我点击复选框,它应该被选中。

但是,需要点击两次才能选中,在第一次单击时,单元格会被选中,第二次单击时复选框才会被选中。如何使复选框在一次单击时被勾选/取消勾选。

我使用的是WPF 4.0。 DataGrid中的列是自动生成的。


4
重复:https://dev59.com/AnM_5IYBdhLWcg3wymc0,但这个标题更好。 - surfen
12个回答

226

对于单击DataGrid复选框,您可以在DataGridTemplateColumn中放置常规复选框控件,并设置UpdateSourceTrigger=PropertyChanged

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

7
太棒了——我很高兴读到最后。这个方法非常有效,而且比较简单,我认为应该将其标记为答案。 - Tod
4
这个方法同样适用于ComboBox。就像这样:比DataGridComboBoxColumn好得多。 - user1454265
3
当我使用空格键来勾选/取消勾选并使用箭头移动到另一个单元格时,它没有反应。 - Yola
3
我理解这个答案是说你需要绑定"IsSelected",但这是不正确的!你只需使用DataGridTemplateColumn.CellTemplate你自己的绑定就可以了!@weidian-huang的回答帮助我理解了这一点,谢谢! - Patrick

69

我用如下样式解决了这个问题:

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

当然可以根据特定的列进一步进行调整...


10
好的,我把它改为了MultiTrigger并添加了一个条件,以便ReadOnly=False,但是基本方法适用于我的简单情况,其中键盘导航并不重要。 - MarcE
我已经解决了这个问题,使用DataGridRow上的触发器而不是单元格,并在鼠标悬停时强制将IsSelected设置为true。这可能不是最佳方法,但它能够正常工作。 - Alkampfer
1
这是我目前看到的最干净的方式!很好!(对于IsReadOnly="True",MultiTrigger可以胜任) - FooBarTheLittle
2
这个解决方案存在一些意外/不希望的行为。请参见http://stackoverflow.com/q/39004317/2881450 - jHilscher
2
为了使绑定起作用,您需要一个UpdateSourceTrigger=PropertyChanged。 - AQuirky
显示剩余2条评论

34

首先,我知道这是一个相当古老的问题,但我仍然想尝试回答它。

几天前我也遇到了同样的问题,并找到了一个出人意料的简短解决方案(请参见此博客)。基本上,你只需要将 XAML 中的 DataGridCheckBoxColumn 定义替换为以下内容:

<DataGridTemplateColumn Header="MyCheckBoxColumnHeader">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=MyViewModelProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
这种解决方案的好处是显而易见的 - 它仅使用XAML,因此有效地避免了将额外的UI逻辑加到您的代码后面所带来的负担。

1
这与Konstantin Salavatov的答案相似,对我有用。 +1包括代码示例,而他没有。 感谢您对一个老问题的好回答。 - Don Herod
1
问题在于,如果您将其应用于组合框列,则小的下拉按钮将始终对该列中的所有单元格可见,而不仅仅是在单击单元格时。 - user3690202
这对我来说仍然需要点击两次。 - Gillespie
我已经弄清楚了。您不能将此解决方案与某些DataGrid配置一起使用,例如DataGridCell.Selected="DataGridCell_Selected" SelectionUnit="Cell" - Gillespie

23

要使Konstantin Salavatov的答案AutoGenerateColumns一起使用,需要向DataGridAutoGeneratingColumn添加事件处理程序,并使用以下代码:

if (e.Column is DataGridCheckBoxColumn && !e.Column.IsReadOnly)
{
    var checkboxFactory = new FrameworkElementFactory(typeof(CheckBox));
    checkboxFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center);
    checkboxFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
    checkboxFactory.SetBinding(ToggleButton.IsCheckedProperty, new Binding(e.PropertyName) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

    e.Column = new DataGridTemplateColumn
        {
            Header = e.Column.Header,
            CellTemplate = new DataTemplate { VisualTree = checkboxFactory },
            SortMemberPath = e.Column.SortMemberPath
        };
}

这会使所有DataGrid自动生成的复选框列可通过"单击"进行编辑。


3
感谢您填写自动生成的列方法,这使我方便地指向了一个合适的方向。 - el2iot2
谢谢您!在.NET 7中,“ToggleButton.IsCheckedProperty”不可用,所以我将其更改为“CheckBox.IsCheckedProperty”。 - Jeson Martajaya

18

根据Goblin的回答所引用的博客,但修改后可在.NET 4.0中运行并带有行选择模式。

请注意,它还可以加快DataGridComboBoxColumn的编辑速度-通过单击或文本输入进入编辑模式并显示下拉列表。

XAML:

        <Style TargetType="{x:Type DataGridCell}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
            <EventSetter Event="PreviewTextInput" Handler="DataGridCell_PreviewTextInput" />
        </Style>

代码后台:

    private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private void DataGridCell_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
    {
        if (cell == null || cell.IsEditing || cell.IsReadOnly)
            return;

        DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
        if (dataGrid == null)
            return;

        if (!cell.IsFocused)
        {
            cell.Focus();
        }

        if (cell.Content is CheckBox)
        {
            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;
                }
            }
        }
        else
        {
            ComboBox cb = cell.Content as ComboBox;
            if (cb != null)
            {
                //DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
                dataGrid.BeginEdit(e);
                cell.Dispatcher.Invoke(
                 DispatcherPriority.Background,
                 new Action(delegate { }));
                cb.IsDropDownOpen = true;
            }
        }
    }


    private 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;
    }

这个解决方案对我来说效果最好。我的绑定 ViewModel 没有更新其他解决方案。 - BrokeMyLegBiking
@surfen,如果我有许多包含数据网格的页面,我需要在每个页面及其代码后台中放置上述样式和代码吗?是否可以在一个公共位置使用样式和代码,而不是在每个页面中创建它? - Angel
@user3690202 这就像在Windows.Forms中的DoEvents方法一样。在调用BeginEdit后,你需要等待单元格实际进入编辑模式。 - Jiří Skála
@JiříSkála - 我不记得在解决这个问题的过程中需要这样做,但我理解你的意思 - 谢谢! - user3690202
这对于复选框非常有效,但会干扰我的DataGrid_OnSelectedCellsChanged()处理程序...当单击组合框时,处理程序不会被调用,但当第二次单击时会被调用。 - Number8
显示剩余2条评论

11

根据Jim Adorno在他的帖子中的回答和评论,这是使用MultiTrigger的解决方案:


<Style TargetType="DataGridCell">
  <Style.Triggers>
    <MultiTrigger>
      <MultiTrigger.Conditions>
    <Condition Property="IsReadOnly" Value="False" />
    <Condition Property="IsMouseOver" Value="True" />
      </MultiTrigger.Conditions>
      <Setter Property="IsEditing" Value="True" />
    </MultiTrigger>
  </Style.Triggers>
</Style>

11

这里有一个简单得多的解决方案。

          <DataGridTemplateColumn MinWidth="20" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Grid>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

如果使用DataGridCheckBoxColumn实现,第一次点击是聚焦,第二次点击是选中。

但是使用DataGridTemplateColumn实现只需要一次点击。

使用DataGridComboboxBoxColumn或者使用DataGridTemplateColumn实现的区别也类似。


很好的解释,对我非常有帮助,立即生效,谢谢! - Patrick
使用这个解决方案后,按空格键无法再进行勾选/取消勾选操作。 - Simon Mourier

10

我尝试过这些建议,以及其他网站上的许多建议,但它们都没有完全适用于我。最终,我创建了以下解决方案。

我创建了自己的 DataGrid 继承控件,并简单地将以下代码添加到其中:

public class DataGridWithNavigation : Microsoft.Windows.Controls.DataGrid
{
    public DataGridWithNavigation()
    {
        EventManager.RegisterClassHandler(typeof(DataGridCell), 
            DataGridCell.PreviewMouseLeftButtonDownEvent,
            new RoutedEventHandler(this.OnPreviewMouseLeftButtonDown));
    }


    private void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
        {
          DependencyObject obj = FindFirstControlInChildren(cell, "CheckBox");
            if (obj != null)
            {
                System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)obj;
                cb.Focus();
                cb.IsChecked = !cb.IsChecked;
            }
        }
    }

    public DependencyObject FindFirstControlInChildren(DependencyObject obj, string controlType)
    {
        if (obj == null)
            return null;

        // Get a list of all occurrences of a particular type of control (eg "CheckBox") 
        IEnumerable<DependencyObject> ctrls = FindInVisualTreeDown(obj, controlType);
        if (ctrls.Count() == 0)
            return null;

        return ctrls.First();
    }

    public IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, string type)
    {
        if (obj != null)
        {
            if (obj.GetType().ToString().EndsWith(type))
            {
                yield return obj;
            }

            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
                {
                    if (child != null)
                    {
                        yield return child;
                    }
                }
            }
        }
        yield break;
    }
}

这段代码的作用是什么?

好的,每次我们在DataGrid中点击任何单元格时,我们都会查看该单元格是否包含一个复选框控件。如果有,则将焦点设置为该复选框,并切换其值。

这对我来说似乎很有效,并且是一个不错、易于重用的解决方案。

但是,需要编写代码来实现这一点确实令人失望。虽然第一次鼠标单击(在 DataGrid 的复选框上)被忽略是因为 WPF 使用它来将行放入编辑模式的解释听起来很合理,但在现实世界中,这与每个真实应用程序的工作方式相违背。

如果用户在屏幕上看到一个复选框,他们应该可以单击一次来选择/取消选择它。就这样。


1
谢谢,我尝试了很多“解决方案”,但这是第一个似乎每次都真正有效的。而且它完美地融入了我的应用程序架构中。 - Guge
这个解决方案会导致更新绑定时出现问题,而这个链接中的解决方案:http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing则不会。 - Justin Simon
2
太复杂了。看我的回答。 :) - Konstantin Salavatov
1
五年过去了,这段代码仍然为社交生活节省时间 :) 对于一些简单的需求,@KonstantinSalavatov的解决方案已经足够了。在我的情况下,我将我的代码与Mike的解决方案混合使用,以实现动态事件关联处理程序,我的网格具有动态列数,在特定单元格中单击必须将更改存储在数据库中。 - Fer R

10
另一个简单的解决方案是将此样式添加到您的DataGridColumn中。您样式的主体可以为空。
<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>

3
按下空格键来勾选或取消勾选将会使CheckBox从左侧移动到中间。在样式里添加<Setter Property="HorizontalAlignment" Value="Center"/> 将会防止CheckBox移动。 - YantingChen
2
很棒的解决方案,但是为什么它能够工作?希望能够给出解释。这个方法也适用于其他DataGridXxxColumns吗? - Peter Huber

8
我用以下方法解决了这个问题:
<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Viewbox Height="25">
                <CheckBox IsChecked="{Binding TheProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </Viewbox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

单击复选框即可激活!

2
我不需要将复选框包装在ViewBox中,但这个答案对我有用。 - JGeerWM
3
对我来说,这比被接受的答案更为简洁。也无需使用Viewbox。有趣的是,这比已定义的复选框列更有效。 - kenjara

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