WPF Datagrid 无需 CTRL 或 Shift 进行多项选择

发布于 2024-08-17 11:15:08 字数 117 浏览 8 评论 0原文

WPF Datagrid 有两种选择模式:单一或扩展。 WPF ListView 有第三个 - Multiple。此模式允许您单击并选择多行,而无需按住 CTRL 或 Shift 键。有人知道如何为数据网格执行此操作吗?

The WPF Datagrid has two selection modes, Single or Extended. The WPF ListView has a third - Multiple. This mode allows you to click and select multiple rows without CTRL or Shift being held down. Anyone know how to do this for the datagrid?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

ゃ人海孤独症 2024-08-24 11:15:08

我正在创建一个具有类似要求的应用程序,该应用程序适用于触摸屏和桌面。花了一些时间后,我想出的解决方案似乎更干净。
在设计器中,我将以下事件设置器添加到数据网格中:

<DataGrid.RowStyle>
   <Style TargetType="DataGridRow" >
     <EventSetter Event="MouseEnter" Handler="MouseEnterHandler"></EventSetter>
     <EventSetter Event="PreviewMouseDown" Handler="PreviewMouseDownHandler"></EventSetter>
   </Style>
</DataGrid.RowStyle>

然后在代码隐藏中,我将事件处理为:

private void MouseEnterHandler(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed &&
        e.OriginalSource is DataGridRow row)
    {
        row.IsSelected = !row.IsSelected;
        e.Handled = true;
    }
}

private void PreviewMouseDownHandler(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed && 
        e.OriginalSource is FrameworkElement element &&
        GetVisualParentOfType<DataGridRow>(element) is DataGridRow row)
    {
        row.IsSelected = !row.IsSelected;
        e.Handled = true;
    }
}

private static DependencyObject GetVisualParentOfType<T>(DependencyObject startObject)
{
    DependencyObject parent = startObject;

    while (IsNotNullAndNotOfType<T>(parent))
    {
        parent = VisualTreeHelper.GetParent(parent);
    }

    return parent is T ? parent : throw new Exception($"Parent of type {typeof(T)} could not be found");
}

private static bool IsNotNullAndNotOfType<T>(DependencyObject obj)
{
    return obj != null && !(obj is T);
}

希望它也对其他人有帮助。

I was creating an application with a similar requirement that would work for both touchscreen and desktop. After spending some time on it, the solution I came up with seems cleaner.
In the designer, I added the following event setters to the datagrid:

<DataGrid.RowStyle>
   <Style TargetType="DataGridRow" >
     <EventSetter Event="MouseEnter" Handler="MouseEnterHandler"></EventSetter>
     <EventSetter Event="PreviewMouseDown" Handler="PreviewMouseDownHandler"></EventSetter>
   </Style>
</DataGrid.RowStyle>

Then in the codebehind, I handled the events as:

private void MouseEnterHandler(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed &&
        e.OriginalSource is DataGridRow row)
    {
        row.IsSelected = !row.IsSelected;
        e.Handled = true;
    }
}

private void PreviewMouseDownHandler(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed && 
        e.OriginalSource is FrameworkElement element &&
        GetVisualParentOfType<DataGridRow>(element) is DataGridRow row)
    {
        row.IsSelected = !row.IsSelected;
        e.Handled = true;
    }
}

private static DependencyObject GetVisualParentOfType<T>(DependencyObject startObject)
{
    DependencyObject parent = startObject;

    while (IsNotNullAndNotOfType<T>(parent))
    {
        parent = VisualTreeHelper.GetParent(parent);
    }

    return parent is T ? parent : throw new Exception($"Parent of type {typeof(T)} could not be found");
}

private static bool IsNotNullAndNotOfType<T>(DependencyObject obj)
{
    return obj != null && !(obj is T);
}

Hope it helps somebody else too.

清晨说晚安 2024-08-24 11:15:08

您可以尝试这个简单的解决方法,而无需通过处理预览鼠标按下事件来修改/继承 DataGrid 控件,如下所示:

TheDataGrid.PreviewMouseLeftButtonDown += 
                 new MouseButtonEventHandler(TheDataGrid_PreviewMouseLeftButtonDown);


void TheDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // get the DataGridRow at the clicked point
    var o = TryFindFromPoint<DataGridRow>(TheDataGrid, e.GetPosition(TheDataGrid));
    // only handle this when Ctrl or Shift not pressed 
    ModifierKeys mods = Keyboard.PrimaryDevice.Modifiers;
    if (o != null && ((int)(mods & ModifierKeys.Control) == 0 &&
                                                (int)(mods & ModifierKeys.Shift) == 0))
    {
        o.IsSelected = !o.IsSelected;
        e.Handled = true;
    }
}

