Daniel Vaughan

free range code

Knockout Style Observables in XAML

clock July 15, 2012 11:22 by author Daniel Vaughan

This week there was a short discussion on the WPF Disciples about Knockout observables and the implications of field level and owner level data bindings. The discussion can be found in the guts of this post.

I wanted to explore the topic further, and I decided to take a stab at implementing something analogous to Knockout Observables and Computed Observables in WPF.

In this article we look at implementing field level change notification in WPF, and how a Lambda Expression can be used to specify a composite property that raises change notifications automatically whenever an associated property changes.

To get started, let’s first take a look at a class that illustrates how composite binding are ordinarily done in a viewmodel designed for XAML (see Listing 1). It’s a simple viewmodel that contains three properties. Two properties have backing fields, while the third (FullName) is a composite property; combining the FirstName and LastName properties. When either of the non-composite properties change, change notification has to occur for the composite as well as non-composite properties.

The downside of this approach is that as the complexity of the viewmodel increases and dependencies creep in, you end up having to raise PropertyChanged events for multiple dependent properties. While this example may seem dead simple (it is), over time potential faults may creep in because of this kind of interdependence.

Listing 1. MainWindowViewModelTraditional Class

class MainWindowViewModelTraditional : INotifyPropertyChanged
{
    string firstName;
        
    public string FirstName
    {
        get
        {
            return firstName;
        }
        set
        {
            if (firstName != value)
            {
                firstName = value;
                OnPropertyChanged("FirstName");
                OnPropertyChanged("FullName");
            }
        }
    }

    string lastName;

