WPFでMVVMなログイン画面を作成する

#概要
・ログインID未入力で、入力欄を赤くする。
・パスワード未入力で、入力欄を赤くする。
・パスワード不正で、入力欄を赤くする。
・ログインIDもしくはパスワードの入力欄に変更があれば、入力欄の色は元に戻す。
・MVVMモデルで開発を目標とする。
　xamlのコード部分には何も書かない。

・画面遷移図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/930d0cf6-7a26-cec8-1a85-b65c4cad8b60.png)

##開発環境
・開発環境はVisualStudio
・言語はwpf(C#,Xaml)

##詳細
**ソース一覧**

|フォルダ | ファイル | 概要 | 
|:-------------|:--------------------|:------------|
|Commands | LoginBtnCommand.cs        | ログインボタン押下時の処理を記述         |
|         | LogoutBtnCommand.cs       | ログアウトボタン押下時の処理を記述       |
| Data    | LoginInfoData.cs          | ログインした人の情報を保持するもの       |
|ViewModel| LoginPageViewModel.cs     | ログイン画面のViewModel                 |
|         | MenuPageViewModel.cs      | メニュー画面のViewModel|
|Views    | MainWindow.xaml           | メインのウィンドウ(NavigationWindow)|
|         | LoginPage.xaml            | ログイン画面(Page)|
|         | MenuPage.xaml             | メニュー画面(Page)|

※参考にした記事
https://www.k-karakuri.com/entry/wpf-passwordbox
https://resanaplaza.com/%E4%B8%96%E7%95%8C%E3%81%A7%E4%B8%80%E7%95%AA%E7%9F%AD%E3%81%84%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%81%A7%E8%A6%9A%E3%81%88%E3%82%8Bmvvm%E5%85%A5%E9%96%80/

```c#:LoginBtnCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;

namespace LoginProject
{
    /// <summary>
    /// ログインボタンのコマンド
    /// </summary>
    public class LoginBtnCommand : ICommand
    {
        /// <summary>
        /// コマンドを読み出す側のクラス（View Model）を保持するプロパティ
        /// </summary>
        private LoginPageViewModel _view { get; set; }

        /// <summary>
        /// コンストラクタ
        /// 　対応するViewModelを引数に設定しておく
        /// </summary>
        /// <param name="view"></param>
        public LoginBtnCommand(LoginPageViewModel view)
        {
            _view = view;
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// /// コマンドの有効／無効を判定するメソッド
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// コマンドの動作を定義するメソッド
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {

            // 空チェック
            if (string.IsNullOrEmpty( _view.LoginId ) )
            {
                MessageBox.Show("ログインIDを入力してください");
                _view.IdColorFlg = true;
                return;
            }
            if (string.IsNullOrEmpty(_view.plainPassword))
            {
                MessageBox.Show("パスワードを入力してください");
                _view.PassColorFlg = true;
                return;
            }
            // 固定のパスワードチェック
            if (!_view.plainPassword.Equals("password"))
            {
                MessageBox.Show("IDまたはパスワードが異なります");
                _view.IdColorFlg = true;
                _view.PassColorFlg = true;
                return;
            }
            var navigationWindow = (NavigationWindow)Application.Current.MainWindow;
            var uri = new Uri("Views/MenuPage.xaml", UriKind.Relative);
            navigationWindow.Navigate(uri, parameter);
        }
    }
}
```

```c#:LogoutBtnCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;

namespace LoginProject
{
    /// <summary>
    /// ログインボタンのコマンド
    /// </summary>
    public class LogoutBtnCommand : ICommand
    {
        /// <summary>
        /// コマンドを読み出す側のクラス（View Model）を保持するプロパティ
        /// </summary>
        private MenuPageViewModel _view { get; set; }

        /// <summary>
        /// コンストラクタ
        /// 　対応するViewModelを引数に設定しておく
        /// </summary>
        /// <param name="view"></param>
        public LogoutBtnCommand(MenuPageViewModel view)
        {
            _view = view;
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// /// コマンドの有効／無効を判定するメソッド
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// 必ず実装しておくメソッド
        /// コマンドの動作を定義するメソッド
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            
            var navigationWindow = (NavigationWindow)Application.Current.MainWindow;
            var uri = new Uri("Views/LoginPage.xaml", UriKind.Relative);
            navigationWindow.Navigate(uri, parameter);
        }
    }
}
```

↓これは結局あまりいらなかった

```c#:LoginInfoData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LoginProject
{
    /// <summary>
    /// ログイン情報を保持するクラス
    /// </summary>
    public class LoginInfoData
    {
        public String LoginId { get; set; } = "";
    }
}
```

```c#:LoginPageViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Navigation;

namespace LoginProject
{
    /// <summary>
    /// ログイン画面のViewModelクラス
    /// </summary>
    public class LoginPageViewModel : INotifyPropertyChanged
    {

        // とにかくVIewModelに実装するもの
        public event PropertyChangedEventHandler PropertyChanged;

        //Modelのインスタンスを保持するプロパティ
        public LoginInfoData LoginInfoData { get; set; }

        // コマンド格納プロパティ
        public LoginBtnCommand LoginBtnCommand { get; private set; } 

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public LoginPageViewModel()
        {
            // コマンドインスタンスの生成
            LoginBtnCommand = new LoginBtnCommand(this);
            LoginInfoData = new LoginInfoData();

        }

        // パスワード背景切り替えフラグ
        private bool passColorFlg = false;
        /// <summary>
        /// ログインID背景切り替えプロパティ
        /// </summary>
        public bool PassColorFlg
        {
            get
            {
                return passColorFlg;
            }
            set
            {
                passColorFlg = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("PassColorFlg"));
                }
            }
        }

        // ログインID背景切り替えフラグ
        private bool loginIdColorFLg = false;
        /// <summary>
        /// ログインID背景切り替えプロパティ
        /// </summary>
        public bool IdColorFlg
        {
            get
            {
                return loginIdColorFLg;
            }
            set
            {
                loginIdColorFLg = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("IdColorFlg"));
                }
            }
        }

        /// <summary>
        /// ログインIDプロパティ
        /// </summary>
        public String LoginId
        {
            get
            {
                // ログインIDを返す
                return LoginInfoData.LoginId;
            }
            set
            {
                // ログイン情報クラスに格納 
                LoginInfoData.LoginId = value;

                // 変更があれば背景色クリア
                IdColorFlg = false;
                PassColorFlg = false;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("LoginId"));
                }
            }
        }

        // パスワード格納用
        public string plainPassword = string.Empty;

        /// <summary>
        /// パスワードプロパティ
        /// </summary>
        public String Password
        {
            get
            {
                return new string('*', plainPassword.Length);
            }
            set
            {
                // 変更があれば背景色クリア
                IdColorFlg = false;
                PassColorFlg = false;

                if (value.Length < plainPassword.Length)
                {
                    plainPassword = plainPassword.Substring(0, value.Length);
                }
                else if (value.Length > plainPassword.Length)
                {
                    plainPassword += value.Substring(plainPassword.Length, value.Length - plainPassword.Length);
                }
                else
                {
                    plainPassword = value;
                }

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Password"));
                }
            }
        }
    }
}
```

↓今回は特に何もしない画面なので、ほとんど記述無し
　コマンドの生成くらい

```c#:MenuPageViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LoginProject
{
    /// <summary>
    /// メニュー画面のViewModelクラス
    /// </summary>
    public class MenuPageViewModel : INotifyPropertyChanged
    {

        // とにかくVIewModelに実装するもの
        public event PropertyChangedEventHandler PropertyChanged;

        //Modelのインスタンスを保持するプロパティ
        public LoginInfoData LoginInfoData { get; set; }

        // コマンド格納プロパティ
        public LogoutBtnCommand LogoutBtnCommand { get; private set; } 

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MenuPageViewModel()
        {
            // コマンドインスタンスの生成
            LogoutBtnCommand = new LogoutBtnCommand(this);
        }

    }
}
```

```xml:LoginPage.xaml
<Page x:Class="LoginProject.LoginPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LoginProject"
        mc:Ignorable="d"
        Title="LoginWindow" Height="450" Width="800">
    <Page.DataContext>
        <local:LoginPageViewModel/>
    </Page.DataContext>
    <Grid Background="#FF5EE8D2">
        <Label Content="ログイン画面" HorizontalAlignment="Center" Margin="0,80,0,0" VerticalAlignment="Top" FontSize="36" FontFamily="HGSGothicM" FontWeight="Bold"/>
        <TextBox Text="{Binding LoginId}" HorizontalAlignment="Left" 
                 TextWrapping="Wrap" VerticalAlignment="Top" 
                 Width="238" Height="51" Margin="341,174,0,0">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Setter Property="Background" Value="White"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IdColorFlg}" Value="True">
                            <Setter Property="Background" Value="#FFE785E4"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
        <TextBox Text="{Binding Password}" HorizontalAlignment="Left" Height="56" Margin="341,244,0,0" VerticalAlignment="Top" Width="238">
            <TextBox.Style>
                <Style TargetType="TextBox">
                <Setter Property="Background" Value="White"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding PassColorFlg}" Value="True">
                        <Setter Property="Background" Value="#FFE785E4"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
         </TextBox.Style>

        </TextBox>
        <Button Content="ログイン" HorizontalAlignment="Center" 
                Margin="0,333,0,0" VerticalAlignment="Top" 
                FontSize="36" Height="59" Width="152" 
                Background="#FF264BE7" Foreground="#FFFAF9F9" 
                Command="{Binding LoginBtnCommand}"/>
        <Label Content="ログインID" HorizontalAlignment="Left" Margin="217,178,0,0" VerticalAlignment="Top" Width="119" Height="42" BorderBrush="Black" Background="{x:Null}" FontSize="24"/>
        <Label Content="パスワード" HorizontalAlignment="Left" Margin="217,251,0,0" VerticalAlignment="Top" Width="119" Height="42" BorderBrush="Black" Background="{x:Null}" FontSize="24"/>

    </Grid>
</Page>

```

```xml:MenuPage.xaml
<Page x:Class="LoginProject.MenuPage"
      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:local="clr-namespace:LoginProject"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MenuPage">
    <Page.DataContext>
        <local:MenuPageViewModel/>
    </Page.DataContext>
    <Grid Background="#FF27C22E">
        <Label Content="メニュー画面" HorizontalAlignment="Left" Margin="278,49,0,0" VerticalAlignment="Top" Height="97" Width="218" FontSize="36" RenderTransformOrigin="0.5,0.5">
            <Label.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform AngleY="0.491"/>
                    <RotateTransform/>
                    <TranslateTransform Y="0.715"/>
                </TransformGroup>
            </Label.RenderTransform>
        </Label>
        <Button Content="テスト" HorizontalAlignment="Left" Margin="151,175,0,0" VerticalAlignment="Top" Height="50" Width="121" FontSize="24"/>
        <Button Content="ログアウト" Command="{Binding LogoutBtnCommand}" HorizontalAlignment="Left" Margin="616,40,0,0" VerticalAlignment="Top" Height="50" Width="121" FontSize="24"/>

    </Grid>
</Page>

```

```xml:MainWindow.xaml
<NavigationWindow x:Class="LoginProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LoginProject"
        mc:Ignorable="d"
        Source="LoginPage.xaml"
        Title="Main" Height="550" Width="800" ShowsNavigationUI="False">

</NavigationWindow>

```

##動作確認

・初期表示画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/19d72663-a9d7-c510-a78e-25f60a18e70a.png)

・未入力だったり、パスワード誤りでメッセージ表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/6eb5e3e6-0d84-eb44-2678-99b723062f35.png)

・入力欄を赤くする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/eda9a59d-75b9-1302-b5df-2e2491ac9d86.png)

・何か入力したら、赤いのはクリア。パスワードは一応*で隠す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/5f8def78-70f2-e5b3-0365-3b5548ae477c.png)

・パスワードが合っていたら、メニュー画面表示。ログアウトを押したらログイン画面に戻る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834885/6fe06b1a-2930-158a-ed03-78651ddbb309.png)

##感想

デザインとプログラムの分担的な意味だとうまくできた気がします。
DataTrigger でコントロールの見た目を変えたるするのは何か好きです。
大した処理をしてないのに、ソースコードが長ったらしくなるのはちょっと嫌ですね。