WPF Datagrid Multiple Selection without CTRL or Space

前端 未结 4 1766
遥遥无期
遥遥无期 2021-01-05 02:20

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

相关标签:
4条回答
  • 2021-01-05 02:35

    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.

    0 讨论(0)
  • 2021-01-05 02:43

    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.

    0 讨论(0)
  • 2021-01-05 02:54

    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.. :)

    0 讨论(0)
  • 2021-01-05 02:55

    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.

    0 讨论(0)
提交回复
热议问题