    public string LastName
    {
        get
        {
            return lastName;
        }
        set
        {
            if (lastName != value)
            {
                lastName = value;
                OnPropertyChanged("LastName");
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get
        {
            return firstName + " " + lastName;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Taking the Knockout JS Approach

I’ve been using Knockout quite a bit lately and I like the flexibility and versatility that the binding expression interpreter offers. Knockout allows you to combine observable values in a way that allows you to forego the kind of artificial dependence seen earlier. For example, on a web page you can produce the same string as the FullName property seen in the last example using something like the following:

<span data-bind="text: firstName() + ' ' + lastName()"></span>

You can also achieve the same result using computed values in your viewmodel if you so desire.

In this article I’ve set out to see whether I could come up with something that emulates Knockout's field level notifications in observables and computed observables. I’m quite happy with the result and I believe it has potential. Stay tuned, because at the end of this article you will see how to create a computed property in C# like the following, which raises change notifications automatically:

readonly ComputedValue<string> fullName;

public ComputedValue<string> FullName
{
    get
    {
        return fullName;
    }
}

public MainWindowViewModel()
{
    fullName = new ComputedValue<string>(() => FirstName.Value + " " + ToUpper(LastName.Value));
}

Bridging the HTML-XAML Ravine

In WPF, when using the INotifyPropertyChanged approach to data binding, change notification occurs at the owner object level. The owner is the source property in a data binding, which is ordinarily the DataContext of your control. If you’re using the MVVM pattern this is likely your viewmodel or a nested property. It is the owner object of the property that has the responsibility of raising a PropertyChanged event whenever the property changes.

Conversely, field level notification is where the source of the notification is the field itself (or in XAML, a property). The main advantage of field level notification is that your viewmodel does not need to contain any plumbing code for change notification. Field level notification isn’t achievable in XAML without defining an event for each property in your viewmodel. (Thanks to Bill Kemph for reminding me of field level notifications using dedicated events). This approach is usually avoided because it adds an undue amount of plumbing code to your viewmodel. A wrapper layer is presented in this article, which takes care of raising property changed events for you.

Two classes exist in the downloadable code that emulate the behaviour of Knockout’s observable and computed observable types: ObservableValue and ComputedValue. Both implement INotifyPropertyChanged and automatically raise the PropertyChanged event when an associated value has changed.

The ObservableValue implementation is rudimentary. Things get more interesting when we look at the ComputedValue implementation that allows you to specify a LINQ expression, which is parsed by the ComputedValue object to locate objects that implement INotifyPropertyChanged. When any such object changes, the computed value is recalculated. This allows you to create arbitrary composite properties in your viewmodel that respond to change notifications of any other associated object.

Understanding the ObservableValue Class

An ObservableValue property is defined much like a property using the traditional approach to INPC. The difference is that an ObservableValue instance wraps the value, as shown in the following excerpt:

class MainWindowViewModel
{
    readonly ObservableValue<string> firstName = new ObservableValue<string>("Alan");

    public ObservableValue<string> FirstName
    {
        get
        {
            return firstName;
        }
    }
…
}

To set the value, the ObservableValue.Value property is used. When the setter is used the PropertyChanged event is raised (see Listing 2).

Listing 2. ObservableValue Class

public class ObservableValue<T> : INotifyPropertyChanged
{
    T valueField;

    public T Value
    {
        get
        {
            return valueField;
        }
        set
        {
            if (!EqualityComparer<T>.Default.Equals(valueField, value))
            {
                valueField = value;
                OnPropertyChanged("Value");
            }
        }
    }

    public ObservableValue(T value = default(T))
    {
        Value = value;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Creating Composite Properties with the ComputedValue Class

Unlike ObservableValue, ComputedValue accepts a Lambda Expression that, when compiled, returns a value for the property. Listing 3 shows how the ComputedValue's constructor uses a specified Lamdba Expression.

Listing 3. ComputedValue Class (Excerpt)

public class ComputedValue<T> : INotifyPropertyChanged, IDisposable
{
…
    readonly Func<T> valueFunc;

    public T Value
    {
        get
        {
            T result = valueFunc();
            return result;
        }
    }

    public ComputedValue(Expression<Func<T>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        Expression body = expression.Body;

        ProcessDependents(body);

        valueFunc = expression.Compile();
    }
…
}

When an instance of ComputedValue is created, the specified expression is parsed recursively. Nested MemberExpressions that refer to objects implementing INotifyPropertyChanged are identified (see Listing 4).

Listing 4. ProcessDependents (Excerpt)

void ProcessDependents(Expression expression)
{
    switch (expression.NodeType)
    {
        case ExpressionType.MemberAccess:
            ProcessMemberAccessExpression((MemberExpression)expression);
            break;
        case ExpressionType.Call:
            ProcessMethodCallExpression((MethodCallExpression)expression);
            break;
...
        default:
            return;
    }
}

When a member expression is discovered, its owner is resolved using the ComputedValue.GetValue method. The class subscribes to the PropertyChanged event of the owner object. A PropertyChangedEventHandler is created and placed in a collection along with a WeakReference to the owner object (see Listing 5). When a change notification is received, the ComputedValue object raises its own PropertyChanged event.

Listing 5. ProcessMemberAccessExpression Method

void ProcessMemberAccessExpression(MemberExpression expression)
{
    Expression ownerExpression = expression.Expression;
    Type ownerExpressionType = ownerExpression.Type;
                                            
    if (typeof(INotifyPropertyChanged).IsAssignableFrom(ownerExpressionType))
    {
        try
        {
            string memberName = expression.Member.Name;
            PropertyChangedEventHandler handler = delegate(object sender, PropertyChangedEventArgs args)
                {
                    if (args.PropertyName == memberName)
                    {
                        OnValueChanged();
                    }
                };

            var owner = (INotifyPropertyChanged)GetValue(ownerExpression);
            owner.PropertyChanged += handler;

            handlers[handler] = new WeakReference(owner);
        }
        catch (Exception ex)
        {
            Console.WriteLine("ComputedValue failed to resolve INotifyPropertyChanged value for property {0} {1}", 
                                expression.Member, ex);
        }
    }
}

Retrieving the property value from the Lambda expression is done via expression compilation (see Listing 6). However, the expression is not able to be compiled directly as it is just a fragment. A Lambda expression is constructed by converting the specified expression into a UnaryExpression. When compiled, the resulting func is called and the result returned. In most cases the result is your viewmodel. Though, it does not have to be.

Listing 6. GetValue Method

object GetValue(Expression expression)
{
    UnaryExpression unaryExpression = Expression.Convert(expression, typeof(object));
    Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(unaryExpression);
    Func<object> getter = getterLambda.Compile();

    return getter();
}

In .NET the target of an event may be ineligible for garbage collection while the event source remains alive. Therefore if we do not unsubscribe from owner objects’ PropertyChanged events, a memory leak may ensue. The ComputedValue class implements IDisposable and when disposed the ComputedValue instance unsubscribes from all change events.

The ComputedValue IDisposable implementation loops over the items in the handlers dictionary (see Listing 7). If KeyValuePair.Value property holds a WeakReference to the owner of the property, where the owner has not been disposed, the PropertyChanged event is unsubscribed using the handler stored as the KeyValuePair.Key property.

Listing 7. ComputedValue IDisposable Implementation

bool disposed;

protected virtual void Dispose(bool disposing)
{
    if (!disposed)
    {
        if (disposing)
        {
            foreach (KeyValuePair<PropertyChangedEventHandler, WeakReference> pair in handlers)
            {
                INotifyPropertyChanged target = pair.Value.Target as INotifyPropertyChanged;
                if (target != null)
                {
                    target.PropertyChanged -= pair.Key;
                }
            }

            handlers.Clear();
        }

        disposed = true;
    }
}

public void Dispose()
{
    Dispose(true);
}

The downloadable code contains a simple single window application that displays two TextBox controls. Each TextBox is bound to a viewmodel property of type ObservableValue. There is a TextBlock that is bound to a ComputedValue property, which combines the result of the two ObservableValue properties (see Figure 1). When the text in either of the TextBoxes is changed, the TextBlock containing the full name is updated.

Note how the LastName property is converted to upper case. This is performed despite the fact that the call is embedded within the Lambda expression of the ComputedValue.

Figure 1.Sample App.

Just to prove no funny business is taking place in the Window XAML, Listing 8 shows how the text properties are bound to the Value property of the ObservableValue properties and the ComputedValue property.

Listing 8. MainWindows.xaml (Excerpt)

<StackPanel Orientation="Horizontal" Margin="40">
    <StackPanel>
        <Label>First Name</Label>
        <TextBox Text="{Binding Path=FirstName.Value, UpdateSourceTrigger=PropertyChanged}" />
        <Label>Last Name</Label>
        <TextBox Text="{Binding Path=LastName.Value, UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
    <StackPanel Margin="20,0">
        <Label>Full Name</Label>
        <TextBlock Text="{Binding Path=FullName.Value}" 
                    Style="{StaticResource FullNameStyle}" />
                
        <TextBlock>fullName = new ComputedValue&lt;string&gt;(<LineBreak />
            () => FirstName.Value + " " + ToUpper(LastName.Value));</TextBlock>
    </StackPanel>
</StackPanel>

The MainWindowViewModel code is shown in its entirety in Listing 9. The FirstName and LastName properties are combined in the ComputedValue, along with an arbitrary call to a ToUpper method.

Listing 9. MainWindowViewModel Class

class MainWindowViewModel
{
    readonly ObservableValue<string> firstName = new ObservableValue<string>("Alan");

    public ObservableValue<string> FirstName
    {
        get
        {
            return firstName;
        }
    }

    readonly ObservableValue<string> lastName = new ObservableValue<string>("Turing");

    public ObservableValue<string> LastName
    {
        get
        {
            return lastName;
        }
    }

    readonly ComputedValue<string> fullName;

    public ComputedValue<string> FullName
    {
        get
        {
            return fullName;
        }
    }

    public MainWindowViewModel()
    {
        fullName = new ComputedValue<string>(() => FirstName.Value + " " + ToUpper(LastName.Value));
    }

    string ToUpper(string s)
    {
        return s.ToUpper();
    }
}

Knockout JS has some elegant features that leverage the versatility of JavaScript and help to bridge the gap between XAML and HTML. In this article we looked at implementing field level change notification in WPF, and how a Lambda Expression can be used to specify a composite property that raises change notifications automatically whenever an associated property changes.

Vaughan.ObservablePrototype_2012_07_15.rar (250.68 kb)

Follow me on twitter



Enforcing Single Instance WPF Applications

clock August 1, 2010 17:39 by author Daniel Vaughan

Introduction

Today the WPF Disciples, and in particular my good friend and fellow WPF Disciple, Pete O'Hanlon, were sitting around the proverbial campfire, discussing how to enforce single instance WPF apps, for Pete's cool Goldlight project. By single instance WPF apps, I mean limiting an executable to only one instance in execution. This can be useful in scenarios where multiple application instances may play havoc with shared state. I took some time away from writing my book, to see if I could come up with something usable.

Building a Single Instance Application Enforcer

The singleton application model works like this:

  1. User starts app1.
  2. User starts app2.
  3. App2 detects app1 is running.
  4. App2 quits.

 

There is, however, a second part to our challenge, as Pete pointed out. What happens if we wish to pass some information from app2 to app1 before app2 quits? If, for example, the application is associated with a particular file type, and the user happens to double click on a file of that type, then we would have app2 tell app1 what file the user was trying to open.

To accomplish this we need a means to communicate between the two application instances. There are a number of approaches that could be taken, and some include:

  • Named pipes
  • Sending a message to the main application's window with a native API call
  • MemoryMappedFile

I chose to go for the MemoryMappedFile, along with an EventWaitHandle. The consensus by the Disciples was for a Mutex (not an EventWaitHandle), but the Mutex turned out not to provide for the initial signaling that I needed. I have encapsulated the logic for the singleton application enforcement, into a class called SingletonApplicationEnforcer (see Listing 1). The class instantiates the EventWaitHandle, and informs the garbage collector via the GC.KeepAlive method, that it should not be garbage collected. If this is the only application that has instantiated the EventWaitHandle with the specified name, then the createdNew argument will be set to true. This is how we determine, if the application is the singleton application.

Listing 1: SingletonApplicationEnforcer Class

/// <summary>
/// This class allows restricting the number of executables in execution, to one.
/// </summary>
public sealed class SingletonApplicationEnforcer
{
    readonly Action<IEnumerable<string>> processArgsFunc;
    readonly string applicationId;
    Thread thread;
    string argDelimiter = "_;;_";

    /// <summary>
    /// Gets or sets the string that is used to join 
    /// the string array of arguments in memory.
    /// </summary>
    /// <value>The arg delimeter.</value>
    public string ArgDelimeter
    {
        get
        {
            return argDelimiter;
        }
        set
        {
            argDelimiter = value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SingletonApplicationEnforcer"/> class.
    /// </summary>
    /// <param name="processArgsFunc">A handler for processing command line args 
    /// when they are received from another application instance.</param>
    /// <param name="applicationId">The application id used 
    /// for naming the <seealso cref="EventWaitHandle"/>.</param>
    public SingletonApplicationEnforcer(Action<IEnumerable<string>> processArgsFunc, 
        string applicationId = "DisciplesRock")
    {
        if (processArgsFunc == null)
        {
            throw new ArgumentNullException("processArgsFunc");
        }
        this.processArgsFunc = processArgsFunc;
        this.applicationId = applicationId;
    }

    /// <summary>
    /// Determines if this application instance is not the singleton instance.
    /// If this application is not the singleton, then it should exit.
    /// </summary>
    /// <returns><c>true</c> if the application should shutdown, 
    /// otherwise <c>false</c>.</returns>
    public bool ShouldApplicationExit()
    {
        bool createdNew;
        string argsWaitHandleName = "ArgsWaitHandle_" + applicationId;
        string memoryFileName = "ArgFile_" + applicationId;

        EventWaitHandle argsWaitHandle = new EventWaitHandle(
            false, EventResetMode.AutoReset, argsWaitHandleName, out createdNew);

        GC.KeepAlive(argsWaitHandle);

        if (createdNew)
        {
            /* This is the main, or singleton application. 
                * A thread is created to service the MemoryMappedFile. 
                * We repeatedly examine this file each time the argsWaitHandle 
                * is Set by a non-singleton application instance. */
            thread = new Thread(() =>
                {
                    try
                    {
                        using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(memoryFileName, 10000))
                        {
                            while (true)
                            {
                                argsWaitHandle.WaitOne();
                                using (MemoryMappedViewStream stream = file.CreateViewStream())
                                {
                                    var reader = new BinaryReader(stream);
                                    string args;
                                    try
                                    {
                                        args = reader.ReadString();
                                    }
                                    catch (Exception ex)
                                    {
                                        Debug.WriteLine("Unable to retrieve string. " + ex);
                                        continue;
                                    }
                                    string[] argsSplit = args.Split(new string[] { argDelimiter }, 
                                                                    StringSplitOptions.RemoveEmptyEntries);
                                    processArgsFunc(argsSplit);
                                }

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Unable to monitor memory file. " + ex);
                    }
                });

            thread.IsBackground = true;
            thread.Start();
        }
        else
        {
            /* Non singleton application instance. 
                * Should exit, after passing command line args to singleton process, 
                * via the MemoryMappedFile. */
            using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(memoryFileName))
            {
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    var writer = new BinaryWriter(stream);
                    string[] args = Environment.GetCommandLineArgs();
                    string joined = string.Join(argDelimiter, args);
                    writer.Write(joined);
                }
            }
            argsWaitHandle.Set();
        }

        return !createdNew;
    }
}

If the process happens to be the singleton application, then a new thread is started that will block until it receives a signal (via the argsWaitHandle). This signal indicates that the MemoryMappedFile contains data to be read. It is non-singleton applications instances that perform this signalling, via the argsWaitHandle.Set method, after the string arguments have been written to the MemoryMappedFile.

Demonstrating the SingletonApplicationEnforcer Class

The downloadable code includes two solutions, and two projects. Each use the SingletonApplicationEnforcer class in their respective App (App.xaml.cs) classes. To try out the sample, open both solutions. Start debuging the SingleApp project first, and then launch the SingleApp2 project. What will hopefully result is that the SingleApp2 project will detect that it is not the singleton application, and it will write its command line arguments to a MemoryMappedFile. The SingleApp application will detect that data has been written to the file, and present the command line arguments in its main window (see Figure 1).

Figure 1: The demo application consists of a single window, which displays received command line arguments.

 

The entry point for either projects is the App class. It is in this class that we consume the SingletonApplicationEnforcer class, to detect whether the application should be allowed to execute (see Listing 2). The App classes constructor uses the SingletonApplicationEnforcer's ShouldApplicationExit method; which, as we saw in Listing 1, uses an EventWaitHandle to determine if it is the only instance in execution. If it isn't then the method returns true, and the application is explicitly shutdown.

Listing 2: App Class

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    readonly SingletonApplicationEnforcer enforcer = new SingletonApplicationEnforcer(DisplayArgs);

    public App()
    {
        if (enforcer.ShouldApplicationExit())
        {
            this.Shutdown();
        }
    }

    public static void DisplayArgs(IEnumerable<string> args)
    {
        foreach (var arg in args)
        {
            string message = string.Format("Received arg: {0}", arg);
            Debug.WriteLine(message);
        }
            
        var dispatcher = Current.Dispatcher;
        if (dispatcher.CheckAccess())
        {
            ShowArgs(args);
        }
        else
        {
            dispatcher.BeginInvoke(
                new Action(delegate
                {
                    ShowArgs(args);
                }));
        }
    }

    static void ShowArgs(IEnumerable<string> args)
    {
        var mainWindow = Current.MainWindow as MainWindow;
        if (mainWindow != null && args != null)
        {
            foreach (var arg in args)
            {
                mainWindow.ViewModel.Args.Add(arg);
            }
        }
    }

}

As usual, this application uses the MVVM pattern. The MainWindow has its DataContext property set to an instance of the MainWindowViewModel (see Listing 3).

Listing 3: MainWindow XAML

<Window x:Class="SingleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Singleton Application Enforcer Example" 
        Height="350" Width="525" ResizeMode="NoResize" Background="Black">
    <StackPanel>
        <Image Source="Images/DisciplesBackground.jpg" />
        <StackPanel Margin="10">
            <TextBlock Text="Received Args:" FontSize="16" Padding="10" Foreground="White"/>
            <ListBox ItemsSource="{Binding Args}" 
                     VerticalAlignment="Stretch" Height="185" />
        </StackPanel>
    </StackPanel>
</Window>

The viewmodel (MainWindowViewModel), shown in Listing 2, exposes an ObsertableCollection of strings, that reflect arguments that are passed to the application, from other application instances.

Listing 4: MainWindowViewModel

public class MainWindowViewModel : INotifyPropertyChanged
{
    static ObservableCollection<string> args = new ObservableCollection<string>();

    public ObservableCollection<string> Args
    {
        get
        {
            return args;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


Conclusion

In this post we have seen how a MemoryMappedFile can be used in conjunction with a EventWaitHandle, to provide for cross process communication in order to limit execution to a single application instance. We also saw how to communicate command line arguments to the singleton application, so that it can respond adequately to user intent.

I hope you enjoyed this post, and that you find the code and ideas presented here useful.

Download code: SingletonEnforcementV2.zip (345.05 kb)



Compile-Time Validation of Composite Object Data Binding Expressions

clock November 7, 2009 18:06 by author Daniel Vaughan

Introduction

Prompted by a recent comment on the T4 Metadata Generation template article, which I released some weeks ago, I have implemented a new mechanism for concatenating property paths. This allows compile time validation of properties that exist on composite or nested members.

Background

Previously I have demonstrated how generated metadata can be used to provide compile-time validation of binding expressions. Rather than using string literals in binding expressions, one is able to use the x:Static markup extension and a T4 generated constant to indicate the binding path; as shown in the following excerpt.

<Label Content="{Binding Path={x:Static Metadata:PersonMetadata.NamePath}}"/>

Overcoming Limitations

This approach works fine when targeting a property from a single instance in a DataContext, but what happens when we wish to target a nested instance’s property? For example, and as demonstrated in the downloadable sample from the article mentioned above, if we have a ListBox populated with Person instances, and we wish to bind a label to the listbox’s SelectedItem.Address.StreetAddress property, we can do so using the following XAML:

<ListBox x:Name="listBox" Background="Black">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Orientation="Horizontal">
        <Label Content="{Binding Path={x:Static Metadata:PersonMetadata.NamePath}}"/>
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
<Label Content="{Binding ElementName=listBox, 
    Path={Demo:JoinPath 
                SelectedItem, 
                {x:Static Metadata:PersonMetadata.Address}, 
                {x:Static Metadata:AddressMetadata.StreetLine}}}"/>

Here we see a custom MarkupExtension called JoinPathExtension is used to enable the concatenation of path strings to create a PropertyPath that is used to target the nested Address instance. In this case, the string values of ‘SelectedItem’, ‘Address’, and ‘StreetLine’ combine to produce a PropertyPath ‘SelectedItem.Address.StreetLine’.

You will notice, when you open the CS Window1.xaml file in the sample download, that errors are reported for the Path expressions. These don’t prevent the designer from loading in either Visual Studio or Blend. They are, however, annoying.

 

Diagram: Visual Studio Xaml designer errors.

Attempting to resolve this issue I switched to using named arguments. No luck there either I’m afraid, with the x:Static expression resulting in a compile time error:

(Unknown property 'Converter' for type 'MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension' encountered while parsing a Markup Extension. Line x position Y)

My fellow disciple Philipp Sumi has a great post outlining the VS designer bug. 

I have experimented with a number of approaches, including (as Philipp suggests) explicit property syntax, and have settled on the one shown above.

The main parts of the JoinPathExtension are shown:

CS:

/// <summary>
/// Allows a set of property path strings to be concatenates 
/// into a <see cref="PropertyPath"/> instance.
/// </summary>
[MarkupExtensionReturnType(typeof(PropertyPath))]
public class JoinPathExtension : MarkupExtension
{
  readonly List<string> members = new List<string>(); 
 
  public JoinPathExtension()
  {
    /* Intentionally left blank. */
  }
 
  public JoinPathExtension(string member0)
  {
    if (member0 == null)
    {
      throw new ArgumentNullException("member0");
    }
    members.Add(member0);
  }
 
  public JoinPathExtension(string member0, string member1)
    : this(member0)
  {
    if (member1 == null)
    {
      throw new ArgumentNullException("member1");
    }
    members.Add(member1);
  }
 
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    var path = string.Join(".", members.ToArray());
    var result = new PropertyPath(path);
    return result;
  }
 
  void SetMember(int index, string value)
  {
    if (value == null)
    {
      throw new ArgumentNullException("value");
    }
    if (members.Count < index + 1)
    {
      members.Add(value);
      return;
    }
    members[index] = value;
  }
 
  #region Named member properties
  [ConstructorArgument("member0")]
  public string Member0
  {
    get
    {
      return members[0];
    }
    set
    {
      SetMember(0, value);
    }
  }
 
  public string Member1
  {
    get
    {
      return members[1];
    }
    set
    {
      SetMember(1, value);
    }
  }
 
}

VB.NET:

Imports System.Windows.Markup

Public Class JoinPathExtension
    Inherits MarkupExtension
    ' Methods
    Public Sub New()
        Me.members = New List(Of String)
    End Sub

    Public Sub New(ByVal memberList As String())
        Me.members = New List(Of String)
        If (memberList Is Nothing) Then
            Throw New ArgumentNullException("memberList")
        End If
        Me.members.AddRange(memberList)
    End Sub

    Public Sub New(ByVal member1 As String)
        Me.members = New List(Of String)
        If (member1 Is Nothing) Then
            Throw New ArgumentNullException("member1")
        End If
        Me.members.Add(member1)
    End Sub

    Public Sub New(ByVal member1 As String, ByVal member2 As String)
        Me.New(member1)
        If (member2 Is Nothing) Then
            Throw New ArgumentNullException("member2")
        End If
        Me.members.Add(member2)
    End Sub

    Public Overrides Function ProvideValue(ByVal serviceProvider As IServiceProvider) As Object
        Return New PropertyPath(String.Join(".", Me.members.ToArray), New Object(0 - 1) {})
    End Function


    ' Properties
    <ConstructorArgument("member1")> _
    Public Property Member() As String
        Get
            Return Me.members.Item(0)
        End Get
        Set(ByVal value As String)
            If (value Is Nothing) Then
                Throw New ArgumentNullException("value")
            End If
            If (Me.members.Count < 1) Then
                Me.members.Add(value)
            Else
                Me.members.Item(0) = value
            End If
        End Set
    End Property

    <ConstructorArgument("member2")> _
    Public Property Member2() As String
        Get
            Return Me.members.Item(1)
        End Get
        Set(ByVal value As String)
            If (value Is Nothing) Then
                Throw New ArgumentNullException("value")
            End If
            If (Me.members.Count < 2) Then
                Me.members.Add(value)
            Else
                Me.members.Item(1) = value
            End If
        End Set
    End Property


    ' Fields
    Private ReadOnly members As List(Of String)
End Class

Conclusion

We have seen how by using a custom MarkupExtension we are able to concatenate generated property name constants to produce PropertyPaths, which can be consumed by Path binding expressions. Having the capability to join path expression adds a lot to the flexibility of the generated metadata approach. We are now able to fully express property paths for nested objects in binding expressions, without resorting to string literals; increasing dramatically the flexibility of this approach.

Download the sample code from here.

 

 



Article Published: Project Metadata Generation using T4

clock September 3, 2009 22:58 by author Daniel Vaughan

Metadata Generation with T4 Overview

This article is an elaboration of my previous experimentation with T4 (Text Template Transformation Toolkit) and describes how to use T4, which is built into Visual Studio 2008, and the Visual Studio automation object model API, to generate member and type information for an entire project. Generated metadata can then be applied to such things as dispensing with string literals in XAML binding expressions and overcoming the INotifyPropertyChanged property name string code smell, or indeed any place you need to refer to a property, method, or field by its string name. There is also experimental support for obfuscation, so member names can be retrieved correctly even after obfuscation. I've also ported the template to VB.NET, so our VB friends can join in on the action too.

View Article



Property Change Notification using a Weak Referencing Strategy

clock August 2, 2009 22:03 by author Daniel Vaughan

Features

  • Desktop and Silverlight CLR compatibility
  • Capability to perform assignment and raise appropriate events before and after assignment.
  • Weak referenced
  • Provides for both expression tree and loosely typed strings
  • Uses extended EventArgs to supply before and after values
  • Extended PropertyChangingEventArgs for cancellable changes
  • Configurable to use caching of EventArgs to decrease heap fragmentation
  • Comes with unit tests for Desktop and Silverlight CLRs

Introduction

INotifyPropertyChanged is a ubiquitous part of Silverlight and WPF programming. It is used extensively in WPF and Silverlight to enable non-DependencyObjects to signal that a bound value is out of date. I’ve seen many approaches that have been used in order to remove the property name string requirement. Some have employed lambda expressions, or walking the stack, while others have used generated proxies or AOP point cuts. This post and the accompanying code are not so much about ridding us from the loosely typed string, although I do provide the means if you don’t mind a performance hit. Today there is another code smell that I would like to address, and it is the use of base classes for property change notification.

In this post I will demonstrate how we are able to encapsulate two property change interface implementations (INotifyPropertyChanged and INotifyPropertyChanging) in a reusable class, and in a weak referencing manner to avoid any possible leakage.

Using a base class implementation for INotifyPropertyChanged has never sat easy with me. It probably goes back to early 2003 after reading Juval Lowy’s landmark book Programming .NET Components. The principal of favouring aggregation over inheritance is a driver for creating shallow class hierarchies and maintainable components. It is a principal that offers many advantages, and one that I strongly recommend.

Using the Library

DanielVaughan.ComponentModel .PropertyChangeNotifier is the main class. You use it by creating a field in your owner class, and instanciating it within the owner’s constructor. We apply the boiler plate code, which consists of a ‘flow-through’ interface implementation for either or both INotifyPropertyChanged and INotifyPropertyChanging.

An example is shown in the following excerpt.

class MockNotifyPropertyChanged : INotifyPropertyChanged, INotifyPropertyChanging
{
    readonly PropertyChangeNotifier notifier;

    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            notifier.PropertyChanged += value;
        }
        remove
        {
            notifier.PropertyChanged -= value;
        }
    }
    #endregion

    #region INotifyPropertyChanging Implementation
    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            notifier.PropertyChanging += value;
        }
        remove
        {
            notifier.PropertyChanging -= value;
        }
    }
    #endregion

    public MockNotifyPropertyChanged()
    {
        notifier = new PropertyChangeNotifier(this);
    }

    int int1;

    public int TestPropertyAssigned
    {
        get
        {
            return int1;
        }
        set
        {
            notifier.Assign(
                "TestPropertyAssigned", ref int1, value);
        }
    }

    string string1;

    public string TestPropertyAssignedLambda
    {
        get
        {
            return string1;
        }
        set
        {
            notifier.Assign(
                (MockNotifyPropertyChanged mock) => mock.TestPropertyAssignedLambda, 
                ref string1, value);
        }
    }    
}

The two property examples shown, delegate the task of assigning the property to the PropertyChangeNotifier. This provides the PropertyChangeNotifier with the opportunity to raise the PropertyChanging event of the INotifyPropertyChanging interface, perform the assignment (or return if a handler called Cancel() on the EventArgs, then raise the PropertyChangedEvent from the INotifyPropertyChanged interface.

Implementation

The following excerpt is taken from the PropertyChangeNotifier class. It contains both Silverlight and Desktop CLR specific sections.

/// <summary>
/// This class provides an implementation of the <see cref="INotifyPropertyChanged"/>
/// and <see cref="INotifyPropertyChanging"/> interfaces. 
/// Extended <see cref="PropertyChangedEventArgs"/> and <see cref="PropertyChangingEventArgs"/>
/// are used to provides the old value and new value for the property. 
/// <seealso cref="PropertyChangedEventArgs{TProperty}"/>
/// <seealso cref="PropertyChangingEventArgs{TProperty}"/>
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
public sealed class PropertyChangeNotifier : INotifyPropertyChanged, INotifyPropertyChanging
{
    readonly WeakReference ownerWeakReference;

    /// <summary>
    /// Gets the owner for testing purposes.
    /// </summary>
    /// <value>The owner.</value>
    internal object Owner
    {
        get
        {
            if (ownerWeakReference.Target != null)
            {
                return ownerWeakReference.Target;
            }
            return null;
        }
    }

    /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangeNotifier"/> class.
    /// </summary>
    /// <param name="owner">The intended sender 
    /// of the <code>PropertyChanged</code> event.</param>
    public PropertyChangeNotifier(object owner) : this(owner, true)
    {
    }

    /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangeNotifier"/> class.
    /// </summary>
    /// <param name="owner">The intended sender 
    /// <param name="useExtendedEventArgs">If <c>true</c> the
    /// generic <see cref="PropertyChangedEventArgs{TProperty}"/>
    /// and <see cref="PropertyChangingEventArgs{TProperty}"/> 
    /// are used when raising events. 
    /// Otherwise, the non-generic types are used, and they are cached 
    /// to decrease heap fragmentation.</param>
    /// of the <code>PropertyChanged</code> event.</param>
    public PropertyChangeNotifier(object owner, bool useExtendedEventArgs)
    {
        ArgumentValidator.AssertNotNull(owner, "owner");
        ownerWeakReference = new WeakReference(owner);
        this.useExtendedEventArgs = useExtendedEventArgs;
    }

    #region event PropertyChanged

#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangedEventHandler propertyChanged;

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanged += value;
        }
        remove
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanged -= value;
        }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyChanged"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanged != null)
        {
            propertyChanged(owner, e);
        }
    }

    #endregion

    /// <summary>
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// Assignment nor notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<T, TProperty>(
        Expression<Func<T, TProperty>> expression, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        string propertyName = GetPropertyName(expression);
        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    void AssignWithNotificationAux<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        /* Boxing may occur here. We should consider 
         * providing some overloads for primitives. */
        if (Equals(property, newValue)) 
        {
            return;
        }

        if (useExtendedEventArgs)
        {
            var args = new PropertyChangingEventArgs<TProperty>(propertyName, property, newValue);

            OnPropertyChanging(args);
            if (args.Cancelled)
            {
                return;
            }

            var oldValue = property;
            property = newValue;
            OnPropertyChanged(new PropertyChangedEventArgs<TProperty>(
                propertyName, oldValue, newValue));
        }
        else
        {
            var args = RetrieveOrCreatePropertyChangingEventArgs(propertyName);
            OnPropertyChanging(args);

            var changedArgs = RetrieveOrCreatePropertyChangedEventArgs(propertyName);
            OnPropertyChanged(changedArgs);
        }
    }

    readonly Dictionary<string, string> expressions = new Dictionary<string, string>();

    /// <summary>
    /// Notifies listeners that the specified property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="oldValue">The old value before the change occured.</param>
    /// <param name="newValue">The new value after the change occured.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<TProperty>(
        string propertyName, TProperty oldValue, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }
        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        if (ReferenceEquals(oldValue, newValue))
        {
            return;
        }

