引き続き、Microsoft.TeamFoundation.MVVM
名前空間を使って作る、WPFアプリの話です。
前回、ログイン後に権限のチェックを行ってみましたが、ログイン画面が残ったままログイン後の画面を表示していました。
WindowsFormではLoad
イベントの中でShowDIalog()
してログイン画面を閉じるようなことをしていましたが、WPFではどのようにやるのかを試してみました。
2015/12/10 追記 ここから
公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVM
の使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs
2015/12/10 追記 ここまで
環境
- Windows7
- .NET Framework 4.5
- 以下のdllを参照設定に追加
仕様
- ログイン画面でLoginボタンを押す
- ログイン画面が閉じられ、ログイン後の画面が開く
- ログイン画面で閉じるボタンを押す
- プログラムが終了する
ログイン画面を開くコードについて
どの部分に記載すればよいかを調べみたところ、App.xaml.cs
に記載するのが良さそうでした。
WPF showing dialog before main window - Stack Overflow
そこで、Microsoft.TeamFoundation.MVVM
空間を使って、
- DataContextChangedイベントハンドラを使う方法
- ViewでEventTrigerを使う方法
の2つを試してみました。
ViewでDataContextChangedイベントハンドラを使う方法
まずは、書籍「ひと目でわかる Visual C# 2013/2012 アプリケーション開発入門 (MSDNプログラミングシリーズ)」に記載されていた、DataContextChangedイベントハンドラを使用する方法を試してみました。
ViewModel
- VIewのCloseメソッドと紐付けるActionデリゲート
CloseView
を用意 - RelayCommandで呼ばれるExecuteLoginCommandメソッドの中で
CloseView
デリゲートを呼ぶ
public Action<bool> CloseView { get; set; } private void ExecuteLoginCommand(object x) { var identity = new GenericIdentity("DataContextChanged"); var principal = new GenericPrincipal(identity, new string[] { "fuga" }); Thread.CurrentPrincipal = principal; CloseView(true); }
ViewModel
XAML
ButtonのCommandにViewModelのICommand型のプロパティをデータバインディングします。
<Button Command="{Binding Path=LoginCommand}"/>
コードビハインド
DataContextChangedイベントハンドラの中で、ViewのDialogResultとViewModelのActionデリゲート(CloseView)を紐付けます。
public LoginDataContextChangedView() { DataContextChanged += (s, e) => { var x = DataContext as LoginDataContextChangedViewModel; x.CloseView = (p) => DialogResult = p; }; InitializeComponent(); }
なお、WPFでは、ShowDialogで開いたWindowに対してDialogResultに値を設定すれば、自動的にWindowが閉じます。
ダイアログ ボックスの [OK] ボタンと [キャンセル] ボタンも開発者によって提供されます。また、これらのボタンによって DialogResult が設定される場合があります。このプロパティは、ShowDialog の呼び出しによって開いたウィンドウを自動的に閉じます。
Window.Close メソッド (System.Windows) - MSDN
ViewでEventTrigerを使う方法
DataContextChangedを使った実装はできたものの、コードがいろいろな場所に散らばっているので、メンテナンスのことを考えると大変そうでした。
そのため、以下を参考にEventTrigerを使う方法を試してみました。
依存関係プロパティが無いなら添付プロパティを作ればいいじゃない! ってそんな甘くないか - ABCの海岸で
参照の追加
上記の記事にもある通り、XAMLにて
System.Windows.Interactivity.EventTrigger
Microsoft.Expression.Interactivity.Core.ChangePropertyAction
の名前空間を使うため、プロジェクトに以下の参照を追加します。
System.Windows.Interactivity 4.5 (System.Windows.Interactivity.EventTrigger向け) EventTrigger Class (System.Windows.Interactivity) - MSDN
Microsoft.Expression.Interactions 4.5 (Microsoft.Expression.Interactivity.Core.ChangePropertyAction向け) ChangePropertyAction クラス (Microsoft.Expression.Interactivity.Core) - MSDN
ViewModel
Actionデリゲートが不要になり、簡潔になりました。あとは同じため、省略します。
View
XAML
上記の参考ページとほぼ同じです...。
なお、コードビハインドは不要になります。
<Window x:Class="LoginWindowCloseMVVM.LoginEventTrigerView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:activity="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:actions="http://schemas.microsoft.com/expression/2010/interactions" xmlns:local="clr-namespace:LoginWindowCloseMVVM" x:Name="LoginWindow" Title="LoginEventTrigerView" Height="100" Width="200"> <Window.DataContext> <local:LoginEventTrigerViewModel/> </Window.DataContext> <Grid> <Button Content="Login1" IsDefault="True" Margin="10"> <activity:Interaction.Triggers> <activity:EventTrigger EventName="Click"> <!-- コマンド実行 --> <activity:InvokeCommandAction Command="{Binding Path=LoginCommand}"/> <!-- WindowのDialogResultを設定して、Windowを閉じる --> <actions:ChangePropertyAction TargetObject="{Binding ElementName=LoginWindow}" PropertyName="DialogResult" Value="True"/> </activity:EventTrigger> </activity:Interaction.Triggers> </Button> </Grid> </Window>
App.xaml.cs
上記2つのパターンを試せるように、App.xaml.csに実装します。
なお、ログイン画面が閉じられた時にアプリケーションが終了しないよう、
- ログイン画面を開く前に
Current.ShutdownMode
プロパティにOnExplicitShutdown
を設定 - ログイン画面を閉じた後には
OnMainWindowClose
を設定
とします。
参考:
- Application.ShutdownMode プロパティ (System.Windows) - MSDN
- Application.Current プロパティ (System.Windows) - MSDN
public App(): base() { // XAMLに書いたEventTrigerを使用する方法(コードビハインド無し) Startup += (s, e) => Login(new LoginView()); // コードビハインドのDataContextChangedイベントを使用する方法 //Startup += (s, e) => Login(new LoginDataContextChangedView()); } private void Login(System.Windows.Window loginView) { // ログイン画面が閉じられた時にアプリケーションが終了しないよう、OnExplicitShutdownを設定しておく Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; var isLoggedin = loginView.ShowDialog() ?? false; var isAuthenticated = System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated; if (isLoggedin && isAuthenticated) { // MainWindowが閉じられた時にアプリケーションが終了するように変更 Current.ShutdownMode = ShutdownMode.OnMainWindowClose; var vm = new LoggedinViewModel(); vm.UserName = Thread.CurrentPrincipal.Identity.Name; var loggedinView = new LoggedinView(); loggedinView.DataContext = vm; Current.MainWindow = loggedinView; loggedinView.ShowDialog(); } else { // OnExplicitShutdownの場合、明示的なShutdown()呼び出しが必要 Current.Shutdown(-1); } } }
また、App.xaml.csにStartupイベントハンドラを実装したことから、App.xamlのStartupUri="...">
は不要になるため、削除しておきます。
感想
参照設定が増えるもののEventTriggerを使う方が簡潔に書けるので、個人的にはこちらのほうが良さそうでした。
あと、EventTriggerで参照したサイトの、さらに参照先にはDialogResult
にデータバインディングする方法も記載されていましたが、うまい実装方法が思いつかなかったので試しませんでした。
c# - How should the ViewModel close the form? - Stack Overflow
ソースコード
GitHubに上げてあります。
CSharp-Sample/MVVMApp/LoginWindowCloseMVVM at master · thinkAmi/CSharp-Sample