#150 – An Example of Using PropertyChanged and CoerceValue Callbacks

The PropertyChangedCallback and CoerceValueCallback can be used to enforce relationships between properties on an object.  Here’s an example, showing the relationship between BirthYear, MarriageYear and DeathYear properties on a Person object.

The BirthYear property:

        public int BirthYear
        {
            get { return (int)GetValue(BirthYearProperty); }
            set { SetValue(BirthYearProperty, value); }
        }

        public static readonly DependencyProperty BirthYearProperty =
            DependencyProperty.Register("BirthYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    new PropertyChangedCallback(OnBirthYearChanged)));

        public static void OnBirthYearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Person p = (Person)d;
            p.CoerceValue(DeathYearProperty);
            p.CoerceValue(MarriageYearProperty);
        }

The DeathYear property:

        public int DeathYear
        {
            get { return (int)GetValue(DeathYearProperty); }
            set { SetValue(DeathYearProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DeathYear.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DeathYearProperty =
            DependencyProperty.Register("DeathYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    new PropertyChangedCallback(OnDeathYearChanged),
                    new CoerceValueCallback(CoerceDeathYear)));

        public static void OnDeathYearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Person p = (Person)d;
            p.CoerceValue(MarriageYearProperty);
        }

        public static object CoerceDeathYear(DependencyObject d, object value)
        {
            Person p = (Person)d;
            int deathYear = (int)value;

            if (deathYear < p.BirthYear)
                deathYear = p.BirthYear;

            return deathYear;
        }

The MarriageYear property:

        public int MarriageYear
        {
            get { return (int)GetValue(MarriageYearProperty); }
            set { SetValue(MarriageYearProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DeathYear.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MarriageYearProperty =
            DependencyProperty.Register("MarriageYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    null,
                    new CoerceValueCallback(CoerceMarriageYear)));

        public static object CoerceMarriageYear(DependencyObject d, object value)
        {
            Person p = (Person)d;
            int marriageYear = (int)value;

            if (marriageYear < p.BirthYear)
                marriageYear = p.BirthYear;
            if (marriageYear > p.DeathYear)
                marriageYear = p.DeathYear;

            return marriageYear;
        }

#149 – Use PropertyChanged and Coercion Callbacks to Force Dependencies Between Properties

When implementing dependency properties, you typically use the CoerceValueCallback and PropertyChangedCallbacks to enforce relationships between properties.

For example, assume we have a Person class with BirthYear, DeathYear and MarriageYear properties.  We want to ensure that MarriageYear is never earlier than BirthYear or later than DeathYear.  We want these checks done any time that we change any of the three properties.

The basic plan is:

  • Allow setting BirthYear to anything
  • When setting DeathYear, don’t let it be earlier than BirthYear
  • When setting MarriageYear, don’t let it be earlier than birth or later than death

When registering the dependency properties, we therefore do the following:

  • BirthYear
    • In PropertyChangedCallback, force coercion of DeathYear and MarriageYear
  • DeathYear
    • In PropertyChangedCallback, force coercion of MarriageYear
    • In CoerceValueCallback, if value earlier than birth, set to BirthYear
  • MarriageYear
    • In CoerceValueCallback, if marriage earlier than birth, set to birth year; if later than death, set to death year

Here’s an example.

#148 – Property Values Set Using Expressions Overwrite the Base Value

In the list of all possible sources for a dependency property value, we explain that a property’s base value can in turn be overwritten by the result of an expression.  If the property’s value is set using an expression, the value of the expression takes precedence over the property’s base value.

In WPF, if the value of a dependency property is set using an expression, the expression can be one of two types:

  • Data binding – set the value of the property by binding it to another property
  • Resources – set the value of the property to a resource, loaded from a resource dictionary

 

#147 – Use SetCurrentValue When You Want to Set A Dependency Property Value from Within a Control

As a control author, you might want to set the value of one of your own dependency properties.  For example, a numeric up/down control might want to change its Value property in response to clicking on the arrows.

The problem with setting the dependency property directly is that you’ve set a local value, which will take precedence over all other possible sources, like data binding.  If you set a local value, you’ll destroy any data binding that the application has set up.

The solution is to use the DependencyObject.SetCurrentValue method to set the current value from within the control.  This is similar to coercion, in that the effective value is changed without affecting the property’s value source.

A control should always use SetCurrentValue to set the value of its own dependency properties, with the exception of the CLR property setter, which should use SetValue.

See also Vincent Sibal’s blog.

#146 – Use GetValueSource Method to Find the Source of a Dependency Property Value

It’s often helpful to determine the source of the current value of a dependency property.  You can use the DependencyPropertyHelper.GetValueSource method to do this.

In the following example, the source for the value of the Foreground property alternates between the style and the style trigger, based on the value of the IsEnabled property.

    <Window.Resources>
        <Style x:Key="redgreenButton" TargetType="{x:Type Button}">
            <Setter Property="Foreground" Value="Green"/>
            <Style.Triggers>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="Foreground" Value="Red"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <Button Content="A Button" Height="23" Width="75" Style="{StaticResource redgreenButton}" Name="btnTest"/>
        <Button Content="Enable/Disable" Height="24" Width="100" Name="btnDisable" Click="btnDisable_Click"/>
        <Button Content="Display Source" Height="24" Width="100" Name="btnDisplay" Click="btnDisplay_Click"/>
    </StackPanel>


Here’s the code for the Display button’s Click event, which uses GetValueSource to report the base value source.

        private void btnDisplay_Click(object sender, RoutedEventArgs e)
        {
            ValueSource vs = DependencyPropertyHelper.GetValueSource(btnTest as DependencyObject, Button.ForegroundProperty);
            MessageBox.Show(string.Format("Source for Foreground property: {0}", vs.BaseValueSource));
        }

#145 – Dependency Property Value Sources: #11 – Default Value

The eleventh source in the list of sources for the base value of a dependency property is the default value.  A dependency property may define a default value that will become the property’s value if no mechanism with higher precedence assigns a value.

As an example, notice that the value of the Opacity property for most controls inheriting from UIElement (e.g. Button or Label) has a value of 1.0 if you don’t set it to anything else.  This is the default value for the UIElement.Opacity property, specified in its metadata.

#144 – Dependency Property Value Sources: #10 – Inheritance

The tenth source in the list of sources for the base value of a dependency property is inheritance.  A property can get its base value through inheritance if an element higher up in the logical tree sets the property and the property is not overwritten due to a higher precedence rule.

This means that when you set a property value in XAML or in code, that value often “trickles down” the element tree and is applied to other elements that have a property of the same name.

Here’s an example. The value of FontStyle for several controls is inherited from the top-level Window element.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:m="clr-namespace:PersonLib;assembly=PersonLib"
        Title="MainWindow" Height="350" Width="525" FontStyle="Italic">
    <StackPanel Orientation="Vertical">
        <Button Content="Run" Height="23" Width="75" />
        <Button Content="Skip" Height="23" Width="75" />
        <StackPanel Orientation="Horizontal">
            <Label Content="Inside 2nd StackPanel"/>
            <Label Content="I do my own FontStyle" FontStyle="Normal"/>
        </StackPanel>
    </StackPanel>
</Window>

Here’s what the window looks like:

#143 – Dependency Property Value Sources: #9 – Theme Style

The ninth source in the list of sources for the base value of a dependency property is a theme style.  A property gets its base value from a theme style if the value comes from a setter in the default style for a control. Every control that ships with WPF has a default style that dictates its appearance, also known as a theme style.

If we look at the default style for a ComboBox control, we see that the default style sets the Background property of the ComboBox:

		<Style x:Key="ComboBoxStyle1" TargetType="{x:Type ComboBox}">
			<Setter Property="FocusVisualStyle" Value="{StaticResource ComboBoxFocusVisual}"/>
			<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
			<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>

So, by default, the source of this property is DefaultStyle (theme style).

If we then set the IsEditable property to true, a trigger in the default style fires and the source becomes DefaultStyleTrigger (theme style trigger).

#142 – Dependency Property Value Sources: #8 – Theme Style Triggers

The eighth source in the list of sources for the base value of a dependency property is a theme style trigger.  A theme style trigger is a trigger defined in the default style for a control.  Every control that ships with WPF has a default style that dictates its appearance, also known as a theme style.

As an example, notice that when you set the IsEditable property of a ComboBox to true, it’s appearance changes.

IsEditable false:  

IsEditable true:  

The change in appearance is caused by a trigger in the default style.  If you right-click on a ComboBox in Blend and pick Edit a Template | Edit a Copy, you can see the default style for a ComboBox, including the following trigger on IsEditable.

				<Trigger Property="IsEditable" Value="true">
					<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
					<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
					<Setter Property="IsTabStop" Value="false"/>
					<Setter Property="Padding" Value="3"/>
					<Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/>
				</Trigger>

#141 – Dependency Property Value Sources: #7 – Style Setters

The seventh source in the list of sources for the base value of a dependency property is a style setter. A property obtains its value from a style setter when a style is applied to the parent element and the property’s value is set using a Setter in that style.

In the example below, a button has the style redBlueTextButton applied to it. This style sets the Foreground property to red using a Setter.  It also sets the property to blue when you hover the mouse over the control. The source of the Foreground property is style (style setter) to start with and then becomes style trigger when you move the mouse over the control.

    <Window.Resources>
        <Style x:Key="redBlueTextButton" TargetType="{x:Type Button}">
            <Setter Property="Foreground" Value="Red"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Foreground" Value="Blue"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <Button Content="Run" Height="23" Width="75" Style="{StaticResource redBlueTextButton}"/>
        <Button Content="Skip" Height="23" Width="75"/>
    </StackPanel>
Follow

Get every new post delivered to your Inbox.

Join 311 other followers