MVVMを使用したwpfでのダイアログの良いまたは悪い習慣?


148

最近、wpfアプリの追加および編集ダイアログの作成に問題がありました。

私のコードでやりたいのは、このようなものだけでした。(私は主にmvvmでviewmodel最初のアプローチを使用します)

ダイアログウィンドウを呼び出す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;
    }
 }

今、少なくとも私はDataTemplate私のリソースファイル(app.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
(Webサイトはもう存在しません、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
あなたは、おそらくのxmlnsが欠落している:X = " schemas.microsoft.com/winfx/2006/xamlあなたWindowDialog XAMLで" refernece。
Adiel Yaacov 2014年

実際の名前空間は、大括弧なしのxmlns:x = "[http://]
schemas.microsoft.com/winfx/2006/xaml


1
こんにちは!遅刻はこちら。サービスがWindowDialogへの参照を持っている方法を理解していません。モデルの階層は何ですか?私の考えでは、ビューはビューモデルアセンブリへの参照と、サービスおよびモデルアセンブリへのビューモデルを保持しています。これにより、サービスレイヤーはWindowDialogビューを認識しなくなります。何が欠けていますか?
Moe45673 2016年

2
こんにちは@blindmeis、この概念に頭を抱えようとしているだけです。私が選択できるオンラインのサンプルプロジェクトがあるとは思いませんか?混乱している点がたくさんあります。
ハンク

回答:


48

これは良いアプローチであり、過去に同様のアプローチを使用しました。頑張れ!

私が間違いなく行う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はビューのコールバックを起動して、コマンドの結果を表示します。それでも、サービスを使用してViewModelでダイアログの相互作用を処理することで、チームをオンボードにすることはできません。
マシューS

15

ほぼ同じアプローチを数か月間使用してきましたが、とても満足しています(つまり、完全に書き換える衝動をまだ感じていません...)

私の実装ではIDialogViewModel、タイトル、表示する標準ボタン(すべてのダイアログで一貫した外観を実現するため)、RequestCloseイベント、およびウィンドウサイズと動作


thx、タイトルは本当にIDialogViewModelに入るはずです。サイズ、標準ボタンなどのその他のプロパティ。これはすべて、少なくともデータテンプレートから取得されるためです。
blindmeis

1
それも私が最初にしたことです。ウィンドウのサイズを制御するには、SizeToContentを使用します。しかし、1つのケースでは、ウィンドウのサイズを変更できるようにする必要があったため、少し微調整する必要がありました...
Thomas Levesque

@ThomasLevesqueあなたのViewModelに含まれるボタン、それらは実際にはUI Buttonオブジェクトですか、ボタンを表すオブジェクトですか?
Thomas

3
@Thomas、ボタンを表すオブジェクト。ViewModelでUIオブジェクトを参照しないでください。
Thomas Levesque

2

ポップアップメッセージボックスだけでなく、ダイアログウィンドウについて話している場合は、以下のアプローチを検討してください。重要な点は次のとおりです。

  1. Module Controllerそれぞれのコンストラクターへの参照を渡しViewModelます(インジェクションを使用できます)。
  2. これにModule Controllerは、ダイアログウィンドウを作成するためのパブリック/内部メソッドがあります(結果を返さずに作成するだけです)。したがって、ViewModel私が書いたダイアログウィンドウを開くには:controller.OpenDialogEntity(bla, bla...)
  3. 各ダイアログウィンドウは、その結果(OK保存キャンセルなど)を弱いイベントを介して通知します。PRISMを使用する場合、このEventAggregatorを使用して通知を発行する方が簡単です。
  4. ハンドル対話の結果に、私は(再び通知へのサブスクリプション使用しています弱いイベントEventAggregator PRISMの場合)を。このような通知への依存を減らすには、標準の通知で独立したクラスを使用します。

長所:

  • 少ないコード。インターフェースの使用は気にしませんが、インターフェースと抽象化レイヤーの過剰な使用がヘルプよりもトラブルを引き起こすプロジェクトが多すぎます。
  • ダイアログウィンドウを開くことModule Controllerは、強い参照を回避するための簡単な方法であり、モックアップを使用してテストすることもできます。
  • 弱いイベントによる通知は、潜在的なメモリリークの数を減らします。

短所:

  • ハンドラで他のユーザーから必要な通知を区別するのは簡単ではありません。2つのソリューション:
    • 対話ウィンドウを開くときに一意のトークンを送信し、サブスクリプションでそのトークンを確認します
    • 一般的な通知クラスを使用するエンティティの列挙がある(あるいは簡単にするためには、ビューモデルの型を指定できます)。<T>T
  • プロジェクトの場合、通知クラスを使用してそれらが重複しないようにすることについて合意する必要があります。
  • 非常に大規模なプロジェクトの場合、Module Controllerウィンドウを作成する方法に圧倒されます。この場合、複数のモジュールに分割することをお勧めします。

PS私はこのアプローチをかなり長い間使用しており、コメントでその適格性を擁護し、必要に応じていくつかの例を提供する準備ができています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.