一、自定义控件封装
Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。
1、依赖属性DependencyProperty
(1) 依赖属性原型
/* 将其他类型添加为已注册的依赖对象所有者,从而为依赖属性提供依赖属性元数据使其存在于提供的所有者类型上。
* ownerType: 要作为此依赖属性所有者添加的类型。
* typeMetadata: 在依赖属性存在于所提供的类型上时对其进行限定的元数据。
*/
public DependencyProperty AddOwner(Type ownerType);
public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata);
/* 表示可通过诸如样式、数据绑定、动画和继承等方法设置的属性。
* 方法描述: 使用指定的属性名称、属性类型、所有者类型、属性元数据和属性的值验证回叫来注册依赖属性。
* name: 属性名称
* propertyType: 属性类型
* typeMetadata: 依赖属性的属性元数据
* validateValueCallback: 对回调的引用,除了典型的类型验证之外,该引用还应执行依赖属性值的任何自定义验证。
*/
public static DependencyProperty Register(string name, Type propertyType, Type ownerType);
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata);
public static DependencyProperty Register (string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);
/* 在抽象类上注册附加属性。 此附加属性是枚举类型属性,注册会添加验证回调,以验证所提供的值是否为枚举值。
* 方法描述: 使用指定的属性类型、所有者类型、属性元数据和属性的值验证回调来注册附加属性。
* name: 属性名称
* propertyType: 属性类型
* typeMetadata: 依赖属性的属性元数据。 这可以包括默认值和其他特征。
* validateValueCallback: 对回调的引用,除了典型的类型验证之外,该引用还应执行依赖属性值的任何自定义验证。
*/
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType);
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata);
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback);
/* 官方注解
* 附加属性是由可扩展应用程序标记语言定义的属性概念, (XAML) 。 WPF 将附加属性作为依赖项属性实现。 由于 WPF 附加属性是依赖属性,因此它们可以应用元数据,常规属性系统可以使用这些元数据来实现报表布局特征等操作。
*/
/* 为此依赖属性(当它位于指定的对象实例上时)返回元数据。
* dependencyObjectType: 一个特定对象,该对象记录需要其中的依赖属性元数据的依赖项对象类型。
* forType: 要从中检索依赖属性元数据的特定类型。
* dependencyObject: 一个依赖对象,检查了其类型,以便确定元数据应来自依赖属性的哪个类型特定版本。
*/
public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType);
public PropertyMetadata GetMetadata(Type forType);
public PropertyMetadata GetMetadata(DependencyObject dependencyObject);
这里笔者仅提取了几处常用的依赖属性的使用方法,详请类方法请见:DependencyProperty 类 (System.Windows) | Microsoft Learn
(2) 自定义控件资源
<!-- 定义CheckBox 预置属性及模板相关样式 -->
<Style TargetType="CheckBox" x:Key="CheckBoxStyle">
<Setter Property="FontFamily" Value="../Assets/Fonts/#iconfont"></Setter>
<Setter Property="HorizontalAlignment" Value="Right"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<Setter Property="Margin" Value="0 0 5 0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content}"
Margin="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Margin}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
注意,此处FontFamily属性是通过 IconFont字体图标动态进行加载,具体实现方式请参考:
C#使用IconFont字体图标的动态加载(&#xe格式转换)_XBMY的博客-CSDN博客
(3) 密码框属性样式
a. TextBox可见样式
<!-- 定义密码框使用的TextBox 预置属性可见样式 -->
<Style TargetType="TextBox" x:Key="PasswordVisibleStyle">
<Setter Property="FontWeight" Value="Regular"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Foreground" Value="#303133"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border BorderBrush="#dcdfe6" CornerRadius="4" BorderThickness="1"
Background="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" VerticalAlignment="Center"
FontSize="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path= FontSize}" IsTabStop="False" Margin="2"
Background="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path= Background}" />
<!-- 清空内容 -->
<CheckBox Grid.Column="1" Style="{StaticResource CheckBoxStyle}" Content="" Foreground="#e6e6e6"
IsChecked="{Binding (local:CustPasswordBox.IsCleared),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"
Visibility ="{Binding (local:CustPasswordBox.ClearVisibility),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}" />
<!-- 显示内容 -->
<CheckBox Grid.Column="2" Style="{StaticResource CheckBoxStyle}" Content="" Foreground="#bfbfbf"
IsChecked="{Binding (local:CustPasswordBox.IsChecked),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
b. TextBox隐藏样式
<!-- 定义密码框使用的TextBox 预置属性隐藏样式 -->
<Style TargetType="PasswordBox" x:Key="PasswordBoxCollapsedStyle">
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Foreground" Value="#303133" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Border BorderBrush="#dcdfe6" CornerRadius="4" BorderThickness="1"
Background="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" VerticalAlignment="Center"
FontSize="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=FontSize}"
IsTabStop="False" Margin="2"
Background="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Background}" />
<!-- 清空内容 -->
<CheckBox Grid.Column="1" Style="{StaticResource CheckBoxStyle}" Content="" Foreground="#e6e6e6"
IsChecked="{Binding (local:CustPasswordBox.IsCleared),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"
Visibility ="{Binding (local:CustPasswordBox.ClearVisibility),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"/>
<!-- 隐藏内容 -->
<CheckBox Grid.Column="2" Style="{StaticResource CheckBoxStyle}" Content="" Foreground="#bfbfbf"
IsChecked="{Binding (local:CustPasswordBox.IsChecked),
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="#ebeef5"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
c. PasswordBox封装
<Grid>
<TextBox Style="{StaticResource PasswordVisibleStyle}"
Visibility="{Binding (local:CustPasswordBox.TBoxVisibility),RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"
Text="{Binding (local:CustPasswordBox.Password),RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<PasswordBox Style="{StaticResource PasswordBoxCollapsedStyle}"
Visibility="{Binding (local:CustPasswordBox.PwdVisibility),RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Mode=TwoWay}"
local:PasswordBoxHelper.Attach="True" local:PasswordBoxHelper.Password="{Binding (local:CustPasswordBox.Password),
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}},Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
</PasswordBox>
</Grid>
此处,传递的相关属性如下:
- ScrollViewer.Background属性,Border及ScrollViewer对象有受该属性影响;
- ScrollViewer.FontSize属性
- CheckBox.IsChecked属性:引用CustPasswordBox.IsCleared依赖
- CheckBox.Visibility属性:引用CustPasswordBox.ClearVisibility依赖
- TextBox.Visibility属性:引用CustPasswordBox.TBoxVisibility依赖
- TextBox.Text属性:引用CustPasswordBox.Password依赖
- PasswordBox.Visibility属性:引用CustPasswordBox.PwdVisibility依赖
附加属性绑定:
- PasswordBoxHelper.Attach属性
- PasswordBoxHelper.Password属性:引用CustPasswordBox.Password依赖
(4) 依赖属性定义
/// <summary>
/// CustPasswordBox.xaml 的交互逻辑
/// </summary>
public partial class CustPasswordBox : UserControl
{
public CustPasswordBox()
{
InitializeComponent();
}
/// <summary>
/// 清除显示属性
/// </summary>
public static readonly DependencyProperty ClearVisibilityProperty = DependencyProperty.Register(
"ClearVisibility", typeof(Visibility), typeof(CustPasswordBox), new PropertyMetadata(Visibility.Collapsed));
/// <summary>
/// 叉叉图标状态(隐藏/隐藏)字段
/// </summary>
public Visibility ClearVisibility
{
get => (Visibility)GetValue(ClearVisibilityProperty);
set => SetValue(ClearVisibilityProperty, value);
}
/// <summary>
/// 密码属性
/// </summary>
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
"Password", typeof(string), typeof(CustPasswordBox), new PropertyMetadata((s, e) =>
{
var sender = s as CustPasswordBox;
//根据密码框是否有内容来显示叉叉图标
sender.ClearVisibility = !string.IsNullOrEmpty(sender.Password) ? Visibility.Visible : Visibility.Collapsed;
}));
/// <summary>
/// 密码字段
/// </summary>
public string Password
{
get => (string)GetValue(PasswordProperty);
set => SetValue(PasswordProperty, value);
}
/// <summary>
/// 清空元数据的事件属性
/// </summary>
public static readonly DependencyProperty IsClearedProperty = DependencyProperty.Register(
"IsCleared", typeof(bool), typeof(CustPasswordBox), new PropertyMetadata((s, e) =>
{
var sender = s as CustPasswordBox;
sender.Password = "";
}));
/// <summary>
/// 清空元数据字段
/// </summary>
public bool IsCleared
{
get => (bool)GetValue(IsClearedProperty);
set => SetValue(IsClearedProperty, value);
}
/// <summary>
/// TBoxVisibility明文属性
/// </summary>
public static readonly DependencyProperty TBoxVisibilityProperty = DependencyProperty.Register(
"TBoxVisibility", typeof(Visibility), typeof(CustPasswordBox));
/// <summary>
/// 显示或隐藏明文字段
/// </summary>
public Visibility TBoxVisibility
{
get => (Visibility)GetValue(TBoxVisibilityProperty);
set => SetValue(TBoxVisibilityProperty, value);
}
/// <summary>
/// 密文属性
/// </summary>
public static readonly DependencyProperty PwdVisibilityProperty = DependencyProperty.Register(
"PwdVisibility", typeof(Visibility), typeof(CustPasswordBox));
/// <summary>
/// 显示或隐藏密文字段
/// </summary>
public Visibility PwdVisibility
{
get => (Visibility)GetValue(PwdVisibilityProperty);
set => SetValue(PwdVisibilityProperty, value);
}
/// <summary>
/// IsChecked事件属性
/// </summary>
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register(
"IsChecked", typeof(bool), typeof(CustPasswordBox), new PropertyMetadata((s, e) =>
{
var cpb = s as CustPasswordBox;
if ((bool)e.NewValue)
{
//Ischecked为True时
cpb.TBoxVisibility = Visibility.Visible;
cpb.PwdVisibility = Visibility.Collapsed;
}
else
{
//Ischecked为False时
cpb.TBoxVisibility = Visibility.Collapsed;
cpb.PwdVisibility = Visibility.Visible;
}
}));
/// <summary>
/// IsChecked字段
/// </summary>
public bool IsChecked
{
get => (bool)GetValue(IsCheckedProperty);
set => SetValue(IsCheckedProperty, value);
}
}
二、UserControl控件调用
1、DataContext数据源配置
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private readonly PasswordBoxViewModel PwdViewModel;
public MainWindow()
{
InitializeComponent();
PwdViewModel = new PasswordBoxViewModel();
DataContext = PwdViewModel;
}
}
/// <summary>
/// 密码框模型
/// </summary>
public class PasswordBoxViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _password;
public string Password
{
get => _password;
set
{
_password = value;
RaisePropertyChanged();
}
}
}
2. XAML前端调用
<Window xmlns:BaseUI="clr-namespace:WpfAppDemo.BaseUI"
x:Class="WpfAppDemo.MainWindow"
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:local="clr-namespace:WpfAppDemo"
d:DataContext="{d:DesignInstance Type=local:PasswordBoxViewModel}"
mc:Ignorable="d"
WindowStyle="None" WindowStartupLocation="CenterScreen"
AllowsTransparency="True" Background="Transparent"
Title="MainWindow" Height="200" Width="350" >
<Border BorderBrush="#20333333" BorderThickness="1" CornerRadius="8" Background="White" Margin="0">
<Border.Effect>
<DropShadowEffect BlurRadius="8" Color="#20333333" ShadowDepth="0"></DropShadowEffect>
</Border.Effect>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="自定义密码框:" VerticalAlignment="Center"></TextBlock>
<BaseUI:CustPasswordBox Password="{Binding Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Height="24" Width="200"></BaseUI:CustPasswordBox>
</StackPanel>
</Border>
</Window>
挑重点,其中BaseUI:CustPasswordBox需xmlns:BaseUI="clr-namespace:WpfAppDemo.BaseUI"作为引用条件,d:DataContext="{d:DesignInstance Type=local:PasswordBoxViewModel}"则为数据源前端绑定,可自动生成。
控件效果:
--END--