        var args = useExtendedEventArgs
            ? new PropertyChangedEventArgs<TProperty>(propertyName, oldValue, newValue)
            : RetrieveOrCreatePropertyChangedEventArgs(propertyName);

        OnPropertyChanged(args);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Notifies listeners that the property has changed.
    /// Notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="oldValue">The old value of the property before it was changed.</param>
    /// <param name="newValue">The new value of the property after it was changed.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<T, TResult>(
        Expression<Func<T, TResult>> expression, TResult oldValue, TResult newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNull(expression, "expression");

        string name = GetPropertyName(expression);
        NotifyChanged(name, oldValue, newValue);
    }
    
    static MemberInfo GetMemberInfo<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        var member = expression.Body as MemberExpression;
        if (member != null)
        {
            return member.Member;
        }

        /* TODO: Make localizable resource. */
        throw new ArgumentException("MemberExpression expected.", "expression");
    }

    #region INotifyPropertyChanging Implementation
#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangingEventHandler propertyChanging;

    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging += value;
        }
        remove
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging -= value;
        }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyChanging"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanging(PropertyChangingEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanging != null)
        {
            propertyChanging(owner, e);
        }
    }
    #endregion

#if SILVERLIGHT
    readonly object expressionsLock = new object();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        lock (expressionsLock)
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                if (!expressions.TryGetValue(expression.ToString(), out name))
                {
                    var memberInfo = GetMemberInfo(expression);
                    if (memberInfo == null)
                    {
                        /* TODO: Make localizable resource. */
                        throw new InvalidOperationException("MemberInfo not found.");
                    }
                    name = memberInfo.Name;
                    expressions.Add(expression.ToString(), name);
                }
            }
        }

        return name;
    }
