Introduction
This is an introductory article on WPF Dependency Properties to show that they are quick and easy to use.
Background
There was an article posted that left me feeling that readers who didn't understand the topic were left more confused than before they read it. So I will try to address this and hopefully, by the end of this article, explain the virtues of Dependency Properties.
So what is a Dependency Property?
According to the Official Documentation:
Quote:
Represents a property that can be set through methods such as, styling, data binding, animation, and inheritance.
But I like the explanation by Matt Hamilton better:
Quote:
Dependency Properties are properties of classes that derive from DependencyObject, and they're special in that rather than simply using a backing field to store their value, they use some helper methods on DependencyObject. The nicest thing about them is that they have all the plumbing for data binding built in.
And what is a DependencyObject? Again, according to the Official Documentation:
Quote:
Represents an object that participates in the dependency property system.
I like this explanation better:
Quote:
Dependency object is the base object for all WPF objects. All the UI Elements like Buttons TextBox etc and the Content Elements like Paragraph, Italic, Span etc all are derived from Dependency Object. Dependency objects are used for WPF property system.
So DependencyObject implements all the plumbing for the sytyling, databinding, animation systems and more that is transparent to the developer, and Dependency Properties are the access points to store (Set) and retrieve (Get) the associated data.
How to Implement
Implementation is a little bit more involved than standard properties however, as mentioned above, can do far more. To implement, it is a two-part process:
- We need to register the Dependency Property
- We need to implement the getters and setters
Luckily, Visual Studio makes this easy to do via code snippets. For C# we have the propdb. And for VB, CTRL-K, CTRL-X > WPF > "Add a Dependency Property Registration". Here is an example of the code auto-generated:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
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))
Dependency Properties can be used to extend functionality for existing controls (lookless inheritence), adding properties to Usercontrols (templates), Behaviors, and Markup Extensions to add or modify existing controls without inheritance.
For the purpose of this article, I am going to inherit the TextBox control and add a Valid input state. See image below:
There are two parts to the control:
- Text input area
- Visual indicator of validation state
We need to be able to:
- Track the Validation state: Pending, Valid, & Invalid
- Give each Validation state an Image & style
So we need 1 Dependency Properties: ValidState - to track the validation state: Pending, Valid, & Invalid.
Now we can create our custom control. Here I am creating a lookless control and extending the TextBox control:
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); }
}
}
Now that we have our control, we need to style it. I have taken the standard TextBox template and added our elements that use our new Dependency Properties. To do this, I added a normal TextBox to the MainWindow, then right-clicked on the TextBox in the visual XAML editor and selected: "Edit Style" > "Edit a Copy" and the default style was generated. Here is our modified Xaml from our Generic.Xaml ResourceDistionary in the Themes folder (trimmed for briefity):
<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>
To Use
Now to use, we need to reference our new user control location, then we can added to our Xaml code (trimmed for briefity):
<cc:ValidateDataEntry Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
ValidationState="{Binding ValidationState, Mode=TwoWay}"/>
How it Works
When the ValidationState changes, the Data Binding of our new Dependency Property will see the PropertyChanged event, the ControlTemplate Trigger binding will be notified and will change the visual validation image based on the value of the Dependency Property for that control.
To see the Control with our new Dependency Property, download the code, compile, and run.
Summary
Whilst the Dependency Property is a little bit more verbose than a standard Property, they are easy to implement and the Dependency Object handles all the internal plumbing for you using the Binding system behind the scene.