【MVVM】PasswordBoxでマスクされたパスワードを表示する

【MVVM】PasswordBoxでマスクされたパスワードを表示する

はじめに

今回は WPF の PasswdBox のマスクされたパスワードの表示・非表示を MVVM で実装する方法を解説します。

設定を json ファイルで保存・読み込みを行うことを想定しているアプリケーションですが、今回は PasswdBox のみ焦点を当てます。

考え方

PasswdBox 単体では今回の実装が困難だったので、TextBox も併用します。具体的には以下の流れになります。

  1. PasswdBox と TextBox 重ねる
  2. TextBox は常に非表示にしておき、ボタンが押されたときに表示する(バインディングされた Password プロパティのテキストを表示)
  3. ボダンが押された時に、PasswordChar にNull 文字列\0を代入しマスクを無効化する
  4. ボタンが離された時に、PasswordChar にを代入しマスクを有効化する

実際の動きは以下のようになります。

password-mask-show.gif

画面の作成

XAML を以下のように実装します。その他の TextBox は省略しています。

Nuget からMicrosoft.Xaml.Behaviors.Wpfをインストールしておいてください。ボタンをクリック・クリックを離したときのイベントを指定するために使用します。

Microsoft.Xaml.Behaviors.Wpf

"MainWindow.xaml"
<Window
    x:Class="JsonConfigTester.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:helper="clr-namespace:JsonConfigApp.Helper"
    xmlns:local="clr-namespace:JsonConfigTester"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="設定画面"
    MinWidth="350"
    MinHeight="400"
    MaxWidth="250"
    MaxHeight="400"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">

    <Grid>

        <GroupBox
            Margin="16"
            BorderBrush="Black"
            BorderThickness="1.3"
            Header="設定">

            <StackPanel Margin="8">
                <StackPanel>

                    <!-- 省略 -->

                    <StackPanel Margin="0,16,0,0">
                        <StackPanel>
                            <Label
                                Padding="0,5,5,5"
                                Content="パスワード"
                                FontSize="16" />
                        </StackPanel>

                        <StackPanel>

                            <!-- 要素を横並びに重ねるためにGridを配置 -->
                            <Grid Margin="0,4,0,0">

                                <PasswordBox
                                    x:Name="passwordBox"
                                    Padding="5"
                                    VerticalAlignment="Center"
                                    helper:PasswordBoxHelper.IsAttached="True"
                                    helper:PasswordBoxHelper.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
                                    AutomationProperties.IsRequiredForForm="True"
                                    FontSize="16"
                                    PasswordChar="{Binding CharNullify}" />

                                <TextBlock
                                    x:Name="showPassword"
                                    Padding="5"
                                    VerticalAlignment="Center"
                                    FontSize="16"
                                    Text="{Binding UnMaskedPassword}"
                                    Visibility="{Binding ToggleUnMaskedTextBlock}" />

                                <Button
                                    Width="30"
                                    HorizontalAlignment="Right"
                                    Content="表示"
                                    Cursor="Hand">
                                    <bh:Interaction.Triggers>
                                        <bh:EventTrigger EventName="PreviewMouseLeftButtonDown">
                                            <bh:InvokeCommandAction Command="{Binding ShowMaskedPasswordCommand}" />
                                        </bh:EventTrigger>
                                        <bh:EventTrigger EventName="PreviewMouseLeftButtonUp">
                                            <bh:InvokeCommandAction Command="{Binding HiddenMaskedPasswordCommand}" />
                                        </bh:EventTrigger>
                                    </bh:Interaction.Triggers>
                                </Button>
                            </Grid>

                        </StackPanel>
                    </StackPanel>
                </StackPanel>

            <!-- 省略 -->

            </StackPanel>

        </GroupBox>
    </Grid>
</Window>

まずは PasswordBox について見ていきます。

今回の趣旨とはそれるので詳細は省きますが、PasswordBox のPasswordプロパティのデータバインディングはサポートされていません。

そのためヘルパー関数を作成してデータバインディングを実現しています。

PasswordCharではマスクされた文字列の表示・非表示をするためのバインディングしています。

次に、TextBox のVisibilityでは、TextBox 自体の表示・非表示を制御するためのデータを ViewModel で保持します。

Button 要素では、Microsoft.Xaml.Behaviors.Wpfを使用してマウスの左クリックを押した時・話したときに実行する処理を指定しています。

ViewModel の実装

ViewModel の実装は以下のようになります。

"MainWindowViewModel.cs"
using JsonConfigApp.Command;
using JsonConfigApp.Helper;
using JsonConfigApp.Model;
using JsonConfigApp.Model.Repository;
using JsonConfigTester;
using System;
using System.Diagnostics;
using System.Text;
using System.Windows;

namespace JsonConfigApp.ViewModel
{
    public sealed class MainWindowViewModel : BindableBase
    {
        // 省略

        // PasswordBoxの文字列をデータバインディングするプロパティ
        public string Password
        {
            get => _password;
            set => SetProperty(ref _password, value);
        }
        private string _password;

        // マスクされた文字列の表示・非表示を制御
        public string UnMaskedPassword
        {
            get => _unMaskedPassword;
            set => SetProperty(ref _unMaskedPassword, value);
        }
        private string _unMaskedPassword;

        // Textboxの表示・非表示を制御
        public Visibility ToggleUnMaskedTextBlock
        {
            get => _toggleUnMaskedTextBlock;
            set => SetProperty(ref _toggleUnMaskedTextBlock, value);
        }
        // 初期値は非表示に設定
        private Visibility _toggleUnMaskedTextBlock = Visibility.Collapsed;

        // マスクされた文字列の表示・非表示を制御
        // デフォルトでは●を指定して文字列をマスキングする
        public char CharNullify
        {
            get => _charNullify;
            set => SetProperty(ref _charNullify, value);
        }
        private char _charNullify = '●';

        // 左クリックが押されたときに実行されるコマンド
        public DelegateCommand ShowMaskedPasswordCommand
            => _showMaskedPasswordCommand ?? (_showMaskedPasswordCommand = new DelegateCommand(OnShowMaskedPassword));
        private DelegateCommand _showMaskedPasswordCommand;

        // 左クリックが話されたときに実行されるコマンド
        public DelegateCommand HiddenMaskedPasswordCommand
            => _hiddenMaskedPasswordCommand ?? (_hiddenMaskedPasswordCommand = new DelegateCommand(OnHiddenMaskedPassword));
        private DelegateCommand _hiddenMaskedPasswordCommand;

        // コンストラクタ内の処理は今回の実装には関係ないので省略

        // 左クリックが押されたときの処理
        private void OnShowMaskedPassword()
        {
            UnMaskedPassword = Password;
            ToggleUnMaskedTextBlock = Visibility.Visible;
            CharNullify = '\0'; // マスクを無効化
        }

        // 左クリックが話されたときの処理
        private void OnHiddenMaskedPassword()
        {
            UnMaskedPassword = string.Empty;
            ToggleUnMaskedTextBlock = Visibility.Collapsed;
            CharNullify = '●'; // マスクを有効化
        }
    }
}

最後に

次回は設定を json ファイルに保存してアプリケーション実行時に読み込む処理まで見ていきたいと思います。