Хорошая или плохая практика для диалогов в 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
- это особое, но простое окно. Мне нужно, чтобы он держал мой содержание:
<Window x:Class="WindowDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WindowDialog"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter x:Name="DialogPresenter" Content="{Binding .}">
</ContentPresenter>
</Window>
проблема с диалогами в 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<RequestCloseDialogEventArgs> 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<RequestCloseDialogEventArgs>
(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
или что-то):
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>
Ну вот и все, теперь я могу вызвать диалоги из моего просмотр моделей:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
теперь мой вопрос, вы видите какие-либо проблемы с этим решением?
Edit: для полноты картины. ViewModel должен реализовать IDialogResultVMHelper
и тогда он может поднять его в OkCommand
или что-то вроде этого:
public class MyViewmodel : IDialogResultVMHelper
{
private readonly Lazy<DelegateCommand> _okCommand;
public MyViewmodel()
{
this._okCommand = new Lazy<DelegateCommand>(() =>
new DelegateCommand(() =>
InvokeRequestCloseDialog(
new RequestCloseDialogEventArgs(true)), () =>
YourConditionsGoesHere = true));
}
public ICommand OkCommand
{
get { return this._okCommand.Value; }
}
public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
{
var handler = RequestCloseDialog;
if (handler != null)
handler(this, e);
}
}
EDIT 2: я использовал код отсюда, чтобы сделать мой EventHandler регистр weak:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Сайт больше не работает существует, Webarchive Mirror)
public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler)
where TE : EventArgs;
public interface IWeakEventHandler<TE>
where TE : EventArgs
{
EventHandler<TE> Handler { get; }
}
public class WeakEventHandler<T, TE> : IWeakEventHandler<TE>
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<TE> mHandler;
private UnregisterCallback<TE> mUnregister;
public WeakEventHandler(EventHandler<TE> eventHandler,
UnregisterCallback<TE> 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<TE> Handler
{
get { return mHandler; }
}
public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh)
{
return weh.mHandler;
}
}
public static class EventHandlerUtils
{
public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler,
UnregisterCallback<TE> 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<TE>), typeof(UnregisterCallback<TE>)
});
IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
new object[] { eventHandler, unregister });
return weh.Handler;
}
}
3 ответа:
это хороший подход, и я использовал аналогичные в прошлом. Вперед!
одна незначительная вещь, которую я определенно сделал бы, это заставить событие получить логическое значение, когда вам нужно установить "false" в DialogResult.
event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
и класс EventArgs:
public class RequestCloseEventArgs : EventArgs { public RequestCloseEventArgs(bool dialogResult) { this.DialogResult = dialogResult; } public bool DialogResult { get; private set; } }
Я использую почти идентичный подход уже несколько месяцев, и я очень доволен этим (т. е. я еще не чувствовал желания полностью переписать его...)
в моей реализации, я использую
IDialogViewModel
который предоставляет такие вещи, как заголовок, стандартные кнопки для отображения (для того, чтобы иметь последовательную видимость во всех диалогах), aRequestClose
событие, и несколько других вещей, чтобы иметь возможность контролировать размер окна и поведение
если вы говорите о диалоговых окнах, а не только о всплывающих окнах сообщений, пожалуйста, рассмотрите мой подход ниже. Ключевыми моментами являются:
- я передаю ссылку на
Module Controller
в конструктор каждогоViewModel
(можно использовать инъекции).- это
Module Controller
имеет открытые / внутренние методы для создания диалоговых окон (просто создание, без возврата результата). Следовательно, чтобы открыть диалоговое окно вViewModel
Я пишу:controller.OpenDialogEntity(bla, bla...)
- каждый диалоговое окно уведомляет о своем результате (например ОК,сохранить,отмена и т. д.) через Слабых Событий. Если вы используете PRISM, то проще публиковать уведомления с помощью этот EventAggregator.
- для обработки результатов диалога, я использую подписку на уведомления (снова Слабых Событий и EventAggregator в случае призмы). Чтобы уменьшить зависимость от таких уведомлений, использовать независимые классы со стандартными уведомлениями.
плюсы:
- меньше кода. Я не против использования интерфейсов, но я видел слишком много проектов, где чрезмерность использования интерфейсов и слоев абстракции вызывает больше проблем, чем помогает.
- открыть диалоговые окна через
Module Controller
- Это простой способ, чтобы избежать сильных ссылок и по-прежнему позволяет использовать макеты для испытаний.- уведомление через слабые события уменьшить количество потенциальных утечек памяти.
плюсы:
- нелегко отличить требуемое уведомление от других в обработчике. Два решения:
- отправьте уникальный токен при открытии диалогового окна и проверьте этот токен в подписке
- использовать общие классы уведомлений
<T>
здесьT
это перечисление сущностей (или для простоты это может быть тип модель представления.)- для проекта должно быть соглашение об использовании классов уведомлений, чтобы предотвратить их дублирование.
- для чрезвычайно больших проектов
Module Controller
могут быть перегружены методами для создания окон. В этом случае лучше разделить его на несколько модулей.P. S. Я использую этот подход уже достаточно давно и готовы отстаивать свои права в комментариях и привести несколько примеров если требуемый.