首页 文章

使用MVVM的wpf中的Dialogs的好坏?

提问于
浏览
135

我最近遇到了为我的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;
    }
 }

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

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

那就是全部,我现在可以从我的viewmodels调用对话框:

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;
    }
}

3 回答

  • 2

    这是一个很好的方法,过去我使用过类似的方法 . 去吧!

    我必须做的一件小事是,当你需要在DialogResult中设置“false”时,事件会收到一个布尔值 .

    event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
    

    和EventArgs类:

    public class RequestCloseEventArgs : EventArgs
    {
        public RequestCloseEventArgs(bool dialogResult)
        {
            this.DialogResult = dialogResult;
        }
    
        public bool DialogResult { get; private set; }
    }
    
  • 45

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

    在我的实现中,我使用 IDialogViewModel 暴露诸如 Headers ,标准按钮以显示(为了在所有对话框中具有一致的外观), RequestClose 事件以及一些其他能够控制窗口的事物 . 规模和行为

  • 15

    如果您正在谈论对话窗口,而不仅仅是弹出消息框,请考虑我的方法如下 . 关键点是:

    • 我将对 Module Controller 的引用传递给每个 ViewModel 的构造函数(可以使用注入) .

    • Module Controller 有公共/内部方法来创建对话窗口(只是创建,而不返回结果) . 因此,在 ViewModel 中打开一个对话窗口我写道: controller.OpenDialogEntity(bla, bla...)

    • 每个对话窗口通过Weak Events通知其结果(如“确定”,“保存”,“取消”等) . 如果您使用PRISM,则使用this EventAggregator发布通知会更容易 .

    • 为了处理对话结果,我正在使用订阅通知(在PRISM的情况下再次使用Weak EventsEventAggregator) . 要减少对此类通知的依赖性,请使用带有标准通知的独立类 .

    Pros:

    • 更少的代码 . 我没有看到太多的项目,其中使用接口和抽象层的过度导致比帮助更多的麻烦 .

    • 通过 Module Controller 打开对话窗口是一种避免强引用的简单方法,并且仍允许使用模型进行测试 .

    • 通过弱事件通知减少了潜在的内存泄漏次数 .

    Cons:

    • 不容易区分处理程序中的其他通知 . 两种解决方案

    • 在打开对话窗口时发送唯一令牌,并在订阅中检查该令牌

    • 使用通用通知类 <T> 其中 T 是实体的枚举(或者为了简单起见,它可以是ViewModel的类型) .

    • 对于项目,应该是关于使用通知类来防止重复它们的协议 .

    • 对于非常大的项目, Module Controller 可能被创建窗口的方法所淹没 . 在这种情况下,最好将它分成几个模块 .

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

相关问题