MVVM Madness: Команды

Мне нравится MVVM. Мне это не нравится, но нравится. Большая часть этого имеет смысл. Но я продолжаю читать статьи, которые помогут вам написать много кода, чтобы вы могли писать XAML и не нужно писать код в коде.

Позвольте мне привести пример.

Недавно мне захотелось подключить команду в моей ViewModel к ListView MouseDoubleClickEvent. Я не был уверен, как это сделать. К счастью, у Google есть ответы на все. Я нашел следующие статьи:

Хотя решения были полезны в моем понимании команд, были проблемы. Некоторые из вышеупомянутых решений сделали конструктор WPF непригодным из-за обычного взлома добавления "внутреннего" после свойства зависимости; дизайнер WPF не может найти его, но CLR может. Некоторые из решений не позволяли нескольким командам одному и тому же элементу управления. Некоторые из решений не позволяли параметры.

После нескольких экспериментов я решил сделать это:

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
    ListView lv = sender as ListView;
    MyViewModel vm = this.DataContext as MyViewModel;

    vm.DoSomethingCommand.Execute(lv.SelectedItem);
}

Итак, пуристы MVVM, пожалуйста, скажите мне, что с этим случилось? Я могу еще Unit test выполнить мою команду. Это кажется очень практичным, но, похоже, нарушает рекомендацию "ZOMG... у вас есть код в коде!" Пожалуйста, поделитесь своими мыслями.

Спасибо заранее.

Ответ 1

Я думаю, что ошибка заключается в требовании чистоты. Шаблоны проектирования, включая MVVM, являются инструментом в панели инструментов, а не самим собой. Если имеет смысл разобраться с чистотой модели для хорошо продуманного случая (и, похоже, это похоже на то, что вы рассмотрели этот случай), то разрыв с моделью.

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

(Я рассматриваю этот аргумент, аналогичный аргументам для языков многоадресных языков. В то время как подход Pure OO может применяться, иногда делать что-то более функциональным способом является более подходящим. В то время как подход Pure Functional может применяться, иногда компромиссы показывают, что методы OO более чем стоят того времени.)

Ответ 2

Я согласен с вами в том, что многие решения MVVM-Command слишком сложны. Лично я использую смешанный подход и определяю свои команды в представлении, а не в ViewModel, используя методы и свойства из ViewModel.

XAML:

<Window.Resources>
    <RoutedCommand x:Key="LookupAddressCommand" />
</Window.Resources>
<Window.CommandBindings>
    <CommandBinding Command="{StaticResource LookupAddressCommand}" x:Name="cmdLookupAddress" />
</Window.CommandBindings>

Код (Вид):

Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute
    e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2)
End Sub

Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed
    myViewModel.LookupAddress()
End Sub

Это не чистый MVVM, но он прост, он работает, ему не нужны специальные MVVM-командные классы, и он делает ваш код намного легче читать для не-MVVM-экспертов (= мои сотрудники).

Ответ 3

Хотя я предпочитаю не писать код при использовании шаблона MVVM, я думаю, что все в порядке, если этот код имеет чисто отношение к пользовательскому интерфейсу.

Но здесь это не так: вы вызываете команду view-model из кода-позади, так что это не чисто UI-связанное, и связь между представлением и командой view-model напрямую не очевидна в XAML.

Я думаю, вы могли бы легко сделать это в XAML, используя прикрепленное поведение команд. Таким образом, вы можете "привязать" событие MouseDoubleClick к команде вашей модели представления:

<ListView ItemSource="{Binding Items}">
   <local:CommandBehaviorCollection.Behaviors>
      <local:BehaviorBinding Event="MouseDoubleClick" Action="{Binding DoSomething}" />
   </local:CommandBehaviorCollection.Behaviors>

    ...
</ListView>

Вы также можете легко получить доступ к выбранному элементу ListView, не обращаясь к нему напрямую, используя интерфейс ICollectionView:

private ICommand _doSomething;

