none
WPF MVVM Datagrid Command получение строки или ячейки по клику на этой строке или ячейке грида RRS feed

  • Вопрос

  • Требуется сделать реализацию кликов мыши над гридом, используя MVVM, то есть через команды, чтобы обработчик клика во вьюмодели мог работать со строкой или ячейкой, на которых кликнули.

    Если использовать code-behind, то можно сделать просто:

    <DataGrid MouseLeftButtonUp="dg_MouseLeftButtonUp"/>

    ...и дальше в code-behind идет обработчик (private void dg_MouseRightButtonUp(object sender, MouseButtonEventArgs e)), который через дерево элементов грида находит все, что требуется.

    А если делать без code-behind, каким образом это реализовать? Попробовал сделать как тут описано http://kindelephant.blogspot.com/2011/03/mvvm_25.html

    Разметка:

                                    <DataGrid Name="dg">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="MouseLeftButtonDown">
                                                    <i:InvokeCommandAction Command="{Binding MouseCommand}" CommandParameter="{Binding SelectedItem, ElementName=dg}"/>
                                                </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </DataGrid>

    Вьюмодель:

            private DelegateCommand<object> _mouseCommand;
            public ICommand MouseCommand
            {
                get { return _mouseCommand ?? (_mouseCommand = new DelegateCommand<object>(MC)); }
            }
    
            private void MC(object e)
            {
            }

    Но работает не так, как мне надо: для срабатывания клика, приходится сначала выделить строку грида, а потом еще раз щелкнуть.

    Можно привязываться не к EventName="MouseLeftButtonDown", а к EventName="SelectionChanged", тогда поведение становится приемлемым, по клику на строке или ячейке грида срабатывает команда, но исчезает возможность использовать разные клавиши мыши.


    • Изменено Qwester33 21 октября 2012 г. 16:26
    21 октября 2012 г. 16:25

Ответы

  • Привет.

    Попробуйте использовать событие MouseLeftButtonUp вместо MouseLeftButtonDown. Это должно решить проблему.

    Также можно попробовать, если не получится, Preview событие.

    Подобный вопрос - DataGrid MouseLeftButtonDown question


    Для связи [mail]

    • Помечено в качестве ответа Qwester33 23 октября 2012 г. 12:16
    • Снята пометка об ответе Qwester33 23 октября 2012 г. 14:26
    • Помечено в качестве ответа Qwester33 26 октября 2012 г. 9:30
    23 октября 2012 г. 8:05
  • С SelectedCellsChanged это понятно, но каким образом сделать биндинг в xaml так, чтобы эта ячейка грида передавалась? Code-behind я не использую.

    Добавьте в проект вот такой вот класс:

    public sealed class DataGridBehavior
    {
        static void dataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            ICommand command = GetSelectedCellsChangedCommand(sender as DataGrid) as ICommand;
            if (command != null)
            {
                foreach (var item in e.AddedCells)
                {
                    command.Execute(item);
                }
            }
        }
    
        #region Attached Properties
    
        public static ICommand GetSelectedCellsChangedCommand(DataGrid obj)
        {
            return (ICommand)obj.GetValue(SelectedCellsChangedCommandProperty);
        }
    
        public static void SetSelectedCellsChangedCommand(DataGrid obj, ICommand value)
        {
            obj.SetValue(SelectedCellsChangedCommandProperty, value);
        }
    
        public static readonly DependencyProperty SelectedCellsChangedCommandProperty =
            DependencyProperty.RegisterAttached("SelectedCellsChangedCommand", typeof(ICommand), typeof(DataGridBehavior), new UIPropertyMetadata(null, OnSelectedCellsChangedCommandChanged));
        #endregion
    
        private static void OnSelectedCellsChangedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    	{
            if (e.OldValue != null)
            {
                DataGrid dg = d as DataGrid;
                dg.SelectedCellsChanged -= dataGrid_SelectedCellsChanged;
            }
            if (e.NewValue != null)
            {
                DataGrid dg = d as DataGrid;
                dg.SelectedCellsChanged += dataGrid_SelectedCellsChanged;
            }
        }
    }

    Теперь, через это присоединенное свойство, можно биндить команду, которая будет вызываться из ViewModel-а:

    <Window x:Class="CellSelectionExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:behavior="clr-namespace:CellSelectionExample"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <DataGrid ItemsSource="{Binding Items}" SelectionUnit="Cell" behavior:DataGridBehavior.SelectedCellsChangedCommand="{Binding SelectedCellChangedCommand}">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding }" Header="Числа" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    

    Весь пример, можно посмотреть здесь.
    • Помечено в качестве ответа Qwester33 26 октября 2012 г. 9:30
    25 октября 2012 г. 17:04
    Отвечающий

