Colin Eberhardt's Adventures in WPF

Forcing Event Consumer Cleanup without Weak Events

February 19th, 2010

This blog post describes a simple technique for ensuring that consumers of events unsubscribe their event handlers without the need for weak events.

I think the concept of managed memory, where the cleanup of unused objects from the heap is performed by a garbage collector, is a fantastic idea. It means that developers working with Java or C# (or other CLR languages) can often forget all about memory allocation, concentrating on more interesting tasks. However, whereas in the Java language the concept of memory leaks has almost completely vanished, they unfortunately rear their ugly head all to often when developing applications for the Microsoft Common Language Runtime (CLR). This is almost entirely down to one thing, events.

The problem with events is that they form a strong reference (i.e. a link between two object instances that prohibits garbage collection) in a manner that is not immediately obvious due to the syntax that event subscription uses. When subscribing to an event, the following syntax is used:

// subscribe to an event
textBox.TextChanged += new EventHandler(TextBox_TextChanged);
// or
textBox.TextChanged += TextBox_TextChanged;
 
// remove the subscription
textBox.TextChanged -= new EventHandler(TextBox_TextChanged);
// or
textBox.TextChanged -= TextBox_TextChanged;

Note the second examples use the shortened syntax where the delegate instance, EventHandler, is not explicitly constructed but added by the compiler, there is no difference semantically.

By subscribing to an event the following relationships are constructed, with the direction of reference as indicated:

As you can see from the above, adding an handler to an event creates a strong reference from source to listener. This does not cause significant problems if the listener has a shorter or the same lifecycle that the source, as is the case where you add event handlers for controls in a Windows Form for example. However, if the source has a longer lifecycle than the listener, and the listener fails to remove its event subscription, a memory leak will occur.

In practice this kind of problem often occurs in modular applications where there exists some sort of Shell or Environment that raises events or acts as a mediator. Within this hosting environment their exists numerous loosely coupled modules which by necessity have a shorter lifecycle than their container. If a module subscribes to events from the Shell but fails to unsubscribe at the end of its life a memory leak occurs. In complex systems this happens surprisingly often!

A common solution to this problem is to use Weak Events. These are events where either the reference between source and listener is weak, meaning that listener can be garbage collected if it is only referenced via this event handler. An early implementation of this pattern was developed for WPF and is provided by Greg Schechter on his blog. There is also an excellent codeproject article by Daniel Grunwald which details many different approaches to creating weak event listeners and sources.

However, whilst liberally sprinkling weak events about your code will solve potential memory leaks, it should not be used as a replacement for proper event subscription clean-up. What if a module’s event handler performs non-trivial logic? Do you really want this to occur after the module is supposed to have been destroyed? So, how do you ensure that events are being unsubscribed? This can be done by code-review, or by making use of heap profiling tools such as dotTrace. With these tools you can run your application, create and destroy modules, garbage collect then inspect your heap to see that the modules and their related objects have been removed. If not, you can trace the object references to find the offending event handler. However, this is a time consuming process.

An alternative is to make use of the fact that events are simply delegates with a highly restrictive interface applied. However, within the class where the event is defined you can treat it as an delegate, allowing you to inspect its invocation list to find all the event handlers. Therefore, if you have a long-lived service component that raises events which are handled by shorter lived modules, you can check the event invocation list after all the modules have been destroyed at application shutdown to ensure that all modules have correctly unsubscribed. This can be achieved as follows:

/// <summary>
/// An event which indicates that some stock price has changed
/// </summary>
public event EventHandler<StockPriceUpdateEventArgs> StockPriceUpdate;
 
public void Dispose(object sender, EventArgs e)
{
    // check that when this service is destroyed, that there are no event
    // subscriptions
    CheckEventHasNoSubscribers(StockPriceUpdate);
}
 
/// <summary>
/// Detects whether an event has any subscribers
/// </summary>
[Conditional("DEBUG")]
private void CheckEventHasNoSubscribers(Delegate eventDelegate)
{
    if (eventDelegate != null)
    {
        // if the event has any subscribers, create an informative error message.
        if (eventDelegate.GetInvocationList().Length != 0)
        {
            int subscriberCount = eventDelegate.GetInvocationList().Length;
 
            // determine the consumers of this event
            StringBuilder subscribers = new StringBuilder();
            foreach (Delegate del in eventDelegate.GetInvocationList())
            {
                subscribers.Append((subscribers.Length != 0 ? ", " : "") + del.Target.ToString());
            }
 
            // name and shame them!
            Debug.WriteLine(string.Format("Event: {0} still has {1} subscribers, with the following targets [{2}]",
                eventDelegate.Method.Name, subscriberCount, subscribers.ToString()));
        }
    }
}

