使用 MVVM 在 wpf 中执行 Dialogs 是好还是坏?

最近我遇到了为我的 wpf 应用程序创建添加和编辑对话框的问题。

在我的代码中,我想要做的就是这样的事情(我大多使用 mvvm 的 viewmodel first 方法)

调用对话框窗口的 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;
}
}

现在,至少我必须在我的资源文件(app.xaml或其他)中创建一个 DataTemplate:

<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>

好了,现在我可以从我的视图模型中调用对话框了:

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

现在我的问题是,你认为这个解决方案有什么问题吗?

编辑: 为了完整。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
(网站不再存在,网络档案镜像)

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;
}
}
73258 次浏览

这是一个很好的方法,我以前也用过类似的方法!

我肯定会做的一件小事是,当您需要在 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来公开诸如标题、显示的标准按钮(为了在所有对话框中有一个一致的外观)、一个 RequestClose事件以及一些其他的东西来控制窗口的大小和行为

如果你谈论的是对话窗口,而不仅仅是弹出式消息框,请考虑我的方法如下。重点是:

  1. 我将对 Module Controller的引用传递给每个 ViewModel的构造函数(可以使用注入)。
  2. Module Controller具有创建对话窗口的公共/内部方法(只是创建,不返回结果)。因此,为了在 ViewModel中打开一个对话窗口,我写: controller.OpenDialogEntity(bla, bla...)
  3. 每个对话窗口通过 弱项通知其结果(如 好的保存取消等)。如果使用 PRISM,那么使用 这个事件聚合器发布通知会更容易。
  4. 为了处理对话结果,我使用了订阅通知(同样是 弱项事件聚合器,如果是 PRISM 的话)。要减少对此类通知的依赖,请使用具有标准通知的独立类。

优点:

  • 代码更少。我不介意使用接口,但是我见过太多的项目,过度使用接口和抽象层会导致更多的麻烦而不是帮助。
  • 通过 Module Controller打开对话窗口是一种避免强引用的简单方法,并且仍然允许使用模型进行测试。
  • 通过弱事件发出通知可以减少潜在内存泄漏的数量。

缺点:

  • 不容易区分所需的通知与处理程序中的其他通知。有两种解决方案:
    • 在打开对话窗口时发送一个唯一的令牌,并在订阅中检查该令牌
    • 使用通用通知类 <T>,其中 T是实体的枚举(或者为了简单起见,它可以是 ViewModel 类型)。
  • 对于一个项目,应该是一个关于使用通知类以防止重复它们的协议。
  • 对于非常大的项目,Module Controller可能会被创建窗口的方法所淹没。在这种情况下,最好将它分成几个模块。

另外,我已经使用这种方法很长时间了,并准备在评论中捍卫它的合格性,如果需要的话,还可以提供一些例子。