今回も、 Microsoft.TeamFoundation.MVVM
名前空間を使って作る、WPFアプリの話です。
以前、C# + WPFで、ログイン後にログイン画面を閉じる - メモ的な思考的なのようにして、ViewModelからViewを閉じていました。
その後、WindowChromeを使った時に、ViewModelからViewを閉じたり最小化したりする必要があったため、上記とは別の方法を調べてみました。
すると、StackOverflowにCommandParameter
を使ってViewを渡す方法が書かれていました。
c# - Close Window from ViewModel - Stack Overflow
そこで、WindowChromeの練習がてら、ViewModelからViewを閉じるなどの操作を試してみました。
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を参照設定に追加
- Microsoft.TeamFoundation.Controls
View
Window要素のx:Name属性に値をセットし、それをCommandParameterのElementNameとしてバインディングします。
<Window ... x:Name="Main" /> <Grid> ... <Button ... Command="{Binding Path=CloseCommand}" CommandParameter="{Binding ElementName=Main}"/> </Grid> </Window>
ViewModel
Microsoft.TeamFoundation.MVVM.RelayCommand
では、 Action<Object>
を引数として取ることができるため、CommandParameterの値をViewModel側で使用できます。
RelayCommand クラス (Microsoft.TeamFoundation.MVVM) - MSDN
今回はCommandParamterでViewを渡しているため、ViewModelではSystem.Windows.SystemCommands
のCloseWindow
メソッドを使ってViewを閉じることができます。
// MainViewModelT4.tt private ICommand _closeCommand; public ICommand CloseCommand { get { if (_closeCommand == null) { _closeCommand = new RelayCommand(ExecuteCloseCommand); } return _closeCommand; } } // MainViewModel.cs private void ExecuteCloseCommand(object x) { if (x != null) { var window = (Window)x; SystemCommands.CloseWindow(window); } }
なお、CloseWindowメソッドではなく、WindowのCloseメソッドを使っても閉じることができます。
private void ExecuteCloseCommand(object x) { if (x != null) { var window = (Window)x; window.Close(); } }
ちなみに、以前の記事のログイン画面の場合でも、Commandparameterを使ってViewModelからViewを閉じることができました。
ソースコード
GitHubに上げました。
WindowChromeを閉じたり最小化したりする
thinkAmi-sandbox/WindowChrome-sample
ログイン画面を閉じる
以前のリポジトリのものに追加して、リポジトリの場所を移動しています。 thinkAmi-sandbox/LoginWindowCloseMVVM
WindowChromeまわりについて
以上で本題は終了ですが、ついでにWindowChromeまわりのメモも残しておきます。
WindowChromeについては以下の記事が参考になりました。ありがとうございました。
- WPF で Zune のようなウィンドウを作る | grabacr.nét
- [WPF]枠なしでリサイズ&ドラッグ移動可能なウィンドウを作る | OITA: Oika's Information Technological Activities
Styleの継承で使うBasedOnプロパティについて
WindowChromeとは直接関係ありませんが。
今回練習も兼ねていたことから、前者の記事にあったStyle(CaptionButtonStyleKey)を継承して、
- 無効なボタンのForegroundプロパティをBlackにする (Backgroundと同じ色にして見えなくする*1 )
- 閉じるボタンのForegroundプロパティをRedにする
をそれぞれ別のStyleとして作って、Viewで両方を継承しようかなと考えました。
ところが、BasedOnプロパティを使ってStyleを継承する場合、指定できるStyleは一種類のみと分かりました。
解説
各スタイルでサポートされる BasedOn 値は 1 つだけです。
Style.BasedOn プロパティ (System.Windows) - MSDN
そのため、今回は
- 元々のStyle (BaseStyle.xaml)
- BaseStyleを継承して、無効なボタンのForegroundプロパティをBlackにするStyle(InheritedStyle.xaml)
- InheritedStyleを継承して、閉じるボタンのForegroundプロパティをRedにするStyle(MainView.xaml)
という構成にしてみました。
なお、ボタンの無効化は、
// Viewにデータバインディングするコマンド private ICommand _MaximizeCommand; public ICommand MaximizeCommand { get { if (_MaximizeCommand == null) { _MaximizeCommand = new RelayCommand(ExecuteMaximizeCommand, CanExecuteMaximizeCommand); } return _MaximizeCommand; } } // RelayCommandから呼ばれるメソッドのうちの第二引数 private bool CanExecuteMaximizeCommand(object x) { if (x == null) return false; var window = (Window)x; return window.WindowState == WindowState.Maximized ? false : true; }
と、Microsoft.TeamFoundation.MVVM.RelayCommand
のコンストラクタのオーバーロードで実現しています。
別のリソースディクショナリにあるStyleの継承について
以下を参考に、別のリソースディクショナリにあるStyleを読み込みました。
XAML を分割して記述する方法 - present
タイトルバーをダブルクリックした時の挙動について
Styleでは、
<WindowChrome CaptionHeight="{x:Static SystemParameters.CaptionHeight}" ... />
と、タイトルバーを有効にしています。
この場合、最大化/元に戻すボタンの他、タイトルバーをダブルクリックした時もウィンドウのサイズが変わります。
しかし、
- ウィンドウ起動
- タイトルバーをダブルクリックで最大化
- タイトルバーをダブルクリックで元のサイズへ戻す
と操作したところ、元のサイズへ戻っても「元に戻す」ボタンは有効なままでした。
コンソールに出力してみると、
- 1と2ではWindowStateが変更になり、CanExecuteMaximizeCommandメソッドが呼ばれていた
- 3ではWindowStateは変更になったものの、CanExecuteMaximizeCommandメソッドは呼ばれなかった
という動作のようでした。
その後、一度ウィンドウをクリックするとCanExecuteMaximizeCommandメソッドが呼ばれ、「元に戻す」ボタンが無効になりました。
そんな感じの怪しい動きだったため、今回のBackgroundと同じ色にする実装では、
- WindowChromeの
CaptionHeight
を0
にする - Windowの
WindowStartupLocation
をCenterScreen
にする
として、ウィンドウの移動を諦める方がいいのかなとも感じました。
*1:手抜き感...