#else
    readonly ReaderWriterLockSlim expressionsLock = new ReaderWriterLockSlim();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        expressionsLock.EnterUpgradeableReadLock();
        try
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                expressionsLock.EnterWriteLock();
                try
                {
                    if (!expressions.TryGetValue(expression.ToString(), out name))
                    {
                        var memberInfo = GetMemberInfo(expression);
                        if (memberInfo == null)
                        {
                            /* TODO: Make localizable resource. */
                            throw new InvalidOperationException("MemberInfo not found.");
                        }
                        name = memberInfo.Name;
                        expressions.Add(expression.ToString(), name);
                    }
                }
                finally
                {
                    expressionsLock.ExitWriteLock();
                }
            }
        }
        finally
        {
            expressionsLock.ExitUpgradeableReadLock();
        }
        return name;
    }
#endif

    bool cleanupOccured;

    bool OwnerDisposed
    {
        get
        {
            /* We slightly improve performance here 
             * by avoiding multiple Owner property calls 
             * after the Owner has been disposed. */
            if (cleanupOccured)
            {
                return true;
            }
            var owner = Owner;
            if (owner != null)
            {
                return false;
            }
            cleanupOccured = true;
            var changedSubscribers = propertyChanged.GetInvocationList();
            foreach (var subscriber in changedSubscribers)
            {
                propertyChanged -= (PropertyChangedEventHandler)subscriber;
            }
            var changingSubscribers = propertyChanging.GetInvocationList();
            foreach (var subscriber in changingSubscribers)
            {
                propertyChanging -= (PropertyChangingEventHandler)subscriber;
            }

            /* Events should be null at this point. Nevertheless... */
            propertyChanged = null;
            propertyChanging = null;
            propertyChangedEventArgsCache.Clear();
            propertyChangingEventArgsCache.Clear();

            return true;
        }
    }

    [Conditional("DEBUG")]
    void ValidatePropertyName(string propertyName)
    {
#if !SILVERLIGHT
        var propertyDescriptor = TypeDescriptor.GetProperties(Owner)[propertyName];
        if (propertyDescriptor == null)
        {
            /* TODO: Make localizable resource. */
            throw new Exception(string.Format(
                "The property '{0}' does not exist.", propertyName));
        }
#endif
    }

    bool useExtendedEventArgs;
    readonly Dictionary<string, PropertyChangedEventArgs> propertyChangedEventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();
    readonly Dictionary<string, PropertyChangingEventArgs> propertyChangingEventArgsCache = new Dictionary<string, PropertyChangingEventArgs>();