Here we have an event which our service raises, on disposal we check that the event has no subscribers. If any modules have failed to cleanup properly we see the following message:

Event: PriceService_StockPriceUpdate still has 2 subscribers, with the following targets:
[MemoryLeakExamples.StockPriceViewer, Text: StockPriceViewer,
MemoryLeakExamples.StockPriceViewer, Text: StockPriceViewer]

This message tells us how many classes still have referenced event handlers, and their type. This should make it very easy to pinpoint the memory leak.

The sourcecode for this blog post includes a very simple WinForms application where a Stock Price service raises events that are handled by simple UI modules that can be created and destroyed by the user. Run it in DEBUG mode to see the memory leaks! Look in StockPriceViewer to see how to fix the problem.

Download the sourcecode: MemoryLeakExample.zip

Regards, Colin E.

Rippling Reflection Effect with Silverlight 3’s WriteableBitmap

December 16th, 2009

This blog post demonstrates how Silvelight 3’s WriteableBitmap can be used to create a UserControl that renders the content of any other Framework Element as a reflection with an animated ripple effect

I was sad to hear the news earlier this year that Yahoo! was pulling the plug on GeoCities. Somewhere buried deep within GeoCities is the first web page I ever created, complete with “Under Construction” banner, animated GIFs, guestbooks, and nasty background music. Unfortunately, I have no idea what the URL for that page was, and this is long before Google ran my life!

This blog post is a tribute to one of the many dynamic effects that were popular in the 90’s, animated reflections. These Java applets were quite popular for a while, but have gone the same way as GeoCities. Perhaps it is time for a revival?

The Silverlight application shown above contains a UserControl which renders an animated reflection of a referenced FrameworkElement.

The code which produces the ripple is very simple, A DispatcherTimer increments _time and redraws the reflection. The reflection itself is achieved by constructing a WriteableBitmap from the referenced element, allowing us to grab its pixel values. Another WriteableBitmap is constructed for the reflection image, and rows of pixels are copied across with a suitable Y offset to produce the ripple effect:

private double _time = 0.0;
 
private void Timer_Tick(object sender, EventArgs e)
{
    // increment phi and update the reflection
    _time += 0.4;
    UpdateReflection();
}
 
/// <summary>
/// Copies an inverted image of the referenced FrameworkElement
/// with a 'ripple' effect
/// </summary>
private void UpdateReflection()
{
    FrameworkElement reflectedFE = ReflectedElement as FrameworkElement;
 
    if (reflectedFE == null)
        return;
 
    // synchronize the element width
    Width = reflectedFE.ActualWidth;
 
    // copy the source into a writeable bitmap
    WriteableBitmap sourceBitmap = new WriteableBitmap(reflectedFE, null);
 
    // create a target which is the same width / height as the reflection element
    WriteableBitmap targetBitmap = new WriteableBitmap((int)ActualWidth, (int)ActualHeight);
 
    // copy the reflection
    for (int y = 0; y < targetBitmap.PixelHeight; y++)
    {
        double amplitude = ComputeAmplitude(y, targetBitmap.PixelHeight);
        double sinusoid = ComputeRipple(y, targetBitmap.PixelHeight, _time);
 
        // the offset to the y value index caused by the ripple
        int yOffset = (int)(sinusoid * amplitude);
 
        // compute the Y position of the line to copy from the source image
        int sourceYLocation = sourceBitmap.PixelHeight - 1 -
            ((y + yOffset) * sourceBitmap.PixelHeight) / targetBitmap.PixelHeight;
 
        // check that this value is in range
        sourceYLocation = Math.Min(sourceBitmap.PixelHeight - 1, Math.Max(0, sourceYLocation));
 
        // copy the required row
        int sourceIndex = sourceYLocation * sourceBitmap.PixelWidth;
        int targetIndex = y * targetBitmap.PixelWidth;
        for (int i = 0; i < targetBitmap.PixelWidth; i++)
        {
            targetBitmap.Pixels[targetIndex++] = sourceBitmap.Pixels[sourceIndex++];
        }                
    }
 
    targetBitmap.Invalidate();
 
    LayoutRoot.Source = targetBitmap;
}
 
