Лучший подход для создания нового окна в WPF с помощью MVVM


в соседнем сообщении: Как ViewModel должен закрыть форму? Я опубликовал свое видение, как закрыть окна с использованием MVVM. А теперь у меня вопрос: как их открыть.

у меня есть главное окно (главный вид). Если пользователь нажимает на кнопку "Показать", то должно отображаться окно" демо " (модальный диалог). Каков предпочтительный способ создания и открытия окон с использованием шаблона MVVM? Я вижу два общих подхода:

1-й (вероятно, самый простой). Обработчик событий "ShowButton_Click" должен быть реализован в коде позади главного окна следующим образом:

        private void ModifyButton_Click(object sender, RoutedEventArgs e)
        {
            ShowWindow wnd = new ShowWindow(anyKindOfData);
            bool? res = wnd.ShowDialog();
            if (res != null && res.Value)
            {
                //  ... store changes if neecssary
            }
        }
  1. если мы" показываем " состояние кнопки должно быть изменено (включено / отключено), нам нужно будет добавить логику, которая будет управлять состоянием кнопки;
  2. исходный код очень похож на" старые " WinForms и источники MFC-я не уверен, что это хорошо или плохо, пожалуйста, посоветуйте.
  3. что-то еще, что я пропустил?

другой подход:

в MainWindowViewModel мы реализуем свойство "ShowCommand", которое возвращает интерфейс ICommand команды. Запятая в свою очередь:

  • поднимет "ShowDialogEvent";
  • будет управлять состоянием кнопки.

этот подход будет более подходящим для MVVM, но потребует дополнительного кодирования: класс ViewModel не может "показывать диалог", поэтому MainWindowViewModel будет только вызывать "ShowDialogEvent", MainWindowView нам нужно будет добавить обработчик событий в своем методе MainWindow_Loaded, что-то вроде этого:

((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;

(ShowDialog-аналогично методу 'ModifyButton_Click'.)

Так что мои вопросы: 1. Вы видите другой подход? 2. Как вы думаете, один из перечисленных является хорошим или плохим? (зачем?)

любые другие мысли приветствуются.

спасибо.

6 54

6 ответов:

я тоже недавно думал об этом вопросе. Вот идея, которую я имел, если вы используете единство в вашем проекте как "контейнер" или что-то еще для инъекции зависимостей. Я думаю, обычно вы бы переопределить App.OnStartup() и создайте свою модель, просмотрите модель и просмотрите ее, и дайте каждому соответствующие ссылки. Используя Unity, вы даете контейнеру ссылку на модель, а затем используете контейнер для "разрешения" представления. Контейнер Unity вводит вашу модель представления, поэтому вы никогда не будете напрямую создайте его экземпляр. Как только ваше представление будет разрешено, вы позвоните Show() на нем.

в примере видео, которое я смотрел, контейнер Unity был создан как локальная переменная в OnStartup. Что делать, если вы создали его как общедоступное статическое свойство только для чтения в своем классе приложения? Затем вы можете использовать его в своей основной модели представления для создания новых окон, автоматически вводя любые ресурсы, необходимые для нового представления. Что-то вроде App.Container.Resolve<MyChildView>().ShowDialog();.

Я полагаю, вы могли бы как-то глумиться результат этого вызова контейнер единства в ваших тестах. В качестве альтернативы, возможно, вы могли бы написать такие методы, как ShowMyChildView() в класс App, который просто делает то, что я описал выше. Это может быть легко издеваться над вызовом App.ShowMyChildView() так как он просто вернет a bool?, да?

Ну, это может быть не лучше, чем просто использовать new MyChildView(), но это небольшая идея, которую я имел. Я думал, что поделюсь им. =)

некоторые фреймворки MVVM (например MVVM Light) воспользоваться шаблон медиатора. Поэтому, чтобы открыть новое окно (или создать какое-либо представление), некоторый код, специфичный для представления, будет подписываться на сообщения от посредника, и ViewModel будет отправлять эти сообщения.

такой:

Subsription

Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
     // Instantiate new view depending on the message details
}