#if SILVERLIGHT
    readonly object propertyChangingEventArgsCacheLock = new object();

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName, 
            propertyChangingEventArgsCacheLock, 
            propertyChangingEventArgsCache, 
            x => new PropertyChangingEventArgs(x));

        return result;
    }

    readonly object propertyChangedEventArgsCacheLock = new object();

    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName,
            propertyChangedEventArgsCacheLock,
            propertyChangedEventArgsCache,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    static TArgs RetrieveOrCreateEventArgs<TArgs>(
        string propertyName, object cacheLock, Dictionary<string, TArgs> argsCache, 
        Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;

        lock (cacheLock)
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }

            result = createFunc(propertyName);
            argsCache[propertyName] = result;
        }
        return result;
    }
#else
    readonly ReaderWriterLockSlim propertyChangedEventArgsCacheLock = new ReaderWriterLockSlim();
    
    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangedEventArgsCache,
            propertyChangedEventArgsCacheLock,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    readonly ReaderWriterLockSlim propertyChangingEventArgsCacheLock = new ReaderWriterLockSlim();

    static TArgs RetrieveOrCreateArgs<TArgs>(string propertyName, Dictionary<string, TArgs> argsCache,
        ReaderWriterLockSlim lockSlim, Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;
        lockSlim.EnterUpgradeableReadLock();
        try
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }
            lockSlim.EnterWriteLock();
            try
            {
                if (argsCache.TryGetValue(propertyName, out result))
                {
                    return result;
                }
                result = createFunc(propertyName);
                argsCache[propertyName] = result;
                return result;
            }
            finally
            {
                lockSlim.ExitWriteLock();
            }
        }
        finally
        {
            lockSlim.ExitUpgradeableReadLock();
        }
    }

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangingEventArgsCache,
            propertyChangingEventArgsCacheLock,
            x => new PropertyChangingEventArgs(x));

        return result;
    }