/// <summary>
/// Compute the amplitude of the oscillations at a given Y position
/// </summary>
private double ComputeAmplitude(int y, int height)
{
    // our amplitude range is 1 to 3
    return ((double)y * 2) / (double)height + 1.0;
}
 
/// <summary>
/// Compute the sinusoid applied to teh image at the given location
/// </summary>
private double ComputeRipple(int y, int height, double time)
{
    // provide a ripple that is the combination of two out of phase sine waves
    double phaseFactor = (double)y / (double)height;
    return Math.Sin(time + phaseFactor * 16) + Math.Sin(time + phaseFactor * 30)
        + Math.Sin(time + phaseFactor * 62);
}

The XAML for this user control is simply an image with an opacity gradient:

<UserControl x:Class="SilverlightShimmer.ReflectionControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="300" Height="300">
    <Image x:Name="LayoutRoot">
        <Image.OpacityMask>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FF000000" Offset="0"/>
                <GradientStop Color="#00000000" Offset="1"/>
            </LinearGradientBrush>
        </Image.OpacityMask>
    </Image>
</UserControl>

This control is associated with our Christmas-ey image as follows:

<UserControl x:Class="SilverlightShimmer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightShimmer" Width="320" Height="260">
 
    <Grid Background="Black">
 
        <StackPanel Orientation="Vertical" Margin="10">            
            <Border  x:Name="controlToReflect" BorderThickness="5" BorderBrush="LightGray"
                     CornerRadius="3" HorizontalAlignment="Center">
                <Image  Source="christmas.jpg" Margin="3"
                        Stretch="None"/>
            </Border>
            <local:ReflectionControl x:Name="shimmer" Height="80" Margin="3"
                       ReflectedElement="{Binding ElementName=controlToReflect}"/>
        </StackPanel>
    </Grid>
</UserControl>

One interesting point here is the way in which the Border and Image are associated with the ReflectionControl. The ReflectedElement property is bound to the Border via an ElementName binding, however this binding has no Path. Therefore, rather than binding to a property of the referenced element, the ReflectedElement is bound to the element itself. Hence, no need for any code behind to associated the ReflectionControl with the element(s) to render.

This control can be used to render a reflection of anything (even a reflection of a reflection if you so wish). Here is a more complex example:

You can download the full sourcecode here: SilverlightShimmer.zip

Regards, Colin E.

Helpful extension methods for Show / Hide animations in Silverlight

September 26th, 2009

Today’s blog post is a couple of very simple utility methods that I have found myself using again and again …

The animations that Silverlight developers have at their disposal are both varied and powerful. It is easy to get carried away and cover your application with gratuitous animations, which soon become an unwanted distraction. However, animation, when used sparingly, can make the user experience (much as I hate buzzwords, this one seems to have stuck!) smoother and more fluid. One class of animation which I think adds to program fluidity is ones used for showing / hiding parts of the user interface.

With Silvelight, the typical process for animation is to create a storyboard in XAML that animates the element being shown / hidden. This storyboard is looked-up in the resources and triggered at the right moment. The typical code looks something like this:

Storyboard anim = layourRoot.Resources["myAnimation"] as Storyboard;
anim.Begin();

It’s a pretty simple peice of code, however, if you have multiple elements being shown / hidden, the management of resource names, and repeated code is a bit of a pain.

As an alternative I have written a couple of very simple extension methods to FrameworkElement, as shown below:

/// <summary>
/// Shows the element, playing a storyboard if one is present
/// </summary>
/// <param name="element"></param>
public static void Show(this FrameworkElement element)
{
    string animationName = element.Name + "ShowAnim";
 
    // check for presence of a show animation
    Storyboard showAnim = element.Resources[animationName] as Storyboard;
    if (showAnim != null)
    {
        showAnim.Begin();
    }
    else
    {
        element.Visibility = Visibility.Visible;
    }
}
 