public static T TryFindFromPoint<T>(UIElement reference, Point point)
                where T:DependencyObject
{
    DependencyObject element = reference.InputHitTest(point) as DependencyObject;
    if (element == null) 
        return null;
    else if (element is T) 
        return (T)element;
    else return TryFindParent<T>(element);
}

TryFindFromPoint 方法,来自 Philipp Sumi 的博客文章,用于获取 DataGridRow 实例您点击的点。

通过检查 ModifierKeys,您仍然可以将 Ctrl 和 Shift 保留为默认行为。

此方法的唯一一个缺点是您无法像原来那样通过单击并拖动来执行范围选择。

You can try this simple workaround without having to modifying/inheriting DataGrid control by handling preview mouse down event as follows:

TheDataGrid.PreviewMouseLeftButtonDown += 
                 new MouseButtonEventHandler(TheDataGrid_PreviewMouseLeftButtonDown);


void TheDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // get the DataGridRow at the clicked point
    var o = TryFindFromPoint<DataGridRow>(TheDataGrid, e.GetPosition(TheDataGrid));
    // only handle this when Ctrl or Shift not pressed 
    ModifierKeys mods = Keyboard.PrimaryDevice.Modifiers;
    if (o != null && ((int)(mods & ModifierKeys.Control) == 0 &&
                                                (int)(mods & ModifierKeys.Shift) == 0))
    {
        o.IsSelected = !o.IsSelected;
        e.Handled = true;
    }
}

public static T TryFindFromPoint<T>(UIElement reference, Point point)
                where T:DependencyObject
{
    DependencyObject element = reference.InputHitTest(point) as DependencyObject;
    if (element == null) 
        return null;
    else if (element is T) 
        return (T)element;
    else return TryFindParent<T>(element);
}

The TryFindFromPoint method, from a blog post by Philipp Sumi, is used to get the DataGridRow instance from point you clicked.

By checking ModifierKeys, you can still keep Ctrl and Shift as default behavior.

Only one draw back from this method is that you can't click and drag to perform range select like it can originally.

裂开嘴轻声笑有多痛 2024-08-24 11:15:08

工具包中的 DataGrid 不支持此功能,看起来像 也将不受支持。该控件尚未准备好用于生产使用的另一个原因。我会选择以下选项之一:

  1. 使用 ListView/GridView 滚动您自己的网格
  2. 修改工具包中 DataGrid 的源代码(这应该不会太难,因为已经支持扩展选择?)
  3. 寻找任何商业 WPF可用的 DataGrid(它们通常添加大量有用的功能)

我同意 DataGrid 应该支持这一点,并且我认为您应该 为此提交错误/建议。也许现在将其纳入 .NET 4 还为时不晚..:)

This is not supported in the DataGrid in the toolkit, and it looks like it won't be supported when the DataGrid is shipped with .NET 4 either. Yet another reason why this control is not ready for production use. I would go with one of these options:

  1. Roll your own grid with ListView/GridView
  2. Modify the source code of the DataGrid in the toolkit (it shouldn't be too hard since extended selection is already supported?)
  3. Look for any of the commercial WPF DataGrids available (they generally add huge amount of useful functionality)

I agree that the DataGrid should support this and I think you should file a bug/suggestion for this anyway. Maybe it's not too late to get it into .NET 4.. :)

他不在意 2024-08-24 11:15:08

基于上一篇文章,我编写了一个(“类似”)MVVM 代码:

首先将其添加到您的主视图中:

  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

视图的相关部分:

       <DataGrid
              Style="{StaticResource DataGridStyle}"
              ItemsSource="{Binding Results}"
              SelectionUnit="FullRow"
              SnapsToDevicePixels="True"
              SelectionMode="Extended">  <!--You can change selection mode with converter. It will work (i tested it.)-->
        <i:Interaction.Behaviors>  
                         <utils:EventToCommandBehavior Command="{Binding TouchCommand}"
                                                  Event="PreviewTouchDown"
                                                  PassArguments="True"></utils:EventToCommandBehavior> 
                        <utils:EventToCommandBehavior Command="{Binding MouseCommand}"
                                                  Event="PreviewMouseDown"
                                                  PassArguments="True"></utils:EventToCommandBehavior>
        </i:Interaction.Behaviors>
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="IsSelected"<Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background">
                            <Setter.Value>
                                <SolidColorBrush>
                                    <SolidColorBrush.Color>
                                        <Color A="50" R="0" G="0" B="0" />
                                    </SolidColorBrush.Color>
                                </SolidColorBrush>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </Style.Triggers>
            </Style> 
          </DataGrid.Resources>
        <DataGrid.Columns>
         <!-- your columns -->
        </DataGrid.Columns>
       </DataGrid>

有关 EventToCommandBehavior 的更多信息:
这里

这样,你的ViewModel必须实现这些命令:

    //i skipped the TouchCommand definition because MouseCommand runs for touch on screen too.
    public RelayCommand<MouseButtonEventArgs> MouseCommand
    {
        get
        {
            return new RelayCommand<MouseButtonEventArgs>((e)=> {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    //call this function from your utils/models
                    var row = FindTemplatedParentByVisualParent<DataGridRow>((FrameworkElement)e.OriginalSource,typeof(ICommandSource));
                    //add ICommanSource to parameters. (if actual cell contains button instead of data.) Its optional.
                    if(row!=null) 
                    {
                        row.IsSelected = !row.IsSelected;
                        e.Handled = true;
                    }  
                }                 
            });
        }
    }

