MVVMを使用したWPFでのダイアログの処理


235

WPFのMVVMパターンでは、ダイアログの処理はより複雑な操作の1つです。ビューモデルはビューについて何も知らないので、ダイアログ通信は興味深いものになる可能性があります。ICommandビューを呼び出すとダイアログが表示されることを公開できます。

誰かがダイアログからの結果を処理するための良い方法を知っていますか?私はのようなウィンドウダイアログについて話しているMessageBox

これを行う方法の1つは、ダイアログが必要なときにビューがサブスクライブするビューモデルのイベントを使用することでした。

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

これは問題ありませんが、ビューにはコードを必要とすることを意味します。


ビューのヘルパーオブジェクトにバインドしませんか?
ポールウィリアムズ

1
よく分からない。
Ray Booysen、2014年

1
私が質問を理解していれば、VMがダイアログをポップアップすることを望んでおらず、ビューにビハインドビハインドをしたくありません。さらに、イベントよりもコマンドを好むようです。これらすべてに同意するため、ダイアログを処理するコマンドを公開するビューのヘルパークラスを使用します。私はここで別のスレッドでこの質問に回答しました:stackoverflow.com/a/23303267/420400。しかし、最後の文は、あなたが望んでいないようにそれが聞こえる任意のすべてのコードを、どこでもビューで。私はその懸念を理解していますが、問題のコードは条件付きであり、変更される可能性はほとんどありません。
ポールウィリアムズ

4
ビューモデルは、常にダイアログボックスの作成の背後にあるロジックの責任を負う必要があります。それが、そもそもダイアログボックスが存在する理由のすべてです。とはいえ、ビュー自体を作成するという面倒な作業を行うわけではありません(すべきではありません)。このプロジェクトに関する記事をcodeproject.com/Articles/820324/…に書きました。ダイアログボックスのライフサイクル全体が、通常のWPFデータバインディングを介して、MVVMパターンを壊すことなく管理できることを示しています。
Mark Feldman、2015

回答:


131

1990年代のモーダルダイアログの代わりに、VMのブール値に関連付けられた可視性を持つオーバーレイ(キャンバス+絶対配置)としてコントロールを実装することをお勧めします。Ajaxタイプのコントロールに近い。

これは非常に便利です。

<BooleanToVisibilityConverter x:Key="booltoVis" />

のように:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

これは、ユーザーコントロールとして実装した方法です。「x」をクリックすると、ユーザーコントロールのコードビハインドにあるコード行のコントロールが閉じます。(.exeにはビューがあり、dllにはビューモデルがあるので、UIを操作するコードについて気になりません。)

WPFダイアログ


20
ええ、私もこのアイデアが好きですが、このコントロールの表示方法や、ダイアログの結果の取得方法などの例をいくつか見たいと思います。特にSilverlightのMVVMシナリオでは。
Roboblob

16
ユーザーがこのダイアログオーバーレイの下のコントロールを操作できないようにするにはどうすればよいですか?
Andrew Garrison

16
このアプローチの問題は、あなたが...少なくともないオーバーレイシステムにはいくつかの重い変更せずに、最初の1から2番目のモーダルダイアログを開くことができないということです
トーマス・レベスク

6
このアプローチのもう1つの問題は、「ダイアログ」を移動できないことです。私たちのアプリケーションでは、ユーザーが背後にあるものを確認できるように、移動可能なダイアログが必要です。
JAB 2015

12
このアプローチは私にはひどいようです。何が欠けていますか?これは実際のダイアログボックスよりも優れていますか?
ジョナサンウッド

51

これにはメディエーターを使用する必要があります。Mediatorは、一部の実装ではMessengerとも呼ばれる一般的なデザインパターンです。これはタイプRegister / Notifyのパラダイムであり、ビューモデルとビューが低結合メッセージングメカニズムを介して通信できるようにします。

Google WPF Disciplesグループを確認して、Mediatorを検索してください。あなたは答えにとても満足するでしょう...

ただし、これで開始できます。

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

楽しい !

編集:MVVM Light Toolkitを使用して、この問題に対する答えをここで確認できます。

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338