public ICommand DoSomething
{
    get
    {
        if (_doSomething == null)
        {
            _doSomething = new DelegateCommand(
                () =>
                {
                    ICollectionView view = CollectionViewSource.GetDefaultView(Items);
                    object selected = view.CurrentItem;
                    DoSomethingWithItem(selected);
                });
        }
        return _doSomething;
    }
}

Ответ 4

Я считаю, что цель "Без кода в коде" - это именно то, к чему стремиться - не то, что вы должны принять как абсолютную догму. В представлении есть соответствующие места для кода, и это не обязательно плохой пример того, где и как код может быть проще альтернативного подхода.

Преимущество других подходов, которые вы перечисляете, включая прикрепленные свойства или прикрепленные события, состоит в том, что они могут использоваться повторно. Когда вы подключаете событие напрямую, то делайте то, что вы сделали, очень легко закончить дублирование этого кода во всем приложении. Создав одно прикрепленное свойство или событие для обработки этой проводки, вы добавляете дополнительный код в сантехнику - но это код, который можно использовать повторно для любого ListView, где вы хотите обработать двойной щелчок.

Это, как говорится, я предпочитаю использовать более "пуристский" подход. Сохранение всей обработки событий из представления может не повлиять на сценарий тестирования (который вы конкретно адресуете), но это влияет на общую удобство и удобство обслуживания. Внедряя код в свой код позади, вы ограничиваете свой вид всегда с помощью ListView с проводником событий, который связан с вашим представлением в коде и ограничивает гибкость при перепроектировании дизайнером.

Ответ 5

Что @JP описывает в исходном вопросе, а @Heinzi упоминает, что ответ - это прагматичный подход к обработке сложных команд. Использование крошечного кода обработки событий в коде позади особенно удобно, когда вам нужно выполнить небольшую работу пользовательского интерфейса перед вызовом команды.

Рассмотрим классический случай OpenFileDialog. Гораздо проще использовать событие click на кнопке, отобразить диалоговое окно, а затем отправить результаты в команду на ViewModel, чем принять любую из сложных программ обмена сообщениями, используемых инструментами MVVM.

В вашем XAML:

<Button DockPanel.Dock="Left" Click="AttachFilesClicked">Attach files</Button>

В вашем коде позади:

    private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e)
    {
        // Configure open file dialog box
        Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
        dlg.FileName = "Document"; // Default file name
        dlg.DefaultExt = ".txt"; // Default file extension
        dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension

        // Show open file dialog box
        bool? result = dlg.ShowDialog();

        // Process open file dialog box results
        if (result == true)
        {
            string filename = dlg.FileName;

            // Invoke the command.
            MyViewModel myViewModel = (MyViewModel)DataContext;
            if (myViewModel .AttachFilesCommand.CanExecute(filename))
            {
                noteViewModel.AttachFilesCommand.Execute(filename);  
            }
        }
    }

Компьютерное программирование негибкое. Мы, программисты, должны быть гибкими, чтобы справляться с этим.

Ответ 6

Развязка - одна из главных особенностей MVVM. Если предположим, что вы хотите изменить его мнение или привязанную к нему модель. Насколько легко для вашего приложения?

Возьмем пример, когда View1 и View2 используют один и тот же ViewModel. Теперь вы будете внедрять метод кода для обоих.

Кроме того, предположим, что если вам нужно изменить viewmodel для представления на более позднем этапе, ваша команда будет терпеть неудачу по мере изменения модели представления и утверждения

MyViewModel vm = this.DataContext as MyViewModel;

возвращает null и, следовательно, сбой кода. Таким образом, возникает дополнительное бремя для изменения кода. Подобные сценарии возникнут, если вы сделаете это таким образом.

Конечно, есть много способов добиться того же самого в программировании, но лучший из них приведет к наилучшему подходу.

Ответ 7

Командование - это отбой. Реальные мужчины подключают все свои ui к событиям в коде.