Хорошая или плохая практика для Dialogs в wpf с MVVM?
В последнее время у меня возникла проблема создания диалоговых окон добавления и редактирования для моего приложения wpf.
Все, что я хочу сделать в своем коде, было что-то вроде этого. (В основном я использую первый подход viewmodel с mvvm)
ViewModel, который вызывает диалоговое окно:
- Ресурсы изображений WPF
- Как поддерживать привязку ListBox SelectedItems к MVVM в навигационном приложении
- Как отобразить текст по умолчанию «--Select Team -» в поле со списком в pageload в WPF?
- Как динамически создавать столбцы в DataGrid WPF?
- Инструмент Reportviewer отсутствует в visual studio 2017 RC
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; } }
- Динамический генерировать столбец mvvm
- Ошибка ReSharper WPF: «Не удается разрешить символ« MyVariable »из-за неизвестного DataContext»
- Что означает «{x: Static}» в XAML?
- Привязка OneWayToSource от свойства readonly в XAML
- WPF / C #: Где я должен сохранять файлы пользовательских настроек?
- Глобальные исключения исключений в приложении WPF?
- Как создать задачу (TPL), использующую stream STA?
- page.DataContext не унаследован от родительского фрейма?
Это хороший подход, и я использовал подобные в прошлом. Действуй!
Одна незначительная вещь, которую я определенно сделаю, – это сделать событие получающим логическое значение, когда вам нужно установить «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х, а не только о всплывающих сообщениях, рассмотрите мой подход ниже. Ключевыми моментами являются:
- Я передаю ссылку на
Module Controller
в конструктор каждойViewModel
(вы можете использовать инъекцию). - Этот
Module Controller
имеет общедоступные / внутренние методы для создания диалоговых окон (просто создавая, не возвращая результата). Следовательно, чтобы открыть диалоговое окно вViewModel
я пишу:controller.OpenDialogEntity(bla, bla...)
- Каждое диалоговое окно уведомляет о его результате (например, « ОК» , « Сохранить» , « Отмена» и т. Д.) Через « Слабые события» . Если вы используете PRISM, легче публиковать уведомления, используя этот EventAggregator .
- Чтобы обрабатывать результаты диалога, я использую подписку на уведомления (снова « Слабые события» и « EventAggregator» в случае PRISM). Чтобы уменьшить зависимость от таких уведомлений, используйте независимые classы со стандартными уведомлениями.
Плюсы:
- Меньше кода. Я не против использования интерфейсов, но я видел слишком много проектов, где избыточность использования интерфейсов и уровней абстракции вызывает больше проблем, чем помощь.
- Открытые диалоговые windows через
Module Controller
– это простой способ избежать сильных ссылок и до сих пор позволяет использовать макеты для тестирования. - Уведомление через слабые события уменьшает количество потенциальных утечек памяти.
Минусы:
- Нелегко отличить требуемое уведомление от других в обработчике. Два решения:
- отправить уникальный токен при открытии диалогового windows и проверить, что токен в подписке
- используйте общие classы уведомлений
гдеT
– перечисление объектов (или для простоты это может быть тип ViewModel).
- Для проекта должно быть соглашение об использовании classов уведомлений, чтобы предотвратить их дублирование.
- Для чрезвычайно больших проектов
Module Controller
может быть перегружен методами создания окон. В этом случае лучше разделить его на несколько модhive.
PS Я уже давно использую этот подход и готов отстаивать свое право на комментарии и при необходимости давать некоторые примеры.