Daniel Vaughan

.NET Adventures

Article Published: Building a Windows Phone 7 Puzzle Game

clock March 25, 2010 12:42 by author Daniel Vaughan

Get a head start with the new Windows Phone 7 developer tools. Learn how to create a Sokoban game in Silverlight for the WP7 platform.

View Article



Silverlight YouTube Jukebox

clock February 22, 2010 16:24 by author Daniel Vaughan

My better half just published an article on creating a YouTube video viewer with Silverlight 4 and RIA Services. I may be a bit biased, but I think it's great!



Synchronous Invocation of Delegates with the Silverlight Dispatcher

clock January 10, 2010 18:14 by author Daniel Vaughan

Introduction

In this post I would like to briefly discuss the System.Windows.Threading.Dispatcher class, and the significant differences between its Silverlight and Desktop CLR implementations. We are going to look at solving two things:

  1. Consuming the UI’s Dispatcher in Silverlight before a page has been instanciated.
  2. Allowing for synchronous invocation of delegates on the UI thread.

Background

Recently my good friend Sacha Barber published an article, and in the comments of which we briefly touched on a cross threading issue that we have both experienced with the Silverlight Dispatcher class. I thought it was about time that I wrote some of this stuff up. This is the result.

Silverlight Thread Affinity

When working with Silverlight, one has to contend with the usual single threaded apartment model (STA) that is also present when working with WPF or Windows Forms. This means that one must interact with UI components, in particular DependencyObject derived types, from the thread in which they were created on. Fortunately Silverlight/WPF/Windows Forms includes infrastructure that makes acquiring and invoking calls on the UI thread simpler; specifically the System.Windows.Threading.Dispatcher, which is a prioritized message loop that handles thread affinity.

There is an excellent article describing the Dispatcher in detail.

Consuming the UI’s Dispatcher

In Silverlight a Dispatcher becomes associated with the main page during initialization, thereby making it available via the Applications RootVisual property like so:

Application.Current.RootVisual.Dispatcher

We can consume the Dispatcher this way, as long as we do so after the RootVisual has been defined. But in the case where we would like to consume the Dispatcher from the get-go, it leaves us out in the cold. Fortunately though, the Silverlight Dispatcher instance is also available via the System.Windows.Deployment.Current.Dispatcher property. This instance is defined when the Application starts, thereby making it possible to commence asynchronous operations before the first page is instanciated.

Synchronous Invocation of Delegates on the UI Thread

The Silverlight Dispatcher is geared for asynchronous operations. As we can see from the following image, unlike the Desktop CLR Dispatcher, the Silverlight Dispatcher class’s Invoke method overloads have internal visibility.

It has to be said that the Desktop CLR Dispatcher, when compared with the Silverlight version, as with many other classes, has a much richer API. In order to provide a means to synchronously invoke a delegate on the UI thread we need another approach. The approach I have taken is to utilize the System.Windows.Threading.DispatcherSynchronizationContext

By using the Post and Send methods of the DispatcherSynchronizationContext we are able to regain the synchronous Invoke capabilities within the Silverlight environment.

I have rolled this up into a set of reusable classes, located in my core Silverlight library, which you can find in the DanielVaughan.Silverlight project in the Calcium http://www.calciumsdk.net download.

/// <summary>
/// Singleton class providing the default implementation 
/// for the <see cref="ISynchronizationContext"/>, specifically for the UI thread.
/// </summary>
public class UISynchronizationContext : ISynchronizationContext
{
    DispatcherSynchronizationContext context;
    Dispatcher dispatcher;
    
    #region Singleton implementation

    static readonly UISynchronizationContext instance = new UISynchronizationContext();
    
    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    /// <value>The singleton instance.</value>
    public static ISynchronizationContext Instance
    {
        get
        {
            return instance;
        }
    }

    #endregion

    public void Initialize()
    {
        EnsureInitialized();
    }

    readonly object initializationLock = new object();

    void EnsureInitialized()
    {
        if (dispatcher != null && context != null)
        {
            return;
        }

        lock (initializationLock)
        {
            if (dispatcher != null && context != null)
            {
                return;
            }

            try
            {
                dispatcher = Deployment.Current.Dispatcher;
                context = new DispatcherSynchronizationContext(dispatcher);
            }
            catch (InvalidOperationException)
            {
                /* TODO: Make localizable resource. */
                throw new ConcurrencyException("Initialised called from non-UI thread."); 
            }
        }
    }

