Daily Archives: Friday, March 16, 2012

  • Pretty IPropertyNotifyChanged Declarations for Windows Phone

    The Problem

    I’ve been doing a bit of WPF/Windows Phone development. Usually INotifyPropertyChanged declarations for properties are really ugly. The big problem is that the INotifyPropertyChanged interface relies on strings, which don’t refactor well.

    I’m also using the fantastic MVVM Light Toolkit, which makes life a lot easier when doing Windows Phone development.

    MVVM Light has a snippet that you invoke with the shortcut mvvminpc that produces the following:

    /// <summary>
    /// The <see cref="ScreenName" /> property's name.
    /// </summary>
    public const string ScreenNamePropertyName = "ScreenName";
    
    private string _screenName = String.Empty;
    
    /// <summary>
    /// Sets and gets the ScreenName property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public string ScreenName
    {
    	get
    	{
    		return _screenName;
    	}
    
    	set
    	{
    		if (_screenName == value)
    		{
    			return;
    		}
    
    		_screenName = value;
    		RaisePropertyChanged(ScreenNamePropertyName);
    	}
    }

    That saves quite a bit of work, but it’s still ugly. It still doesn’t refactor well because you have to change that property that corresponds to the string. MVVM Light also includes another snippet with the shortcut mvvminpclambda that gets closer to what I want, but it’s still a large declaration with the equality checking, but instead of using ScreenNamePropertyName (and you get to lose that property), that call looks like:

    RaisePropertyChanged(() => ScreenName);

    That’s getting there, but it’s still messy. I found a great solution for this problem from Christian Mosers, but it doesn’t compile in Windows Phone Silverlight due to the lambda compile, and it relies on PropertyChangedEventHandler. Also, Christian’s sets the value after the event handler is called. I’m not sure if that’s a mistake, but it seems like it is, and someone else already pointed that out in his comments.

    I’ve modified it a bit and now it works, gives me an inpcpretty shortcut, and cleans up my declarations a lot. I tried making it an extension method of the delegate I created, but that doesn’t work. In the end, I made it an extension of the value type T.

    The New Code

            private string _screenName = String.Empty;
            public string ScreenName
            {
                get { return _screenName; }
                set { value.ChangeAndNotify(RaisePropertyChanged, ref _screenName, () => ScreenName); }
            }

    The Extension Method

    namespace System
    {
        public static class INotifyPropertyChangedExtensions
        {
            public delegate void OnPropertyNotifyChangedDelegate(string input);
    
            public static bool ChangeAndNotify<T>(this T value, OnPropertyNotifyChangedDelegate handler,
                ref T field, Expression<Func<T>> memberExpression)
            {
                if (memberExpression == null)
                {
                    throw new ArgumentNullException("memberExpression");
                }
                var body = memberExpression.Body as MemberExpression;
                if (body == null)
                {
                    throw new ArgumentException("Lambda must return a property.");
                }
                if (EqualityComparer<T>.Default.Equals(field, value))
                {
                    return false;
                }
    
                var vmExpression = body.Expression as ConstantExpression;
                if (vmExpression != null)
                {
                    field = value;
    
                    if (handler != null)
                    {
                        handler(body.Member.Name);
                    }
                }
                return true;
            }
        }
    }

    The Snippet

    <?xml version="1.0" encoding="utf-8"?>
    <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
      <CodeSnippet Format="1.0.0">
        <Header>
          <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
          </SnippetTypes>
          <Title>INPC Property</Title>
          <Author>Chris Benard</Author>
          <Description>A property raising PropertyChanged with a string. The class using this property should inherit GalaSoft.MvvmLight.ObservableObject.</Description>
          <HelpUrl>http://www.galasoft.ch/mvvm</HelpUrl>
          <Shortcut>inpcpretty</Shortcut>
        </Header>
        <Snippet>
          <Declarations>
            <Literal Editable="true">
              <ID>Type</ID>
              <ToolTip>Property type</ToolTip>
              <Default>bool</Default>
              <Function>
              </Function>
            </Literal>
            <Literal Editable="true">
              <ID>AttributeName</ID>
              <ToolTip>Attribute name</ToolTip>
              <Default>_myProperty</Default>
              <Function>
              </Function>
            </Literal>
            <Literal Editable="true">
              <ID>InitialValue</ID>
              <ToolTip>Initial value</ToolTip>
              <Default>false</Default>
              <Function>
              </Function>
            </Literal>
            <Literal Editable="true">
              <ID>PropertyName</ID>
              <ToolTip>Property name</ToolTip>
              <Default>MyProperty</Default>
              <Function>
              </Function>
            </Literal>
          </Declarations>
          <Code Language="csharp">
            <![CDATA[private $Type$ $AttributeName$ = $InitialValue$;
            public $Type$ $PropertyName$
            {
                get { return $AttributeName$; }
                set { value.ChangeAndNotify(RaisePropertyChanged, ref $AttributeName$, () => $PropertyName$); }
            }]]>
          </Code>
        </Snippet>
      </CodeSnippet>
    </CodeSnippets>

    Conclusion

    There is probably a better way to do this, but I’m not that experienced in WPF/Silverlight/Windows Phone yet. If you know of a better way, please let me know.

    To use this snippet, click the raw button in the code listing (looks like <>), save the contents as inpcpretty.snippet. Then, in Visual Studio, go to Tools -> Code Snippets Manager. Switch the language to C# and select Import. Choose the .snippet file you saved and you should be in business!