Please visit http://www.ningzhang.org!

This is a backup site. Please go to http://www.ningzhang.org for the latest content and best viewing. Thanks!

Nov 26, 2008

Customize Silverlight Toolkit Controls: Expander

Introduction

This is the second post of the Customize Silverlight Toolkit Controls series. This post demonstrates how to customize Expander, and provides the most asked customizations from Silverlight Toolkit forum.

Expander Customization

To customize Expander, it is very important to understand its API and default template, and know how to use Blend to re-template a control. I'd highly recommend you read my previous post Expander Control in Silverlight Toolkit if you haven't yet. The key to customize Expander:

  • understand Expander interface, especially its control contract:
    • Expander expects one template part, a toggle button named "ExpanderButton", in its template. Its expand/collapse function depends on the existence of this template part, because Silverlight doesn't support two way binding.
    • Expander exposes ExpansionStates visual state group (including Expanded and Collapsed two visual states) for users to customize its expand and collapse behavior.
    • Expander exposes ExpandDirectionStates visual state group (including ExpandDown, ExpandUp, ExpandLeft, ExpandRight) for users to customize the layout for each of the four expand direction.
    • Expander also exposes HeaderTemplate and ContentTemplate properties for users to customize its header and content part. You can find this kind of customization from Silverlight Control Toolkit Sample.

image

  • understand Expander's default template. The screen shot below shows how the default template handles the layout of the four expand directions and the behavior of expand/collapse by providing StoryBoard for the ExpansionStates and ExpandDirectionStates visual state groups:
    • The whole Header is a templated ToggleButton, so clicking anyway on the Header may expand/collapse the Content area.
    • It uses a 2x2 Grid to layout Header and Content area.
    • It lays out the Expander according to ExpandDirection by having state animation for ExpandDirectionStates:
      • It lays out Header and Content areas by animating their Grid.Row and Grid.Column properties.
      • It draws the header toggle button correctly by changing its template.
    • It expands/collapses content areas by animating its Visibility attribute in ExpanionStates state transitions.

ExpandDirection state animation

Below screen shot shows the most asked customizations. If you have Silverlight 2.0 installed and your reader supports live Silverlight application, you can play with it here too:

Expander Customization

  • PDC08 shows the Expander in current release on codeplex. There is a bug in ExpanderRightHeaderTemplate (used if you set ExpandDirection to Right) that causes high CPU and memory usage. This is fixed and the fix will be in our next release in a few weeks. I apologize for the inconvenience. Expander is my very first Silverlight control. I didn't know anything about Silverlight or WPF before I joined Shawn's team a few months back, and I still can't believe how fragile xaml and animation are. It took me a long time to track down the one character fix: one (out of dozens) ObjectAnimationKeyFrames has Duration attribute accidentally set to "1" instead of "0". That drove Silverlight runtime nuts and hog cpu/memory.
  • New (ExpanderStyle) shows the new template to be released soon.
  • Fade In/Out (ExpanderFadeStyle) customizes the expand/collapse behavior by animating Opacity to fade the content in and out.
  • Scale In/Out (ExpanderScaleStyle) customizes the expand/collapse behavior by animating ScaleTransform.ScaleX and ScaleTransform.ScaleY between 1 and 0 to expand and shrink the content.
  • No Button (ExpanderNoButtonStyle) customizes the layout by removing the circle with arrow toggle button. You can still expand/collapse content with mouse or keyboard.
  • Bottom/Right Button (ExpanderBottomRightButtonStyle) customizes the layout by putting the the circle with arrow toggle button to the bottom/right part of the header.

 

Source Code

You can find the zipped project file:

Conclusion

Expander has been the top keyword to my blog. It is a very useful control and relatively hard to customize. Hopefully this post will help you in using and customizing Expander. Thanks!

Nov 19, 2008

Customize Silverlight Toolkit Controls: NumericUpDown

Introduction

The beauty of WPF/Silverlight control architecture is the separation of behavior from presentation, in another word, the capability to style/template a control so that it may look very different but still behave the same, more or less, all without a single line of code. There is no way a control's default UI can satisfy all possible user needs, so this capability is extremely useful, and there have been asks in the forum and from search queries to my blog for various customization of the default control templates in Silverlight Toolkit. It is actually fairly easy to re-templating/style a control with tools like Expression Blend. For your convenience, I will write a series of posts to demonstrate the most asked customizations, and provide the xaml that you can copy and paste for your own use. I will start with NumericUpDown.

