Подтвердить что ты не робот

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

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

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

Первый (возможно, самый простой). Обработчик событий "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
            }
        }
  • Если состояние кнопки "Показать" кнопки должно быть изменено (включено/отключено), нам нужно будет добавить логику, которая будет управлять состоянием кнопки;
  • Исходный код очень похож на "старые" WinForms и источники MFC - я не уверен, что это хорошо или плохо, пожалуйста, сообщите нам.
  • Что-то еще, что я пропустил?

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

В MainWindowViewModel мы реализуем свойство ShowCommand, которое вернет интерфейс ICommand команды. Comman в свою очередь:

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

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

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

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

Итак, мои вопросы:  1. Вы видите какой-либо другой подход?  2. Считаете ли вы, что один из перечисленных хорош или плох? (Почему?)

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

Спасибо.

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Некоторые рамки 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 передает сообщения типа "Мне нужно создать представление", и пользовательский интерфейс прослушивает эти сообщения и действует на них.

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

Ответ 3

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

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

Ответ 4

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

  • Достаточно получить доступ к ViewModels из View,
  • Неправильно обращаться к Views из ViewModels, поскольку он вводит циклическую зависимость и затрудняет проверку ViewModels.

Benny Jobigan anwer:

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

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

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

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

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

arconaut anwer:

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

это лучше. Похоже, что Messenger или EventAggregator или другие паб/вспомогательные шаблоны являются универсальным решением для каждого пользователя в MVVM:) Недостатком является то, что сложнее отлаживать или перемещаться по DialogMessageHandler. Это слишком косвенное imho. Например, как бы вы читали вывод из диалогового окна? путем изменения 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, поэтому ваши режимы просмотра полностью независимы от 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 или оба.

Ответ 5

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

Ответ 6

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

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

.

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

JBE