#endif

}

 

I’ve extended the PropertyChangedEventArgs and the PropertyChangingEventArgs to include before and after values. The following excerpt shows the PropertyChangedEventArgs.

/// <summary>
/// Provides data for the <see cref="INotifyPropertyChanged.PropertyChanged"/> event,
/// exposed via the <see cref="PropertyChangeNotifier"/>.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
public sealed class PropertyChangedEventArgs<TProperty> : PropertyChangedEventArgs
{
    /// <summary>
    /// Gets the value of the property before it was changed.
    /// </summary>
    /// <value>The old value.</value>
    public TProperty OldValue { get; private set; }
        /// <summary>
    /// Gets the new value of the property after it was changed.
    /// </summary>
    /// <value>The new value.</value>
    public TProperty NewValue { get; private set; }
        /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangedEventArgs{TProperty}"/> class.
    /// </summary>
    /// <param name="propertyName">Name of the property that changed.</param>
    /// <param name="oldValue">The old value before the change occured.</param>
    /// <param name="newValue">The new value after the change occured.</param>
    internal PropertyChangedEventArgs(
        string propertyName, TProperty oldValue, TProperty newValue) 
        : base(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

INotifyPropertyChanging doesn’t exist in Silverlight, so I’ve implemented it.

In order to turn of the extended EventArgs, pass an extra argument to the constructor. By turning of the extended EventArgs, we enable to arg caching feature. I implemented this after reading Josh Smith’s nice articles on the subject.

The PropertyChangeNotifier retains a link to the Owner using a WeakReference. Each time a change is handled, the PropertyChangeNotifier checks to see if the Owner is still alive. If it isn’t, the PropertyChangeNotifier will remove all event handlers.

Unit Tests

The download includes various unit tests for both the Desktop and Silverlight environments. I recommend examining them, to further your understanding about how it all works.

Figure: Desktop CLR test results.

 

Figure: Silverlight CLR test results.

 

Future Enhancements

  • Batch support
  • Event Suppression

Download source code for the Desktop and Silverlight CLRs: Core_01_01.zip (1.42 mb)



A location agnostic Message Service

clock June 15, 2009 21:20 by author Daniel Vaughan

When developing an application, clearly it’s prudent to have uniformity in the manner certain tasks are carried out, thereby avoiding violation of the DRY principle. An example of this is displaying common dialog boxes. But wait, if you think this post is just going to be about an abstracted dialog box system, then think again. While Calcium does provide a common dialog system, it also allows us to display a dialog to the user from the server during any WCF call! Moreover, it allows us to consume the same api on the client and the server. This means we are able to interact with the user directly, without having to e.g., interpret the result of a WCF call in the client. It effectively narrows the tier gap.

In this post, firstly we will discuss the client-side message service, then we will examine how it is leveraged from the server-side to provide the location agnosticism.

So, obviously it’s unwise for each member of a development team to be creating his or her own dialogs for simple tasks such as asking the user a closed ended question (e.g. a Yes/No question box). If we decide to change the caption in the dialogs across the board, it is rather more difficult if dialogs are scattered across the project. Likewise if we wish to port the application from WPF to Silverlight, or even to a command line interface, think Powershell or mobile applications, it’s great to be able to swap out the implementation for any given scenario. Clearly an abstracted system is in order.

Out of the box, Calcium comes with a number of Message Service implementations. There is a client implementation for WPF, another client-side implementation for command line driven applications, and a server-side implementation that sends messages back to the client via a callback, and leverages the client-side IMessageService implementation.

The Message Service has various overloads which allow the caption and message to be specified.

Let’s take a look at the IMessageService interface and client-side implementations.

Diagram: IMessageService allows us to interact with the user in a UI agnostic manner.

The Message Service allows for a Message Importance level to be specified. This allows for a threshold noise level to be specified by the user. If the MessageImportance is lower than the user’s preference, then the user won’t be bothered with the message. In a later version of Calcium we shall see a Preferences Service for specifying the preferred level.

Diagram: MessageService and CommandLineMessageService both override ShowCustomDialog method of MessageServiceBase to cater for their particular environments.

By applying variation through merely overriding the ShowCustomDialog method it also makes it very easy to mock the MessageServiceBase class for testing.

Our client-side WPF implementation channels all messaging requests through the ShowCustomDialog method as shown in the following excerpt.

/// <summary>
/// WPF implementation of the <see cref="IMessageService"/>.
/// </summary>
public class MessageService : MessageServiceBase
{
    public override MessageResult ShowCustomDialog(string message, string caption,
        MessageButton messageButton, MessageImage messageImage, 
        MessageImportance? importanceThreshold, string details)
    {
        /* If the importance threshold has been specified 
         * and it's less than the minimum level required (the filter level) 
         * then we don't show the message. */
        if (importanceThreshold.HasValue && importanceThreshold.Value < MinumumImportance)
        {
            return MessageResult.OK;
        }

        if (MainWindow.Dispatcher.CheckAccess())
        {    /* We are on the UI thread, and hence no need to invoke the call.*/
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            return messageBoxResult.TranslateToMessageBoxResult();
        }

        MessageResult result = MessageResult.OK; /* Satisfy compiler with default value. */
        MainWindow.Dispatcher.Invoke((ThreadStart)delegate
        {
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            result = messageBoxResult.TranslateToMessageBoxResult();
        });

        return result;
    }

    static Window MainWindow
    {
        get
        {
            return UnitySingleton.Container.Resolve<IMainWindow>() as Window;
        }
    }
}

I have created various extension methods for translating between the native WPF enums MessageBoxButton, MessageBoxImage, and MessageBoxResult. Why go to all of this trouble? On first appearances it appears like an antipattern. The reason is in fact a good one: there are differences in these enums in WPF and Silverlight, and this allows us to cater for bother without duplication.

I am considering expanding the service to support message details, and perhaps reducing the number of overloads with a reference type Message parameter. Another improvement would be to implement a Don’t show again checkbox system. I leave that for a future incarnation. I may even use Karl Shifflet’s excellent Common TaskDialog project http://www.codeproject.com/KB/WPF/WPFTaskDialogVistaAndXP.aspx (with Karl's permission).

Server-side Message Service

We’ve looked at the basic client-side implementation of IMessageService, but now things are going to get a little more interesting. With Calcium we have the capability to consume the IMessageService in the same way on both the client and server. To accomplish this we use a WCF custom header and a duplex service callback.

The demonstration application contains a module called the MessageServiceDemoModule. The view for this module contains a single button that becomes enabled when the CommunicationService has connectivity with the server.

This is code in a WCF service, which we can execute asynchronously as shown, and present the user with dialogs on the client side. Remember, this code executes server-side!

        public void DemonstrateMessageService()
        {
            var messageService = UnitySingleton.Container.Resolve<IMessageService>();
            ThreadPool.QueueUserWorkItem(delegate
            {
                messageService.ShowMessage(
                    "This is a synchronous message invoked from the server." + 
                    "The service method is still executing and waiting for your response.",
                    "DemoService", MessageImportance.High);
                bool response1 = messageService.AskYesNoQuestion(
                    "This is a question invoked from the server. Select either Yes or No.",
                    "DemoService");
                string responseString = response1 ? "Yes" : "No";
                messageService.ShowMessage(string.Format(
                    "You selected {0} as a response to the last question.", responseString), 
                    MessageImportance.High);
            });
        }
    }

In the above excerpt, we retrieve the IMessageService instance from the Unity container. This is done in same manner as we would do on the client! Being location agnostic allows us to move business logic far more easily. I have placed the Message Service calls here inside a QueueUserWorkItem delegate just to demonstrate the independence of the Message Service, in that the WCF call returns almost immediately, yet a child thread will continue to work in the background and will still retain the capability to communicate with the user. Without using a child thread to communicate with the user, we may experience a timeout if the user fails to respond fast enough. Please note that we are required to retrieve the Message Service from the Unity container before the service call completes. Without doing so will raise an exception, as the OperationContext for the service call will no longer be present.

