Лучший подход для создания нового окна в WPF с использованием MVVM - PullRequest
55 голосов
/ 21 января 2010

В соседнем посте: Как 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 ]

17 голосов
/ 28 февраля 2010

В некоторых средах MVVM (например, MVVM Light ) используется шаблон Mediator . Таким образом, чтобы открыть новое окно (или создать любое представление), некоторый специфичный для представления код будет подписываться на сообщения от посредника, и 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(...));

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

Хотя «идеального» подхода не существует, конечно.

16 голосов
/ 18 февраля 2010

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

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

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

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

4 голосов
/ 01 июля 2016

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

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

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

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

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

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

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

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

Ответ арконавта:

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 использует только ViewModel диалогового окна, поэтому ваши view-модели полностью независимы от Views. Во время выполнения 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 или оба.

2 голосов
/ 22 января 2010

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

См. Полное решение Silverlight 4 здесь Модальные диалоги с MVVM и Silverlight 4

1 голос
/ 21 января 2010

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

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

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();
      ...
    }
}
0 голосов
/ 26 января 2010

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

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

.

С наилучшими пожеланиями,

jbe

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...