初学者最简单的WPF依赖属性“ValidState”





5.00/5 (8投票s)
这是“初学者最简单的WPF依赖属性‘背景颜色’”的替代方案
引言
这是一篇关于 WPF 依赖属性的入门文章,旨在展示它们快速易用的特性。
背景
有一篇文章让我觉得,对于不理解该主题的读者,反而比阅读前更困惑。因此,我将尝试解决这个问题,并希望在本文结束时,能够解释依赖属性的优点。
那么,什么是依赖属性?
根据 官方文档
引用表示可以通过样式、数据绑定、动画和继承等方法设置的属性。
但我更喜欢 Matt Hamilton 的解释
引用依赖属性 是派生自 DependencyObject 的类的属性,它们很特别,因为它们不只是使用后备字段来存储值,而是使用 DependencyObject 上的一些辅助方法。它们最棒的地方在于,它们内置了所有用于数据绑定的支持。
那么,什么是 DependencyObject?同样,根据 官方文档
引用表示一个参与依赖属性系统的对象。
我更喜欢 这个解释
引用Dependency object 是所有 WPF 对象的基础对象。所有 UI 元素,如 Buttons、TextBox 等,以及 Content 元素,如 Paragraph、Italic、Span 等,都派生自 Dependency Object。Dependency objects 用于 WPF 属性系统。
因此,DependencyObject 为样式、数据绑定、动画系统等实现了所有底层支持,这些对开发者来说是透明的,而 Dependency Properties 则是用于存储(Set)和检索(Get)相关数据的访问点。
如何实施
实现比标准属性稍微复杂一些,但如上所述,它们能做更多的事情。实现它需要两个步骤:
- 我们需要注册 Dependency Property
- 我们需要实现 getter 和 setter
幸运的是,Visual Studio 可以通过代码片段轻松完成。对于 C#,我们有 `propdb`。对于 VB,可以使用 `CTRL-K, CTRL-X` > `WPF` > `"Add a Dependency Property Registration"`。以下是自动生成的代码示例:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty",
typeof(int),
typeof(ownerclass),
new PropertyMetadata(0));
Public Property Prop1 As String
Get
Return GetValue(Prop1Property)
End Get
Set(ByVal value As String)
SetValue(Prop1Property, value)
End Set
End Property
Public Shared ReadOnly Prop1Property As DependencyProperty =
DependencyProperty.Register("Prop1",
GetType(String), GetType(),
New PropertyMetadata(Nothing))
依赖属性 可用于扩展现有控件的功能(无外观继承),向 Usercontrols(模板)、Behaviors 和 Markup Extensions 添加属性,从而在不继承的情况下添加或修改现有控件。
为了本文的目的,我将继承 TextBox 控件并添加一个 Valid 输入状态。请看下图:
控件包含两个部分:
- 文本输入区域
- 验证状态的视觉指示器
我们需要能够:
- 跟踪验证状态:待定(Pending)、有效(Valid)和无效(Invalid)。
- 为每个验证状态指定一个图像和样式。
所以我们需要 1 个 Dependency Properties:ValidState - 用于跟踪验证状态:待定、有效和无效。
现在我们可以创建自定义控件了。在这里,我创建了一个无外观控件并扩展了 TextBox 控件。
public class ValidateDataEntry : TextBox
{
static ValidateDataEntry()
{
DefaultStyleKeyProperty.OverrideMetadata(ctrlType, new FrameworkPropertyMetadata(ctrlType));
}
private static readonly Type ctrlType = typeof(ValidateDataEntry);
private const string ctrlName = nameof(ValidateDataEntry);
public static readonly DependencyProperty ValidationStateProperty =
DependencyProperty.Register(nameof(ValidationState),
typeof(ValidationStateType), ctrlType, new PropertyMetadata(ValidationStateType.Pending));
public ValidationStateType ValidationState
{
get { return (ValidationStateType)GetValue(ValidationStateProperty); }
set { SetValue(ValidationStateProperty, value); }
}
}
有了控件后,我们需要对其进行样式设置。我采用了标准的 TextBox 模板,并添加了使用我们新 Dependency Properties 的元素。为此,我将一个普通的 TextBox 添加到 MainWindow,然后右键单击 XAML 编辑器中的 TextBox,选择:“编辑样式” > “编辑副本”,即可生成默认样式。这是我们从 Themes 文件夹中的 Generic.Xaml ResourceDistionary 修改后的 Xaml(为简洁起见已裁剪):
<ControlTemplate x:Key="ValidateDataEntryTemplate" TargetType="{x:Type cc:ValidateDataEntry}">
<Grid x:Name="root" UseLayoutRounding="True" SnapsToDevicePixels="True">
<Border x:Name="border" Margin="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
<Grid Grid.Column="1">
<Path x:Name="Valid"
Style="{StaticResource ValidStyle}"
Data="{StaticResource ValidGeometry}"/>
<Path x:Name="InValid"
Style="{StaticResource InvalidStyle}"
Data="{StaticResource InvalidGeometry}"/>
<Path x:Name="Pending" Opacity="1"
Style="{StaticResource PendingStyle}"
Data="{StaticResource PendingGeometry}"/>
</Grid>
</Grid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="border"
Value="{StaticResource ValidateDataEntry.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" TargetName="border"
Value="{StaticResource ValidateDataEntry.Focus.Border}"/>
</Trigger>
<Trigger Property="ValidationState" Value="Valid">
<Setter Property="Opacity" TargetName="Valid" Value="1"/>
<Setter Property="Opacity" TargetName="Pending" Value="0"/>
</Trigger>
<Trigger Property="ValidationState" Value="Invalid">
<Setter Property="Opacity" TargetName="InValid" Value="1"/>
<Setter Property="Opacity" TargetName="Pending" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type cc:ValidateDataEntry}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{StaticResource ValidateDataEntry.Static.Border}"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="SnapsToDevicePixels" Value="False"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template" Value="{StaticResource ValidateDataEntryTemplate}"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush"
Value="{DynamicResource
{x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
</MultiTrigger>
</Style.Triggers>
</Style>
使用方法
现在,要使用它,我们需要引用我们新用户控件的位置,然后我们就可以将其添加到 Xaml 代码中(为简洁起见已裁剪):
<cc:ValidateDataEntry Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
ValidationState="{Binding ValidationState, Mode=TwoWay}"/>
工作原理
当 ValidationState 更改时,我们新的 Dependency Property 的数据绑定将看到 PropertyChanged 事件,ControlTemplate Trigger 绑定将被通知,并将根据该控件的 Dependency Property 的值更改视觉验证图像。
要查看带有我们新 Dependency Property 的控件,请下载代码,编译并运行。
摘要
虽然 Dependency Property 比标准 Property 稍微冗长一些,但它们易于实现,并且 Dependency Object 会使用后台的 Binding 系统为你处理所有内部逻辑。