Delay Binding Updates within a certain Context

by Damon Payne 16. March 2011 14:16

This came up on Twitter today and since I have a solution that's been working well for me I opted to share. When making a series of data changes that will update Bindings, How can you keep a WPF/Silverlight UI from reflecting any of these changes until you're all done?  In other words, we need the ability to treat a series of Property changes as a single unit of work.

While this works better with the design patterns from my series "Great Features for MVVM Friendly Objects" I will define a more general mechanism here.

Step 1 - Define a mechanism for not firing PropertyChanged

The first step is to give your data objects a means of not firing updates.  We'll create a simple base class here to demonstrate the principles.

public class SampleDataObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Don't fire updates!
        /// </summary>
        public bool SuspendChangeNotification { get; set; }

        protected void OnPropertyChanged(string propName)
        {
            if (null != PropertyChanged && !SuspendChangeNotification)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }

 

Step 2 - Define Reasonable Programming Semantics for Context Boundaries

Whenever I'm solving a problem I always think about the Developer Experience.  Is this going to be so annoying or hard to use that people will seek reasons to avoid using it?  Since we want to define a context with a clear beginning and end I like to see if the problem can be expressed with using() semantics.  It turns out that this one can be.  We'll create an object that does the work we need to do and implements IDisposable so it can be used in a using() {} block:

public class SuspendChangeNotifyContext: IDisposable
    {
        public SuspendChangeNotifyContext(ISupportInitialize target) : this(new List<ISupportInitialize>{target})
        {
        }

        public SuspendChangeNotifyContext(IEnumerable<ISupportInitialize> targets)
        {
            _targets = targets;
            foreach (var t in _targets)
            {
                t.BeginInit();
            }
        }

        IEnumerable<ISupportInitialize> _targets;

        public void Dispose()
        {
            foreach (var t in _targets)
            {
                t.EndInit();
            }
        }
    }

 

This means that our SampleDataObject needs to implement ISupportInitialize, which causes us to implement methods for BeginInit() and EndInit().  I chose this because this interface is already in the Silverlight libraries.  You could certainly make your own interface that looks directly at some other property or methods.

    public class SampleDataObject : INotifyPropertyChangedISupportInitialize
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        /// <summary>
        /// Don't fire updates!
        /// </summary>
        public bool SuspendChangeNotification { getset; }
 
        protected void OnPropertyChanged(string propName)
        {
            if (null != PropertyChanged && !SuspendChangeNotification)
            {
                PropertyChanged(thisnew PropertyChangedEventArgs(propName));
            }
        }
 
        public void BeginInit()
        {
            SuspendChangeNotification = true;
        }
 
        public void EndInit()
        {
            SuspendChangeNotification = false;
        }
    }

 

Now that we have all of this, we can write reasonable looking code like the following:

            var dataObject = new SampleDataObject();
            using (var ctx = new SuspendChangeNotifyContext(new List<ISupportInitialize> { dataObject }))
            {
                //make Property changes here!
            }

That's an extremely reasonable looking way to accomplish our goal, but we're not quite done yet.

Step 3 - Done Making Changes

Of course, what's missing from the above example is that after we're done, we want to notify the UI all at once about the changes we made during the SuspendChangeNotify batch.  Given what we already have that is extremely easy to do.  We can keep track of which properties changed and fire Notification for them all from EndInit().

    public class SampleDataObject : INotifyPropertyChangedISupportInitialize
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        List<string> _batchChanges;
 
        /// <summary>
        /// Don't fire updates!
        /// </summary>
        public bool SuspendChangeNotification { getset; }
 
        protected void OnPropertyChanged(string propName)
        {
            if (SuspendChangeNotification)
            {
                if (!_batchChanges.Contains(propName))
                {
                    _batchChanges.Add(propName);
                }
            }
            else if (null != PropertyChanged)
            {
                PropertyChanged(thisnew PropertyChangedEventArgs(propName));
            }
        }
 
        public void BeginInit()
        {
            SuspendChangeNotification = true;
            _batchChanges = new List<string>();
        }
 
        public void EndInit()
        {
            SuspendChangeNotification = false;
            foreach (string propName in _batchChanges)
            {
                OnPropertyChanged(propName);
            }
            _batchChanges = null;
        }
    }

Because we've done this with using() blocks this is a fairly safe way to accomplish this without concerns that the data objects will get "wedged" if an exception is thrown.

A Final Note on Design

The approach shown here, works but might appear a little untidy.  If you check out my (ongoing) series on Great Features for MVVM Friendly Objects you can see a means of an elegant repackaging of what's shown here.  This is especially true of the change-tracking portion of the code.

Enjoy!

Tags:

Architecture and Design | Silverlight | WPF

MEF for Windows Phone 7

by Damon Payne 1. March 2011 17:45

I wrote a while back about my port of MEF to Windows Phone 7, and apprently didn't make a binary available as I've been getting email and twitter requests for it.

Download it here.

See the original article for detailed notes.  The big points are that dynamic export metadata won't work due to not having Reflection Emit on WP7 and that DeploymentCatalog doesn't really make sense so I excluded System.ComponentModel.Composition.Initialization -  use AssemblyCatalogs instead.

I will be maintaining this so please report any issues.

Why is this not part of MEF Contrib?  Mostly due to some timing issues last year and my own GitHub issues.

Tags:

Silverlight | Windows Phone 7 Development | WindowsPhone7 | MEF

About the author

Damon Payne is a Microsoft MVP specializing in Smart Client solution architecture. 

INETA Community Speakers Program

Month List

Page List

flickr photostream