Обработка диалогов в WPF с помощью MVVM - PullRequest
226 голосов
/ 18 января 2009

В шаблоне MVVM для WPF обработка диалогов является одной из более сложных операций. Так как ваша модель представления ничего не знает о представлении, диалоговое общение может быть интересным. Я могу выставить ICommand, чтобы, когда представление вызывает его, мог появиться диалог.

Кто-нибудь знает хороший способ обработки результатов из диалогов? Я говорю о диалоговых окнах, таких как MessageBox.

Одним из способов, которым мы это сделали, было событие в модели представления, на которое представление подписывалось, когда требовался диалог.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Это нормально, но это означает, что для представления требуется код, от которого я бы хотел остаться в стороне.

Ответы [ 23 ]

3 голосов
/ 21 марта 2010

Интересной альтернативой является использование контроллеров, которые отвечают за отображение представлений (диалогов).

Как это работает, показывает WPF Application Framework (WAF) .

3 голосов
/ 18 января 2009

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

Если вы измените взаимодействие ViewModel - View для обработки диалогов, тогда ViewModel зависит от этой реализации. Самый простой способ справиться с этой проблемой - сделать View ответственным за выполнение задачи. Если это означает показ диалога, то все в порядке, но также может быть сообщение о состоянии в строке состояния и т. Д.

Суть в том, что весь смысл шаблона MVVM состоит в том, чтобы отделить бизнес-логику от GUI, поэтому не следует смешивать логику GUI (для отображения диалога) на бизнес-уровне (ViewModel).

2 голосов
/ 12 марта 2010

У меня была такая же ситуация и я обернул MessageBox в невидимый элемент управления дизайнера. Подробности в моем блоге

http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx

То же самое можно распространить на любые модальные диалоги, управление просмотром файлов и т. Д.

2 голосов
/ 24 апреля 2009

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

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

1 голос
/ 28 апреля 2010

Я свернул свой собственный загрузчик окон, описанный в ответе на этот вопрос:

Управление несколькими представлениями WPF в приложении

1 голос
/ 08 ноября 2016

Потратив некоторое время на это, я наконец-то нашел следующее решение. Вот несколько ключевых преимуществ этого подхода:

  1. Реализует собственный MVVM Light IDialogService.
  2. Представлению не нужно добавлять ссылку на MVVM Light.
  3. ВМ не нужно выполнять какие-либо действия на уровне презентации. Не нуждается даже в справке PresentationFramework.
  4. Использует собственный канал Messenger MVVM Light, поэтому уровни представления и виртуальной машины не связаны.
  5. Поддерживает диалоги с возвращаемым значением, такие как «Да / Нет вопросов» или «ОК / Отмена».
  6. Поддерживает пользовательские диалоги.

Вот реализация IDialogService (входит в ViewModel проект):

using System;
using System.Linq;
using System.Threading.Tasks;

namespace VM
{
  public enum MessageBoxButtonVM
  {
    OK,
    OKCancel,
    YesNo
  }

  public enum MessageBoxImageVM
  {
    None,
    Information,
    Question,
    Error
  }

  public class MessageBoxArgs
  {
    public MessageBoxButtonVM Buttons { get; set; }
    public MessageBoxImageVM Icon { get; set; }
    public string Title { get; set; }
    public string Message { get; set; }
  }

  //For custom dialogs that return a value
  public class MessageBoxNotificationWithAction<T>
  {
    private readonly Action<T> _callback;

    public MessageBoxArgs Notification { get; set; }

    public MessageBoxNotificationWithAction(MessageBoxArgs notification, Action<T> callback)
    {
      Notification = notification;

      CheckCallback(callback);
      _callback = callback;
    }

    public virtual void Execute(T argument)
    {
      _callback.Invoke(argument);
    }

    private static void CheckCallback(Delegate callback)
    {
      if (callback == null)
      {
        throw new ArgumentNullException(nameof(callback), "Callback must not be null");
      }
    }
  }