NumericUpDown Customization

Below screen shot shows common customizations for NumericUpDown control. If you have Silverlight 2.0 RTM installed, you can also play with it here.

NumericUpDown Customization

Source Code

You can download the zipped project below to play with. I will add those customized templates to Silverlight Toolkit sample project in next release.  

Conclusion

Hope this is helpful! As always, feedback is welcome. Thanks!

Nov 15, 2008

NumericUpDown Control in Silverlight Toolkit

Introduction

I wrote the NumericUpDown control image in Silverlight Toolkit that was released to CodePlex three weeks ago. There are NumericUpDown or similar controls on Win32, WinForm and other platforms, but it is new to WPF and Silverlight. We went through rounds of designs to make sure its interface and implementation are simple, extensible and reusable. Some of the design benefits may not be obvious, so it may be helpful to write a post about NumericUpDown and its related types in Microsoft.Windows.Controls.Input assembly.

Overview

The NumericUpDown control exposes following properties:

  • Minimum, Maximum and Value of double type: Minimum and Value have default value 0, Maximum defaults to 100. NumericUpDown ensures that Minimum <= Value <= Maximum, regardless of how those properties are set (via xaml, code, or typed in by user) or in what order.
  • DecimalPlaces of int type: it decides how many digits after decimal point are displayed. It must be a value between 0 and 15, and default to 0.
  • IsEditable of bool type: if true, user can type in a value directly into the text box of the NumericUpDown control; if false, user can still change the value by clicking the up or down repeat button, or selecting the text box and then hitting up or down arrow key, or via code.
  • Increment of double type: it decides by how much Value will change each time when user clicks the up/down button or hits up/down arrow key. It must be a positive double value.

NumericUpDown fires three events, usually in the following order:

  1. ParseError event of type EventHandler<UpDownParseErrorEventArgs>. The event handler has a parameter e of type UpDownParseErrorEventArgs: UpDownParseErrorEventArgs
    e.Text is the string user typed in, and e.Error is the exception thrown while trying to parse e.Text into a double value. This event is fired when user typed in an invalid string in the control's text box. The ParseError event gives client code a chance to do its own error handling. If the event handler handles the event , it should set e.Handled to true. If the event is not handled, NumericUpDown by default will discard user input and refresh the text box to display previous value. Either way, when ParseError event fires, the following two events usually will not be fired.
  2. ValueChanging event of type RoutedPropertyChangingEventHandler<double> type. The event handler takes a parameter e of type RoutedPropertyChangingEventArgs<double>:
    RoutedPropertyChangingEventArgs<T>
    ValueChanging event is fired when the control's Value property is about to change from e.OldValue to e.NewValue. This event gives client code a chance to modify or cancel the event. If the event handler modifies e.NewValue, the the following ValueChanged event will be fired with the modified e.NewValue. If e.IsCancelable is true, the event handler can cancel the value changing event all together by setting e.Cancel to true, then the NumericUpDown control's Value property will not change, and no ValueChanged event will be fired.
  3. ValueChanged event of type RoutedPropertyChangedEventHandler<double> type. The event handler takes a parameter e of type RoutedPropertyChangedEventArgs<double>:
    RoutedPropertyChangedEventArgs<T>
    ValueChanged event is fired when the previous ValueChanging event is not canceled and the control's Value property has changed from e.OldValue to e.NewValue. This event gives client code a chance to respond to the value change.

NumericUpDown has the following control contract:

NumericUpDown Control Contract

It expects two template parts:

  • A TextBox named "Text", which allows user to type in a value directly;
  • A Spinner named "Spinner", which allows user to pick up a value from a range instead of typing it in directly. In default template, it is a ButtonSpinner control with the up and down repeat buttons.

It also has a StyleTypeProperty named "SpinnerStyle" that allows NumericUpDown control visual to be customized without requiring re-templating.

Design & Implementation

Below is class diagram of NumericUpDown and its related types:

Class Diagram of NumericUpDown & Co.

  • The class hierarchy of UpDownBase <- UpDownBase<T> <- NumericUpDown is designed to:
    • reuse control template/visual among similar UpDown controls like DomainUpDown, DateTimeUpDown etc.
    • reuse common logic among UpDown controls
  • The non generic UpDownBase class implements simulated covariance among all UpDown controls.
  • The generic UpDownBase<T> implements:
    • the control contract: the Text and Spinner template parts, common and focused visual state groups, and the SpinnerStyle property.
    • the Value property.
    • the ParseError, ValueChanging and ValueChanged events.
    • the ApplyValue, ParseValue and FormatValue value input and output protocol.
  • The NumericUpDown class implements NumericUpDown specific semantics, like the Minimum and Maximum properties and the Minimum <= Value <= Maximum coercion logic; the DecimalPlaces property and display logic etc.
  • The Spinner class is an abstraction over the up/down buttons, to provide support for more flexible UI paradigms and controls.
  • The ValueChanging event is an attempt to provide WPF like preview events.

Scenarios

Basic Scenario

The most basic scenario of NumericUpDown control is to use it to allow user to type or pick a value from a range. For example:

    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Age: "/>
        <input:NumericUpDown x:Name="age" Width="100"/>
    </StackPanel>
Customize Visual
NumericTextBox: NumericUpDown Without Spinner

It may be common to use NumericUpDown without the Spinner (the up and down buttons) as a special TextBox to input a number. The beauty of WPF/Silverlight control design is that control behavior and UI are separated. A NumericUpDown instance without the spinner is still a fully functional NumericUpDown:

  • All properties like Minimum, Maximum, Value, IsEditable etc are still there, can be set via xaml or code, and function as expected.
  • Coercion logic to enforce Minimum <= Value <= Maximum still functions as expected.
  • All events (ParseError, ValueChanging, ValueChanged) still fires as expected.
  • User can still use up/down arrow keys to increment/decrement Value.

There are two easy ways to implement this:

  • Change SpinnerStyle StyleTypedProperty, like the following xaml hides the spinner:
    <StackPanel Orientation="Horizontal">
        <StackPanel.Resources>
            <Style x:Key="HideSpinner" TargetType="input:Spinner">
                <Setter Property="Visibility" Value="Collapsed"/>
            </Style>
        </StackPanel.Resources>
        <TextBlock Text="Age: "/>
        <input:NumericUpDown x:Name="age" Width="100"
            SpinnerStyle="{StaticResource HideSpinner}"/>
    </StackPanel>
  • Re-template: as shown below, it is pretty easy to use Blend to remove the spinner from NumericUpDown's default template:

Remove Spinner via templating

Remove Spinner via templating

Customize Layout

NumericUpDown uses a ButtonSpinner, and ButtonSpinner has Content property that is marked with [ContentProperty("Content")] attribute. This is a very elaborate design to enable NumericUpDown's like those:

NumericUpDown Top/Down Layout NumericUpDown Left/Right Layout

The trick is to make the NumericUpDown's TextBox the ButtonSpinner's Content. This is actually rather easy to do with Blend:

  • First, edit the NumericUpDown's template, drag and drop the Text TextBox element to be inside the Spinner element, thus making it the Spinner's Content property. Below screenshot is for NudTopDown template:

Edit NumericUpDown template, move Text inside Spinner.

  • then edit the Spinner's template to fine turn the layout of the two RepeatButton's relative to the TextBox. Below screen shot is for the NudLeftRight template, where we need to make StackPanel's Orientation Horizontal, and make RepeatButton's VerticalAlignment Center and HorizontalAlignment Left or Right.

NumericUpDown Left/Right Layout

Customize Behavior

We can easily add snap behavior to NumericUpDown: make the Value always snap to the next integral multiple of Increment. This can be done easily by handling ValueChanging event. We can either provide an event handler for ValueChanging event, or override OnValueChanging method, to modify the new value to be the next integral multiple of Increment. The coding is trivial, so I don't provide the code here.

Conclusion