  Figure: Server causes dialog to be presented client-side.

 

Figure: Question asked from server.

Figure: Response received and echoed back to user.

 

How does it all work? When we need a service channel we use our IChannelManager instance to retrieve an instance.

I’ve discussed the IChannelManager in a couple of articles, in particular here and here here

In order to support identification of the application instance from any WCF service call we place a custom header into every service channel we create, as shown in the following excerpt from the ServiceManagerSingleton and InstanceIdHeader classes:

public static class InstanceIdHeader
{
    public static readonly String HeaderName = "InstanceId";
    public static readonly String HeaderNamespace 
            =  OrganizationalConstants.ServiceContractNamespace;
}

We use this static class to place the header, and also to retrieve the header on the server.

void AddCustomHeaders(IClientChannel channel)
{
    MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        InstanceIdHeader.HeaderName,
        InstanceIdHeader.HeaderNamespace,
        instanceId.ToString());

    var scope = new OperationContextScope(channel);
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
}

So, when we create the service channel we add the custom header as shown in the following excerpt.

public TChannel GetChannel<TChannel>()
{
    Type serviceType = typeof(TChannel);
    object service;

    channelsLock.EnterUpgradeableReadLock();
    try
    {
        if (!channels.TryGetValue(serviceType, out service))
        {    /* Value not in cache, therefore we create it. */
            channelsLock.EnterWriteLock();
            try
            {
                /* We don't cache the factory as it contains a list of channels 
                 * that aren't removed if a fault occurs. */
                var channelFactory = new ChannelFactory<TChannel>("*");

                service = channelFactory.CreateChannel();
                AddCustomHeaders((IClientChannel)service);

                var communicationObject = (ICommunicationObject)service;
                communicationObject.Faulted += OnChannelFaulted;
                channels.Add(serviceType, service);
                communicationObject.Open(); 
                ConnectIfClientService(service, serviceType);

                UnitySingleton.Container.RegisterInstance<TChannel>((TChannel)service);
            }
            finally
            {
                channelsLock.ExitWriteLock();
            }
        }
    }
    finally
    {
        channelsLock.ExitUpgradeableReadLock();
    }

    return (TChannel)service;
}