/// <summary>
/// Hides the element, playing a storyboard if one is present
/// </summary>
/// <param name="element"></param>
public static void Hide(this FrameworkElement element)
{
    string animationName = element.Name + "HideAnim";
 
    // check for presence of a hide animation
    Storyboard showAnim = element.Resources[animationName] as Storyboard;
    if (showAnim != null)
    {
        showAnim.Begin();
    }
    else
    {
        element.Visibility = Visibility.Collapsed;
    }
}

What each of these extension methods does is check for the presence of a Storyboard named "ShowAnim" or "HideAnim", prefixed with the element name in order to ensure uniqueness. If the storyboard is present, the animation is played. Otherwise, the element visibility is set directly. This is a very useful feature, in that the Show() and Hide() extension methods can be used if you want to change an elements visibility without animation. If, at a later date, you then decide that you want this change in visibility to be animated, you simple add a storyboard to the XAML, without having to change the code.

Here is a simple example based on a previous blog post of mine:

Here is a XAML snippet from the example:

...
<Grid Name="Crosshair" Opacity="0">
    <Grid.Resources>
        <Storyboard x:Name="CrosshairShowAnim">
            <DoubleAnimation Storyboard.TargetName="Crosshair" 
                        Storyboard.TargetProperty="(UIElement.Opacity)"
                        To="1"  Duration="00:00:00.1"/>
        </Storyboard>
        <Storyboard x:Name="CrosshairHideAnim">
            <DoubleAnimation Storyboard.TargetName="Crosshair"                        
                        Storyboard.TargetProperty="(UIElement.Opacity)"
                        Duration="00:00:00.1" To="0"/>
        </Storyboard>
    </Grid.Resources>
    <Line Name="Vertical" X1="{Binding Path=X}" Y1="0"
          X2="{Binding Path=X}" Y2="400" Stroke="Black"/>
    <Line Name="Horizontal" X1="0" Y1="{Binding Path=Y}"
          X2="400" Y2="{Binding Path=Y}" Stroke="Black"/>
</Grid>
...

Note the storyboard name is prefixed with the element name. And here is how this animation, and the legend’s, is triggered from code:

private void CrosshairContainer_MouseEnter(object sender, MouseEventArgs e)
{
    LocationIndicator.Show();
    Crosshair.Show();
    ChartRoot.Cursor = Cursors.None;
}
 
private void CrosshairContainer_MouseLeave(object sender, MouseEventArgs e)
{
    LocationIndicator.Hide();
    Crosshair.Hide();
    ChartRoot.Cursor = Cursors.Arrow;
}

You can download the full sourcecode for the example project: showHideAnimations.zip.

Regards,
Colin E.

Declarative Dependency Property Definition with T4 + DTE

August 18th, 2009

This blog post describes a technique for specifying WPF / Silverlight Dependency Properties declaritively via attributes as illustrated by the following example:

[DependencyPropertyDecl("Maximum", typeof(double), 0.0)]
[DependencyPropertyDecl("Minimum", typeof(double), 0.0)]
public partial class RangeControl : UserControl
{
    ...
}

At design-time the declarations are read via a T4 template and the required code is generated. For more information, read on …

A few months ago I wrote an article on codeproject about a technique for generating dependency properties via T4 templates. In brief; with this technique, you provide an XML description of your classes and their dependency properties, run the T4 template, and partial classes are generated which contain these properties – no more writing DP boiler plate code! For those of you who are unfamiliar with T4, it is a code generation engine which is built into Visual Studio, for a primer, I refer you to my codeproject article.

A few days back Daniel Vaughan published a blog post about using T4 templates to generate project meta-data. He demonstrated how this could be used to solve the age-old problem of hard-coded property name strings when implementing INotifyPropertyChanged. His solution uses both T4 and DTE – an automation library which allows you to programmatically access the items within your projects and solutions. Daniel’s blog post inspired me to revisit my DP code generation solution in order to simplify it and remove the need for the XML file.

My first step was to define a custom attribute which can be used to indicate that a class requires the template to generate a DP:

[AttributeUsage(AttributeTargets.Class , AllowMultiple = true)]
public class DependencyPropertyDecl : Attribute
{
    public DependencyPropertyDecl(string name, Type type, object defaultValue)
    {
        this.name = name;
        this.type = type;
        this.defaultValue = defaultValue;
    }
 
    public string name;
    public Type type;
    public object defaultValue;
}