    public void Initialize(Dispatcher dispatcher)
    {
        ArgumentValidator.AssertNotNull(dispatcher, "dispatcher");
        lock (initializationLock)
        {
            this.dispatcher = dispatcher;
            context = new DispatcherSynchronizationContext(dispatcher);
        }
    }

    public void InvokeAsynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentValidator.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Post(callback, state);
    }

    public void InvokeAsynchronously(Action action)
    {
        ArgumentValidator.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            dispatcher.BeginInvoke(action);
        }
    }

    public void InvokeSynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentValidator.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Send(callback, state);
    }

    public void InvokeSynchronously(Action action)
    {
        ArgumentValidator.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            context.Send(delegate { action(); }, null);
        }
    }

    public bool InvokeRequired
    {
        get
        {
            EnsureInitialized();
            return !dispatcher.CheckAccess();
        }
    }
}

A further advantage of using either a Silverlight or Desktop CLR implementation of the ISynchronizationContext is that we are able to write CLR agnostic code. That is, code that was written for the Desktop CLR can be easily moved to the Silverlight.

Using the code:

UISynchronizationContext.Instance.InvokeSynchronously(delegate
                                {
/* Code to execute on the UI thread. */
                                });

Conclusion

In this post we have looked at consuming the UI’s Dispatcher in Silverlight as soon as an Application starts. We also saw how it is possible in Silverlight to accomplish synchronous invocation of delegates on the UI thread.

The full source shown in this article is available on the Calcium Codeplex site

 

 

 



Calling Web Services from Silverlight after the Browser has closed

clock October 19, 2009 23:07 by author Daniel Vaughan

Introduction

Today I was reading an excellent post by my fellow Disciple Laurent Bugnion, which led on to a short discussion about performing actions after a user attempts to close a browser window. It got me thinking about the capability to dispatch a web service call in Silverlight just after a user attempts to close the browser window or navigate elsewhere. I have been asked the question before, yet before now, have not attempted to answer it. So today I decided to do some experimenting.

There are two things I wanted to look at. Firstly, I wanted to allow a web service to be called after the Silverlight application’s Exit event is raised. Secondly, I wanted to provide the Silverlight application with the opportunity to cancel, or at least interrupt the close window process.

In the first scenario I found the way I could achieve the call after the Silverlight applications Exit event was raised, was to call a JavaScript method from Silverlight, and then finally a PageMethod; to perform the housekeeping.

 

Figure: A web service is called after the user closes the browser.

We see that in our App.xaml.cs the Exit event handler is called, which then calls a JavaScript function, as demonstrated in the following excerpt:

void Application_Exit(object sender, EventArgs e)
{
    HtmlPage.Window.Invoke("NotifyWebserviceOnClose", 
          new[] { guid.ToString() });
}

We then call a PageMethod on the page hosting the application like so:

function NotifyWebserviceOnClose(identifier) {
    PageMethods.SignOff(identifier, OnSignOffComplete);            
}

The PageMethod is where we are able to update a list of connected clients, or do whatever takes our fancy.

[WebMethod]
[System.Web.Script.Services.ScriptMethod] 
public static void SignOff(string identifier)
{
    /* Update your list of signed in users here etc. */
    System.Diagnostics.Debug.WriteLine("*** Signing Off: " + identifier);
}

Caveat lector- According to Laurent this is not reliable. So, you will need to test it for yourself. I’ve tested it locally in Firefox 3.5 and IE8. But, your results may differ.

Now the second thing I wanted to look at was the ability to provide some interaction with the user, either cancelling or calling a web service, when the user attempts to close the browser; but before the browser is actually closed.

The way I did this was to place an onbeforeunload handler in the body tag of the page, as shown:

<body onbeforeunload="OnClose()">

I then created the OnClose handler in a JavaScript script block as shown in the following excerpt:

function OnClose() {           
    var plugin = document.getElementById("silverlightControl");
    var shutdownManager = plugin.content.ShutdownManager;
    var shutdownResult = shutdownManager.Shutdown();
    if (shutdownResult) {
        event.returnValue = shutdownResult;
    }
}

Here we see that we retrieve the Silverlight application and resolve an instance of the ShutdownManager class present in the Silverlight App class. The ShutdownManager is a class that is able to perform some arbitrary duty when the application is closing. It is made accessible to the host webpage by registering it with a call to RegisterScriptableObject.