This post so far has only focused on NumericUpDown, but our design actually allows customization, extension and reuse of all the classes shown in above class diagrams, like Spinner, ButtonSpinner, UpDownBase<T> etc. We may provide DomainUpDown, DateUpDown etc classes in future releases of the Silverlight Toolkit, which may further demonstrate the power of the design for NumericUpDown.

As always, feedbacks (comments, suggestion, corrections etc) are welcome!

Nov 14, 2008

DependencyProperty: Validation, Coercion & Change Handling (Part III: Dependency Property Code Snippet)

Introduction

This is the last part of the three part series on how to implement dependency property with validation, coercion and eventing on WPF and Silverlight. In Part I, I implemented a simple dependency property and debugged through to demonstrate how dependency property works on WPF and the common pattern for implementing dependency property on WPF. In Part , I did the same on Silverlight. Because Silverlight only supports PropertyChangedCallback, but not CoerceValueCallback and ValidateValueCallback, so validation, coercion and change handling all have to be implemented with PropertyChangedCallback. This causes PropertyChangedCallback being called recursively when validation or coercion changes the effective value of the dependency property. Plus other limitations of Silverlight property system, it can get very tricky to implement dependency property correctly. This post I will show some obscure behaviors of Silverlight RangeBase controls and their causes to further demonstrate how tricky this can be. And at last, I will provide a code snippet that implements the complete pattern of dependency property implementation I discussed in Part , as a reward to those who read through the series :-)

Silverlight RangeBase Controls

Overview

ScrollBar, ProgressBar and Slider inherits from RangeBase, which implements Minimum, Maximum and Value dependency properties, with the coercion constraint that Minimum <= Value <= Maximum.

RangeBase

This sounds simple :-), but RangeBase controls' behavior can get very odd. Take a few examples:

  1. <Slider x:Name="sl"/> gives you a slider with Minimum = 0, Value = 0, and Maximum = 10, while <Slider x:Name="sl2" Minimum="-1"/> produces a slider with Minimum = -1, Value = 0, and Maximum = 0.
  2. Click on slider sl2's drag thumb to make sl2 in focus, hit right or up arrow key, then change sl2.Maximum to a positive number 10, sl2.Value magically changes from 0 to 0.1.
  3. Set sl2's property like Value to double.NaN, an invalid value, an exception will throw, but sl2.Value is changed to double.NaN anyway, and no ValueChanged event is fired.
Source Code

Below is the code we will debug and experiment with:

  • page.xaml:
<UserControl x:Class="SLApp2.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:my="clr-namespace:SLApp2"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Slider x:Name="sl"/>
        <my:Slider2 x:Name="sl2" Minimum="-1"/>
        <Button x:Name="btn" Content="Break!"
                Click="btn_Click" HorizontalAlignment="Center"/>
    </StackPanel>
</UserControl>
  • page.xaml.cs:

 

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace SLApp2
{
    // subclass Slider to easily break at property change and key down events.
    public class Slider2 : Slider
    {
        protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
        {
            base.OnMinimumChanged(oldMinimum, newMinimum);
        }
        protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
        {
            base.OnMaximumChanged(oldMaximum, newMaximum);
        }
        protected override void OnValueChanged(double oldValue, double newValue)
        {
            base.OnValueChanged(oldValue, newValue);
        }
        protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
        {
            base.OnKeyDown(e);
        }
    }
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
        }
        // the button click event handler provides a chance to break into debugger 
        // and experiment with RangeBase validation, coercion and change handling.
        private void btn_Click(object sender, RoutedEventArgs e)
        {
        }
    }
}
Debug & Experiment

Build and run above simple Silverlight application, you will see below screen shot. Please notice the thumb location difference between the two sliders:

SilverlightApplication1

Click the Break! button to break into its event handler in Visual Studio, check the value of slider sl and sl2:

  • <Slider x:Name="sl"/>: sl has the correct value of Mininum = Value = 0 and Maximum = 10, while <Slider x:Name="sl2" Minimum="-1"/> sl2 has Minimum = -1 but Maximum = Value = 0;

image

For those who read Part II and are now familiar with the implementation pattern, this is the root cause of sl2's odd behavior:

  • RangeBase leaves _initialMax, _initialVal, _requestedMax, _requestedVal to be initialized by CLR to default value 0. This is OK for Value property, since it defaults to zero anyway; but it is wrong for Maximum property, which defaults to 1 in its DependencyProperty.Register call and uses its default value as its effective value:

RangeBase not initialize its fields

  • Setting sl2.Minimum to -1 triggers OnMinimumPropertyChanged -> CoerceMaximum, which uses the CLR initialized _requestedMax to coerce Maximum. Since _requestedMax is 0 instead of Maximum's current effective value of 1, and 0 happens to be greater than Minimum's current value of -1, so Maximum is change to 0:

RangeBase.CoerceMaximum

  • The reason <Slider x:Name="sl"> has a Maximum value of 10 instead of 1 by default is that its default template has a <Setter Property="Maximum" Value="10"/>. This line also fixes the problem that _requestedMax isn't initialized. This is why sl._requestedMax is 10 in above screen shot.

Slider default template

Next, set a break point at each function, let the application run. Select sl2's thumb to make it in focus, hit right or up arrow key, it breaks into OnKeyDown event handler. Check sl2's value: _requestedVal is 0; step through base.OnKeyDown(e); check sl2 again: _requestedVal becomes 0.1. This is because base.OnKeyDown handles the right/up arrow key stroke and tries to increase Value by SmallIncrement of 0.1; this triggers OnValuePropertyChanged -> CoerceValue, which then coerces Value back to 0, since Maximum is 0; but it remembers the request of increment by setting _requestedVal to 0.1.

image

While still inside Immediate window, set sl2.Maximum to 10. This triggers OnMaxiumPropertyChanged:

image

which in turns triggers CoerceValue, which checks _requestedVal, whose value is now 0.1. Since 0.1 is in the range of [-1, 10], so Value is changed to 0.1:

image

Last, let's set Value to double.NaN. This triggers an ArgumentException, as expected:

image

but Value is changed to double.NaN anyway, and no ValueChanged event is fired because of the ArgumentException:

image

Dependency Property Code Snippet