2
マーロンのグレックはちょうどメディエーターのブランドの新しい実装を掲載しています marlongrech.wordpress.com/2009/04/16/...
Roubachof

21
備考:MediatorパターンはWPF弟子によって導入されたのではなく、古典的なGoFパターンです...(dofactory.com/Patterns/PatternMediator.aspx)。そうでなければいい答え;)
トーマス・レベスク

10
神よ、調停人やとんでもないメッセンジャーを使わないでください。何十ものメッセージが飛び交うこの種のコードは、すべてのイベントをサブスクライブして処理するコードベース全体の多くのポイントを何とか覚えておかないと、デバッグが非常に難しくなります。それは新しい開発者にとって悪夢になります。実際、MvvMLightライブラリ全体は、非同期メッセージングの普及と不必要な使用のための大規模なアンチパターンであると考えています。解決策は簡単です。デザインの個別のダイアログサービス(IDialogServiceなど)を呼び出します。インターフェイスには、コールバック用のメソッドとイベントがあります。
Chris Bordeman、2015年

34

優れたMVVMダイアログは次のとおりです。

  1. XAMLのみで宣言されている。
  2. データバインディングからすべての動作を取得します。

残念ながら、WPFはこれらの機能を提供していません。ダイアログを表示するには、への分離コード呼び出しが必要ShowDialog()です。ダイアログをサポートするWindowクラスはXAMLで宣言できないため、に簡単にデータバインドできませんDataContext

これを解決するために、論理ツリーに配置され、データバインディングをに中継しWindow、ダイアログの表示と非表示を処理するXAMLスタブコントロールを作成しました。あなたはここでそれを見つけることができます:http : //www.codeproject.com/KB/WPF/XAMLDialog.aspx

これは実際に使用するだけで、ViewModelへの奇妙な変更を必要とせず、イベントやメッセージを必要としません。基本的な呼び出しは次のようになります。

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

おそらく、を設定するスタイルを追加する必要がありますShowing。私の記事でそれを説明します。これがお役に立てば幸いです。


2
これは、MVVMでダイアログウィンドウを表示する問題に対する非常に興味深いアプローチです。
dthrasher 2010年

2
"Showing a dialog requires a code-behind"mmmあなたはViewModelでそれを呼び出すことができます
Brock Hensley

ポイント3を追加します-ビュー内の他のオブジェクトに自由にバインドできます。ダイアログのコードを空のままにすることは、ビューのどこにもC#コードがないことを意味し、データバインドはVMへのバインドを意味しません。
ポールウィリアムズ

25

私は、MVVMとのダイアログにこのアプローチを使用ています。

私がしなければならないのは、ビューモデルから以下を呼び出すことだけです。

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

uiDialogServiceはどのライブラリからのものですか?
aggietech 2014

1
ライブラリはありません。小さなインターフェースと実装です:stackoverflow.com/questions/3801681/…。公平なatmになるには、私のニーズに合わせていくつかのオーバーロードがあります:)(高さ、幅、プロパティ設定など)
blindmeis

16

私の現在のソリューションは、あなたが言及した問題のほとんどを解決していますが、プラットフォーム固有のものから完全に抽象化されており、再利用できます。また、ICommandを実装するDelegateCommandsとのバインドのみの分離コードを使用しませんでした。ダイアログは基本的にビューです-独自のViewModelを持つ独立したコントロールであり、メイン画面のViewModelから表示されますが、DelagateCommandバインディングを介してUIからトリガーされます。

Silverlight 4の完全なソリューションについては、こちらのMVVMおよびSilverlight 4のモーダルダイアログをご覧ください


@Elad Katzのアプローチと同じように、あなたの回答にはリンクされたコンテンツがありません-ここでSOの良い回答と見なされるものを挿入して、回答を改善してください。それにもかかわらず、あなたの貢献に感謝します!:)
ヨーダ

6

MVVMの学習(まだ学習中)のとき、私はこの概念にしばらく苦労しました。私が決定したこと、そして他の人がすでに決定したと私が思うが、私にははっきりしなかったことはこれです:

