Lectures On WPF Dependency Properties
Lectures On WPF Dependency Properties
2
The property system also provides for sparse storage of property values. Because objects can have hundreds of properties, and most of the values are in their default state (inherited, set by styles, etc.), not every instance of an object needs to have the full weight of every property defined on it. Understanding most of the nuances of dependency properties is usually only important for custom control authors. However, even casual users of WPF end up needing to be aware of what dependency properties are and how they work. See: MSDN: Dependency Properties Overview See: MSDN: WPF Architecture Copy the following code into a text editor and store the text file to: C:\temp\FontSize.xaml
<Page xmlns="http://schemas.microsoft.com/winfx/2006 /xaml/presentation" WindowTitle="Dependency Properties" WindowWidth="300" WindowHeight="400" FontSize="30"> <StackPanel VerticalAlignment="Center"> <Button HorizontalAlignment="Center" Content="Button1"/> <StackPanel TextBlock.FontSize="20"> <Button HorizontalAlignment="Center" Content="Button2"/> <Button HorizontalAlignment="Center" Content="Button3" FontSize="10"/> </StackPanel> </StackPanel> </Page>
Start the Internet Explorer and open C:\temp\FontSize.xaml or drag and drop C:\temp\FontSize.xaml onto the Internet Explorer icon of your screen. The browser will display three useless Buttons. The two nested StackPanels remain invisible.
Experiments varying the three Dependency Properties FontSize of C:\temp\FontSize.xaml: (Restore the original code after any experiment.)
Button1 shrinks because it inherits its FontSize from Page. Button2 grows because it inherits the TextBlock.FontSize from the inner StackPanel. Button3 grows because it carries its own FontSize. Button3 inherits the same TextBlock.FontSize as Button2 from the inner StackPanel. Button2 shrinks and Button3 inherits its FontSize from the inner StackPanel.
Remove FontSize="10" from Button2 and All Buttons inherit their FontSize from Page. TextBlock.FontSize="20" from the inner StackPanel. Remove all FontSizes from Button3, from the inner StackPanel and from Page. All Buttons inherit their FontSize from the default Button Template of WPF (and from the current Vista-theme).
Summary: WPF cares about us in many ways. 1. It provides a default FontSize for lazy programmers (Experiment 7). 2. It provides a property inheritance sytem where the property is propagated down the object tree. 3. It adjusts automatically further properties as Button.Width and Button.Height to the string length, to the Font and to its FontSize.
Start the Internet Explorer and open C:\temp\Trigger.xaml or drag and drop C:\temp\Trigger.xaml onto the Internet Explorer icon of your screen.
This trigger acts upon Button's IsMouseOver property, which becomes True at the same time the MouseEnter event is raised and False at the same time the MouseLeave event is raised. There is no need to worry about reverting Foreground to black when IsMouseOver changes to False. This is automatically done by WPF! Property triggers are just one of three types of triggers. A data trigger is a form of property trigger that works for all .NET properties (not just dependency properties). An event trigger invokes animations or sounds.
Property Inheritance
The term property inheritance (more precise: property value inheritance) doesn't refer to traditional object oriented class-based inheritance, but rather the flowing of property values down the element tree. A simple example of property inheritance can be seen in paragraph Why Dependency Properties ?. Important: Not every dependency property participates in property value inheritance. Internally, dependency properties can opt in to inheritance or they can refuse it. A few controls such as StatusBar, Menu, and ToolTip internally set their font properties to match current system Control Panel settings. The result can be confusing, however, because such controls end up "swallowing" any inheritance from proceeding further down the element tree. For example, if you add a Button as a logical child of the StatusBar, its FontSize and FontStyle would be the default values of 12 and Normal, respectively, unlike the other Buttons outside of the StatusBar.
Multiple Providers
An object 1 which influences a property p of another object 2 is called a "Property Provider" or shortly "provider" of property p of object 2. Object 2 can have and normally has a lot of providers for each of its properties. A well-defined mechanism for handling these disparate property value providers prevents from chaos. Providers a are classified in 8 classes of precedence: Priority 2 3 4 5 6 7 8 Provider Style triggers Template triggers Style setters Theme Style triggers Theme Style setters Default value Source From inside the element's definition tag From the element's Style From the element's Template From the element's Style From the current Vista-theme From the current Vista-theme Initial value from the element's Template
Property value inheritance Coming down from higher elements of the element tree
Sample: The setting of StatusBar's font properties is done via theme style setters. Although this has precedence over property value inheritance, you can still override these font settings using any mechanism with a higher precedence, such as simply setting local values on the StatusBar. Clearing the local value: The earlier "Change Notification by Triggers" section demonstrated the use of procedural code to change a Button's Foreground to blue in response to the MouseEnter event, and then changing it back to black in response to the MouseLeave event. The problem with this approach is that black is set as a local value inside MouseLeave, which is much different from the Button's initial state in which its black Foreground comes from a setter in its theme style. If the theme is changed and the new theme tries to change the default Foreground color (or if other providers with higher precedence try to do the same), it gets trumped by the local setting of black. What you likely want to do instead is clear the local value and let WPF set the value from the relevant provider with the next-highest precedence. Fortunately, DependencyObject provides exactly this kind of mechanism with its ClearValue method. This can be called on a Button b as follows in C#: b.ClearValue( Button.ForegroundProperty );
Attached Properties
An attached property is a special form of dependency property that can be attached to arbitrary objects. This may sound strange at first. We already used this feature in our first sample in Why Dependency Properties ?. We set TextElement.FontSize on the inner StackPanel so it is inherited only by the two lower buttons. Moving the property FontSize to the inner StackPanel element doesn't work, however, because StackPanel doesn't have any font-related properties of its own! Instead, we must use the FontSize attached property that happens to be defined on a class called TextElement. The most confusing part about the FontSize attached property is that it isn't defined by Button or even Control, the base class that defines the normal FontSize dependency property! Instead, it is defined by the seemingly unrelated TextElement class (and also by the TextBlock class, which could also be used). How can this possibly work when TextElement.FontSizeProperty is a separate DependencyProperty field from Control.FontSizeProperty (and TextElement.FontStyleProperty is separate from Control.FontStyleProperty)? The key is that Control internally borrows its FontSize property from TextElement and exposes this foreign property as its own. Advantage: Setting an attached property once in a layout-control is much simplier than to set it within each of its children.
Dependency Objects
It is much more complicated to define a new dependency property than to define a normal property, because dependency properties have to be registered within WPF's Property Engine. Most developers will never define a new dependency property. The pre-defined controls contain enough of them. However it is important to keep in mind how different they are compared to normal properties. Sample of how to define a (superfluous) normal property:
class myButton : Button { public double myFontSize; //normal property }
A DependencyProperty must register a DependencyObject which has to contain: 1. Metdata containing a default value (in the sample: 11.0), and some boolean flags for property inheritance and rendering, 2. a get and set method enabling to access it in the same way as a normal property. Sample of how to define a (superfluous) dependency property:
class myButton : Button { public class myFontSize : DependencyObject { public static readonly DependencyProperty myFontSizeProperty = DependencyProperty.Register ( "myFontSize", typeof(double), typeof(Button), new FrameworkPropertyMetadata( 11.0, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender ) ); public double myFontSize { get { return (double)GetValue( myFontSizeProperty ); } set { SetValue( myFontSizeProperty, value ); } } }