В ViewModel

Messenger.Default.Send(new DialogMessage(...));

Я предпочитаю делать подписку в одноэлементном классе, который "живет" до тех пор, пока часть пользовательского интерфейса приложение делает. Подводя итог: ViewModel передает сообщения типа" мне нужно создать представление", а пользовательский интерфейс прослушивает эти сообщения и действует на них.

там нет "идеального" подхода, хотя, конечно.

Я немного опоздал, но я нахожу существующие ответы недостаточными. Я объясню, почему. В общем:

  • это нормально, чтобы получить доступ к ViewModels из вида,
  • это неправильно, чтобы получить доступ к представлениям из ViewModels, потому что он вводит циклическую зависимость и делает ViewModels трудно проверить.

ответ Бенни Джобигана:

App.Container.Resolve<MyChildView>().ShowDialog();

это на самом деле ничего не решает. Вы получаете доступ к своему представлению из ViewModel в a тигрово спаренная мода. Единственное отличие от new MyChildView().ShowDialog() заключается в том, что вы пошли через слой. Я не вижу никакого преимущества перед прямым вызовом mychildview ctor.

было бы чище, если бы вы использовали интерфейс для просмотра:

App.Container.Resolve<IMyChildView>().ShowDialog();`

теперь ViewModel не сильно связан с представлением. Однако я считаю совершенно непрактичным создавать интерфейс для каждого представления.

arconaut это:
Messenger.Default.Send(new DialogMessage(...));

лучше. Похоже Messenger или EventAggregator или другие шаблоны pub/sub являются универсальным решением для каждого из MVVM:) недостатком является то, что его сложнее отлаживать или переходить к DialogMessageHandler. Это слишком косвенное имхо. Например, как бы вы прочитали вывод из диалогового окна? путем изменения DialogMessage?

Мое Решение:

вы можете открыть окно из MainWindowViewModel следующим образом:

var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
   //you can read user input from childWindowViewModel
}

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

в моем случае я использую эту интерфейсы:

interface IDialogService
{
   void Show(IDialogViewModel dialog);
   void Close(IDialogViewModel dialog); 
   bool? ShowModal(IDialogViewModel dialog);
   MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}

interface IDialogViewModel 
{
    string Caption {get;}
    IEnumerable<DialogButton> Buttons {get;}
}

где DialogButton указывает DialogResult или ICommand или оба.

взгляните на мое текущее решение MVVM для отображения модальных диалогов в Silverlight. Он решает большинство проблем, о которых вы упомянули, но полностью абстрагируется от конкретных вещей платформы и может быть использован повторно. Также я не использовал код за пределами только привязки с DelegateCommands, которые реализуют ICommand. Диалог-это в основном представление-отдельный элемент управления, который имеет свою собственную ViewModel и отображается из ViewModel главного экрана, но запускается из пользовательского интерфейса через DelagateCommand обязательный.

Смотрите полное решение Silverlight 4 здесь модальные диалоги с MVVM и Silverlight 4

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

это выглядит примерно так:

class MainViewModel {
    public MainViewModel(IView view, IModel model, IController controller) {
       mModel = model;
       mController = controller;
       mView = view;
       view.DataContext = this;
    }

    public ICommand ShowCommand = new DelegateCommand(o=> {
                  mResult = controller.GetSomeData(mSomeData);
                                                      });
}

class Controller : IController {
    public void OpenMainView() {
        IView view = new MainView();
        new MainViewModel(view, somemodel, this);
    }

    public int GetSomeData(object anyKindOfData) {
      ShowWindow wnd = new ShowWindow(anyKindOfData);
      bool? res = wnd.ShowDialog();
      ...
    }
}

мой подход похож на adrianm В. Однако, в моем случае контроллер никогда не работает с конкретными типами представления. Контроллер полностью отделен от представления-так же, как и ViewModel.

Как это работает можно увидеть в Примере ViewModel WPF Application Framework (WAF).

.

С Уважением,

jbe