Wha' happened? Property-changed detection mechanisms in WPF (Part One)
Introduction
Windows Presentation Foundation features quite a variety of different mechanisms that provide notifications of when a property changes its value. These mechanisms come from several different feature areas: from data binding, from the WPF property system, and also from the cluster of features around styling, templating and controls. This topic (at least when you add Part One and the upcoming Part Two together) is a summation of various different property-changed notification mechanisms that are part of WPF, and will attempt to describe the design intentions, and also how these design intentions might influence your choices about how to report property changes if you are defining custom properties and objects.
As it happens, this is proving to be such a toothy subject that I think I will need to break it up into parts. Consider this Part One.
In Part One, we’ll discuss the property-change mechanisms that have the most exposure for markup writers, and for those of us that are using existing controls and classes rather than writing our own. If you derive from existing classes, you have the opportunity to add or change properties of a class. Obviously that’s going to open up even more possibilities, and we’ll save that discussion for Part Two (or Part X?)
Property triggers
XAML and support for defining entire UIs in XAML markup is a big draw of using WPF as an application programming foundation. So, wouldn't it be nice if there was a way of responding to user-driven property changes that affect the UI, without even needing to write any code at all? Basically, this would mean introducing runtime logic and branching decision trees into the object instantiation paradigm that XAML was designed for. In fact, there is such a concept. It's called a property trigger, and you can use this technique in a style or template for a control.
The class you use for this technique is Trigger, and we refer to this class in the SDK documentation sometimes as a property trigger, to better differentiate it from other derived classes of the TriggerBase class, such as the less frequently used EventTrigger.
A Trigger is conceptually a logical operation, akin to an "if" statement. In a Trigger element, you define the logic for a value check of a property that is identified by the Property attribute. The property must have the exact value specified by the Value attribute.If so, then the "body" of your logical operation is "executed". In this case, it's all markup we are talking about here, so strictly speaking there is no "body" of code nor is anything geniunely "executed". Instead, the "body" is one or more Setter elements. You use these Setter elements to dynamically change the value of other properties based on detection of changes to the property where the Trigger is defined. By using references such as Binding or TemplateBinding, you can set properties in one object based on the values in another.
The Trigger technique is great because it is so easy to use, but ease of use sometimes has the cost of certain limitations of application.
- You can only use a Trigger inside a style or template. Maybe that's not a true "limitation"; the ability to use triggers is really quite a strong motivator towards defining your UI in a style or template in the first place.
- The property you base your Trigger on must be a dependency property. Fortunately, the trigger scenario is a big contributor towards what made the WPF team decide to expose properties as dependency properties. More often than not, a property in UI that you might reasonably want to specify a trigger for will be a dependency property.
- Although a Trigger is like an "if" statement, there is no directly analogous "else" statement available as a trigger. This matters because when you use the "if" capability to set another property in a Setter, you might naturally want something else that acts when your "if" is no longer true to set that other property back again. However, this is pretty easy to work around, because you can apply a value for that same property in some other area of the same style or template to have a value for the same property in a standard Setter in the same style or template. Or, you can give that property a default value from the compositing in a template. That way, only the triggered state changes the appearance, and when the trigger no longer applies, the property reverts to the Setter or compositing default value. This works because in terms of property value precedence, a trigger has higher precedence than setters or templated values. For more information on property value precedence, see Dependency Property Value Precedence.
- Triggers in general work best when the property you create the Trigger for has a finite and small range of possible values, such that you can either create an "if" statement for all of them, or use an encompassing "else" in a Setter. Boolean properties are great for this, as are properties that take a small, dedicated enumeration. The WPF team that designed the controls exposed a number of properties on controls that are well compatible with trigger scenarios. For instance, there are often "Is*" properties on controls that detect a useful true-false UI condition that might have ripple effects for the control's appearance. These properties are often read-only, because they often represent an effect that might result from multiple causes, rather than a single initiating read-write property change. One example of this situation is the MenuItem.IsHighlighted property.
- Each Value you specify for a Trigger must be an exact value match. Because it must be an exact value match, this generally entails that you can only use triggers for a value property, and not for anything where you need a reference. For the common properties where this scenario is used, with types of Boolean or an enumeration, this limitation won't be a problem.
Here's an example of some triggers that are deployed as part of a re-template of an existing control. In this case, a template for ToggleButton that places any button content on a control label rather than the button surface itself, with the button being a sort of "LED" look. There are two triggers: one to give a disabled button a grayed-out look, another to reflect the toggle state in the "LED" part of the button. The IsEnabled trigger stays applied; the toggle trigger is only active when the button is selected, and otherwise reverts to the template default.
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Window.Resources>
<RadialGradientBrush x:Key="Outie"
GradientOrigin="0.45,0.45"
Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">
<GradientStop Color="DarkRed" Offset="0.1" />
<GradientStop Color="Black" Offset="1" />
</RadialGradientBrush>
<RadialGradientBrush x:Key="Innie"
GradientOrigin="0.55,0.55"
Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">
<GradientStop Color="Red" Offset="0.1" />
<GradientStop Color="DarkRed" Offset="0.8" />
<GradientStop Color="#400000" Offset="0.9" />
<GradientStop Color="DarkRed" Offset="1" />
</RadialGradientBrush>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Label>
<ContentPresenter Block.TextAlignment="Center"/>
</Label>
<Ellipse Name="LightArea" Width="{TemplateBinding Width}" Height="{TemplateBinding Width}" Fill="{StaticResource Outie}"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.3"/>
</Trigger>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="LightArea" Property="Ellipse.Fill" Value="{StaticResource Innie}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<ToggleButton Width="60" Height="90">Hello</ToggleButton>
<ToggleButton Width="60" Height="90" IsEnabled="false">Goodbye</ToggleButton>
</StackPanel>
</Window>
By the way, Trigger is being pitched first because it is a markup concept. A lot of developers will probably come into WPF and gravitate immediately towards code and discount the markup, on the grounds that their previous experience with markup wasn't very satisfying, especially once they tried to modify designer-produced output. Microsoft is betting that XAML will be a markup language that changes at least some developers' minds. The benefits you will get in terms of application development workflow by using XAML as part of your application (as a bridge to the designer's world and as a UI definition format in general) are worth it.
Data binding with INotifyPropertyChanged implementing source
Data binding is another big draw for using WPF for applications. Certainly, data binding in some form has existed in Microsoft development previously, but WPF databinding is particularly well integrated with setting UI properties, and with the ability to bind to a wide variety of target properties.
One big "gotcha" that first-time data binding users might encounter is making the source property report its changes to targets that it is bound to. This notification doesn't happen by itself. The source property does need to follow a contract by which WPF (and the greater .NET Framework version 3.0) can know that the value of the source property has changed. However, that contract is quite simple. You just need to implement one interface (INotifyPropertyChanged) that defines one event, on the type that owns the property. Then you raise that event from each set accessor of a property that should report effective value changes to data binding. You pass the name of the changing property along through the event data (you don't need to pass new value in the event, that'll update through the binding itself).
This looks something like:
public
class SimpleData : INotifyPropertyChanged
{
private string simpleDataProperty = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string SimpleDataProperty
{
get { return this.simpleDataProperty; }
set {
if (value != this.simpleDataProperty)
{
this.simpleDataProperty = value;
NotifyPropertyChanged("SimpleDataProperty");
}
}
}
}
You can see similar code with more surrounding explanation in the SDK topic How to: Implement Property Change Notification.
(You don't need INotifyPropertyChanged to do data binding, but you do need it to bind to anything that's not an intentional OneTime binding. OneTime binding is not that common a scenario, so in general we state that INotifyPropertyChanged support is necessary, for OneWay and TwoWay bindings.
As long as you get this INotifyPropertyChanged infrastructure in place on your data class, all further updates to the property value of the target property (or the source for TwoWay) are automatic. You don't need to handle the events to update values, it just happens. A proviso: the timing of WHEN updates happen will depend on the nature of your binding. Generally, data binding is asynchronous, so that the UI updating thread that is really the main thread for WPF won't get hung up waiting for data. And you can also specify an UpdateSourceTrigger that queues the data until a certain action takes place.
In case you still want an event that tells you that a databound property has changed, you have several routes you could follow:
1) Does the property itself have a dedicated Changed event? If so, the changed value coming from data binding will raise that event. This approach might be useful if you aren't so much interested in HOW the property changed, and more interested in whether it changed by any means. Property-changed events are introduced at the end of this topic and will be covered in more detail in Part Two.
2) Use the SourceUpdated event. The SourceUpdated event comes from the Binding object that established the binding, but the same event is also surfaced by the FrameworkElement class where such a binding is applied. The SourceUpdated event from FrameworkElement will fire whenever any of the bindings that are active on the FrameworkElement report a source change, so if you are interested in a particular source, you must check the event data, specifically the value of DataTransferEventArgs.Property. There is also a TargetUpdated event that is relevant for TwoWay binding notifications.
3) Are you in a subclassing scenario like a custom control? If so, there's an OnPropertyChanged callback that comes from DependencyObject that will be called whenever any dependency property of a DependencyObject changes. However, think hard before you go this route. OnPropertyChanged is metaphorically a big tool for what might be a small job. There will be more about OnPropertyChangedin Part Two.
Binding from collections, and changes to the collection
is for individual properties that change. But what about detecting subproperty changes, specifically, changes to the contents of a collection property? Many binding scenarios involve binding the collection to a control, such as binding to a ListView or TreeView. Notifying on changes to the source collection requires a different interface, INotifyCollectionChanged. The simplest way to make this scenario work is to have your collection be a version of the ObservableCollection generic class, because ObservableCollection already has INotifyCollectionChanged built-in. Otherwise, you must implement INotifyCollectionChanged on your collection similar to how you must implement INotifyCollectionChanged on the data source property. But again, it's a pretty simple interface: one event, CollectionChanged, which you have your collection raise whenever items are added, changed, or removed. ****
Dependency properties
Having programs be able to receive notifications when properties change was one of the biggest design inputs for why WPF introduced the concept of a dependency property.
Dependency properties are a technique of backing a CLR property with a special type of programming construct, stored in declaring types as a field. By using that DependencyProperty construct, the property can now have its runtime effective value adjusted by the WPF property system. Because the property system is already a background contributor to property value changes, there is a lot of infrastructure built in to the property system that can report a property value change to various listeners.
Native property-change detection
WPF provides many properties that are dependency properties. Dependency properties also possess metadata, and the metadata can be altered for each possible type that holds a value for the property. For instance, a particular Control derived class such as Label might have different metadata applied to the Focusable property. The purpose of changing metadata in WPF is usually to alter some subsystem's behavior with regards to a certain class. Such a subsystem often reads metadata on properties of instances internally.
One behavior that is very important for the WPF framework level (and thus for application authors) is the layout system. Generally speaking, layout is designed such that it does not choose to re-render on every screen cycle, it only updates when some property in the system changes in such a way that a re-render is required. Part of the mechanism that keeps track of this condition is built in to the FrameworkElement class. Essentially, FrameworkElement uses callbacks that are called whenever ANY property on the object changes. Within the logic, FrameworkElement code examines the metadata to see if the property that changed was a dependency property. There are properties in that metadata that declare whether that particular property affects the arrange, measure, passes of layout, or rendering in general. If any of these properties come up true, then the FrameworkElement calls another method that assures that some aspect of layout gets reconstructed to address the change. Layout time is potentially deferred somewhat from realtime, but the assurance with this system is that the layout change happens in time for the user to see the change without detectable lag.
Implementation for this behavior is within the FrameworkElement.OnPropertyChanged callback, which is inherited from DependencyObject. We'll talk more about OnPropertyChanged in Part Two.
Even if you are a control author, and your primary needs are to change related properties when a property changes, and to cause a layout refresh, you might be able to accomplish everything you need by a combination of using triggers in the style or template of the control, plus tapping into native layout behavior by setting one of the metadata properties such as AffectsMeasure. You can also change the behaviors of properties that you inherit from base classes, by changing metadata on the property through the property system APIs AddOwner or OverrideMetadata, and use them as triggers in the styles and templates that use your type as the target type. But that’s more Part Two stuff.
Dedicated "Something Changed" Events in Controls
Some controls expose events that you can monitor to see if a particular property changed. Usually, these events are for a dedicated class-property pairing.
These events tend to come in three flavors:
- CLR (non routed) events that use the DependencyPropertyChangedEventHandler delegate. An example is UIElement.IsVisibleChanged.
- Routed (usually bubble-only) events that use the RoutedPropertyChangedEventHandler generic delegate. An example is RangeBase.ValueChanged.
- Control-specific events with dedicated handler and event data classes. An example is TextBoxBase.TextChanged.
The scenario for these property-changed events is that the property that’s changing has greater implications than just needing to change the UI. Sometimes the property is one that user input typically has access to. Sometimes the property that’s changing is a system-calculated property that is indirectly tied to user action. Some of the focus properties fall into this category.
The takeaway: if you have a control you’d like to use, and you have a particular property you want to track, scout its members listing for these types of events. There may already be a relevant event available.
If not: you can subclass, and add an event yourself. More Part Two stuff.
A Preview of Part Two
Have all the references to what’s coming in Part Two whetted your appetite? Or merely enraged you? (If the latter, apologies ...)
Here’s an outline of what I still want to cover with regard to property changed events and related concepts:
- Creating a custom DependencyProperty, or overriding the metadata
- PropertyChanged callbacks
- CoerceValue callbacks
- The DependencyObject.OnPropertyChanged callback (aka the Big Hammer)
- Properties that are collections, and detecting changes to the collection
- Freezables
- Resources
About Us
We are the Windows Presentation Foundation SDK writers and editors.
Comments
Anonymous
February 14, 2007
.. waiting for the part 2 :)Anonymous
February 14, 2007
Uh oh. Now I am on the hook! There was one key technical question I wanted to review before I released Part Two. I will take this as a call to action to follow up on that and will get Part Two up by end of month. -wolfAnonymous
February 16, 2007
heh, no stress ;) I'm mainly interested in the "Properties that are collections, and detecting changes to the collection" subject form part 2. I might aswell make a small question here. I have a custom control that has Items of type ICollectionView. This is a dependency property. I want to get notified every time items get added or removed from the collection or when the whole collection is changed. I make an eventhandler to the Items.CollectionChanged event and thought this would be enough. This event handler gets fired every time I add or remove to the source but if I change the source totally in the parent: _items.Source = NEWSOURCE then I don't get any notifications. What makes this even more weird is that I seem to sometimes get an event fired with e.Action = NotifyCollectionChanged.Reset and sometimes not. On the parent control I set the CollectionViewSources Source property to MyItem.Items. At this time I don't get CollectionChanged event fired on my control. Then I change the Source property to Items[0].Items. At this time I don't get event fired. After this when I set the Source property to Item.Parent.Items. I DO get NotifyCollectionChanged.Reset event fired. I'm making a simple control that shows items in 3D. I want to only show one level of items in the control and navigate through the hierarchy on the parent element. I hope this makes some sense. Basically what I would need is to get notified every time something is added, removed, or if the whole collection is changed. One way I could do this is to make PropertyChangedCallback for my Items dependency property and then call a public "ItemsChanged()" method from from the PropertyChangedCallback function. This seems to be a little weird way of doing this though.. any ideas? ;)Anonymous
March 22, 2007
Introduction Windows Presentation Foundation features quite a variety of different mechanisms that provideAnonymous
March 25, 2007
Wha‘ Happened Part Two: More Property Changes in WPFIntroductionWindows Presentation Foundation features quite a variety of different mechanisms that provide notifications of when a property changes its value. These mechanisms come from several differentAnonymous
March 25, 2007
Wha‘ Happened Part Two: More Property Changes in WPFIntroductionWindows Presentation Foundation features quite a variety of different mechanisms that provide notifications of when a property changes its value. These mechanisms come from several different