読者です 読者をやめる 読者になる 読者になる

C# + WPFで、CommandParameterを使って、ViewModelからViewを閉じるなどの操作をしてみた

C# WPF

今回も、 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 追記 ここまで

 

環境

 

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.SystemCommandsCloseWindowメソッドを使って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については以下の記事が参考になりました。ありがとうございました。

 

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. タイトルバーをダブルクリックで最大化
  3. タイトルバーをダブルクリックで元のサイズへ戻す

と操作したところ、元のサイズへ戻っても「元に戻す」ボタンは有効なままでした。

f:id:thinkAmi:20141205045723p:plain

コンソールに出力してみると、

  • 1と2ではWindowStateが変更になり、CanExecuteMaximizeCommandメソッドが呼ばれていた
  • 3ではWindowStateは変更になったものの、CanExecuteMaximizeCommandメソッドは呼ばれなかった

という動作のようでした。

f:id:thinkAmi:20141205045657p:plain

 
その後、一度ウィンドウをクリックするとCanExecuteMaximizeCommandメソッドが呼ばれ、「元に戻す」ボタンが無効になりました。

f:id:thinkAmi:20141205045716p:plain

f:id:thinkAmi:20141205045707p:plain

 
そんな感じの怪しい動きだったため、今回のBackgroundと同じ色にする実装では、

  • WindowChromeのCaptionHeight0にする
  • WindowのWindowStartupLocationCenterScreenにする

として、ウィンドウの移動を諦める方がいいのかなとも感じました。

*1:手抜き感...