私の当初の考えは、ダイアログの表示方法を決定するビジネスがないため、ViewModelがダイアログボックスを直接呼び出すことは許可されるべきではないというものでした。これが原因で、MVPと同じようにメッセージを渡す方法について考え始めました(つまり、View.ShowSaveFileDialog())。しかし、これは間違ったアプローチだと思います。

ViewModelがダイアログを直接呼び出すことは問題ありません。ただし、ViewModelをテストしている場合は、テスト中にダイアログがポップアップするか、すべてが失敗することになります(これを実際に試みたことはありません)。

したがって、テスト中にダイアログの「テスト」バージョンを使用する必要があります。つまり、現在のダイアログでは、インターフェイスを作成し、ダイアログの応答をモックアウトするか、デフォルトの動作を持つテストモックを作成する必要があります。

コンテキストに応じて正しいバージョンを提供するように構成できる、ある種のService LocatorまたはIoCをすでに使用しているはずです。

このアプローチを使用しても、ViewModelは引き続きテスト可能であり、ダイアログをモックアウトする方法に応じて、動作を制御できます。

お役に立てれば。


6

これを行うには2つの良い方法があります。1)ダイアログサービス(簡単、クリーン)、および2)ビュー支援。View Assistedは、いくつかの優れた機能を提供しますが、通常は価値がありません。

対話サービス

a)コンストラクターや依存関係コンテナーなどのダイアログサービスインターフェイス:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b)IDialogServiceの実装はウィンドウを開く(またはアクティブウィンドウにコントロールを挿入する)必要があります。指定されたdlgVmタイプの名前に対応するビューを作成します(コンテナー登録または規則、またはタイプに関連付けられたDataTemplatesのContentPresenterを使用)。ShowDialogAsyncはTaskCompletionSourceを作成し、その.Taskプロパティを返す必要があります。DialogViewModelクラス自体は、閉じるときに派生クラスで呼び出すことができるイベントを必要とし、ダイアログビューで監視して実際にダイアログを閉じる/非表示にし、TaskCompletionSourceを完了します。

b)使用するには、いくつかのDialogViewModel派生クラスのインスタンスでawait this.DialogService.ShowDialog(myDlgVm)を呼び出すだけです。awaitが戻った後、ダイアログVMに追加したプロパティを見て、何が起こったかを判断します。コールバックすら必要ありません。

アシストを表示

これにより、ビューがビューモデルのイベントをリッスンします。これはすべて、ブレンドビヘイビアにまとめて、コードビハインドやリソースの使用を回避することができます(FMI、「Behavior」クラスをサブクラス化して、ステロイドの一種のBlendable添付プロパティを表示する)。ここでは、各ビューでこれを手動で行います。

a)カスタムペイロード(DialogViewModel派生クラス)でOpenXXXXXDialogEventを作成します。

b)ビューのOnDataContextChangedイベントでイベントをサブスクライブします。古い値!= nullおよびウィンドウのUnloadedイベントの場合は、必ず非表示にしてサブスクライブを解除してください。

c)イベントが発生したら、ビューを開いてビューを開きます。ビューはページのリソースにある場合があります。または、慣例により(ダイアログサービスアプローチのように)場所を特定できます。

このアプローチはより柔軟ですが、使用するにはさらに多くの作業が必要です。あまり使いません。1つの優れた利点は、たとえば、ビューを物理的にタブ内に配置できることです。アルゴリズムを使用して現在のユーザーコントロールの境界に配置するか、十分に大きくない場合は、十分な大きさのコンテナーが見つかるまでビジュアルツリーを上に移動します。

これにより、ダイアログを実際に使用されている場所に近づけることができ、現在のアクティビティに関連するアプリの部分のみを暗くすることができます。ユーザーは、ダイアログを手動で押し出すことなく、アプリ内を移動することができます。モーダルダイアログは、異なるタブまたはサブビューで開きます。


ダイアログサービスははるかに簡単です。また、親ビューモデルが閉じているときやキャンセルしているときに必要な、親ビューモデルからのビューのダイアログを簡単に閉じることもできます。
Chris Bordeman、2015

4

フリーズ可能なコマンドを使用する

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

