使用 MVVM 在 wpf 中使用 Dialogs 的好做法还是坏做法?

Posted

技术标签:

【中文标题】使用 MVVM 在 wpf 中使用 Dialogs 的好做法还是坏做法?【英文标题】:Good or bad practice for Dialogs in wpf with MVVM? 【发布时间】:2011-04-17 15:09:41 【问题描述】:

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

我想要在我的代码中做的就是这样。 (我主要使用 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;
    
 

现在至少我必须在我的资源文件中创建一个DataTemplateapp.xaml 或其他东西):

<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 (网站已不存在,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;
    

【问题讨论】:

您可能在 WindowDialog XAML 中缺少 xmlns:x="schemas.microsoft.com/winfx/2006/xaml" 引用。 其实命名空间是 xmlns:x="[http://]schemas.microsoft.com/winfx/2006/xaml" 没有括号 见***.com/questions/16993433/… 嗨!来晚了我不明白您的服务如何引用 WindowDialog。你的模型的层次结构是什么?在我看来,视图包含对 Viewmodel 程序集的引用,而 Viewmodel 对服务和模型程序集的引用。因此,服务层将不知道 WindowDialog 视图。我错过了什么? 嗨@blindmeis,我只是想围绕这个概念来思考一下,我不认为有一些在线示例项目我可以选择吗?我对很多事情感到困惑。 【参考方案1】:

这是一个很好的方法,我过去使用过类似的方法。加油!

当您需要在 DialogResult 中设置“false”时,我肯定会做的一件小事就是让事件接收一个布尔值。

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

和 EventArgs 类:

public class RequestCloseEventArgs : EventArgs

    public RequestCloseEventArgs(bool dialogResult)
    
        this.DialogResult = dialogResult;
    

    public bool DialogResult  get; private set; 

【讨论】:

如果不使用服务,而是使用一种回调来促进与 ViewModel 和 View 的交互,该怎么办?例如,View 在 ViewModel 中执行一个命令,然后当一切都说完之后,ViewModel 会为 View 触发一个 Callback 以显示命令的结果。我仍然无法让我的团队使用服务来处理 ViewModel 中的对话框交互。【参考方案2】:

几个月来我一直在使用几乎相同的方法,我对此非常满意(即我还没有完全重写它的冲动......)

在我的实现中,我使用了 IDialogViewModel 来公开诸如标题、要显示的标准按钮(为了在所有对话框中具有一致的外观)、RequestClose 事件和其他一些内容能够控制窗口大小和行为

【讨论】:

thx,标题应该真的出现在我的 IDialogViewModel 中。其他属性,如大小,标准按钮我将离开,因为这些至少都来自数据模板。 我一开始也是这么干的,就是用SizeToContent来控制窗口的大小。但在一种情况下,我需要调整窗口大小,所以我不得不稍微调整一下...... @ThomasLevesque 包含在您的 ViewModel 中的按钮,它们实际上是 UI Button 对象还是代表按钮的对象? @Thomas,代表按钮的对象。你不应该在 ViewModel 中引用 UI 对象。【参考方案3】:

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

    我将Module Controller 的引用传递给每个ViewModel 的构造函数(您可以使用注入)。 Module Controller 具有用于创建对话窗口的公共/内部方法(只是创建,不返回结果)。因此在ViewModel 中打开一个对话窗口我写:controller.OpenDialogEntity(bla, bla...) 每个对话窗口都会通过Weak Events 通知其结果(如OKSaveCancel 等)。如果您使用 PRISM,则使用 this EventAggregator 发布通知会更容易。 为了处理对话结果,我使用订阅通知(同样是Weak Events 和EventAggregator,如果是PRISM)。要减少对此类通知的依赖,请使用带有标准通知的独立类。

优点:

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

缺点:

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

附:我已经使用这种方法很长时间了,并准备在 cmets 中捍卫其资格,并在需要时提供一些示例。

【讨论】:

以上是关于使用 MVVM 在 wpf 中使用 Dialogs 的好做法还是坏做法?的主要内容,如果未能解决你的问题,请参考以下文章

WPF 在异步等待模式中使用 MVVM 和 WCF

DataGridTemplateColumn 绑定在 WPF 中使用文本框和 MVVM

在 WPF 中使用 MVVM 将 n 个矩形添加到画布

如何在 WPF/MVVM 应用程序中处理依赖注入

使用 MVVM 在 WPF 中创建新窗口的最佳方法

WPF经典编程模式-MVVM示例讲解