WPF中使用附加属性解决PasswordBox的数据绑定问题

WPF 中使用附加属性解决 PasswordBox 的数据绑定问题

1、前言

在 WPF 开发中 View 中的数据展示我们常通过 Binding 进行绑定。但是,使用 Binding 有一个前提:绑定的目标只能是依赖属性。 而 PasswordBox 控件中的 Password 并不是一个依赖属性,所以我们在使用 Password 时无法直接进行数据绑定。为了解决这个问题,我们就需要自己定义依赖属性。标题中的 “附加属性” 是依赖属性的一种特殊形式。

2、实现步骤

注:附加属性的定义方式:在 Visual Studio 中输入 propa ,然后按下两次 Tab 键即可。

2.1、定义一个 LoginPasswordBoxHelper 类,并在页面 xaml 代码中添加命名空间,该类用于辅助解决数据绑定问题。

xmlns:vm="clr-namespace:PasswordBoxDemo.ViewModel"

2.2、在类中添加用于绑定的 Password 属性

public static class LoginPasswordBoxHelper
{
    public static string GetPassword(DependencyObject obj)
    {
        return (string)obj.GetValue(PasswordProperty);
    }

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

    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.RegisterAttached("Password", typeof(string), typeof(LoginPasswordBoxHelper), new PropertyMetadata(""));
    
}

这个时候就可以在页面的 xaml 中的 PasswordBox 中添加如下数据绑定了:

<PasswordBox Width="200" Height="30"
            vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

但是,这时候只是提供了一个属性给 PasswordBox 用于 Binding,输入内容后数据没有任何更改效果。

因为当在 PasswordBox 中填写密码时,没有启动对应的事件将密码 Changed 到后端 ViewModel 中的 Password 属性

这时就需要再建一个附加属性 IsPasswordBindingEnable,用于给 PasswordBox 的更改添加事件,并在事件中更改到 后端 ViewModel 中的 Password 属性。

2.3、添加附加属性 IsPasswordBindingEnable,用于给 PasswordBox 的添加更改事件

当 IsPasswordBindingEnable="True" 时,给 PasswordBox 的 PasswordChanged 事件添加处理程序PasswordBoxPasswordChanged;

PasswordBoxPasswordChanged 作用:当页面中 PasswordBox 输入的值发生改变时,通过 SetPassword 完成数据更改,从而实现完整的数据绑定功能。

<PasswordBox Width="200" Height="30"
             vm:LoginPasswordBoxHelper.IsPasswordBindingEnable="True"
             vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public static bool GetIsPasswordBindingEnable(DependencyObject obj)
{
    return (bool)obj.GetValue(IsPasswordBindingEnableProperty);
}

public static void SetIsPasswordBindingEnable(DependencyObject obj, bool value)
{
    obj.SetValue(IsPasswordBindingEnableProperty, value);
}

public static readonly DependencyProperty IsPasswordBindingEnableProperty =
    DependencyProperty.RegisterAttached("IsPasswordBindingEnable", typeof(bool), typeof(LoginPasswordBoxHelper),
                                        new FrameworkPropertyMetadata(OnIsPasswordBindingEnabledChanged));

private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var passwordBox = obj as PasswordBox;
    if (passwordBox != null)
    {
        passwordBox.PasswordChanged -= PasswordBoxPasswordChanged;
        if ((bool)e.NewValue)
        {
            passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
        }
    }
}

static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
{
    var passwordBox = (PasswordBox)sender;
    if (!String.Equals(GetPassword(passwordBox), passwordBox.Password))
    {
        SetPassword(passwordBox, passwordBox.Password);
    }
}

3、完整代码

3.1、页面代码

Login.xaml

<Window
    x:Class="PasswordBoxDemo.Login"
    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:vm="clr-namespace:PasswordBoxDemo.ViewModel"
    Title="MainWindow"
    Width="450"
    Height="400"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox x:Name="tbUserName"
            Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Width="200" Height="30" />
        <PasswordBox Grid.Row="1" Width="200" Height="30"
            vm:LoginPasswordBoxHelper.IsPasswordBindingEnable="True"
            vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Button x:Name="btnLogin" Grid.Row="2"
            Content="登录"
            Width="150"
            Height="30" />
    </Grid>
</Window>

Login.xaml.cs

using PasswordBoxDemo.ViewModel;
using System.Windows;

namespace PasswordBoxDemo
{
    public partial class Login : Window
    {
        private MainViewModel resource;
        public Login()
        {
            InitializeComponent();
            resource = new MainViewModel();
            this.DataContext = resource;
        }

    }
}

3.2、数据绑定辅助类 LoginPasswordBoxHelper

