четверг, 25 октября 2012 г.

Обертка для привязки команд к произвольным событиям

При использовании паттерна MVVM часто возникает желание вызвать команду на событие, которое получает параметры необходимые в методе выполняемом командой. И если просто вызов команды сделать достаточно легко, я сегодня бы хотел продемонстрировать пример именно с передаче параметров. Собственно, как достаточно часто в последнее время, на написание этого топика меня подтолкнул вот этот вопрос на форумах MSDN.

 Итак, я буду делать все в максимально простом исполнении, чтобы не перегружать оишними подробностями.
В пустой проект, я добавил вот такой класс для реализации команды:

class MyCommand : ICommand
{
    private Action<DataGridCellInfo> _handler;

    public MyCommand(Action<DataGridCellInfo> p_handler)
    {
        _handler = p_handler;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        if (_handler != null)
        {
            _handler((DataGridCellInfo)parameter);
        }
    }
}

Вот такой ViewModel:

public class MyGridViewModel : DependencyObject
{
    public int[] Items
    {
        get { return (int[])GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }

    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register("Items", typeof(int[]), typeof(MyGridViewModel), new PropertyMetadata(null));

    public ICommand SelectedCellChangedCommand
    {
        get { return (ICommand)GetValue(SelectedCellChangedCommandProperty); }
        set { SetValue(SelectedCellChangedCommandProperty, value); }
    }

    public static readonly DependencyProperty SelectedCellChangedCommandProperty =
        DependencyProperty.Register("SelectedCellChangedCommand", typeof(ICommand), typeof(MyGridViewModel), new PropertyMetadata(null));

    public MyGridViewModel()
    {
        Items = new int[] { 1, 2, 3, 4, 5 };
        SelectedCellChangedCommand = new MyCommand(SelectedCellChanged);
    }

    private void SelectedCellChanged(DataGridCellInfo cell)
    {
        MessageBox.Show(cell.Item.ToString());
    }
}

Ну и главную форму размечаем вот так:

<Grid>
    <DataGrid ItemsSource="{Binding Items}" SelectionUnit="Cell">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding }" Header="Числа" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Ну и для присвоения ViewModel п=воспользуемся конструктором окна:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MyGridViewModel();
}

Вполне логично, что метод из ViewModel не вызывается, хотя числа мы уже видим. Итак, нам необходимо из XAML подписать команду из ViewModel на событие, причем так, чтобы в команду передавался параметр. Для этого добавим в проект вот такой класс:

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;
        }
    }
}

Ну и подключив локальное пространство имен, подключаем через присоединенное свойство команду:

<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>

Все, теперь при клике на ячейке мы будем видеть MessageBox со значением из этой ячейки.

3 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
  2. Почему вы не используете INotifyPropertyChanged?

    ОтветитьУдалить
    Ответы
    1. Вообще использую. Но у каждого подхода есть свои плюсы и минусы. Применение INotifyPropertyChanged требует меньше памяти, но работает медленнее чем DependencyObject. Ну и другие есть особенности применения.

      Удалить