2010-09-27 36 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.我現在一直在使用這種方法很長一段時間,並準備捍衛其評論的資格,並在必要時提供一些示例。