2010-09-27 43 views
127

我最近有问题为我的wpf应用程序创建添加和编辑对话框。wpf与MVVM中对话框的好坏做法?

我想在我的代码中做的事情就是这样的。 (我主要使用与MVVM视图模型第一种方法)

视图模型这就要求一个对话窗口:

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

每当我的视图模型认为是时候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); 

现在我的问题,你看到这个解决方案的任何问题?

编辑:为了完整。该视图模型应该实现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:我以前从这里的代码,使我的事件处理程序注册弱:
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; 
    } 
} 
+1

您可能在您的WindowDialog XAML中缺少xmlns:x =“http://schemas.microsoft.com/winfx/2006/xaml”参考。 – 2014-01-02 11:47:40

+0

实际上命名空间是xmlns:x =“[http://] schemas.microsoft.com/winfx/2006/xaml”没有括号 – reggaeguitar 2014-04-22 22:04:27

+0

请参阅http://stackoverflow.com/questions/16993433/mvvm-light-wpf - 绑定 - 窗口到视图模型的多个实例/ 16994523#16994523 – reggaeguitar 2014-04-24 22:07:41

回答

43

这是一个很好的方法,我在过去使用类似的报告。去吧!

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

event EventHandler<RequestCloseEventArgs> RequestCloseDialog; 

和EventArgs类:

public class RequestCloseEventArgs : EventArgs 
{ 
    public RequestCloseEventArgs(bool dialogResult) 
    { 
     this.DialogResult = dialogResult; 
    } 

    public bool DialogResult { get; private set; } 
} 
+0

thx,我会改变我的活动:) – blindmeis 2010-09-28 05:43:10

+7

我认为,而不是'bool',必须有一个自定义的EventArgs派生自包含'bool'属性的基类'EventArgs'类。 'EventHandler'委托对通用参数有一个类约束,它要求类型从'EventArgs'派生。用'bool'作为通用参数,这不会编译(至少在VS2010中不会,我不知道这是否可能从早期版本中改变)。 – Slauma 2011-02-09 20:41:55

+0

你是正确的,我修复了示例代码。谢谢 – 2011-02-10 17:57:50

15

我已经使用了好几个月几乎相同的方式,现在,我很高兴与它(即我还没有感觉到敦促完全重写它...)

在我的实现中,我使用了一个IDialogViewModel,它公开诸如标题,standad按钮(为了在所有对话框中保持一致的服从),事件以及其他一些事情能够控制窗口大小和行为

+0

thx,标题应该真的在我的IDialogViewModel中。其他属性如尺寸,标准按钮我会离开,因为这些至少都来自数据模板。 – blindmeis 2010-09-28 05:42:28

+1

这就是我刚才所做的,只是使用SizeToContent来控制窗口的大小。但在一个案例中,我需要使窗口可调整大小,所以我不得不稍微调整一下... – 2010-09-28 09:15:22

+0

mhh thx这个信息:) – blindmeis 2010-09-28 12:38:53

2

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

  1. 我传递给Module Controller参考到每个ViewModel的构造函数(你可以使用注射用)。
  2. Module Controller有创建对话窗口的公共/内部方法(只是创建,没有返回结果)。因此,在ViewModel打开一个对话窗口,我写:controller.OpenDialogEntity(bla, bla...)
  3. 每个对话窗口通知有关通过Weak Events其结果(如OK保存取消等)。如果您使用PRISM,那么使用this EventAggregator发布通知更容易。
  4. 要处理对话结果,我正在使用订阅通知(对于PRISM,再次使用Weak EventsEventAggregator)。为了减少对这些通知的依赖,使用具有标准通知的独立类。

优点:

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

缺点:

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

P.S.我现在一直在使用这种方法很长一段时间,并准备捍卫其评论的资格,并在必要时提供一些示例。