WPF で PasswordBox に データバインド する方法

0 件のコメント

WPF の PasswordBox は セキュリティ上の問題から直接データバインドすることができません。 …が、セキュリティ上の問題を承知したうえで、それでもデータバインドしたい、簡便さを優先したい場合があると思います。 今回は、セキュリティ上の問題を承知したうえで、データバインドを実現する方法をまとめます。

目次

サンプルコード

ダウンロード

概要

依存関係プロパティを利用して、PasswordBox.Password を読み出す仕組みを作ります。 PasswordBox の PasswordChanged をフックし、PasswordBox.PasswordChanged が発生したとき、依存関係プロパティ Password を更新するようにします。

基本的な使い方(実装手順)は以下のようになります。

  1. PasswordBoxHelper.cs を 自プロジェクト へ コピペ
  2. XAML で 名前空間(WpfPasswordBox) を追加
  3. XAML で PasswordBoxHelper.Password に データバインド

PasswordBoxHelper クラス

PasswordProperty の生成は、DependencyProperty.Regist ではなく DependencyProperty.RegisterAttached を用いて生成します。 また、PasswordProperty を生成するとき、 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault を指定しておくと、XAML側で余計な指定( Mode=TwoWay)がなくなるので便利です。

XAML では local:PasswordBoxHelper.Password にデータバインドを行いますが、初回のみ PasswordChanged をフックできるよう確認を行います。

PasswordBoxHelper.cs

namespace WpfPasswordBox
{
    using System.Windows;
    using System.Windows.Controls;

    public class PasswordBoxHelper : DependencyObject
    {
        public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached(
            "IsAttached",
            typeof(bool),
            typeof(PasswordBoxHelper),
            new FrameworkPropertyMetadata(false, PasswordBoxHelper.IsAttachedProperty_Changed));

        public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached(
            "Password",
            typeof(string),
            typeof(PasswordBoxHelper),
            new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PasswordBoxHelper.PasswordProperty_Changed));

        public static bool GetIsAttached(DependencyObject dp)
        {
            return (bool)dp.GetValue(PasswordBoxHelper.IsAttachedProperty);
        }

        public static string GetPassword(DependencyObject dp)
        {
            return (string)dp.GetValue(PasswordBoxHelper.PasswordProperty);
        }

        public static void SetIsAttached(DependencyObject dp, bool value)
        {
            dp.SetValue(PasswordBoxHelper.IsAttachedProperty, value);
        }

        public static void SetPassword(DependencyObject dp, string value)
        {
            dp.SetValue(PasswordBoxHelper.PasswordProperty, value);
        }

        private static void IsAttachedProperty_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var passwordBox = sender as PasswordBox;

            if ((bool)e.OldValue)
            {
                passwordBox.PasswordChanged -= PasswordBoxHelper.PasswordBox_PasswordChanged;
            }

            if ((bool)e.NewValue)
            {
                passwordBox.PasswordChanged += PasswordBoxHelper.PasswordBox_PasswordChanged;
            }
        }

        private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var passwordBox = sender as PasswordBox;
            PasswordBoxHelper.SetPassword(passwordBox, passwordBox.Password);
        }

        private static void PasswordProperty_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var passwordBox = sender as PasswordBox;
            var newPassword = (string)e.NewValue;

            if (!GetIsAttached(passwordBox))
            {
                SetIsAttached(passwordBox, true);
            }

            if ((string.IsNullOrEmpty(passwordBox.Password) && string.IsNullOrEmpty(newPassword)) ||
                passwordBox.Password == newPassword)
            {
                return;
            }

            passwordBox.PasswordChanged -= PasswordBoxHelper.PasswordBox_PasswordChanged;
            passwordBox.Password = newPassword;
            passwordBox.PasswordChanged += PasswordBoxHelper.PasswordBox_PasswordChanged;
        }
    }
}

XAML記述(使い方)

ヘルパークラスを記述した名前空間を追加(xmlns:local)し、PasswordBoxタグ 内で local:PassowrdBoxHelper.Password に データバインド します。

MainView.xaml

<Window x:Class="WpfPasswordBox.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfPasswordBox"
        xmlns:vm="clr-namespace:WpfPasswordBox.ViewModel"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <Grid>
        <PasswordBox
            HorizontalAlignment="Center" 
            VerticalAlignment="Center"
            Width="160"
            local:PasswordBoxHelper.Password="{Binding Path=User.Password}"/>
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Margin="264,173,0,0"
            VerticalAlignment="Top"
            Width="74"
            Command="{Binding Path=ExecuteCommand}"/>
    </Grid>
</Window>

参考記事