void Application_Startup(object sender, StartupEventArgs e)
{
    HtmlPage.RegisterScriptableObject("ShutdownManager", shutdownManager);
    this.RootVisual = new MainPage();
}

ShutdownManager

The ShutdownManager is decorated with two attributes that allow it to be consumed by an external source. They are SciptableType and ScriptableMember. As we see below, they are used to allow the ShutdownManager class to be called via Ajax.

[ScriptableType]
public class ShutdownManager
{
    [ScriptableMember]
    public string Shutdown()
    {
        string result = null;
        var page = (MainPage)Application.Current.RootVisual;

        if (page.Dirty)
        {
            var messageBoxResult = MessageBox.Show(
                "Save changes you have made?", "Save changes?", MessageBoxButton.OKCancel);
            if (messageBoxResult == MessageBoxResult.OK)
            {
                var waitingWindow = new WaitingWindow();
                waitingWindow.Show();

                waitingWindow.NoticeText = "Saving work on server. Please wait...";
                page.SetNotice("Saving work on server. Please wait...");

                var client = new ShutdownServiceClient();
                client.SaveWorkCompleted += ((sender, e) =>
                    {
                        waitingWindow.NoticeText = "It is now safe to close this window.";
                        page.SetNotice("It is now safe to close this window.");
                        page.Dirty = false;
                        waitingWindow.Close();
                    });
                client.SaveWorkAsync("Test id");
                result = "Saving changes.";
            }
            else
            {
                result = "If you close this window you will lose any unsaved work.";
            }
        }

        return result;
    }
}

Here we see that once the Shutdown method is called we test if the main (Silverlight) page is dirty. If so, we prompt the user to save changes. If changes are to be saved we dispatch our async web service call using a service client. The key thing to note here is that we are returning a string value if the page is deemed dirty. This string value then indicates to the consuming JavaScript code that the window should not be closed immediately. Once the call makes it back to our JavaScript onbeforeload handler, we assign the event.returnValue the value of this string. If this value is not an empty string it will cause the web browser to display a dialog; asking the user whether they really wish to leave the page.

Figure: Browser prompts with ‘Are you sure ...’ message.

In the demo application accompanying this post, you will notice that we simulate a user modification. This is done by ticking the Page dirty checkbox.

 

Figure: The demonstration application simulates a modification to the data model.

Unticked, closing the browser will result in the window closing immediately. However, ticked will cause the presentation of a Save Changes dialog, as shown below.

Figure: Save changes dialog prompts user.

What happens if the user cancels the operation? Remember our event.returnValue string in the JavaScript OnClose function. If the user cancels, then this value is assigned a string value of “If you close this window you will lose any unsaved work.” We can see how the browser chooses to display the string shown below:

Figure: User is prompted with browser initiated dialog.

Obviously we must deal with when the user selects the nominal Ok button in the save dialog. To provide a somewhat more agreeable user experience, I have dropped in a Silverlight ChildWindow control, with a ProgressBar control for good measure.

Figure: A progress bar is presented to the user while saving.

Once the user hits Ok on the Save Changes dialog, that web service call is dispatched almost immediately. In order for the web service call to occur though, we must perform interrupt the thread so that our Silverlight App has a chance to dispatch the call. We can do that with a JavaScript alert call, or as we see here, we assign the event.returnValue, which causes a dialog to be displayed in much the same manner.

Conclusion

In this post we have seen how it is possible to call a web service after a Silverlight App’s Exit event has been raised. While this may not be reliable, I’d be interested to know your experience with this approach, and certainly of any alternate approaches. Finally we looked at interacting with the user, to allow cancellation of a close window event or to perform last minute web service calls. This was achieved using the onbeforeunload event and a JavaScript handler.

Thank you for reading.

Download the sample (557.06 kb)



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)



About the author

Daniel VaughanDaniel Vaughan is a software developer with a decade of commercial experience across a wide range of industries including e-commerce, multimedia, and finance. While originally from Australia and the UK, Daniel is currently based in Geneva Switzerland; working with WPF, WCF, and WF within the finance industry. In his spare time Daniel likes to spend time concocting novel ideas, such as employing neural networks to predict user navigation behaviour in WPF applications, and a grid computing framework for Silverlight. 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
CodeProject MVP
WPF and Silverlight Insiders

 

 

RecentComments

Comment RSS

Sign in