  /// <summary>
  /// Provides an implementation-agnostic way of communicating with the user through dialog boxes. Clients must register for communication messages using
  /// MVVM Light messaging system.
  /// </summary>
  public class DialogService : GalaSoft.MvvmLight.Views.IDialogService
  {
    private static GalaSoft.MvvmLight.Messaging.IMessenger Messenger = GalaSoft.MvvmLight.Messaging.Messenger.Default;

    private string _ProductName = "";

    public string ProductName
    {
      get
      {
        if (_ProductName == "")
        {
          //The following statement returns the Title attribute of the current assembly, as defined in project properties (Assembly Information dialog).
          var TitleAttrib = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributesData().First(x => x.AttributeType.Name == "AssemblyTitleAttribute");

          if (TitleAttrib != null)
          {
            _ProductName = TitleAttrib.ConstructorArguments[0].Value.ToString();
          }
          else
          {
            _ProductName = "Default Application Name";
          }
        }

        return _ProductName;
      }
    }

    public Task ShowError(Exception error, string title, string buttonText, Action afterHideCallback)
    {
      return ShowError(error.Message, title, buttonText, afterHideCallback);
    }

    public Task ShowMessage(string message, string title)
    {
      return Task.Run(() => MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error));
    }

    public Task ShowError(string message, string title, string buttonText, Action afterHideCallback)
    {
      return Task.Run(() =>
      {
        MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error);
        afterHideCallback?.Invoke();
      });
    }

    public Task ShowMessage(string message, string title, string buttonText, Action afterHideCallback)
    {
      return Task.Run(() =>
      {
        MessengerSend(message, title);
        afterHideCallback?.Invoke();
      });
    }

    public Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback)
    {
      if ((buttonConfirmText == "OK" && buttonCancelText == "Cancel") ||
        (buttonConfirmText == "Yes" && buttonCancelText == "No"))
      {
        return Task.Run<bool>(() =>
        {
          MessageBoxButtonVM btn;
          if (buttonConfirmText == "OK")
            btn = MessageBoxButtonVM.OKCancel;
          else
            btn = MessageBoxButtonVM.YesNo;


          bool Response = false;
          Messenger.Send(new MessageBoxNotificationWithAction<bool>(
                                                      new MessageBoxArgs()
                                                      {
                                                        Buttons = btn,
                                                        Icon = MessageBoxImageVM.Question,
                                                        Title = (string.IsNullOrEmpty(title) ? _ProductName : title),
                                                        Message = message
                                                      },
                                                      (result) => Response = result
                                                        ));

          afterHideCallback?.Invoke(Response);

          return Response;
        });
      }
      else
        throw new ArgumentException($"{nameof(buttonConfirmText)} and {nameof(buttonCancelText)} must either be OK/Cancel or Yes/No.");
    }

    /// <summary>
    /// For debugging purpose only
    /// </summary>
    /// <param name="message"></param>
    /// <param name="title"></param>
    /// <returns></returns>
    public Task ShowMessageBox(string message, string title) => ShowMessage(message, title);

    private void MessengerSend(string msg, string title = "", MessageBoxButtonVM btn = MessageBoxButtonVM.OK, MessageBoxImageVM icon = MessageBoxImageVM.Information)
    {
      Messenger.Send(new MessageBoxArgs()
      {
        Buttons = MessageBoxButtonVM.OK,
        Icon = MessageBoxImageVM.Information,
        Title = (string.IsNullOrEmpty(title) ? _ProductName : title),
        Message = msg
      });
    }
  }
}

Вот слой представления (входит в View project)

using System.Windows;
using VM;

namespace View
{
  class DialogPresenter
  {
    private Window _Parent;

    public DialogPresenter()
    {
      //For simple information boxes
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxArgs>(this, (arg) => ShowDialog(arg));

      //For Yes/No or OK/Cancel dialog boxes.
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<bool>>(this, (arg) => arg.Execute(ShowDialog(arg.Notification)));

      //For notifications that require a string response (such as Manual Timeslot Description)
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<string>>(this,
        (arg) => arg.Execute(ShowStringInputDialog(arg.Notification.Title, arg.Notification.Message)));
    }