With DTE it should be possible to inspect all the classes in the project, identifying those for which this attribute exists and generate the required DPs. However, looking at the DTE APIs this is not such an easy task, whilst the API defines a ‘base’ interface CodeElement from which CodeClass, CodeNamespace etc… derive, there is no unified concept of ‘Children’, also the Project and Solution are not CodeElements. In other words, in order to find all the classes in a solution you must recurse over a heterogeneous collection of classes and properties.

In order to ease the task of locating the classes within the project that have the DependencyPropertyDecl attribute associated, I created a simple Linq-to-DTE implementation:

public IEnumerable<CodeElement> CodeElementsInProjectItems(ProjectItems projectItems)
{
    foreach (ProjectItem projectItem in projectItems)
    {
        foreach(CodeElement el in CodeElementsInProjectItem(projectItem))
        {
            yield return el;
        }
    }
}
 
public IEnumerable<CodeElement> CodeElementsInProjectItem(ProjectItem projectItem)
{
    FileCodeModel fileCodeModel = projectItem.FileCodeModel;
 
    if (fileCodeModel != null)
    {
        foreach (CodeElement codeElement in fileCodeModel.CodeElements)
        {
            //WalkElements(codeElement, null);
            foreach(CodeElement el in CodeElementDescendantsAndSelf(codeElement))
            {
                yield return el;
            }
        }
    }
 
    if (projectItem.ProjectItems != null)
    {
        foreach (ProjectItem childItem in projectItem.ProjectItems)
        {
            foreach (CodeElement el in CodeElementsInProjectItem(childItem))
            {
                yield return el;
            }
        }
    }        
}
 
 
public IEnumerable<CodeElement> CodeElementsDescendants(CodeElements codeElements)
{
    foreach(CodeElement element in codeElements)
    {
        foreach (CodeElement descendant in CodeElementDescendantsAndSelf(element))
        {
            yield return descendant;                
        }
    }
}
 
public IEnumerable<CodeElement> CodeElementDescendantsAndSelf(CodeElement codeElement)
{
    yield return codeElement;
 
    CodeElements codeElements;
 
    switch(codeElement.Kind)
    {        
 
        /* namespaces */
        case vsCMElement.vsCMElementNamespace:
        {
            CodeNamespace codeNamespace = (CodeNamespace)codeElement;                                        
            codeElements = codeNamespace.Members;
            foreach(CodeElement descendant in CodeElementsDescendants(codeElements))
            {
                yield return descendant;                
            }
            break;
        }
 
        /* Process classes */
        case vsCMElement.vsCMElementClass:
        {            
            CodeClass codeClass = (CodeClass)codeElement;            
            codeElements = codeClass.Members;
            foreach(CodeElement descendant in CodeElementsDescendants(codeElements))
            {                
                yield return descendant;                
            }            
            break;    
        }        
    }    
}

There might be simpler way of achieving this, it is the first time I have created a recursive IEnumerable implementation, so please let me know if I have done something stupid! For a much more elegant solution to the problem of creating an IEnumerable implementation on a tree like structure via recursion have a look at this blog post by David Jade. However, as I mentioned earlier, the heterogeneous nature of the DTE API onto the project structure prohibits this method.

Anyhow, with this nasty IEnumerable implementation out of the way, we can use the power of Linq to provide a concise implementation of ‘Find all the classes in the solution which have one or more DependencyPropertyDecl attributes’, and perform the required code-gen as follows:

// for details of how the DTE 'project' is located see Daniel's blog post or the attached source of this blog.
var elements = CodeElementsInProjectItems(project.ProjectItems);
var classes = elements.Where(el => el.Kind == vsCMElement.vsCMElementClass)
                               .Cast<CodeClass>()
                               .Where(cl => Attributes(cl).Any(at => at.Name=="DependencyPropertyDecl"));
 
foreach(var clazz in classes)
{
    GenerateClass(clazz);
}

The GenerateClass function is a simple modification of the one I wrote for generating classes based on XML descriptions. In this implementation we acquire the CodeAttribute.Value property for each of our custom attribute instances and from there obtain the DP name, type and default value:

<#+
/// 
/// Generates a class along with its associated DPs
/// 
private void GenerateClass(CodeClass clazz)
{
    string classNamespace = clazz.Namespace.Name;
    string className =  clazz.Name;

    bool classRaisesPropertyChanged = false;

#>

namespace <#= classNamespace #>
{
    public partial class <#= className #> <#+ if(classRaisesPropertyChanged){ #>: INotifyPropertyChanged<#+ } #>
    {
<#+
    var attributes = Attributes(clazz).Where(att => att.Name=="DependencyPropertyDecl");
    foreach(CodeAttribute attribute in attributes)
    {
        string[] attributeValues = attribute.Value.Split(',');

        string propertyName = attributeValues[0].Trim().Replace("\"","");
        string propertyType = attributeValues[1].Trim().Substring(7, attributeValues[1].Length - 9);
        string summary = null;
        string metadata = null;
        string defaultValue = attributeValues[2].Trim();
        string typeConverter = null;
        bool propertyChangedCallback = true;
        bool isAttached = false;
        #>

        #region <#= propertyName #>
        <#+        

        GenerateCLRAccessor(typeConverter, propertyType, propertyName, summary);

        bool handleDPPropertyChanged = propertyChangedCallback || classRaisesPropertyChanged;

        GenerateDependencyProperty(className, propertyType, defaultValue, propertyName,
                                   handleDPPropertyChanged, isAttached, metadata, summary);        

        if (handleDPPropertyChanged)
        {
            GenerateChangeEventHandler(className, propertyName, propertyChangedCallback, classRaisesPropertyChanged);
        } 

        if (isAttached)
        {
            GenerateAttachedPropertyAccessor(propertyName, propertyType);
        }
        #>
        #endregion
    <#+
    } // end foreach dps

    if (classRaisesPropertyChanged)
    {
        GenerateINotifyPropertChangedImpl();
    }
    #>
    }
}

<#+
}
#>

(please forgive the lack of syntax highlighting wordpress/codeproject do not support T4 templates!)

The GenerateCLRAccessor, GenerateDependencyProperty, … functions are re-used from my previous XML-based approach to DP generation.

For a simple demonstration of this approach, here is the complete implementation for a simple range control which has a Maximum and Minimum property, where if the user enters a Minimum which is greater that the Maximum, the values are swapped:

<UserControl x:Class="WpfDeclarativeDPCodeGen.RangeControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Horizontal">
        <TextBox  Width="50" Text="{Binding Minimum}"/>
        <TextBlock Text=" : " VerticalAlignment="Center"/>
        <TextBox  Width="50" Text="{Binding Maximum}"/>
    </StackPanel>
</UserControl>
[DependencyPropertyDecl("Maximum", typeof(double), 0.0)]
[DependencyPropertyDecl("Minimum", typeof(double), 0.0)]
[TypeConverter(typeof(string))]
public partial class RangeControl : UserControl
{
    public RangeControl()
    {
        InitializeComponent();
    }
 
    /// <summary>
    /// If max is less than min, swap their values
    /// </summary>
    private void Swap()
    {
        if (Maximum < Minimum)
        {
            double swap = Minimum;
            Minimum = Maximum;
            Maximum = swap;
        }
    }
 
    partial void OnMaximumPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        Swap();
    }
 
    partial void OnMinimumPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        Swap();
    }
}

When using this approach the workflow is quite simple, create your class, add the DependencyPropertyDecl attributes, then execute the code generation template. The output is an accompanying partial class which contains the DP code itself, plus partial methods which can be implemented in order to perform logic when the property changes.

I have not-yet completely ported my XML-based DP generation templates to this new declarative approach, for example the DependencyPropertyDecl attribute needs to also indicate property meta-data, whether a property is attached etc … However, I thought I would share my ideas in case I didn’t get the time (or urge) to make a complete implementation.

I feel that this approach has great potential far beyond DP code generation, simple examples could include attributes that indicate that a T4 template should generate a ‘generic’ INotifyPropertyChanged or IEditableObject implementation, and of course, the more complex application-specific possibilities are endless.

You can download an example project containing the RangeControl above: WpfDeclarativeDpCodeGen.zip

Try adding new DependencyPropertyDecl attributes, and re-run the code generation template (right click CodeGen/GeneratedObjects.tt -> Run Custom Tool), and inspect the generated output – CodeGen/GeneratedObjects.cs.

Finally, thanks to Daniel Vaughan for the inspiration.

Regards,
Colin E.