このコードにはいくつかの作業が必要ですが、特にファイルやプリンターのダイアログなどのシステムダイアログの場合、これは断然最良のアイデアです。ダイアログはビューに属しています。ファイルダイアログの場合、結果(選択したファイル名)をパラメーターとして内部コマンドに渡すことができます。
Anton Tykhyy 2017年

3

ダイアログの処理はビューの責任である必要があると思います。ビューにはそれをサポートするコードが必要です。

ViewModel-Viewインタラクションを変更してダイアログを処理する場合、ViewModelはその実装に依存しています。この問題に対処する最も簡単な方法は、ビューにタスクの実行を任せることです。それがダイアログを表示することを意味する場合は問題ありませんが、ステータスバーなどのステータスメッセージである可能性もあります。

私のポイントは、MVVMパターンの要点はビジネスロジックをGUIから分離することであるため、ビジネスレイヤー(ViewModel)で(ダイアログを表示するために)GUIロジックを混在させるべきではありません。


2
VMがダイアログを処理することは決してありません。私の例では、ダイアログが起動し、何らかの形式のEventArgsで情報を返す必要があるイベントがあります。ビューが責任を負う場合、どのようにしてVMに情報を返しますか?
レイブイセン2009年

VMが何かを削除する必要があるとしましょう。VMは、ブール値を返すビュー削除のメソッドを呼び出します。ビューは、アイテムを直接削除してtrueを返すか、ユーザーの回答に応じて確認ダイアログを表示してtrue / falseを返すことができます。
キャメロンマクファーランド

VMはダイアログについて何も認識していませんが、ビューに確認または拒否した何かを削除するように要求するだけでした。
キャメロンマクファーランド

MVVMのポイントは、モデル:ビジネスロジック、ViewModel:GUIロジック、View:ロジックなしであるといつも思っていました。これはあなたの最後の段落とはどういうわけか矛盾しています。説明してください!
デビッドシュミット

2
まず、削除前の確認がビジネスロジックなのかビューロジックなのかを判断する必要があります。ビジネスロジックの場合、モデルのDeleteFileメソッドはそれを実行するのではなく、確認の質問オブジェクトを返す必要があります。これには、実際の削除を行うデリゲートへの参照が含まれます。ビジネスロジックでない場合、VMは2つのICommandメンバーを使用して、DeleteFileCommandで問題のVMを構築する必要があります。1つは「はい」、もう1つは「いいえ」です。おそらく両方のビューについて議論があり、RLではほとんどの使用がおそらく両方に遭遇します。
なGuge


3

VMでイベントを発生させ、ビューでイベントをサブスクライブしないのはなぜですか?これにより、アプリケーションロジックとビューが分離されたままになり、ダイアログに子ウィンドウを使用できるようになります。


3

ViewModelからのメッセージをリッスンするBehaviorを実装しました。これはLaurent Bugnionソリューションに基づいていますが、背後でコードを使用せず、再利用性が高いため、よりエレガントだと思います。

WPFをそのままMVVMがサポートされているかのように動作させる方法


1
完全なコードをここに含める必要があります。これは、SOが適切な回答を得るために必要なものだからです。それにもかかわらず、リンクされたアプローチはかなりきちんとしているので、感謝します!:)
ヨーダ

2
@yoda完全なコードはかなり長いので、リンクしたいのはそのためです。私は、変更を反映するようにして壊れていないリンクを指すように私の答えを編集した
ELADカッツ

改善をありがとう。それにもかかわらず、いつかオフラインになる可能性のあるリンクよりも、コード3のフルページスクロールをここSOで長く提供する方が良いです。複雑なトピックに関する優れた記事は常にかなり長く、新しいタブを開いて切り替えてそこにスクロールしても、以前に使用していたのと同じページ/タブをスクロールしても何のメリットもありません。;)
ヨーダ

@EladKatzあなたはあなたが提供したリンクであなたのWPF実装のいくつかを共有しているのを見ました。ViewModelから新しいウィンドウを開くための解決策はありますか?基本的に2つのフォームがあり、それぞれに1つのViewModelがあります。あるユーザーがボタンをクリックすると、別のフォームがポップアップし、viewmodel1がそのオブジェクトをviewmodel2に送信します。フォーム2では、ユーザーはオブジェクトを変更でき、ウィンドウを閉じると、更新されたオブジェクトが最初のViewModelに送り返されます。これに対する解決策はありますか?
イーサン

