RaisePropertyChanged using StackTrace takes away all magic strings

Jan 31, 2011 at 8:36 AM

I was thinking of adding a stack trace implementation of RaisePropertyChanged same as here: http://www.wintellect.com/CS/blogs/jlikness/archive/2010/12/17/jounce-part-8-raising-property-changed.aspx by adding an extension method to MvvmLight. 

The code (not tested on WPF yet): 

        public static void RaisePropertyChanged(this ViewModelBase viewModelBase)
        {
            var frames = new System.Diagnostics.StackTrace();
            for (var i = 0; i < frames.FrameCount; i++)
            {
                var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
                if (frame != null)
                    if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
                    {
                        viewModelBase.RaisePropertyChanged(frame.Name.Substring(4));
                        return;
                    }
            }
            throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
        }

However I found ViewModelBase RaisePropertyChanged to be marked as protected. No biggy since I can copy the code from codeplex but I'd rather hook into the API and pre built dlls.  

Also have you considered adding it to mvvmlight. Its great for refactoring :) 

Jan 31, 2011 at 8:48 AM

I went with inheritance. But I don't like it : 

public class ViewModelBaseEx : ViewModelBase
    {        
        public void RaisePropertyChanged()
        {
            var frames = new System.Diagnostics.StackTrace();
            for (var i = 0; i < frames.FrameCount; i++)
            {
                var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
                if (frame != null)
                    if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
                    {
                        RaisePropertyChanged(frame.Name.Substring(4));
                        return;
                    }
            }
            throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
        }        
    }

Coordinator
Jan 31, 2011 at 3:02 PM

Hi,

In V4, there will be a way to avoid using string, but rather with the syntax RaisePropertyChanged(vm => vm.MyProperty).

However, already in V3 the risk when refactoring is small:

  • If a property is raised, but does not actually exists on the VM, an exception is thrown. This check is only made in DEBUG configuration but should allow to catch all errors.
  • In addition, the strings are declared as constants (using the code snippets provided), so changing one is quite easy.

The reason why I am not using reflection for the moment is that the impact on perf can be an issue, especially on small systems (like the phones). But still, by popular demand, there will be the way I described above in V4 (as an optional possibility to raise the PropertyChanged event).

Cheers.

Laurent

 

Feb 1, 2011 at 12:10 PM
Edited Feb 2, 2011 at 5:46 AM

It looks like StackTrace solution is working only for Debug mode, and it's very slow.

Maybe this one could be used. Haven't tried it on mobile devices.

 Code in ViewModel base:

 

		protected void Set<TModel>(Expression<Func<TModel>> expr, ref TModel field, TModel value) where TModel : class
		{
			Set(expr, ref field, value, () => { });
		}

		protected void Set<TModel>(Expression<Func<TModel>> expr, ref TModel field, TModel value, Action callback) where TModel : class
		{
			if (field != value)
			{
				field = value;
				RaisePropertyChanged(expr);
				callback();
			}
		}

		public void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> property)
		{
			var member = (MemberExpression) property.Body;
			RaisePropertyChanged(member.Member.Name);
		}
	
Code in actual ViewModel:
		private QuestionModel question;
		public QuestionModel Question
		{
			get { return question; }
			set { Set(() => Question, ref question, value); }
		}

 

Coordinator
Feb 7, 2011 at 1:50 PM

Hi,

Something similar is coming up in MVVM Light V4. I am going to push that code to Codeplex very soon, check my blog for announcements (http://blog.galasoft.ch).

With the new syntax, you will be able to call

RaisePropertyChanged(); // Only within a Setter
RaisePropertyChanged(() => PropertyName);
RaisePropertyChanged(() => PropertyName, oldValue, value, true); // With broadcast
RaisePropertyChanged("PropertyName");
RaisePropertyChanged("PropertyName", oldValue, value, true); // With broadcast

Cheers,

Laurent

Feb 7, 2011 at 1:58 PM

Awesomeness / Thanks. Already subscribed :)

Jul 21, 2011 at 3:09 AM

I like Aurimas86's solution; it cuts down a lot of the very repetitive code needed to check that a property's value has changed before calling RaisePropertyChanged. Additionally, as Aurimas86 observes, the StackTrace solution is broken in Release mode,  which you could regard as a bit of a show-stopper!

Coordinator
Aug 2, 2011 at 7:53 AM

For info, I just ditched RaisePropertyChanged() (without parameters).

After thinking about it, I did however add a slightly modified version of Aurima86's idea for a Set method. There are now 4 of those:

  • On ObservableObject:
        protected void Set<T>(
            Expression<Func<T>> propertyExpression,
            ref T field,
            T newValue)
       protected void Set<T>(
           string propertyName,
           ref T field,
           T newValue)
  • On ViewModelBase:
        protected void Set<T>(
            Expression<Func<T>> propertyExpression,
            ref T field,
            T newValue,
            bool broadcast)
        protected void Set<T>(
            string propertyName,
            ref T field,
            T newValue,
            bool broadcast)

Corresponding snippets are also available. This will be released this week in V4 beta 1. I plan to have a blog post about that in the same timeframe.

Cheers,

Laurent

Aug 2, 2011 at 12:58 PM

I look forward to it! I'm off TDY on a non-MVVM project for a week or two, but I'll look forward to getting my hands on the new builds soon.