WPF Prism episode: 20 ~ Prism ダイアログに MahApps.Metro が舞い降りた! ~
前回は Prism のプロジェクトへ MahApps.Metro と Material Design In XAML Toolkit を導入する方法を紹介したので、今回は Prism から表示するメッセーボックスやダイアログを MahApps.Metro の MetroWindow で表示する方法を紹介します。
尚、この記事は Visual Studio 2019 Community Edition で .NET Core 3.0 以上 と C# + Prism 7.2 以降 + ReactiveProperty + Livet + MahApps.Metro + Material Design In XAML Toolkit を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。
目次
Material Design In XAML Toolkit の Style とコントロール
以前 episode: 6.5 で紹介したサンプルアプリは fig. 1 のような WPF 標準の良く見かける画面でした。
このサンプルアプリを元に Material Design In XAML Toolkit で装飾を軽く追加すると fig. 2 のような画面に変わります。
この UI デザインを良いと思うかどうかは置いておいて、fig. 1 の画面と比べるとかなり雰囲気が変わったと思います。
TreeView のアイコンを画像ファイルから Material Design In XAML Toolkit の【PackIcon】に変更して、TextBox へ【MaterialDesignFloatingHintTextBox】を設定しただけで fig. 2 のような画面になるので色々試してみるだけでも楽しいと思います。
MahApps.Metro と Material Design In XAML Toolkit のアイコン
episode: 5 ではアセンブリリソースのアイコンを Image コントロールへ表示する方法を紹介しましたが、Material Design In XAML Toolkit をインストールすると src. 1 のように PackIcon コントロールが使用できるようになります。
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 | <UserControl x:Class="PrismNetCoreApp.NavigationTree" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:bh="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" xmlns:rp="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NETCore" xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:ni="clr-namespace:PrismNetCoreApp.NavigationItems" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" prism:ViewModelLocator.AutoWireViewModel="True"> <UserControl.Resources> <ResourceDictionary> <HierarchicalDataTemplate x:Key="TreeItem" DataType="{x:Type ni:NavigationItemViewModel}" ItemsSource="{Binding Children}"> <StackPanel Orientation="Horizontal"> <md:PackIcon Kind="{Binding IconImage.Value}" Width="24" Height="24" Foreground="{DynamicResource PrimaryHueMidBrush}"/> <TextBlock VerticalAlignment="Center" Style="{StaticResource MaterialDesignBody1TextBlock}" Text="{Binding Text.Value}" /> </StackPanel> </HierarchicalDataTemplate> </ResourceDictionary> </UserControl.Resources> ~ 略 ~ <TreeView ItemsSource="{Binding TreeItems}" ItemTemplate="{StaticResource ResourceKey=TreeItem}"> ~ 略 ~ </TreeView> </UserControl> |
このような SVG フォーマットのアイコンは Material Design In XAML Toolkit だけでなく MahApps.Metro にも多数含まれていますし、MahApps.Metro には本体とは別に IconPack も Nuget から入手できますが、Material Design In XAML Toolkit の PackIcon 程お手軽には扱えないのでアイコンは Material Design In XAML Toolkit の方だけを使った方が良いと管理人的には思います。(いろんな種類のアイコンを使うと統一感もなくなると思いますし…)
↑上に『Material Design In XAML Toolkit の PackIcon 程お手軽には扱えない』と書いたのは間違いでした。
MahApps.Metro.IconPacks をインストールすれば Material Design In XAML Toolkit の PackIcon と同じくらい手軽に扱えます。
MahApps.Metro.IconPacks については【WPF UI Gallery case: 1-1 ~ MahApps.Metro の HamburgerMenu から Prism の RequestNavigate で画面を切り替える ~】で紹介しているので良ければ読んでみてください。
2020/2/29 追記
又、PackIcon の Foreground を指定しないと黒で描画されますが、管理人的にちょっと味気なく感じたので Foreground にプライマリカラーを指定しています。
Material Design In XAML Toolkit では src. 1 の PackIcon.Foreground に指定しているように DynamicResource に定義された下表のような 8 種類の色を指定できます。
背景色 | 前景色 | 設定内容 |
---|---|---|
PrimaryHueLightBrush | PrimaryHueLightForegroundBrush | 明るいプライマリカラー |
PrimaryHueMidBrush | PrimaryHueMidForegroundBrush | 標準プライマリカラー |
PrimaryHueDarkBrush | PrimaryHueDarkForegroundBrush | 暗めのプライマリカラー |
SecondaryAccentBrush | SecondaryAccentForegroundBrush | アクセントカラー |
前景色は標準では白か黒なので、あまり指定することはないでしょうが、背景色を良い感じに使うと画面全体に統一感が出ると思います。
FloatingHint
又、src. 1 のように TextBox の Style へ【MaterialDesignFloatingHintTextBox】を設定すると fig. 2 のように項目ラベルを兼ねたウォーターマークが追加され軽い動きまで付加されるので管理人的には重宝しそうです。
【MaterialDesignFloatingHintTextBox】を設定すると以下のようなプロパティも有効になります。
プロパティ | 設定内容 |
---|---|
HintAssist.Hint | 項目ラベル・ウォーターマークとして表示する文字列を指定します。 |
HintAssist.FloatingScale | HintAssist.Hint に設定した文字列がフローティング状態になった際のスケールを設定します。 0.8、0.9、1.2 のような指定が可能です。 |
尚、HintAssist には上表で紹介したプロパティ以外に HintAssist.FloatingOffse や HintAssist.HintOpacity 等のプロパティもありますが、読み取り専用のため設定はできません。
実際の XAML は以下の GitHub へのリンクから見てください。
Livet の WindowCloseCancelBehavior で Window Close 時にメッセージを表示する
本章では episode: 18 でインストールした LivetCask.Behaviors.WindowCloseCancelBehavior を利用して Window Close 時に問い合わせメッセージを表示する方法を紹介します。
と思って試してみた所、そのままでは使えませんでした…
WindowCloseCancelBehavior は Window の Close を Cancel するためのだけのビヘイビアのようで、Windows Form で良く見かける「アプリケーションを終了してよろしいですか?」的なメッセージボックスの戻り値によって Window の Close をコントロールできる訳ではないようです。
とは言っても、GitHub で WindowCloseCancelBehavior のソースコード を見ると 1 か所修正するだけで管理人の希望通りに動いてくれそうなのでパクってみました。
WindowCloseCancelBehavior を改造する
GitHub から WindowCloseCancelBehavior のソースコード を丸々コピーして新規に作成したビヘイビアが src. 2 の WindowClosingBehavior です。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | using System; using System.Diagnostics.CodeAnalysis; using System.Windows; using System.Windows.Input; using Livet.Behaviors; using Microsoft.Xaml.Behaviors; namespace PrismNetCoreApp.Behaviors { public class WindowClosingBehavior : Behavior<Window> { // Using a DependencyProperty as the backing store for CanClose. This enables animation, styling, binding, etc... [NotNull] public static readonly DependencyProperty CanCloseProperty = DependencyProperty.Register("CanClose", typeof(bool), typeof(WindowClosingBehavior), new PropertyMetadata(true)); // Using a DependencyProperty as the backing store for CloseCanceledCallbackCommand. This enables animation, styling, binding, etc... [NotNull] public static readonly DependencyProperty CloseCanceledCallbackCommandProperty = DependencyProperty.Register("CloseCanceledCallbackCommand", typeof(ICommand), typeof(WindowClosingBehavior), new PropertyMetadata(null)); // Using a DependencyProperty as the backing store for CloseCanceledCallbackMethodTarget. This enables animation, styling, binding, etc... [NotNull] public static readonly DependencyProperty CloseCanceledCallbackMethodTargetProperty = DependencyProperty.Register("CloseCanceledCallbackMethodTarget", typeof(object), typeof(WindowClosingBehavior), new PropertyMetadata(null)); // Using a DependencyProperty as the backing store for CloseCanceledCallbackMethodName. This enables animation, styling, binding, etc... [NotNull] public static readonly DependencyProperty CloseCanceledCallbackMethodNameProperty = DependencyProperty.Register("CloseCanceledCallbackMethodName", typeof(string), typeof(WindowClosingBehavior), new PropertyMetadata(null)); [NotNull] private readonly MethodBinder _callbackMethod = new MethodBinder(); /// <summary> /// このWindowを閉じることが可能かどうかを取得、または設定します。このプロパティがfalseを示す場合、WindowClose処理はキャンセルされます。デフォルト値はtrueです。 /// </summary> public bool CanClose { get { return (bool)(GetValue(CanCloseProperty) ?? default(bool)); } set { SetValue(CanCloseProperty, value); } } /// <summary> /// Windowを閉じる処理がこのビヘイビアによってキャンセルされた場合に実行するコマンドを取得、または設定します。 /// </summary> public ICommand CloseCanceledCallbackCommand { get { return (ICommand)GetValue(CloseCanceledCallbackCommandProperty); } set { SetValue(CloseCanceledCallbackCommandProperty, value); } } /// <summary> /// Windowを閉じる処理がこのビヘイビアによってキャンセルされた場合に実行するメソッドを持つオブジェクトを取得、または設定します。 /// </summary> public object CloseCanceledCallbackMethodTarget { get { return GetValue(CloseCanceledCallbackMethodTargetProperty); } set { SetValue(CloseCanceledCallbackMethodTargetProperty, value); } } /// <summary> /// Windowを閉じる処理がこのビヘイビアによってキャンセルされた場合に実行するメソッドの名前を取得、または設定します。 /// </summary> public string CloseCanceledCallbackMethodName { get { return (string)GetValue(CloseCanceledCallbackMethodNameProperty); } set { SetValue(CloseCanceledCallbackMethodNameProperty, value); } } protected override void OnAttached() { var associatedObject = AssociatedObject; if (associatedObject == null) throw new InvalidOperationException(); base.OnAttached(); associatedObject.Closing += (sender, e) => { if (e == null) throw new ArgumentNullException(nameof(e)); if (CanClose) return; if (CloseCanceledCallbackCommand != null && CloseCanceledCallbackCommand.CanExecute(null)) CloseCanceledCallbackCommand.Execute(null); if (CloseCanceledCallbackMethodTarget != null && CloseCanceledCallbackMethodName != null) _callbackMethod.Invoke(CloseCanceledCallbackMethodTarget, CloseCanceledCallbackMethodName); e.Cancel = !this.CanClose; }; } } } |
実際に修正が必要だったのは src. 2 の 91 行目のみで他のハイライト行はビヘイビア名(クラス名)の変更に伴う修正です。この 91 行目は元々 true 固定で VM から CanClose プロパティを変更しても反映されなかったので CanClose の値を反映するよう修正しました。
WindowClosingBehavior をサンプルアプリに組み込む
WindowClosingBehavior は WindowCloseCancelBehavior を丸ごとコピーしただけなので XAML への組み込み方は WindowCloseCancelBehavior と全く同じで、src. 3 のハイライト部分を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <metro:MetroWindow x:Class="PrismNetCoreApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls" xmlns:bh="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:local="clr-namespace:PrismNetCoreApp.Behaviors" ~ 略 ~> <bh:Interaction.Behaviors> <local:WindowClosingBehavior CanClose="{Binding CanClose.Value}" CloseCanceledCallbackMethodTarget="{Binding}" CloseCanceledCallbackMethodName="ConfirmClose"/> </bh:Interaction.Behaviors> <bh:Interaction.Triggers> <bh:EventTrigger EventName="Closed"> <l:DataContextDisposeAction /> </bh:EventTrigger> </bh:Interaction.Triggers> ~ 略 ~ </metro:MetroWindow> |
WindowCloseCancelBehavior(WindowClosingBehavior)には ICommand とバインド可能な CloseCanceledCallbackCommand も用意されていますが、src. 3 の 11 ~ 15 行目のように記述すると Livet の Core 機能を利用して DataContext から CloseCanceledCallbackMethodName に指定した名前と同じメソッドを自動的にバインドしてくれるようです。
そのため、VM へ ICommand を定義する必要もなく WPF 標準のコマンドバインディングより高速に動作するようなので、Livet のお作法に則った指定にしています。
但し、src. 3 のようにバインドすると通常のコマンドバインディングと Livet のメソッドバインディングが混在してしまうため、統一したい場合は CloseCanceledCallbackCommand を呼ぶ方が良いかもしれません。(管理人は動作未確認)
CloseCanceledCallbackMethodName と併せて ReactvieProperty で定義している CanClose プロパティもバインドしているので XAML 側の CanClose に【.Value】を付ける必要がありますが、今回も【.Value】をつけ忘れてメソッドが呼ばれない原因に気付くのにしばらくかかってしまったので皆さんは忘れないように気を付けてください(苦笑)
そして src. 3 の MainWindow.xaml とバインドする 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 | using System; using System.Reactive.Disposables; using Prism.Mvvm; using Prism.Regions; using Prism.Services.Dialogs; using Prism.Services.Dialogs.Extensions; using Reactive.Bindings; using Reactive.Bindings.Extensions; namespace PrismNetCoreApp { /// <summary>アプリケーションのメイン画面を表します。</summary> public class MainWindowViewModel : BindableBase, IDisposable { ~ 略 ~ /// <summary>WindowがClose可能かを取得します。</summary> public ReactivePropertySlim<bool> CanClose { get; } /// <summary>WindowのCloseを確認します。</summary> public void ConfirmClose() => this.CanClose.Value = this.dialogService.ShowConfirmationMessage("ウィンドウを閉じますか?") == ButtonResult.Yes; ~ 略 ~ private IDialogService dialogService = null; ~ 略 ~ /// <summary>コンストラクタ。</summary> public MainWindowViewModel(IRegionManager regionMan, IDialogService dlgService) { this.regionManager = regionMan; this.dialogService = dlgService; ~ 略 ~ this.CanClose = new ReactivePropertySlim<bool>(false) .AddTo(this.disposables); } } } |
ConfirmClose 内で呼び出しているメッセーボックスは episode: 16 で紹介したメッセーボックスの実装を丸ごとコピーしただけなのでここでは紹介しません。必要な場合は GitHub か episode: 16 を見てください。
実際の実装は src. 4 の通りですが、1 つだけ注意があって CanClose プロパティは Window.Close イベントが呼ばれる直前には必ず false に設定されている必要があることは覚えておいてください。Window.Close が呼ばれた時に true が設定されていると ConfirmClose コールバックは呼ばれません。
ここまでの状態で実行すると fig. 4 のように Window Close 時にメッセージが表示されるようになります。
fig. 4 ではメッセーボックスだけでなくダイアログウィンドウも表示していますが、見て分かる通り両ウィンドウとも WPF の標準 Window で表示されています。
メッセージボックス、ダイアログウィンドウの両プロジェクトとも MahApps.Metro と Material Design In XAML Toolkit はインストール済みなのでボタン等は Material Design In XAML Toolkit のデザインテンプレートで表示されますが、継承元の変更が必要な MetroWindow には自動で変わりません。
Prism 7.1 までは『WPF PrismのPopupWindowActionで出すダイアログをMetroWindowにする – Qiita』に書かれている方法を使っていたようですが、Prism 7.2 からはかなり簡単になりました。
Prism から表示するダイアログを MetroWindow にする
Prism 7.2 で導入された IDialogService から表示するウィンドウを MetroWindow にするにはまず、スタートアッププロジェクトへ src. 5 のような partial クラスを追加します。(クラス名は自由に付けて構いません)
1 2 3 4 5 6 7 8 9 10 11 | using System.Windows; using MahApps.Metro.Controls; using Prism.Services.Dialogs; namespace PrismNetCoreApp { public partial class PrismNetCoreAppDialogWindow : MetroWindow, IDialogWindow { public IDialogResult Result { get; set; } } } |
追加した partial クラスを継承したい Window(ここでは MetroWindow)と IDialogWindow から継承して、IDialogWindow のメンバを実装します。
スタートアッププロジェクトへ追加と書きましたが、Prism の Shell か Module プロジェクトであればどこへ追加しても構いません。(IContainerRegistry へ登録できるプロジェクトであればどのプロジェクトでも構いません)
但し、この partial クラスは 全ダイアログウィンドウで共有されるので管理人的には Shell のプロジェクトへ置くのが 1 番自然だと考えています。
そして、src. 6 のように RegisterTypes で DialogWindow として登録します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System; using System.Reflection; using System.Windows; using Prism.Ioc; using Prism.Modularity; using Prism.Mvvm; using PrismNetCoreApp.PersonalManagements; namespace PrismNetCoreApp { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App { ~ 略 ~ protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterInstance<IPrismNetCoreData>(new PrismNetCoreAgent().LoadData(this.dataFilePath)); containerRegistry.RegisterDialogWindow<PrismNetCoreAppDialogWindow>(); } } } |
ここまで設定して実行すると fig. 5 のようにメッセージボックスもダイアログウィンドウも MetroWindow で表示されるようになります。
ダイアログを外からドラッグして来ているのは呼出元 Window から離れた位置に表示されるからです。
又、表示位置だけではなくタイトルも設定されていないのでちゃんと表示されるよう設定します。
Prism Dialog Window の設定
episode: 16 でも紹介しましたが、Prism 7.2 で導入された IDialogService から表示するダイアログのプロパティは src. 7 のように XAML から指定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <UserControl x:Class="PrismNetCoreApp.MessageBoxes.ConfirmMessageBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Width="300" Height="150"> <prism:Dialog.WindowStyle> <Style TargetType="Window"> <Setter Property="ResizeMode" Value="NoResize" /> <Setter Property="ShowInTaskbar" Value="False" /> <Setter Property="SizeToContent" Value="WidthAndHeight" /> </Style> </prism:Dialog.WindowStyle> ~ 略 ~ </UserControl> |
WindowStartupLocation は Window の Load 前に設定しないと反映されないため、Window Load 後に読み込まれる UserControl に書いても意味が無いからです。
では、WindowStartupLocation のように Dialog.WindowStyle から設定できないプロパティをどこで設定するかと言うと、追加した PrismNetCoreAppDialogWindow クラスは Window の単なるコードビハインドなので src. 8 のように記述するだけで前項の問題は解決できます。
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 | using System.Windows; using MahApps.Metro.Controls; using Prism.Services.Dialogs; namespace PrismNetCoreApp { public partial class PrismNetCoreAppDialogWindow : MetroWindow, IDialogWindow { public IDialogResult Result { get; set; } /// <summary>WindowのLoadイベントハンドラ。</summary> /// <param name="sender">イベントのソース。</param> /// <param name="e">イベントデータを格納しているRoutedEventArgs。</param> private void PersonSelectWindow_Loaded(object sender, RoutedEventArgs e) { if (this.DataContext is IDialogAware) this.Title = (this.DataContext as IDialogAware).Title; this.Loaded -= this.PersonSelectWindow_Loaded; } /// <summary>コンストラクタ。</summary> public PrismNetCoreAppDialogWindow() { this.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner; this.Loaded += this.PersonSelectWindow_Loaded; } } } |
コンストラクタで WindowStartupLocation を設定して、Load イベント内で DataContext から Title プロパティを取得して自分自身の Title へ設定するよう修正しています。
src. 8 のコードを追加して実行すると fig. 6 のように変わります。
呼出元 Window の中央に表示されるようになり、タイトルも表示されるようになりました。
加えて Window 内に表示される View(UserControl)が Load される時に右から左へスライドする効果も付加されるようになるので、管理人的には結構満足しています。
ちなみに、Window 内部の View がスライドする効果は MetroWindow.WindowTransitionsEnabled で ON/OFF を切り替える事ができます。
MetroWindow のプロパティを XAML から設定する
WindowStartupLocation はプロパティの性質上、Prism ダイアログの partial から設定しましたが、ウィンドウが Load された後からでも反映可能なプロパティであれば、MetroWindow 固有のプロパティを src. 9 のように XAML から設定することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <UserControl x:Class="PrismNetCoreApp.MessageBoxes.ConfirmMessageBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Width="300" Height="150"> <prism:Dialog.WindowStyle> <Style TargetType="metro:MetroWindow"> <Setter Property="ResizeMode" Value="NoResize" /> <Setter Property="ShowInTaskbar" Value="False" /> <Setter Property="SizeToContent" Value="WidthAndHeight" /> <Setter Property="TitleCharacterCasing" Value="Normal" /> </Style> </prism:Dialog.WindowStyle> ~ 略 ~ </UserControl> |
src. 9 のように Style タグの TargetType に MetroWindow を設定すれば 13 行目のように MetroWindow 固有のプロパティを XAML から設定できます。
メッセージボックスではあまりメリットは無いかもしれませんが、MetroWindow で表示されるダイアログでは最小化ボタンや最大化ボタン、閉じるボタン等を非表示にすることもできます。
但し、episode: 19 で紹介した Window の位置やサイズを保存・復元する SaveWindowPosition に true を指定するのは注意が必要です。
SaveWindowPosition は WindowStartupLocation 等と違い Setter Property の候補にも出て来ますし実際に設定も可能ですが、単一の Window を使い回しているため位置もサイズも全ダイアログで共有されてしまう問題があるので、使用しない方が良いと思います。
このように、追加した partial クラスに IDialogWindow の継承を追加すると MahApps.Metro の MetroWindow だけでなく Infragistics の XamRibbonWindow や Fluent.Ribbon の RibbonWindow でダイアログを表示できるようになります。
おわりに
さて、これで Prism 7.2 で追加された機能については全て紹介できたと思うので、WPF Prism episode シリーズとしてはしばらくお休みをして新シリーズを開始しようと思っています。
同じようなサンプルアプリばかりで少し飽きて来たので、別のアプリを作りながら WPF について紹介記事を書いていく予定なので、今までより更新間隔がさらに長くなりそうです。
そして今回紹介したソースコードもいつものように GitHub リポジトリ に上げています。
以前、https://elf-mission.net/programming/wpf/episode12/ にて教えていただいた者です。
その節は大変お世話になりました。
episode16以降の記事を参考に、私が作成しているアプリケーションでPrismのバージョン7.2に対応させているところです。
そこでまた壁にぶつかっているので、ご教示いただけますでしょうか。
このアプリケーションにはメイン画面にメニューバーを持たせており、そこでアプリケーションを終了するという項目があります。
構成は
メイン画面(MainWindow、MainWindowViewModel)、
メニュー(MainMenuCtrl、MainMenuCtrlViewModel)
としています。
MainMenuCtrlの「アプリケーション終了」メニューからMainMenuCtrlViewModelのExitCommandが実行
→MainMenuCtrlViewModelからExitRequestを起こす
→MainMenuCtrlに設定したトリガアクションでWindowをClose
という流れにしているのですが、Prism7.2に準じてInteractionRequestを使わないようにしようと思えば
どのような実装になるのでしょうか?
読みづらくて申し訳ないのですが、下記が現在の実装です。
(そもそも現在の実装そのものがマズいのかもしれませんが)
MainMenuCtrl.xamlから抜粋
MainMenuCtrlViewModel.csから抜粋
public DelegateCommand ExitCommand { get; }
public InteractionRequest ExitRequest { get; } = new InteractionRequest();
public MainMenuCtrlViewModel()
{
ExitCommand = new DelegateCommand(() =>
{
// 終了処理を起こす
ExitRequest.Raise(null);
});
}
WindowCloseAction.csから抜粋
protected override void Invoke(object parameter)
{
Window.GetWindow(AssociatedObject)?.Close();
}
よろしくお願いいたします。
管理人です。
episode: 12 でお役に立てたようなら良かったです。
確かに Prism 7.2 からは InteractionRequest が Obsolute でマークされるので使わなくて済むなら使わなくしたいですよね…
盲点でした。
今まで VM 側から View を操作する例はあまり考えていなかったので悩む所ですが、調べてみて管理人なら以下の 2 つの内どちらかを使うと思います。
■ VMからウィンドウを閉じる添付ビヘイビア(http://sourcechord.hatenablog.com/entry/2014/04/05/225250)
■ ウィンドウを閉じるコマンドを ViewModel 側で実装する例(https://yoshiiz.blog.fc2.com/blog-entry-882.html)
実装して試してみたいと思うのは『ウィンドウを閉じるコマンドを ViewModel 側で実装する例』の方ですが、Window を VM で操作するのは微妙な気がするのと、ビヘイビアを作っておけば他のプロジェクトへ使い回しもできるので最終的には『VMからウィンドウを閉じる添付ビヘイビア』側の方法を採用するような気がします。
モヤっとするような回答しかできなくてすいません。
回答ありがとうございます。
両方の実装を試してみて、目的を達成できることを確認しました。
ただ、私も添付ビヘイビアを使う方がしっくりきましたので、こちらを採用しようと思います。
全然モヤっとするような内容ではありませんし、本当に助かりました。
ありがとうございました。