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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM