Хорошая или плохая практика для Dialogs в wpf с MVVM?

В последнее время у меня возникла проблема создания диалоговых окон добавления и редактирования для моего приложения wpf.

Все, что я хочу сделать в своем коде, было что-то вроде этого. (В основном я использую первый подход viewmodel с mvvm)

ViewModel, который вызывает диалоговое окно:

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); // Do anything with the dialog result 

Как это работает?

Во-первых, я создал службу диалога:

 public interface IUIWindowDialogService { bool? ShowDialog(string title, object datacontext); } public class WpfUIWindowDialogService : IUIWindowDialogService { public bool? ShowDialog(string title, object datacontext) { var win = new WindowDialog(); win.Title = title; win.DataContext = datacontext; return win.ShowDialog(); } } 

WindowDialog – это специальное, но простое окно. Мне нужно, чтобы он содержал мой контент:

     

Проблема с диалогами в wpf заключается в том, что dialogresult = true может быть достигнуто только в коде. Вот почему я создал интерфейс для моей dialogviewmodel для ее реализации.

 public class RequestCloseDialogEventArgs : EventArgs { public bool DialogResult { get; set; } public RequestCloseDialogEventArgs(bool dialogresult) { this.DialogResult = dialogresult; } } public interface IDialogResultVMHelper { event EventHandler RequestCloseDialog; } 

Всякий раз, когда мой ViewModel думает, что пришло время для dialogresult = true , поднимите это событие.

 public partial class DialogWindow : Window { // Note: If the window is closed, it has no DialogResult private bool _isClosed = false; public DialogWindow() { InitializeComponent(); this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged; this.Closed += DialogWindowClosed; } void DialogWindowClosed(object sender, EventArgs e) { this._isClosed = true; } private void DialogPresenterDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { var d = e.NewValue as IDialogResultVMHelper; if (d == null) return; d.RequestCloseDialog += new EventHandler (DialogResultTrueEvent).MakeWeak( eh => d.RequestCloseDialog -= eh;); } private void DialogResultTrueEvent(object sender, RequestCloseDialogEventArgs eventargs) { // Important: Do not set DialogResult for a closed window // GC clears windows anyways and with MakeWeak it // closes out with IDialogResultVMHelper if(_isClosed) return; this.DialogResult = eventargs.DialogResult; } } 

Теперь, по крайней мере, мне нужно создать DataTemplate в файле ресурсов ( app.xaml или что-то еще):

    

Ну вот и все, теперь я могу вызывать диалоги из своих моделей просмотра:

  var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); 

Теперь, на мой вопрос, вы видите проблемы с этим решением?

Изменить: для полноты. ViewModel должен реализовать IDialogResultVMHelper а затем он может поднять его в OkCommand или что-то вроде этого:

 public class MyViewmodel : IDialogResultVMHelper { private readonly Lazy _okCommand; public MyViewmodel() { this._okCommand = new Lazy(() => new DelegateCommand(() => InvokeRequestCloseDialog( new RequestCloseDialogEventArgs(true)), () => YourConditionsGoesHere = true)); } public ICommand OkCommand { get { return this._okCommand.Value; } } public event EventHandler RequestCloseDialog; private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e) { var handler = RequestCloseDialog; if (handler != null) handler(this, e); } } 

EDIT 2: я использовал код здесь, чтобы сделать регистр EventHandler слабым:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Веб-сайт больше не существует, WebArchive Mirror )

 public delegate void UnregisterCallback(EventHandler eventHandler) where TE : EventArgs; public interface IWeakEventHandler where TE : EventArgs { EventHandler Handler { get; } } public class WeakEventHandler : IWeakEventHandler where T : class where TE : EventArgs { private delegate void OpenEventHandler(T @this, object sender, TE e); private readonly WeakReference mTargetRef; private readonly OpenEventHandler mOpenHandler; private readonly EventHandler mHandler; private UnregisterCallback mUnregister; public WeakEventHandler(EventHandler eventHandler, UnregisterCallback unregister) { mTargetRef = new WeakReference(eventHandler.Target); mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate( typeof(OpenEventHandler),null, eventHandler.Method); mHandler = Invoke; mUnregister = unregister; } public void Invoke(object sender, TE e) { T target = (T)mTargetRef.Target; if (target != null) mOpenHandler.Invoke(target, sender, e); else if (mUnregister != null) { mUnregister(mHandler); mUnregister = null; } } public EventHandler Handler { get { return mHandler; } } public static implicit operator EventHandler(WeakEventHandler weh) { return weh.mHandler; } } public static class EventHandlerUtils { public static EventHandler MakeWeak(this EventHandler eventHandler, UnregisterCallback unregister) where TE : EventArgs { if (eventHandler == null) throw new ArgumentNullException("eventHandler"); if (eventHandler.Method.IsStatic || eventHandler.Target == null) throw new ArgumentException("Only instance methods are supported.", "eventHandler"); var wehType = typeof(WeakEventHandler).MakeGenericType( eventHandler.Method.DeclaringType, typeof(TE)); var wehConstructor = wehType.GetConstructor(new Type[] { typeof(EventHandler), typeof(UnregisterCallback) }); IWeakEventHandler weh = (IWeakEventHandler)wehConstructor.Invoke( new object[] { eventHandler, unregister }); return weh.Handler; } } 