2

ビューには、ビューモデルからのイベントを処理するコードを含めることができると思います。

イベント/シナリオによっては、ビューモデルイベントにサブスクライブするイベントトリガーと、それに応じて呼び出す1つ以上のアクションを含めることもできます。




1

Karl Shifflettは、サービスアプローチとPrism InteractionRequestアプローチを使用してダイアログボックスを表示するためのサンプルアプリケーションを作成しました。

サービスアプローチが好き-柔軟性が低く、ユーザーが何かを壊す可能性が低くなります:)アプリケーションのWinForms部分(MessageBox.Show)とも一致しますが、さまざまなダイアログを多数表示する場合は、InteractionRequestは行くより良い方法。

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/


1

古い質問であることはわかっていますが、この検索を行ったところ、関連する質問がたくさん見つかりましたが、明確な回答は見つかりませんでした。だから私はダイアログボックス/メッセージボックス/ポピンの独自の実装を作り、それを共有しています!
それは「MVVMの証拠」だと思い、シンプルかつ適切なものにしようとしていますが、WPFは初めてなので、気軽にコメントしたり、プルリクエストを行ったりすることもできます。

https://github.com/Plasma-Paris/Plasma.WpfUtils

次のように使用できます。

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

または、より洗練されたpopinが必要な場合は次のようにします。

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

そしてそれはこのようなものを示しています:

2


1

標準的なアプローチ

WPFでこの問題に対処するために何年も費やした後、私はようやくWPFでダイアログを実装する標準的な方法を理解しました。このアプローチの利点は次のとおりです。

  1. 掃除
  2. MVVM設計パターンに違反していない
  3. ViewModalがUIライブラリ(WindowBase、PresentationFrameworkなど)を参照することはありません。
  4. 自動テストに最適
  5. ダイアログは簡単に置き換えることができます。

だから鍵は何ですか。それはDI +のIoC

これがどのように機能するかです。私はMVVM Lightを使用していますが、このアプローチは他のフレームワークにも拡張できます。

  1. WPFアプリケーションプロジェクトをソリューションに追加します。それをAppと呼びます。
  2. ViewModalクラスライブラリを追加します。それをVMと呼びます
  3. アプリがVMプロジェクトを参照しています。VMプロジェクトはアプリについて何も知りません。
  4. MVVM LightへのNuGet参照を両方のプロジェクトに追加します。私は最近MVVM Light Standardを使用していますが、完全なFrameworkバージョンでも大丈夫です。
  5. インターフェイスIDialogServiceをVMプロジェクトに追加します。

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. IDialogServiceタイプのパブリック静的プロパティをで公開しますViewModelLocatorが、ビューレイヤーが実行する登録部分は残します。これが鍵です。

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. このインターフェイスの実装をAppプロジェクトに追加します。

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. これらの機能のいくつかは(ジェネリックですがShowMessageAskBooleanQuestionなど)、他の人がこのプロジェクトと使用カスタムに固有のものですWindow秒。同じ方法で、カスタムウィンドウをさらに追加できます。重要なのは、UI固有の要素をビューレイヤーに保持し、返されたデータをVMレイヤーのPOCOを使用して公開することです。
  9. このクラスを使用して、ビューレイヤーでインターフェイスにIoC登録を実行します。これは、メインビューのコンストラクターで(InitializeComponent()呼び出し後)実行できます。

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. どうぞ。これで、VMレイヤーとビューレイヤーの両方ですべてのダイアログ機能にアクセスできます。VMレイヤーは、次のようにこれらの関数を呼び出すことができます。

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. きれいに見えます。VMレイヤーは、UIレイヤーによってユーザーにはい/いいえの質問がどのように提示されるかについて何も知らず、ダイアログから返された結果を引き続き正常に処理できます。