    private bool ShowDialog(MessageBoxArgs arg)
    {
      MessageBoxButton btn = MessageBoxButton.OK;
      MessageBoxImage ico = MessageBoxImage.None;

      switch (arg.Buttons)
      {
        case MessageBoxButtonVM.OK: btn = MessageBoxButton.OK; break;
        case MessageBoxButtonVM.OKCancel: btn = MessageBoxButton.OKCancel; break;
        case MessageBoxButtonVM.YesNo: btn = MessageBoxButton.YesNo; break;
      }

      switch (arg.Icon)
      {
        case MessageBoxImageVM.Error: ico = MessageBoxImage.Error; break;
        case MessageBoxImageVM.Information: ico = MessageBoxImage.Information; break;
        case MessageBoxImageVM.None: ico = MessageBoxImage.None; break;
        case MessageBoxImageVM.Question: ico = MessageBoxImage.Question; break;
      }

      bool Result = false;
      _Parent.Dispatcher.Invoke(() =>
      {
        var Res = MessageBox.Show(arg.Message, arg.Title, btn, ico);
        Result = (Res == MessageBoxResult.OK || Res == MessageBoxResult.Yes);
      });

      return Result;
    }

    private string ShowStringInputDialog(string title, string description, string value = "", int maxLength = 100)
    {
      string Result = null;

      _Parent.Dispatcher.Invoke(() =>
      {
        //InputBox is a WPF Window I created for taking simple
        //string values from the user. This also shows that you can
        //any custom dialog using this approach.

        InputBox input = new InputBox();
        input.Title = title;
        input.Owner = _Parent;
        if (input.ShowDialog(description, value, maxLength).Value)
          Result=input.Value;
        else
          Result=null;
      });

      return Result;
    }

    //Call this somewhere at application startup so that the dialog boxes
    //appear as child windows.
    public void SetParentWindow(Window parent)
    {
      _Parent = parent;
    }
  }
}
1 голос
/ 19 октября 2016

Я знаю, что это старый вопрос, но когда я выполнил этот поиск, я нашел много связанных вопросов, но я не нашел действительно четкого ответа. Поэтому я делаю свою собственную реализацию диалогового окна / messagebox / popin и делюсь ею!
Я думаю, что это «доказательство MVVM», и я пытаюсь сделать его простым и правильным, но я новичок в WPF, поэтому не стесняйтесь комментировать или даже делать запрос на извлечение.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Вы можете использовать это так:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Или вот так, если вы хотите более сложный попин:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

И это показывает такие вещи:

2

1 голос
/ 12 декабря 2011

Карл Шиффлетт создал образец приложения для отображения диалоговых окон, используя сервисный подход и подход Prism InteractionRequest.

Мне нравится сервисный подход - он менее гибок, поэтому у пользователей меньше шансов что-то сломать :) Это также согласуется с частью WinForms моего приложения (MessageBox.Show). Но если вы планируете показывать много разных диалогов, лучше использовать InteractionRequest.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

0 голосов
/ 07 мая 2018

Извините, но я должен вмешаться. Я прошел через несколько предлагаемых решений, прежде чем нашел пространство имен Prism.Wpf.Interactivity в проекте Prism. Вы можете использовать запросы взаимодействия и действие всплывающего окна, чтобы либо свернуть пользовательское окно, либо для более простых нужд есть встроенные всплывающие окна уведомлений и подтверждений. Они создают настоящие окна и управляются как таковые. Вы можете передать объект контекста с любыми зависимостями, которые вам нужны в диалоге. Мы используем это решение на моей работе, так как я его нашел. У нас здесь много старших разработчиков, и никто не придумал ничего лучшего. Нашим предыдущим решением было использование диалогового сервиса в оверлее и использование класса презентатора, чтобы это произошло, но вам нужно было иметь фабрики для всех моделей представления диалога и т. Д.

Это не тривиально, но и не очень сложно. И он встроен в Призму и поэтому является лучшей (или лучшей) практикой ИМХО.

Мои 2 цента!

0 голосов
/ 05 апреля 2018

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

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

...