By now you probably have got the idea that it is pretty tricky to implement dependency property with validation, coercion and change handling correctly on Silverlight. To make this easier, below is a code snippet that provides most of the code for above mentioned implementation pattern. This code snippet was originally authored by Ted Glaza, and I modified it to provide the complete pattern. You can remove stuff you don't need for simpler dependency properties, like those that don't need validation, coercion, or change handling. You do still need to fill in your own validation, coercion, and change handling logic by modifying functions like IsValid$property$, Coerce$property$, On$property$PropertyChanged, or On$property$Changed etc, and add a single definition of private int _nestLevel. But at least you don't have to worry about most of differences between WFP and Silverlight property systems, and focus only on validation, coercion and change handling logic themselves.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Silverlight Dependency Property</Title>
      <Shortcut>sdp</Shortcut>
      <Description>Code snippet for a dependency property with validation, coercion and changed event.</Description>
      <Author>Ning Zhang</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>property</ID>
          <ToolTip>Property name</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
        <Literal>
          <ID>type</ID>
          <ToolTip>Property type</ToolTip>
          <Default>object</Default>
        </Literal>
        <Literal>
          <ID>defaultValue</ID>
          <ToolTip>Default value</ToolTip>
          <Default>null</Default>
        </Literal>
        <Literal Editable="false">
          <ID>classname</ID>
          <ToolTip>Class name</ToolTip>
          <Function>ClassName()</Function>
          <Default>MyClass</Default>
        </Literal>
        <Literal Editable="false">
          <ID>SystemWindowsDependencyProperty</ID>
          <Function>SimpleTypeName(global::System.Windows.DependencyProperty)</Function>
        </Literal>
        <Literal Editable="false">
          <ID>SystemWindowsDependencyObject</ID>
          <Function>SimpleTypeName(global::System.Windows.DependencyObject)</Function>
        </Literal>
        <Literal Editable="false">
          <ID>SystemWindowsDependencyPropertyChangedEventArgs</ID>
          <Function>SimpleTypeName(global::System.Windows.DependencyPropertyChangedEventArgs)</Function>
        </Literal>
        <Literal Editable="false">
          <ID>SystemWindowsPropertyMetadata</ID>
          <Function>SimpleTypeName(global::System.Windows.PropertyMetadata)</Function>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[#region public $type$ $property$
        /// <summary>
        /// Gets or sets the value of $property$ dependency property.
        /// </summary>
        public $type$ $property$
        {
            get { return ($type$)GetValue($property$Property); }
            set { SetValue($property$Property, value); }
        }
        /// <summary>
        /// Identifies the $property$ dependency property.
        /// </summary>
        public static readonly $SystemWindowsDependencyProperty$ $property$Property =
            $SystemWindowsDependencyProperty$.Register(
                "$property$",
                typeof($type$),
                typeof($classname$),
                new $SystemWindowsPropertyMetadata$($defaultValue$, On$property$PropertyChanged));
        /// <summary>
        /// $property$Property property changed handler.
        /// </summary>
        /// <param name="d">$classname$ that changed its $property$.</param>
        /// <param name="e">Event arguments.</param>
        private static void On$property$PropertyChanged($SystemWindowsDependencyObject$ d, $SystemWindowsDependencyPropertyChangedEventArgs$ e)
        {
            $classname$ source = ($classname$)d;
            $type$ newValue = ($type$)e.NewValue;
            $type$ oldValue = ($type$)e.OldValue;
            
            // validate newValue
            if (!IsValid$property$(newValue))
            {
                // revert back to e.OldValue
                source._nestLevel++;
                source.SetValue(e.Property, e.OldValue);
                source._nestLevel--;
                
                // throw ArgumentException
                throw new ArgumentException("Invalid $property$Property value", "e");
            }
            
            if (source._nestLevel == 0)
            {
                // remember initial state
                source._initial$property$ = oldValue;
                source._requested$property$ = newValue;
            }
            
            source._nestLevel++;
            
            // coerce newValue
            $type$ coercedValue = ($type$)Coerce$property$(d, e.NewValue);
            if (newValue != coercedValue)
            {
                // always set $property$Property to coerced value
                source.$property$ = coercedValue;
            }
            
            source._nestLevel--;
            
            if (source._nestLevel == 0 && source.$property$ != source._initial$property$)
            {
                // fire changed event only at root level and when there is indeed a change
                source.On$property$Changed(oldValue, source.$property$);
            }
        }
        /// <summary>
        /// $property$Property validation handler.
        /// </summary>
        /// <param name="value">New value of $property$Property.</param>
        /// <returns>
        /// Returns true if value is valid for $property$Property, false otherwise.
        /// </returns>
        private static bool IsValid$property$($type$ value)
        {
            return true;
        }
        /// <summary>
        /// $property$Property coercion handler.
        /// </summary>
        /// <param name="d">$classname$ that changed its $property$.</param>
        /// <param name="value">Event arguments.</param>
        /// <returns>
        /// Coerced effective value of $property$Property from input parameter value.
        /// </returns>
        private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value)
        {
            $classname$ source = ($classname$)d;
            $type$ newValue = ($type$)value;
            return newValue;
        }
        /// <summary>
        /// $property$Property changed event.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<$type$> $property$Changed;
        
        /// <summary>
        /// Called by On$property$PropertyChanged static method to fire $property$Changed event.
        /// </summary>
        /// <param name="oldValue">The old value of $property$.</param>
        /// <param name="newValue">The new value of $property$.</param>
        protected virtual void On$property$Changed($type$ oldValue, $type$ newValue)
        {
            RoutedPropertyChangedEventArgs<$type$> e = 
                new RoutedPropertyChangedEventArgs<$type$>(oldValue, newValue);
            if ($property$Changed != null)
            {
                $property$Changed(this, e);
            }
        }
        
        /// <summary>
        /// Cached previous value of $property$Property.
        /// </summary>
        private $type$ _initial$property$ = $defaultValue$;
        /// <summary>
        /// Cached originally requested value of $property$Property by user.
        /// </summary>
        private $type$ _requested$property$;
        #endregion public $type$ $property$
]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

 

Conclusion

I hope the series helped you in understanding and implementing dependency properties on WPF and Silverlight. Both WPF and Silverlight are great platforms, and will be fundamental for software development, possibly more than what Win32 did before.

As always, feedback, suggestions, corrections are welcome. Thanks!