Это хороший подход, и я использовал подобные в прошлом. Действуй!

Одна незначительная вещь, которую я определенно сделаю, – это сделать событие получающим логическое значение, когда вам нужно установить «false» в DialogResult.

 event EventHandler RequestCloseDialog; 

и class EventArgs:

 public class RequestCloseEventArgs : EventArgs { public RequestCloseEventArgs(bool dialogResult) { this.DialogResult = dialogResult; } public bool DialogResult { get; private set; } } 

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

В моей реализации я использую IDialogViewModel который предоставляет такие вещи, как заголовок, кнопки standad для отображения (для обеспечения согласованного видимости во всех диалоговых windowsх), событие RequestClose и несколько других функций, позволяющих управлять окном размер и поведение

Если вы говорите о диалоговых windowsх, а не только о всплывающих сообщениях, рассмотрите мой подход ниже. Ключевыми моментами являются:

  1. Я передаю ссылку на Module Controller в конструктор каждой ViewModel (вы можете использовать инъекцию).
  2. Этот Module Controller имеет общедоступные / внутренние методы для создания диалоговых окон (просто создавая, не возвращая результата). Следовательно, чтобы открыть диалоговое окно в ViewModel я пишу: controller.OpenDialogEntity(bla, bla...)
  3. Каждое диалоговое окно уведомляет о его результате (например, « ОК» , « Сохранить» , « Отмена» и т. Д.) Через « Слабые события» . Если вы используете PRISM, легче публиковать уведомления, используя этот EventAggregator .
  4. Чтобы обрабатывать результаты диалога, я использую подписку на уведомления (снова « Слабые события» и « EventAggregator» в случае PRISM). Чтобы уменьшить зависимость от таких уведомлений, используйте независимые classы со стандартными уведомлениями.

Плюсы:

  • Меньше кода. Я не против использования интерфейсов, но я видел слишком много проектов, где избыточность использования интерфейсов и уровней абстракции вызывает больше проблем, чем помощь.
  • Открытые диалоговые windows через Module Controller – это простой способ избежать сильных ссылок и до сих пор позволяет использовать макеты для тестирования.
  • Уведомление через слабые события уменьшает количество потенциальных утечек памяти.

Минусы:

  • Нелегко отличить требуемое уведомление от других в обработчике. Два решения:
    • отправить уникальный токен при открытии диалогового windows и проверить, что токен в подписке
    • используйте общие classы уведомлений где T – перечисление объектов (или для простоты это может быть тип ViewModel).
  • Для проекта должно быть соглашение об использовании classов уведомлений, чтобы предотвратить их дублирование.
  • Для чрезвычайно больших проектов Module Controller может быть перегружен методами создания окон. В этом случае лучше разделить его на несколько модhive.

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

  • Редактирование одного клика в WPF DataGrid
  • Как показать прогресс во время цикла занятости?
  • Как создать WPat Datagrid с неизвестным количеством столбцов
  • Как я могу создать «предварительный просмотр» FlowDocument в приложении WPF?
  • Настройка свойств canvasа в ItemControl DataTemplate
  • Почему RelayCommand
  • Как установить ViewModel в окне в XAML с использованием свойства DataContext?
  • Разница между {Binding PropertyName} и {Binding Path = PropertyName}
  • Как получить активные размеры экрана?
  • Установка кнопки FlatStyle в WPF
  • Индикатор выполнения с динамическим обновлением текста и текста
  • Давайте будем гением компьютера.