Одна кнопка на два ListView — как сделать, чтобы кнопка действовала на активном списке?

Рейтинг: 2Ответов: 2Опубликовано: 16.04.2015

По шаблону MVVM в WPF есть 2 ListView для реализации схемы Master/Detail. Также есть панель с кнопками, управляющими добавлением, удалением и т.д. элементов в эти списки.

Вопрос: как можно узнавать о том, в каком из ListView пользователь был, когда нажал кнопку на панели кнопок? Хотелось бы иметь по одной кнопке на каждую операцию редактирования элементов в этих списках. Т.е. что бы кнопка "знала" для какого ListView ее нажали.

Ответы

▲ 5Принят

Можно сделать таким образом:

  1. Заведите во вью-модели свойство, определяющее текущий активный контрол. Это должна быть не ссылка на контрол из вью, а, например, перечисление или целочисленный индекс.

  2. Реагируйте на событие GotFocus в списках установкой соответствующего значения этого свойства.

  3. В обработчиках событий нажатия на кнопки сверяйтесь со значением этого свойства.

Ниже простой пример, использующий System.Windows.Interactivity.* из Blend (NuGet: Exression.Blend.Sdk) и GalaSoft.MvvmLight.CommandWpf.RelayCommand из MVVM Light (NuGet: MvvmLight). Если вы пользуетесь более мощной библиотекой, например, Caliburn.Micro (NuGet: Caliburn.Micro), то писанины будет заметно меньше.

MainWindow.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using GalaSoft.MvvmLight.CommandWpf;

namespace Sor416977WpfActiveList
{
    public enum MasterDetail
    {
        Master,
        Detail,
    }

    public class ShellModel
    {
        public ObservableCollection<string> ItemsMaster { get; private set; }
        public ObservableCollection<string> ItemsDetail { get; private set; }
        public MasterDetail CurrentList { get; private set; }

        public ICommand SetCurrentListCommand { get; private set; }
        public ICommand FooCommand { get; private set; }

        public ShellModel ()
        {
            ItemsMaster = new ObservableCollection<string> { "M1", "M2" };
            ItemsDetail = new ObservableCollection<string> { "D1", "D2" };
            SetCurrentListCommand = new RelayCommand<MasterDetail>(SetCurrentListExecute);
            FooCommand = new RelayCommand(FooExecute);
        }

        private void SetCurrentListExecute (MasterDetail currentList)
        {
            CurrentList = currentList;
        }

        private void FooExecute ()
        {
            MessageBox.Show("Executing Foo on " + CurrentList);
        }
    }

    public partial class MainWindow
    {
        public MainWindow ()
        {
            DataContext = new ShellModel();
            InitializeComponent();
        }
    }
}

MainWindow.xaml

<Window x:Class="Sor416977WpfActiveList.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sor416977WpfActiveList"
        Width="400" Height="200"
        d:DataContext="{d:DesignInstance local:ShellModel}">
    <DockPanel LastChildFill="True">
        <Button Content="Foo" Command="{Binding FooCommand}" DockPanel.Dock="Bottom"/>
        <ListBox ItemsSource="{Binding ItemsMaster}" DockPanel.Dock="Left" Width="200">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="GotFocus">
                    <i:InvokeCommandAction Command="{Binding SetCurrentListCommand}"
                            CommandParameter="{x:Static local:MasterDetail.Master}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListBox>
        <ListBox ItemsSource="{Binding ItemsDetail}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="GotFocus">
                    <i:InvokeCommandAction Command="{Binding SetCurrentListCommand}"
                            CommandParameter="{x:Static local:MasterDetail.Detail}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListBox>
    </DockPanel>
</Window>
▲ 2

Я бы реализовал это как CompositeCommand (например, из Prism, или легко самому написать).

При таком подходе каждая из VM, соответствующих спискам, выставляет наружу ICommand, соответствующую добавлению элемента. CompositeCommand ссылается на эти команды, переключаясь между ними через ShouldExecute (VM знает, какой из списков активен!).

Теперь кнопку можно забиндить на эту самую CompositeCommand.


Пример:

addCommand = new CompositeCommand(c =>
    {
        var activeList = DocumentVM.ActiveListVM;
        return activeList != null && c == activeList.AddCommand;
    });

и при создании нового ActiveListVM нужно зарегистртировать его:

addCommand.RegisterCommand(list.SaveCommand);

Здесь создаётся команда, которая перенаправляет запросы к команде AddCommand активного списка.

Если вы не используете Prism, вот вам код CompositeCommand: http://pastebin.com/kbCuA1Cz