WPF Prism episode: 14 ~ ListBox 相手は ReactiveCollection、ダイアログな、Prism。 ~
2021/3/25 追記
このエントリの内容を再構成して新規連載中の .NET 5 WPF Prism MVVM 入門 2020 step: 9 で公開しました。ListBox と MVVM パターンでバインドする方法と ListBox の取り扱いについてこのエントリより詳しく紹介しています。
前回は Prism 組み込みのメッセージボックスをカスタマイズする方法を紹介したので、今回は Prism でダイアログウィンドウを表示する方法を紹介します。
2019/7/25 に Prism 7.2 がリリースされ、ダイアログの表示に使用していた INotification、IConfirmation が廃止になり、新たに IDialogService が導入されました。
【WPF Prism episode: 16】で使用方法を紹介しています。
尚、この記事は Visual Studio 2017 Community Edition で .NET Framework 4.7.2 以上 と C# + Prism 7.1 + ReactiveProperty + Extended WPF Toolkit™ を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。
Prism のダイアログウィンドウを MVVM パターンで表示する
WPF でアプリを作成する場合、Windows Form と違ってダイアログはあまり使用せず、シングルウィンドウ 内で View を動的に切り替えるような UI が主流のように感じますし、この連載でも画面遷移と言えば Prism の IRegionManager.RequestNavigate を使用した View の切替しか紹介していません。
とは言えダイアログの表示が必要な場合も当然あると思います。Prism でもダイアログを表示する方法は用意されていて、前回のエントリ で紹介した自作メッセージボックスを表示する方法を応用してダイアログウィンドウも表示することができます。
前回のエントリ で作成したメッセージボックスはコードビハインドベースで作成しましたが、今回作成するダイアログは MVVM パターンで作成します。
この連載では episode: 3 から同じサンプルアプリに手を入れ続けてきましたが、今までのサンプルアプリではダイアログを表示するための良い例が思い付かないので新しくサンプルプロジェクトを作成しました。
あくまでも別ウィンドウを表示するためだけの簡単なサンプルアプリなので、今までのように作成方法を細かには説明しません。アプリを 1 から新規作成する方法は episode: 3 から連載物として公開しているので、そちらを読んでもらえるとありがたいです。
今まで紹介してきたサンプルアプリのソリューションに【PrismDialog】プロジェクトを新規で追加しているので、実行する際はスタートアッププロジェクトを【PrismDialog】に設定してください。
Prism のダイアログウィンドウと複数行・複数列表示の ListBox
今回は最終的に下 fig. 1 のようなダイアログウィンドウを表示します。
実はダイアログウィンドウと言っても Prism ではダイアログウィンドウとメッセージボックスは同じものなので、前回のエントリで紹介した内容を応用するだけで作成できますが、それだけでは記事に取り上げる意味が無いので ReactiveCollection を ListBox へバインドする方法も併せて紹介します。
又、fig. 1 の画面は業務系ではよく見かけるポップアップする検索ダイアログですが、単純な単項目リストでは面白くないと思ったので 1 レコードを複数行・複数列で表示する ListBox にしました。
src. 1 は ListBox を置いた UserControl の XAML です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <UserControl x:Class="WpfTestApp.Views.SearchDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:prism="http://prismlibrary.com/" xmlns:rp="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NET46" xmlns:vm="clr-namespace:WpfTestApp.ViewModels" Width="640" Height="480" prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="0.85*"/> <RowDefinition Height="0.15*"/> </Grid.RowDefinitions> <Grid Grid.Row="0" Margin="0, 10, 0, 0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="30"/> <ColumnDefinition /> <ColumnDefinition Width="30"/> </Grid.ColumnDefinitions> <ListBox Grid.Column="1" ItemsSource="{Binding Characters}" HorizontalContentAlignment="Stretch" SelectedItem="{Binding SelectedItem.Value, Mode=TwoWay}" VirtualizingPanel.IsContainerVirtualizable="True" VirtualizingPanel.VirtualizationMode="Recycling"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseDoubleClick"> <rp:EventToReactiveCommand Command="{Binding ListBoxDoubleClick}" /> </i:EventTrigger> </i:Interaction.Triggers> <ListBox.ItemTemplate> <DataTemplate DataType="{x:Type vm:SearchItemViewModel}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="40"/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Row="1" Grid.Column="0" FontSize="12pt" Text="{Binding Code}" /> <TextBlock Grid.Row="0" Grid.Column="1" FontSize="9pt" Text="{Binding Yomigana.Value}" /> <TextBlock Grid.Row="1" Grid.Column="1" FontSize="16pt" Text="{Binding Name.Value}" /> <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal"> <TextBlock Width="80" FontSize="10pt" Text="{Binding Zanpakuto.Value}" /> <TextBlock Margin="10, 0, 0, 0" FontSize="10pt" Text="{Binding Bankai.Value}" /> </StackPanel> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0, 15, 30, 0" HorizontalAlignment="Right"> <Button Content="OK" Width="100" Margin="0, 0, 0, 25" Command="{Binding OkCommand}" IsDefault="True" /> <Button Content="キャンセル" Margin="10, 0, 0, 25" Width="100" IsCancel="True" /> </StackPanel> </Grid> </UserControl> |
Windows Form の ListBox で複数行・列表示するにはそれなりに手間でしたが、WPF では List 系コントロールの ItemTemplate を Grid 等で行・列方向に分割してコントロールを配置するだけで簡単に複数行・複数列の表示ができますし、表示系コントロール以外にもボタンや TextBox 等の入力系コントロールを置くこともできます。
複数行・複数列構成の List を簡単に作成できるのは Windows Form と比べて WPF の大きなアドバンテージだと思います。
※ 但し、ListView も DataGrid もヘッダの複数行・列表示はできません。
検索ダイアログの VM です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using System; using System.Collections.ObjectModel; using System.Linq; using Prism.Interactivity.InteractionRequest; using Prism.Mvvm; using Reactive.Bindings; namespace WpfTestApp.ViewModels { public class SearchDialogViewModel : BindableBase, IInteractionRequestAware { /// <summary>ListBoxのItemsSourceを取得します。</summary> public ReadOnlyReactiveCollection<SearchItemViewModel> Characters { get; } /// <summary>ListBoxの選択項目を取得・設定します。</summary> public ReactiveProperty<SearchItemViewModel> SelectedItem { get; set; } private Notifications.ISearchDialogNotification notification; /// <summary>INotificationを取得・設定します。</summary> public INotification Notification { get { return notification; } set { this.SetProperty(ref notification, (Notifications.ISearchDialogNotification)value); } } /// <summary>終了処理アクションを取得・設定します。</summary> public Action FinishInteraction { get; set; } /// <summary>OKボタンのコマンドを取得します。</summary> public ReactiveCommand OkCommand { get; } /// <summary>ListBoxのMouseDoubleClickコマンドを取得します。</summary> public ReactiveCommand ListBoxDoubleClick { get; } /// <summary>ListBoxのMouseDoubleClickイベントハンドラ。</summary> private void listBox_DoubleClick() { if (this.SelectedItem.Value == null) return; this.okButton_Click(); } /// <summary>OKボタンのClickイベントハンドラ。</summary> private void okButton_Click() { this.notification.Confirmed = true; this.notification.SelectedCharacter = this.SelectedItem.Value.TargetCharacter; this.FinishInteraction.Invoke(); } private ObservableCollection<Character> bleachCharacters { get; set; } /// <summary>コンストラクタ。</summary> public SearchDialogViewModel() { var charaList = new BleachAgent().GetAllCharacters(); this.bleachCharacters = new ObservableCollection<Character>(charaList.OrderBy(c => c.Code.Value)); this.Characters = this.bleachCharacters .ToReadOnlyReactiveCollection(c => new SearchItemViewModel(c)); this.SelectedItem = new ReactiveProperty<SearchItemViewModel>(this.Characters.First()); this.OkCommand = new ReactiveCommand(); this.OkCommand.Subscribe(() => this.okButton_Click()); this.ListBoxDoubleClick = new ReactiveCommand(); this.ListBoxDoubleClick.Subscribe(() => this.listBox_DoubleClick()); } } } |
まず、メッセージボックスを自作した時と同じく VM で IInteractionRequestAware インタフェースを継承して、ListBox の ItemsSource、SelectedItems とバインドするプロパティも作成しています。
又、ListBox の SelectedItem プロパティと MouseDoubleClick をバインドしてダイアログ起動時に ListBox の 1 行目を選択して、選択項目をダブルクリックすると選択項目が確定されるようにしています。
ワンポイント MouseDoubleClick イベントをハンドルする場合は、ListBox.HorizontalContentAlignment=”Stretch” を設定しないとマウスに反応しないエリアができてしまうので併せて設定することをお勧めします。
そして、XAML 側でキャンセルボタンの IsCancel を true に設定すると Windows Form の場合と同じくボタンクリックでダイアログが閉じるので、画面をただ閉じるだけであればキャンセルボタンの Command はバインド不要です。
(これはメッセージボックスの場合も同じ)
ReactiveCollection を List 系コントロールにバインドする rev.2
episode: 5 で紹介したように List 系コントロールとバインドする場合は、項目単位にバインドする VM(ここでは SearchItemViewModel)も作成しますがここでは ReadOnly の項目にバインドするだけの単純なものなので、GitHub リポジトリ で見てください。
List 系コントロールとのバインドは基本的に episode: 4.5 で紹介した場合と同じく、ItemsSource と ReactiveCollection をバインドしますが、episode: 4.5 で紹介しているサンプルコードは ReactiveCollection を使用する例として適していなかったと後悔してるので改めて紹介します。
src. 3 は ダイアログの VM からコンストラクタの部分のみ抜粋しました。
1 2 3 4 5 6 7 8 9 10 11 | private ObservableCollection<Character> bleachCharacters { get; set; } /// <summary>コンストラクタ。</summary> public SearchDialogViewModel() { var charaList = new BleachAgent().GetAllCharacters(); this.bleachCharacters = new ObservableCollection<Character>(charaList.OrderBy(c => c.Code.Value)); this.Characters = this.bleachCharacters .ToReadOnlyReactiveCollection(c => new SearchItemViewModel(c)); } |
episode: 5 で紹介した通り、一般的な MVVM パターンでは List 系コントロールの各項目用 VM と Model が 1 対 1 になるように作成するので、ソース変数に指定する List のメンバ(Model)と View に公開する List のメンバ(VM)の型が違うことは多々あります。
そのため、ReactiveProperty には ObservableCollection → ReadOnlyReactiveCollection に変換するための ToReadOnlyReactiveCollection 拡張メソッドが用意されています。
上のサンプルコード 9 ~ 10 行目のように書くことで ObservableCollection(bleachCharacters フィールド)を SearchItemViewModel(Characters プロパティ)に変換できます。
この変換ができて何が嬉しいかと言うと、例えばダイアログへ【追加ボタン】を追加して VM へ src. 4 のハイライト部を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | using System; using System.Collections.ObjectModel; using System.Linq; using Prism.Interactivity.InteractionRequest; using Prism.Mvvm; using Reactive.Bindings; namespace WpfTestApp.ViewModels { public class SearchDialogViewModel : BindableBase, IInteractionRequestAware { /// <summary>ListBoxのItemsSourceを取得します。</summary> public ReadOnlyReactiveCollection<SearchItemViewModel> Characters { get; } /// <summary>ListBoxの選択項目を取得・設定します。</summary> public ReactiveProperty<SearchItemViewModel> SelectedItem { get; set; } ~ 略 ~ /// <summary>追加ボタンのClickコマンドを取得します。</summary> public ReactiveCommand AddCommand { get; } /// <summary>追加ボタンのClickイベントハンドラ。</summary> private void addButtonClick() { this.bleachCharacters.Add(new Character("011", "京楽 春水", "きょうらく しゅんすい", "花天狂骨", "花天狂骨枯松心中")); } ~ 略 ~ private ObservableCollection<Character> bleachCharacters { get; set; } /// <summary>コンストラクタ。</summary> public SearchDialogViewModel() { var charaList = new BleachAgent().GetAllCharacters(); this.bleachCharacters = new ObservableCollection<Character>(charaList.OrderBy(c => c.Code.Value)); this.Characters = this.bleachCharacters .ToReadOnlyReactiveCollection(c => new SearchItemViewModel(c)); ~ 略 ~ this.AddCommand = new ReactiveCommand(); this.AddCommand.Subscribe(() => this.addButtonClick()); } } } |
addButtonClick メソッド内で bleachCharacters(ObservableCollection<Character>)へ Model を追加するだけで Characters プロパティ(ReactiveCollection)にもメンバが追加され、以下のように View にも追加されます。
このような動作になるのは ReactiveCollection 初期化時に指定する ToReadOnlyReactiveCollection のパラメータに以下のような変換式を指定しているからです。
上記の変換式を指定していると、bleachCharacters フィールドへメンバを追加した時 Characters プロパティ側にもメンバを自動で追加してくれます。
上記のような変換式を書きやすくするため、子項目用 VM のコンストラクタパラメータへ Model を定義することが必然的に多くなります。
(ReactiveProperty を使用しない場合でも構造を考えればそうなるのが当然だと思います)
ReactiveProperty の罠
前回エントリまで使用していたサンプルプロジェクトの Model は BindableBase を継承して作成していましたが、今回新たに追加した PrismDialog サンプルプロジェクトの Model は今までと異なり、バインドプロパティを ReactiveProperty で定義していますが、ダイアログの VM を書いていた時、以下のコードでドハマリしました… orz
別に何てことは無い単なる Linq の OrderBy ですが、太字にした部分の【.Value】を非常に!ひじょうに!ひっじょうに!よく忘れます!
上のコードで【.Value】を付け忘れると実行時例外:「System.ArgumentException: ‘少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。’」が飛んで来るので、Linq の仕様が変わったのかと思って小一時間悩みましたw
ReactiveProperty は使っていることを忘れるくらい直感的で出来の良いライブラリなので Model に使用する場合でもあまり意識せず使うことができるので、使っていることをついつい忘れてしまいがちです。
Linq 等の引数に ReactiveProperty を使った Model を指定して、よく分からない実行時エラー等が出た場合は【.Value】の付け忘れが無いか見直すと良いかもしれません。
ReactiveCollection の使いどころ
Model → 項目用 VM の同期もできて使い勝手が良さそうな ReactiveCollection ですが、使用するのは VM のバインドプロパティに限定するのが良い気がしています。(あくまでも現時点での管理人個人的な意見です)
そう思った 1 番の理由は Linq の戻り値を ReactiveCollection に代入できない点です。
例えば取得した Model の List を VM のフィールド(ReactiveCollection 型)へ src. 5 のように代入したくても上手くいきません。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /// <summary>ListBoxのItemsSourceを取得します。</summary> public ReadOnlyReactiveCollection<SearchItemViewModel> Characters { get; } ① ReactiveCollection<Character> col = new BleachAgent().GetAllCharacters().ToReactiveCollection(); ② ReactiveCollection<Character> col2 = new ReactiveCollection<Character>(); col2.AddRange(new BleachAgent().GetAllCharacters().OrderBy(c => c.Code.Value)); ③ col2.AddRange(new BleachAgent().GetAllCharacters()); col2 = col2.OrderBy(c => c.Code.Value); ④ this.Characters = col2.OrderBy(c => c.Code.Value) .ToReadOnlyReactiveCollection<Character>(); |
BleachAgent.GetAllCharacters() メソッドは List<Character> を返すので、① のように代入したいと思っても ToReactiveCollection 拡張メソッドは無いのでコンパイルエラーになります。
② のようにすれば ReactiveCollection への代入はできますが ③ のように代入後に Linq で加工しようとすると、これもコンパイルエラーになります。
そして ④ のようにプロパティにセットしようとしてもこれもコンパイルエラーになります… orz
管理人がやり方を知らないか、間違えているだけかもしれませんが、このように ReactiveCollection を一時保管用の変数として使用するのは少し取り回ししずらい印象を受けました。
そのため、VM のフィールドや Model の List 型プロパティには ReactiveCollection より ObservableCollection を使う方が融通が利きそうだと思っています。
ここまでは Prism から表示するダイアログ本体の作成方法を紹介してきたので、次はダイアログの呼出元ウィンドウの作成について紹介します。
ダイアログの呼出元親 Window
ダイアログの呼び出し元である MainWindow(Prism の Shell)には業務系では見かけることも多い、コードを入力すると名称を表示する UI を下 fig.3 のように配置しています。
TextBox にコードを入力すると右側の TextBlock に名称が表示され、【検索ボタン】を Click するとダイアログが表示されます。最初に書いた通り Prism ではメッセージボックスとダイアログは同じものなので、ダイアログの表示方法は基本的に episode: 12 で紹介した内容と同じですが、INotification(IConfirmation)を継承したクラスは親画面とデータをやり取りするためのプロパティ等を定義する必要があるため、通常はダイアログごとに作成します。
ダイアログと呼出元親 Window 間でデータを受け渡す役目の INotification
検索結果ダイアログ用の INotification として ISearchDialogNotification と SearchDialogNotification を作成します。この Notification はダイアログで選択した項目を取得・設定するための SelectedCharacter プロパティを 1 つ定義しているだけのシンプルなクラスなので、GitHub リポジトリ で見てください。
一言メモ インタフェースの作成は必須ではありませんが作成しておくとユニットテストの作成が楽になります。
ダイアログの表示には episode: 12 で紹介した Service を使用しますが、メッセージボックス表示 Service とは別に scr. 6 のような Service クラスを新規で作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using Prism.Interactivity.InteractionRequest; using Reactive.Bindings; namespace WpfTestApp { public class DialogService : IDialogService { public InteractionRequest<INotification> DialogRequest { get; } public DialogService() { this.DialogRequest = new InteractionRequest<INotification>(); } public MessageBoxResult ShowDialog(INotification notification) { var msgRet = MessageBoxResult.Cancel; this.DialogRequest.Raise(notification, r => { msgRet = (r as IConfirmation).Confirmed ? MessageBoxResult.OK : MessageBoxResult.Cancel; }); return msgRet; } } } |
内部の処理は episode: 12 で作成したメッセージボックス表示 Service と変わりませんが、ダイアログ表示用と分かるようなメソッド名に変更しています。
※ この Service は別のダイアログ表示にも使えるようインタフェースは汎用的な型で定義しています
又、MainWindow の VM からダイアログを呼び出す方法も episode: 12 と同じくコンストラクタへインジェクションした Service から ShowDialog メソッドを呼び出すので、ソースコードは GitHub リポジトリで見てください。
MainWindow の XAML に指定する PopupWindowAction は episode: 12 で紹介した設定と全く同じで、違うのは PopupWindowAction.WindowContent に指定するダイアログ用の View 名のみです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <Window x:Class="WpfTestApp.Views.MainWindow" ~ 略 ~ WindowStartupLocation="CenterScreen"> <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding DialogRequest}"> <prism:PopupWindowAction IsModal="True" WindowStartupLocation="CenterOwner"> <prism:PopupWindowAction.WindowStyle> <Style TargetType="Window"> <Setter Property="ShowInTaskbar" Value="False" /> <Setter Property="SizeToContent" Value="WidthAndHeight" /> <Setter Property="ResizeMode" Value="NoResize" /> </Style> </prism:PopupWindowAction.WindowStyle> <prism:PopupWindowAction.WindowContent> <views:SearchDialog /> </prism:PopupWindowAction.WindowContent> </prism:PopupWindowAction> </prism:InteractionRequestTrigger> </i:Interaction.Triggers> ~ 略 ~ </Window> |
実行して MainWindow の検索ボタンを Click するとダイアログが表示されます。
(以下はダイアログを 2 回表示しています)
WindowContent に指定したダイアログの生存期間
上のキャプチャをよく見ると 2 回目にダイアログを表示した時(キャンセルボタン Click 後の再表示)、ListBox の選択位置が先頭ではなく前回選択した位置から変わっていません。VM のコンストラクタで SelectedItem に List の先頭項目をセットしているのに選択位置は前回のままです。
これには当然原因があって、SearchDialogViewModel のコンストラクタへブレークポイントを張って実行すると一目瞭然ですが、Prism から表示するダイアログは MainWindow(ダイアログの呼び出し元)表示前にインスタンスが作成され、それをそのまま使い回しています。
Prism のダイアログは Windows Form と違ってダイアログを表示する度に新規インスタンスを作成しないので、コンストラクタに記述した処理も 1 度しか呼ばれないことが選択位置がリセットされない原因です。
Prism ではそれを回避するため『公式サンプル No.28 CustomRequest』のように INotification へバインドプロパティを定義する方法があります。
今回のエントリで紹介した PrismDialog サンプルの場合、VM へ定義していたバインドプロパティを INotification へ移して XAML のバインド定義を修正すると対応できます。
基本的に VM には IInteractionRequestAware インタフェースのメンバと OK ボタンの Command のみ残して他のバインドプロパティは INotification へ移す形になります。
INotification はダイアログを表示する度に毎回呼出元で new するので、INotification にバインドプロパティを定義すると毎回初期化されますが、その方法は Prism の公式サンプルを見て頂くとしてここでは紹介しません。
実はダイアログのインスタンス生成タイミングをコントロールする方法はもう1つあります。
WindowContentType に指定したダイアログの生存期間
Prism からダイアログを表示する際、今までは XAML の【WindowContent】へ View 名を指定していましたが、【WindowContentType】へ指定することもできます。
Prism 内部でダイアログをどのように扱っているかは『PopupWindowActionのコンテンツ指定方法WindowContentとWindowContentTypeで挙動が異なる – Qiita』に書かれていますが【WindowContentType】に表示する View 名を設定すると、InteractionRequest.Raise を呼び出した後(ここではダイアログ表示サービスの ShowDialog メソッド呼出し後)にインスタンスが作成されるようになります。
src. 8 は MainWindow.xaml の PopupWindowAction の部分を抜粋したものですが、ハイライト部を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <Window x:Class="WpfTestApp.Views.MainWindow" ~ 略 ~ xmlns:views="clr-namespace:WpfTestApp.Views" ~ 略 ~ WindowStartupLocation="CenterScreen"> <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding DialogRequest}"> <prism:PopupWindowAction IsModal="True" WindowStartupLocation="CenterOwner" WindowContentType="{x:Type views:SearchDialog}"> <prism:PopupWindowAction.WindowStyle> <Style TargetType="Window"> <Setter Property="ShowInTaskbar" Value="False" /> <Setter Property="SizeToContent" Value="WidthAndHeight" /> <Setter Property="ResizeMode" Value="NoResize" /> </Style> </prism:PopupWindowAction.WindowStyle> <!--<prism:PopupWindowAction.WindowContent> <views:SearchDialog /> </prism:PopupWindowAction.WindowContent>--> </prism:PopupWindowAction> </prism:InteractionRequestTrigger> </i:Interaction.Triggers> ~ 略 ~ </Window> |
WindowContent に指定していた部分はとりあえずコメントアウトして、10 行目の【WindowContentType】にダイアログの View を指定してサンプルアプリを実行すると下 fig. 5 のように動作が変わります。
ダイアログ起動時の ListBox は常に先頭項目が選択されるような動作に変わっています。
このようにダイアログを【WindowContent】に設定した場合と【WindowContentType】に設定した場合でダイアログの生存期間が変わるので注意が必要です。
WindowContent に設定する意味が無いように感じる人もいるかもしれませんが、WindowContent へ設定すると呼出元画面と同時に Window(ダイアログの枠部分)が Load されているため、InteractionRequest.Raise を呼び出すと一瞬で表示されるメリットがあります。
そのため、メッセージボックスの表示には WindowContent へ設定するほうが適していると言えますが、その分メモリは余計に消費するとも言えます。
管理人個人的にはメッセージボックスのように単純な画面で瞬時に表示したいダイアログを指定する場合は【WindowContent】。ダイアログウィンドウとしていくつかの操作が含まれている場合は【WindowContentType】に設定して、あとは表示速度と消費メモリのトレードオフで決めれば良いと思っています。
Prism 7.2 以降のダイアログ
episode: 12 からこのエントリまで 3 回に渡って Prism の InteractionRequest で別ウィンドウを表示する方法を紹介してきましたが、実は現時点(2019/4/24)で IInteractionRequestAware、PopupWindowAction、INotification 等今まで紹介したダイアログ表示関連のクラスに【Obsolete】が付加されています。
これは Prism 7.2 から IDialogService の導入が予定されているからで、Prism 7.2 以降ではメッセージボックスを含むダイアログの取り扱いがガラッと変わるようです。
ただ、IDialogService が導入されたとしても PopupWindowAction 等は完全に廃止される訳ではなく、IDialogService と並行して使用できるようです。
Prism 7.2 は 2019/2/21 に Preview 版がリリース されていますが、2019/4/24 時点での進捗は 33% となっているので本リリースは 1 ~ 2 か月程度先かな?と予想しています。(管理人の個人的な予想です)
Prism 7.2 が正式リリースされた後はここでも IDialogService についてのエントリを書こうとは思っていますが、Preview 版でダイアログを表示する情報が『Prism7.2(pre)の新機能 IDialogServiceを試してみた – Qiita』で紹介されています。
今回で 3 回目となった Prism から別ウィンドウを表示する方法は次回、Prism からシステムダイアログを表示する方法で最終章となる予定です。
そしていつものように今回紹介したコードも GitHub リポジトリ へ上げています。