Все ответы

  • Привет.

    Попробуйте использовать событие MouseLeftButtonUp вместо MouseLeftButtonDown. Это должно решить проблему.

    Также можно попробовать, если не получится, Preview событие.

    Подобный вопрос - DataGrid MouseLeftButtonDown question


    Для связи [mail]

    • Помечено в качестве ответа Qwester33 23 октября 2012 г. 12:16
    • Снята пометка об ответе Qwester33 23 октября 2012 г. 14:26
    • Помечено в качестве ответа Qwester33 26 октября 2012 г. 9:30
    23 октября 2012 г. 8:05
  • Добрый день!

    ButtonUp решило большую часть проблем. Левый клик работает идеально, правый MouseRightButtonUp тоже работает, но немного криво.

    Проблема вот в чем: если кликать правым кликом по уже выделенной строке, то SelectedItem отсылается корректно (отсылается та строка, на которой кликнул), а вот если кликнуть правым кликом на другой строке, то параметром приходит та строка, которая до этого была выделена (причем неважно, выделена каким кликом, правым или левым, так как правый клик тоже выделяет строку).

    Таким образом, при правом клике не всегда приходит строка, на которой кликнули, а сначала приходит та строка, которая была выделена до этого, затем происходит выделение строки, на которой был произведен клик, и только при повторном клике на ней придет уже эта строка. PreviewMouseRightButtonUp и PreviewMouseRightButtonDown поведения не меняют.

    При левом клике такой проблемы нет, всегда параметром приходит та строка грида, на которой кликнули.

    Иначе говоря, левый клик как-будто сначала меняет выделенную строку, и потом отсылает событие нажатия с параметром в виде этой строки, а правый клик сначала отсылает, а потом меняет.

    1. Можно добиться, чтобы правый клик работал аналогично левому?

    2. И возник еще один вопрос. Если в настройках грида стоит

    <Setter Property="SelectionUnit" Value="FullRow"/>

    ...то при таком биндинге все работает ок:

                                            <i:Interaction.Triggers>
                                                <i:EventTrigger EventName="MouseLeftButtonUp">
                                                    <i:InvokeCommandAction Command="{Binding MouseLeftButtonUp}" CommandParameter="{Binding SelectedItem, ElementName=dg}"/>
                                                </i:EventTrigger>
                                            </i:Interaction.Triggers>

    Но если нужно добиться различного поведения, в зависимости от того, на каком Cell строки был сделан клик? SelectionUnit ставим в "Cell", выделение на гриде происходят ячейками, но вот вышеприведенный биндинг перестает корректно работать, параметр все время приходит null.


    • Изменено Qwester33 23 октября 2012 г. 14:26
    23 октября 2012 г. 12:17
  • При изменении на Cell, вам нужно обрабатывать событие SelectedCellsChanged. Если быстрое решение, то в соответствующем обработчике, через рефлекшен выдергиваете из DataContext команду по ее имени и вызываете ее передав в нее выделенную ячейку из e.AddedCells. Решение де юре не нарушает концепцию MVVM, т.к. прямых ссылок по прежнему нет, но поддерживать его станет труднее, т.к. биндинг будет не только в XAML, но и в методах обработчиках событий.

    23 октября 2012 г. 15:06
    Отвечающий
  • С SelectedCellsChanged это понятно, но каким образом сделать биндинг в xaml так, чтобы эта ячейка грида передавалась? Code-behind я не использую.

    25 октября 2012 г. 7:55
  • С SelectedCellsChanged это понятно, но каким образом сделать биндинг в xaml так, чтобы эта ячейка грида передавалась? Code-behind я не использую.

    Добавьте в проект вот такой вот класс:

    public sealed class DataGridBehavior
    {
        static void dataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            ICommand command = GetSelectedCellsChangedCommand(sender as DataGrid) as ICommand;
            if (command != null)
            {
                foreach (var item in e.AddedCells)
                {
                    command.Execute(item);
                }
            }
        }
    
        #region Attached Properties
    
        public static ICommand GetSelectedCellsChangedCommand(DataGrid obj)
        {
            return (ICommand)obj.GetValue(SelectedCellsChangedCommandProperty);
        }
    
        public static void SetSelectedCellsChangedCommand(DataGrid obj, ICommand value)
        {
            obj.SetValue(SelectedCellsChangedCommandProperty, value);
        }
    
        public static readonly DependencyProperty SelectedCellsChangedCommandProperty =
            DependencyProperty.RegisterAttached("SelectedCellsChangedCommand", typeof(ICommand), typeof(DataGridBehavior), new UIPropertyMetadata(null, OnSelectedCellsChangedCommandChanged));
        #endregion
    
        private static void OnSelectedCellsChangedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    	{
            if (e.OldValue != null)
            {
                DataGrid dg = d as DataGrid;
                dg.SelectedCellsChanged -= dataGrid_SelectedCellsChanged;
            }
            if (e.NewValue != null)
            {
                DataGrid dg = d as DataGrid;
                dg.SelectedCellsChanged += dataGrid_SelectedCellsChanged;
            }
        }
    }

    Теперь, через это присоединенное свойство, можно биндить команду, которая будет вызываться из ViewModel-а:

    <Window x:Class="CellSelectionExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:behavior="clr-namespace:CellSelectionExample"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <DataGrid ItemsSource="{Binding Items}" SelectionUnit="Cell" behavior:DataGridBehavior.SelectedCellsChangedCommand="{Binding SelectedCellChangedCommand}">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding }" Header="Числа" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    

    Весь пример, можно посмотреть здесь.
    • Помечено в качестве ответа Qwester33 26 октября 2012 г. 9:30
    25 октября 2012 г. 17:04
    Отвечающий