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、效果展示

WPF 中使用附加属性解决 PasswordBox 的数据绑定问题的更多相关文章

  1. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  2. 由一次PasswordBox密码绑定引发的疑问 ---> WPF中的附加属性的定义,以及使用。

    1,前几天学习一个项目的时候,遇到了PasswordBox这个控件,由于这个控件的Password属性,不是依赖属性,所以不能和ViewModel层进行数据绑定. 2,但是要实现前后端彻底的分离,就需 ...

  3. WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)

    不知从什么时候开始,头像流行使用圆形了,于是各个平台开始追逐显示圆形裁剪图像的技术.WPF 作为一个优秀的 UI 框架,当然有其内建的机制支持这种圆形裁剪. 不过,内建的机制仅支持画刷,而如果被裁剪的 ...

  4. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  5. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  6. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  7. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  8. MVVM设计模式和在WPF中的实现(四) 事件绑定

    系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中的实现(三)命令绑定 MVVM模式解析和在WPF中的 ...

  9. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  10. WPF中使用第三方字体选择器

    原文:WPF中使用第三方字体选择器 起因 到WPF的字体可以设置的东西变得非常的多,而却没有提供专用的字体选择对话框,甚至于WinFrom的FontDialog也是不能直接用来设置WPF中的字体.解决 ...

随机推荐

  1. HDC2021技术分论坛:OpenHarmony驱动框架解读和开发实践

    转自:OpenAtom OpenHarmony    作者:yuanbo,华为高级工程师 在IoT时代下,终端设备差异较大.形态各异.尺寸各异.交互方式各异,解决设备适配问题无疑是实现万物互联的一个关 ...

  2. textfsm 案例分享

    由于安全需要,需要定期对接入层交换机配置进行合规检查,避免不规范配置存在的漏洞给公司网络带来安全风险. 如下案例是通过textfsm 提取交换机接口的配置信息,进一步进行检查准入配置是否开启: 1.首 ...

  3. 力扣1773(java&python)-统计匹配检索规则的物品数量(简单)

    题目: 给你一个数组 items ,其中 items[i] = [typei, colori, namei] ,描述第 i 件物品的类型.颜色以及名称. 另给你一条由两个字符串 ruleKey 和 r ...

  4. OpenSergo 正式开源,多家厂商共建微服务治理规范和实现

    ​简介 OpenSergo,Open 是开放的意思,Sergo 则是取了服务治理两个英文单词 Service Governance 的前部分字母 Ser 和 Go,合起来即是一个开放的服务治理项目. ...

  5. 浅谈专有云MQ存储空间的清理机制

    简介: 浅谈专有云MQ存储空间的清理机制 在近⼀年的项⽬保障过程中,对专有云MQ产品的存储⽔位清理模式⼀直存疑,总想一探究竟但又苦于工作繁忙.精力有限,直到最近⼀次项⽬保障过程中再次出现了类似的问题, ...

  6. Quick BI产品核心功能大图(六)开放集成

    ​简介:企业想要拥有领先的数据分析能力,自研往往需要投入巨大的人力和财力. Quick BI作为唯一一个连续两年入选Gartner魔力象限的中国BI产品,具备强大的全链路开放集成能力,可以轻松的与企业 ...

  7. Maxcompute造数据-方法详解

    简介: 造一点模拟数据的方法 概述 造数据在一些奇怪的场合会被用到.一般我们是先有数据才有基于数据的应用场合,但是反过来如果应用拿到另外一个场景,没有数据功能是没有方法演示的.一般较为真实的数据,脱敏 ...

  8. 七天.NET 8操作SQLite入门到实战 - (2)第七天Blazor班级管理页面编写和接口对接

    前言 上一章节我们引入BootstrapBlazor UI组件完成了EasySQLite后台界面的基本架子的搭建,本章节的主要内容是Blazor班级管理页面编写和接口对接. 七天.NET 8 操作 S ...

  9. dotnet OpenXML 文本删除线解析方法

    本文来告诉大家如何解析读取在 OpenXML 里面存放的文本删除线,本文使用 PowerPoint 作为例子来告诉大家如何读取然后在 WPF 应用里面显示 在开始之前,期望大家已了解如何在 dotne ...

  10. k8s核心资源Job&&CronJob调度pod

    一.Job与CronJob概念与与原理解读 Job 控制器用于管理 Pod 对象运行一次性任务,比方说我们对数据库备份,可以直接在 k8s 上启动一个 mysqldump 备份程序,也可以启动一个 p ...