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

Comments (2) -

Josh Smeaton
Josh Smeaton Australia
7/9/2011 11:26:09 PM #

I read the "ongoing series" Great Features for MVVM Friendly Objects and found them quite enlightening to be honest. When do you anticipate that the series will be continuing?

My biggest pet peeve with MVVM is the boilerplate property code that needs to be written for basic data binding. This can obviously be minimised with snippets and such, but that's a horrible work around for a pattern that relies on this boilerplate.

Taking a little from the series, this post and the TwoWay binding to Name/Value post, do you think it'd be relatively straight forward to generate the backing field of the property with Reflection.Emit, while keeping the attached behaviour properties you describe in your series? That, I think, would be one of the better approaches to replacing the boilerplate without direct compiler support or using some form of AOP. Thoughts?

Reply

Damon Payne
Damon Payne United States
7/10/2011 10:33:20 AM #

Josh, you could definately do this, though it might be better to use ICustomTypeProvider to make the runtime see properties for fields or the other way around.  

Reply

Pingbacks and trackbacks (1)+

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


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