using System;
using System.Windows;
using System.Windows.Controls;

namespace PasswordBoxDemo.ViewModel
{
    public static class LoginPasswordBoxHelper
    {
        public static string GetPassword(DependencyObject obj)
        {
            return (string)obj.GetValue(PasswordProperty);
        }

        public static void SetPassword(DependencyObject obj, string value)
        {
            obj.SetValue(PasswordProperty, value);
        }
        
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.RegisterAttached("Password", typeof(string), typeof(LoginPasswordBoxHelper), new PropertyMetadata(""));

        public static bool GetIsPasswordBindingEnable(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsPasswordBindingEnableProperty);
        }

        public static void SetIsPasswordBindingEnable(DependencyObject obj, bool value)
        {
            obj.SetValue(IsPasswordBindingEnableProperty, value);
        }
        
        public static readonly DependencyProperty IsPasswordBindingEnableProperty =
            DependencyProperty.RegisterAttached("IsPasswordBindingEnable", typeof(bool), typeof(LoginPasswordBoxHelper),
                new FrameworkPropertyMetadata(OnIsPasswordBindingEnabledChanged));

        private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var passwordBox = obj as PasswordBox;
            if (passwordBox != null)
            {
                passwordBox.PasswordChanged -= PasswordBoxPasswordChanged;
                if ((bool)e.NewValue)
                {
                    passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
                }
            }
        }

        static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
        {
            var passwordBox = (PasswordBox)sender;
            if (!String.Equals(GetPassword(passwordBox), passwordBox.Password))
            {
                SetPassword(passwordBox, passwordBox.Password);
            }
        }
    }
}

3.3、其它代码

ViewModel:

using GalaSoft.MvvmLight;

namespace PasswordBoxDemo.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
        }

        private string userName;

        public string UserName
        {
            get { return userName; }
            set { userName = value; RaisePropertyChanged(); }
        }

        private string password;

        public string Password
        {
            get { return password; }
            set { password = value; RaisePropertyChanged(); }
        }

    }
}

4、附加功能:输入框添加水印

实现水印添加也可以用类似上述的方法实现,具体步骤如下:

4.1、在 LoginPasswordBoxHelper 类中添加附加属性 ShowWaterMark,用与切换水印展示状态;

public static bool GetShowWaterMark(DependencyObject obj)
{
    return (bool)obj.GetValue(ShowWaterMarkProperty);
}

public static void SetShowWaterMark(DependencyObject obj, bool value)
{
    obj.SetValue(ShowWaterMarkProperty, value);
}

/// <summary>
/// 控制水印显示
/// </summary>
public static readonly DependencyProperty ShowWaterMarkProperty =
    DependencyProperty.RegisterAttached("ShowWaterMark", typeof(bool), typeof(LoginPasswordBoxHelper),
                                        new FrameworkPropertyMetadata(true, OnShowWaterMarkChanged));

private static void OnShowWaterMarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}

4.2、自定义水印展示样式

<Window.Resources>
    <Style x:Key="textbox" TargetType="{x:Type TextBox}">
        <Setter Property="Padding" Value="2,5,0,0"/>
        <Setter Property="FontSize" Value="14"/>
        <Style.Triggers>
            <Trigger Property="Text" Value="">
                <Setter Property="Background">
                    <Setter.Value>
                        <VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None">
                            <VisualBrush.Visual>
                                <TextBlock Padding="5,3,0,0" Background="Transparent" Foreground="Silver" FontSize="14" Text="请输入用户名"></TextBlock>
                            </VisualBrush.Visual>
                        </VisualBrush>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="password" TargetType="{x:Type PasswordBox}">
        <Setter Property="Padding" Value="2,5,0,0"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type PasswordBox}">
                    <Border Background="{TemplateBinding Background}"  BorderThickness="{TemplateBinding BorderThickness}"
                            BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true">
                        <Grid>
                            <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            <StackPanel Orientation="Horizontal" Visibility="Visible" Name="myWaterMark">
                                <TextBlock Padding="3" Background="Transparent" Foreground="Silver" FontSize="14" 
                                           Text="请输入密码"/>
                            </StackPanel>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Visibility" TargetName="myWaterMark" Value="Collapsed"/>
                        </Trigger>
                        <Trigger Property="vm:LoginPasswordBoxHelper.ShowWaterMark" Value="False">
                            <Setter Property="Visibility" TargetName="myWaterMark" Value="Collapsed"/>

                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

5、效果展示

image-20210207193228330

posted @ 2021-02-07 19:35  深秋无痕  阅读(1593)  评论(0编辑  收藏  举报