はじめに
今回は WPF の PasswdBox のマスクされたパスワードの表示・非表示を MVVM で実装する方法を解説します。
設定を json ファイルで保存・読み込みを行うことを想定しているアプリケーションですが、今回は PasswdBox のみ焦点を当てます。
考え方
PasswdBox 単体では今回の実装が困難だったので、TextBox も併用します。具体的には以下の流れになります。
- PasswdBox と TextBox 重ねる
 - TextBox は常に非表示にしておき、ボタンが押されたときに表示する(バインディングされた Password プロパティのテキストを表示)
 - ボダンが押された時に、PasswordChar にNull 文字列の
\0を代入しマスクを無効化する - ボタンが離された時に、PasswordChar に
●を代入しマスクを有効化する 
実際の動きは以下のようになります。

画面の作成
XAML を以下のように実装します。その他の TextBox は省略しています。
Nuget からMicrosoft.Xaml.Behaviors.Wpfをインストールしておいてください。ボタンをクリック・クリックを離したときのイベントを指定するために使用します。
      
  
    
<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 の実装は以下のようになります。
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 ファイルに保存してアプリケーション実行時に読み込む処理まで見ていきたいと思います。
