C# + WPFで、ログイン後にログイン画面を閉じる

引き続き、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 追記 ここまで

 

環境

 

仕様

  • ログイン画面でLoginボタンを押す
    • ログイン画面が閉じられ、ログイン後の画面が開く
  • ログイン画面で閉じるボタンを押す
    • プログラムが終了する

 

ログイン画面を開くコードについて

どの部分に記載すればよいかを調べみたところ、App.xaml.csに記載するのが良さそうでした。
WPF showing dialog before main window - Stack Overflow

 
そこで、Microsoft.TeamFoundation.MVVM空間を使って、

の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

名前空間を使うため、プロジェクトに以下の参照を追加します。

 

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を設定

とします。

参考:

 

    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.xamlStartupUri="...">は不要になるため、削除しておきます。

 

感想

参照設定が増えるもののEventTriggerを使う方が簡潔に書けるので、個人的にはこちらのほうが良さそうでした。

あと、EventTriggerで参照したサイトの、さらに参照先にはDialogResultにデータバインディングする方法も記載されていましたが、うまい実装方法が思いつかなかったので試しませんでした。
c# - How should the ViewModel close the form? - Stack Overflow

 

ソースコード

GitHubに上げてあります。
CSharp-Sample/MVVMApp/LoginWindowCloseMVVM at master · thinkAmi/CSharp-Sample