【问题标题】:How to perform Single click checkbox selection in WPF DataGrid?如何在 WPF DataGrid 中执行单击复选框选择?
【发布时间】:2011-04-19 12:17:21
【问题描述】:

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

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

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

【问题讨论】:

标签: wpf datagrid wpfdatagrid


【解决方案1】:

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

几天前我遇到了同样的问题,并且遇到了一个令人惊讶的简短解决方案(请参阅this blog)。基本上,您需要做的就是将 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 逻辑给你的代码隐藏带来负担。

【讨论】:

  • 这类似于 Konstantin Salavatov 的答案,这对我有用。 +1 包括他没有的代码示例。感谢您对旧问题的良好回答。
  • 这样做的问题是,如果您使用组合框列进行操作,那么该列中的所有单元格都将始终可见小下拉按钮。不仅仅是当你点击单元格时。
  • 这对我来说仍然需要点击 2 次​​span>
  • 我想通了。您不能将此解决方案与某些 DataGrid 配置结合使用,例如 DataGridCell.Selected="DataGridCell_Selected" SelectionUnit="Cell"
【解决方案2】:

另一个简单的解决方案是将此样式添加到您的 DataGridColumn。样式的主体可以为空。

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

【讨论】:

  • 按空格键选中/取消选中会将CheckBox从左边移动到中间。在样式中添加 会阻止 CheckBox 移动。
  • 很好的解决方案,但它为什么有效?解释将不胜感激。它也适用于其他 DataGridXxxColumns 吗?
【解决方案3】:

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

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

【讨论】:

  • 哇——我很高兴我读到了最后。这完美地工作并且相当简单,IMO这应该被标记为答案。
  • 这也适用于 ComboBox。如:方式,比 DataGridComboBoxColumn 更好。
  • 当我使用空格键选中/取消选中和箭头移动到另一个单元格时,它不会。
  • 我已经将这个答案解释为您必须绑定“IsSelected”,但那不是真的!您可以将DataGridTemplateColumn.CellTemplate您自己的绑定一起使用b> 它会起作用的! @weidian-huang 的The answer 帮助我理解了这一点,谢谢!
【解决方案4】:

我解决了这个问题:

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

单击即可激活复选框!

【讨论】:

  • 我不需要将复选框包装在 ViewBox 中,但这个答案对我有用。
  • 这对我来说是一个比公认的答案更干净的解决方案。也不需要 Viewbox。有趣的是,这比定义的 Checkbox 列更有效。
【解决方案5】:

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

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

如果使用DataGridCheckBoxColumn实现,第一次点击对焦,第二次点击查看。

但是使用DataGridTemplateColumn来实现只需要一键。

使用DataGridComboboxBoxColumnDataGridTemplateColumn实现的区别也差不多。

【讨论】:

  • 对我有很好的解释,并立即工作,谢谢!
【解决方案6】:

基于 Jim Adorno 在他的帖子中的回答和 cmets,这是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>

【讨论】:

    【解决方案7】:
    <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
    

    【讨论】:

      【解决方案8】:

      要使Konstantin Salavatov's answerAutoGenerateColumns 一起工作,请使用以下代码将事件处理程序添加到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 的所有自动生成的复选框列都可以“单击”编辑。

      【讨论】:

      • 感谢您填写自动生成的列方法,这很容易为我指明合适的方向。
      【解决方案9】:

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

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

      当然可以针对特定列进一步调整...

      【讨论】:

      • 不错。我将其更改为 MultiTrigger 并为 ReadOnly=False 添加了一个条件,但基本方法适用于键盘导航不重要的简单情况。
      • 将该样式添加到我的网格会引发异常操作在使用 ItemsSource 时无效。改为使用 ItemsControl.ItemsSource 访问和修改元素。
      • 这是迄今为止我见过的最干净的方式!好的! (对于 IsReadOnly="True",MultiTrigger 将完成这项工作)
      • 此解决方案有一些意外/不需要的行为。见stackoverflow.com/q/39004317/2881450
      • 要使绑定生效,您需要 UpdateSourceTrigger=PropertyChanged
      【解决方案10】:

      基于 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 没有使用其他解决方案进行更新。
      • @surfen,如果我有很多页面中包含数据网格,我是否需要将上述样式和代码放在每个页面及其代码隐藏中。是否可以在其中使用样式和代码一个共同的地方,而不是在每个页面中创建它
      • 为什么需要 dispatch 一个空的 Action?
      • @user3690202 就像 Windows.Forms 中的 DoEvents。调用 BeginEdit 后,需要等待单元格真正进入编辑模式。
      • @JiříSkála - 我不记得在我的解决方案中需要这样做,但我明白你在说什么 - 谢谢!
      【解决方案11】:

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

      我创建了自己的 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 控件。如果它确实,那么我们会将焦点设置到该复选框并切换它的值

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

      令人失望的是,我们需要编写代码来做到这一点。第一次鼠标单击(在 DataGrid 的 CheckBox 上)被“忽略”,因为 WPF 使用它将行置于编辑模式中的解释听起来合乎逻辑,但在现实世界中,这与每个实际应用程序的工作方式背道而驰。

      如果用户在他们的屏幕上看到一个复选框,他们应该能够单击它一次以勾选/取消勾选它。故事结束。

      【讨论】:

      • 谢谢,我已经尝试了一堆“解决方案”,但这是第一个似乎每次都真正有效的方法。它非常适合我的应用程序架构。
      • 此解决方案会导致更新绑定出现问题,而此处的解决方案:wpf.codeplex.com/wikipage?title=Single-Click%20Editing 不会。
      • 太复杂了。看我的回答。 :)
      • 5 年后,这段代码仍然为社交生活节省了时间:) 对于一些简单的需求,@KonstantinSalavatov 解决方案就足够了。在我的情况下,我将我的代码与 Mike 的解决方案混合以实现与处理程序的动态事件关联,我的网格具有动态列数,单击特定单元格必须将更改存储在数据库中。
      猜你喜欢
      • 2017-08-10
      • 1970-01-01
      • 1970-01-01
      • 2018-08-03
      • 1970-01-01
      • 2021-12-16
      • 2011-09-21
      • 1970-01-01
      相关资源
      最近更新 更多