We cache the channel until it is closed or faults. But as soon as we create a new channel, we add the header to be consumed on the server. We also have duplex channel support, which works in much the same way.

We must talk to the ICommunicationService in order to create a callback, before we attempt to consume the Message Service on the server-side. This is done automatically by the CommunicationModule. In fact, the Communication Module polls the server periodically to let it know that it is still alive. It also will detect network connectivity, or the lack thereof, and disable or enable the polling accordingly.

/// <summary>
/// Notifies the server communication service that the client is still alive.
/// Occurs on a ThreadPool thread. <see cref="NotifyAlive"/>
/// </summary>
/// <param name="state">The unused state.</param>
void NotifyAliveAux(object state)
{
    try
    {
        if (!NetworkInterface.GetIsNetworkAvailable())
        {
            return;
        }
        var channelManager = UnitySingleton.Container.Resolve<IChannelManager>();
        var communicationService 
            = channelManager.GetDuplexChannel<ICommunicationService>(callback);
        communicationService.NotifyAlive();
        if (!connected)
        {
            connected = true;
            connectionEvent.Publish(ConnectionState.Connected);
        }
        lastExceptionType = null;
    }
    catch (Exception ex)
    {
        Type exceptionType = ex.GetType();
        if (exceptionType != lastExceptionType)
        {
            Log.Warn("Unable to connect to communication service.", ex);
        }
        lastExceptionType = exceptionType;
        if (connected)
        {
            connected = false;
            connectionEvent.Publish(ConnectionState.Disconnected);
        }
    }
    finally
    {
        connecting = false;
    }
}

As an aside, the Communication Module itself has no UI. Its purpose is to interact with the CommunicationService, and to provide notifications to the client when various server-side events take place. One such is CompositeEvent is the ConnectionEvent, which can be seen in the previous excerpt.

I plan on expanding the Message Service system to allow text input, drop down list selection, and maybe even custom dialogs.

To find out more about Calcium please see the CodePlex site.



Order the Book

Ready to take your Windows Phone development skills to the next level? My book is the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 8. In it I teach through complete sample apps that illuminate each key concept with fully explained code and real-world context. Order Windows Phone 8 Unleashed

Windows Phone Experts Windows Phone Experts
LinkedIn Group

 

Bio

Daniel VaughanDaniel Vaughan is cofounder and president of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular Silverlight, WPF, WinRT, and Windows Phone.

He is a Microsoft MVP for Client Application Development, with more than a decade of commercial experience across a wide range of industries including finance, e-commerce, and multimedia.

Daniel is also the author of Windows Phone 7.5 Unleashed, the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 7.5.

Daniel is a Silverlight and WPF Insider, a member of the elite WPF Disciples group, and threetime CodeProject MVP.

Daniel is also the creator of a number of open-source projects, including Calcium, and Clog. E-mail me Send mail

 

Microsoft MVP logo Disciple
WPF and Silverlight Insiders
 

 

 

Sign in