如何在 WPF DataGrid 中执行单击复选框选择?

我有一个 DataGrid,第一列作为文本列,第二列作为 CheckBox 列。我想要的是,如果我单击复选框。应该检查一下。

但是,需要两次单击才能被选中,因为第一次单击单元格将被选中,第二次单击复选框将被选中。如何通过单击使复选框选中/取消选中。

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

86977 次浏览

我尝试过这些建议,也在其他网站上找到了很多其他的建议,但没有一个对我有效。最后,我创建了以下解决方案。

我已经创建了自己的 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 中的任何单元格时,都会看到该单元格中是否包含一个 CheckBox 控件。如果是 是的,那么我们将焦点设置为 CheckBox然后切换它的值

这似乎对我有用,而且是一个很好的、易于重用的解决方案。

令人失望的是,我们 需要编写代码这样做虽然。关于第一次鼠标点击(在 DataGrid 的 CheckBox 上)被“忽略”,因为 WPF 使用它将行放入编辑模式的解释可能听起来合乎逻辑,但在现实世界中,这违背了每个真正的应用程序的工作方式。

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

我用以下方式解决了这个问题:

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

当然,我们可以针对特定的栏目进一步修改... ..。

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

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

基于 Goblin 的回答中引用的博客,但是修改后可以在.NET 4.0和 Row-Selection 模式下工作。

注意,它还加速了 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;
}

要使 康斯坦丁 · 萨拉瓦托夫的回答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的所有自动生成的复选框列都是“单击”可编辑的。

<Style x:Key="StilCelula" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource CheckBoxColumnToEditingConvertor}}" />
</Trigger>
</Style.Triggers>
<Style>
Imports System.Globalization
Public Class CheckBoxColumnToEditingConvertor
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
Try


Return TypeOf TryCast(value, DataGridCell).Column Is DataGridCheckBoxColumn
Catch ex As Exception
Return Visibility.Collapsed
End Try
End Function


Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class

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

几天前我遇到了同样的问题,并且遇到了一个令人惊讶的简短的解决方案(参见 这个博客)。基本上,您所需要做的就是将 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 逻辑加重代码隐藏的负担。

基于 吉姆 · 阿多诺的回答和对他的帖子的评论,这是 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>

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

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

如果您使用 DataGridCheckBoxColumn来实现,第一次单击是为了焦点,第二次单击是为了检查。

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

使用 DataGridComboboxBoxColumn和由 DataGridTemplateColumn实现的区别也是相似的。

我用这个解决了问题:

<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Viewbox Height="25">
<CheckBox IsChecked="{Binding TheProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Viewbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

单击激活的复选框!

另一个简单的解决方案是将此样式添加到 DataGridColumn 中。

<DataGridCheckBoxColumn>
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>

这里有一种具有自己的列类的方法,该类基于默认的 DataGridCheckBoxColumn 类,可以像普通类一样使用。只是复制/粘贴。

public class DataGridCheckBoxColumn : System.Windows.Controls.DataGridCheckBoxColumn
{
private static Style _noFocusEditElementStyle;


static DataGridCheckBoxColumn()
{
ElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
EditingElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
}




protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);


if (e.Property.Name == nameof(IsReadOnly))
{
ElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
EditingElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
}
}


public static Style NoFocusEditElementStyle
{
get
{
if (_noFocusEditElementStyle == null)
{
Style style = new Style(typeof(System.Windows.Controls.CheckBox));


// When not in edit mode, the end-user should not be able to toggle the state
style.Setters.Add(new Setter(UIElement.FocusableProperty, false));
style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center));
style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.VerticalAlignmentProperty, VerticalAlignment.Top));


style.Seal();
_noFocusEditElementStyle = style;
}


return _noFocusEditElementStyle;
}
}
}

读/写属性的用法:

<myNamespace:DataGridCheckBoxColumn Header="Name"
Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

使用 ReadOnly 属性:

<myNamespace:DataGridCheckBoxColumn Header="Name"
IsReadOnly="True"
Binding="{Binding Name, Mode=OneWay}" />

说明:

  • 默认列类有两个样式属性,一个用于编辑模式,一个用于视图。
  • 对于 ReadOnly,我们在两种情况下都使用视图样式
  • 在编辑模式的另一种情况下,我们设置编辑样式
  • 与 EditElementStyle 不同,我更喜欢复选框没有获得焦点的样式(NoFocusEditElementStyle) ,因为这种行为看起来有点奇怪