その他の無料特典

  1. 単体テストを作成するにIDialogServiceは、テストプロジェクトでのカスタム実装を提供し、そのクラスをテストクラスのコンストラクターのIoCに登録できます。
  2. Microsoft.Win32[開く]ダイアログや[保存]ダイアログにアクセスするなど、いくつかの名前空間をインポートする必要があります。これらのダイアログのWinFormsバージョンも利用可能であり、加えて誰かが独自のバージョンを作成する可能性があるため、それらは省略しました。また、使用されてDialogPresenterいる識別子の一部は、自分のウィンドウの名前です(例:)SettingsWindow。インターフェースと実装の両方からそれらを削除するか、独自のウィンドウを提供する必要があります。
  3. VMがマルチスレッドを実行する場合DispatcherHelper.Initialize()は、アプリケーションのライフサイクルの早い段階でMVVM Lightを呼び出します。
  4. DialogPresenterどちらがビューレイヤーに挿入されるかを除いて、他のViewModalsが登録されViewModelLocator、そのタイプのパブリック静的プロパティがビューレイヤーで使用できるように公開される必要があります。このようなもの:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. ほとんどの場合、ダイアログには、DataContextのバインドや設定などの分離コードがあってはなりません。コンストラクタパラメータとして渡すこともできません。XAMLは、次のようにそれをすべて実行できます。

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. DataContextこのように設定すると、Intellisenseやオートコンプリートなど、設計時のあらゆる種類の利点が得られます。

皆さんのお役に立てば幸いです。


0

タスクまたはダイアログのビューモデルがどのように見えるべきかを尋ねたとき、私は同様の問題を考えていました。

私の現在の解決策は次のようになります:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

ビューモデルは、ユーザー入力が必要であると判断すると、ユーザーがSelectionTaskModel選択できるのインスタンスをプルアップします。インフラストラクチャが対応するビューを表示します。適切なタイミングChoose()で、ユーザーが選択した関数が呼び出されます。


0

私は同じ問題で苦労しました。ViewとViewModelを相互通信する方法を考え出しました。ViewModelからViewへのメッセージの送信を開始して、メッセージボックスを表示するように指示すると、結果が報告されます。次に、ViewModelは、ビューから返された結果に応答できます。

私は私のブログでこれを示しています


0

このトピックについてかなり包括的な記事を書き、MVVMダイアログ用のポップインライブラリも開発しました。MVVMへの厳密な準拠は可能であるだけでなく、適切に実装されている場合は非常にクリーンであり、MVVM自体に準拠していないサードパーティライブラリに簡単に拡張できます。

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM


0

申し訳ありませんが、同意する必要があります。PrismプロジェクトでPrism.Wpf.Interactivity名前空間を見つける前に、提案された解決策のいくつかを試してみました。インタラクションリクエストとポップアップウィンドウアクションを使用して、カスタムウィンドウをロールするか、より単純なニーズのために通知ポップアップと確認ポップアップが組み込まれています。これらは真のウィンドウを作成し、そのように管理されます。ダイアログで必要な依存関係を持つコンテキストオブジェクトを渡すことができます。私はそれを見つけたので、私は仕事でこのソリューションを使用します。私たちはここに多くの上級開発者を抱えており、誰もこれ以上良いものを思いついたことがありません。以前のソリューションは、オーバーレイへのダイアログサービスとそれを実現するためのプレゼンタークラスの使用でしたが、すべてのダイアログビューモデルなどのファクトリが必要でした。

これは簡単なことではありませんが、それほど複雑でもありません。また、Prismに組み込まれているため、IMHOのベスト(またはそれ以上)のプラクティスです。

私の2セント!


-1

編集:はい、これは正しいMVVMアプローチではないことに同意し、私は現在、blindmeisによって提案されているものと同様のものを使用しています。

これを実現する方法の1つは

メインビューモデル(モーダルを開く場所):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

そしてあなたのモーダルウィンドウビュー/ビューモデルで:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

またはここに投稿されたものと同様WPF MVVM:ウィンドウを閉じる方法


2
私は反対票ではありませんでしたが、それはビューモデルがビューに直接参照しているためだと思います。
ブライアンギデオン

@BrianGideon、コメントありがとうございます。これは分離されたソリューションではないことに同意します。実際、私はblindmeisによって提案されたwharに似たものを使っていません。再度、感謝します。
Simone

ビューに到達するのが非常に簡単なときにビューに到達するのは悪い形です。
Chris Bordeman、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.