最后实现一个方法(模型中的某个位置)查找行。

   public static T FindTemplatedParentByVisualParent<T>(FrameworkElement element,Type exceptionType = null) where T : class
    {
        if (element != null && (exceptionType == null || element.TemplatedParent == null || (exceptionType != null  && element.TemplatedParent !=null && !exceptionType.IsAssignableFrom(element.TemplatedParent.GetType()))))
        {
            Type type = typeof(T);
            if (type.IsInstanceOfType(element.TemplatedParent))
            {
                return (element.TemplatedParent as T);
            }
            else
            {
                return FindTemplatedParentByVisualParent<T>((FrameworkElement)VisualTreeHelper.GetParent(element));
            }
        }
        else
            return null;
    }

这个解决方案非常适合我,所以我希望它也对您有帮助。

Based on a previous article, i wrote a ("like") MVVM code:

Firstly add this to your main View:

  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

The relevant part of View:

       <DataGrid
              Style="{StaticResource DataGridStyle}"
              ItemsSource="{Binding Results}"
              SelectionUnit="FullRow"
              SnapsToDevicePixels="True"
              SelectionMode="Extended">  <!--You can change selection mode with converter. It will work (i tested it.)-->
        <i:Interaction.Behaviors>  
                         <utils:EventToCommandBehavior Command="{Binding TouchCommand}"
                                                  Event="PreviewTouchDown"
                                                  PassArguments="True"></utils:EventToCommandBehavior> 
                        <utils:EventToCommandBehavior Command="{Binding MouseCommand}"
                                                  Event="PreviewMouseDown"
                                                  PassArguments="True"></utils:EventToCommandBehavior>
        </i:Interaction.Behaviors>
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="IsSelected"<Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background">
                            <Setter.Value>
                                <SolidColorBrush>
                                    <SolidColorBrush.Color>
                                        <Color A="50" R="0" G="0" B="0" />
                                    </SolidColorBrush.Color>
                                </SolidColorBrush>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </Style.Triggers>
            </Style> 
          </DataGrid.Resources>
        <DataGrid.Columns>
         <!-- your columns -->
        </DataGrid.Columns>
       </DataGrid>

More information about EventToCommandBehavior:
here

In this way, your ViewModel must implement these commands:

    //i skipped the TouchCommand definition because MouseCommand runs for touch on screen too.
    public RelayCommand<MouseButtonEventArgs> MouseCommand
    {
        get
        {
            return new RelayCommand<MouseButtonEventArgs>((e)=> {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    //call this function from your utils/models
                    var row = FindTemplatedParentByVisualParent<DataGridRow>((FrameworkElement)e.OriginalSource,typeof(ICommandSource));
                    //add ICommanSource to parameters. (if actual cell contains button instead of data.) Its optional.
                    if(row!=null) 
                    {
                        row.IsSelected = !row.IsSelected;
                        e.Handled = true;
                    }  
                }                 
            });
        }
    }

Finally implement a method (somewhere in Model) to find the row(s).

   public static T FindTemplatedParentByVisualParent<T>(FrameworkElement element,Type exceptionType = null) where T : class
    {
        if (element != null && (exceptionType == null || element.TemplatedParent == null || (exceptionType != null  && element.TemplatedParent !=null && !exceptionType.IsAssignableFrom(element.TemplatedParent.GetType()))))
        {
            Type type = typeof(T);
            if (type.IsInstanceOfType(element.TemplatedParent))
            {
                return (element.TemplatedParent as T);
            }
            else
            {
                return FindTemplatedParentByVisualParent<T>((FrameworkElement)VisualTreeHelper.GetParent(element));
            }
        }
        else
            return null;
    }

This solution works for me perfectly so i hope it will help for you too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文