用MVVM与wpf对话的好习惯或不好的做法?
我最近有问题为我的WPF应用程序创build添加和编辑对话框。
我想在我的代码中做的就是这样的。 (我主要使用MVVM的viewmodel第一种方法)
调用对话窗口的ViewModel:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); // Do anything with the dialog result
它是如何工作的?
首先,我创build了一个对话服务:
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
创build一个接口来实现它。
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; }
现在至less我必须在我的资源文件( app.xaml
或其他)中创build一个DataTemplate
:
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" > <DialogView:EditOrNewAuswahlItem/> </DataTemplate>
那么所有,我现在可以从我的viewmodels调用对话框:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
现在我的问题,你看到这个解决scheme的任何问题?
编辑:完整性。 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); } }
编辑2:我用这里的代码,使我的EventHandler注册弱:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(网站不再存在, WebArchive镜像 )
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; } }
这是一个很好的方法,过去我使用了类似的方法。 去吧!
我确实要做的一件小事是让事件在DialogResult中需要设置“false”时收到一个布尔值。
event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
和EventArgs类:
public class RequestCloseEventArgs : EventArgs { public RequestCloseEventArgs(bool dialogResult) { this.DialogResult = dialogResult; } public bool DialogResult { get; private set; } }
我几个月来一直在使用几乎相同的方法,而且我对此感到非常满意(即,我还没有感觉到完全重写它的冲动…)
在我的实现中,我使用了一个IDialogViewModel
,它公开了一些东西,比如标题,展示的button(为了在所有的对话框中拥有一致的服从),一个RequestClose
事件以及一些能够控制窗口的事情大小和行为
如果您正在讨论对话窗口而不仅仅是popup的消息框,请考虑下面的方法。 关键是:
- 我将
Module Controller
的引用传递给每个ViewModel
的构造函数(可以使用注入)。 - 该
Module Controller
具有创build对话窗口的公共/内部方法(只是创build,没有返回结果)。 因此,在ViewModel
打开一个对话窗口我写:controller.OpenDialogEntity(bla, bla...)
- 每个对话窗口通过弱事件通知其结果(如确定 , 保存 , 取消等)。 如果您使用PRISM,那么使用此EventAggregator发布通知更容易。
- 为了处理对话结果,我正在使用订阅通知(在PRISM的情况下再次弱事件和EventAggregator )。 要减less对这些通知的依赖,请使用具有标准通知的独立类。
优点:
- 代码less 我不介意使用接口,但是我看到太多的项目,过度使用接口和抽象层导致更多的麻烦而不是帮助。
- 通过
Module Controller
打开对话窗口是避免强引用的简单方法,仍然允许使用模型进行testing。 - 通过弱事件通知减less潜在的内存泄漏的数量。
缺点:
- 在处理程序中不容易区分所需的通知。 两种解决scheme
- 在打开对话窗口时发送一个唯一的标记,并在订阅中检查该标记
- 使用generics通知类
<T>
其中T
是实体的枚举(或为简单起见,它可以是ViewModel的types)。
- 对于一个项目应该是关于使用通知类来防止重复的一个协议。
- 对于非常大的项目,
Module Controller
可能会被创build窗口的方法所淹没。 在这种情况下,最好把它分成几个模块。
PS我一直在使用这种方法已经有相当长的一段时间,并准备捍卫它的评论资格,并提供一些例子,如果需要的话。