views:

198

answers:

4

So I am working through my first WPF project and I am liking what I see so far. There was more of learning curve than what I anticipated, but nevertheless WPF is pretty cool. However, I am struggling a little bit with the data binding concepts. One specific question I have is how do I make my data binding declarations refactor safe? Consider this example.

public class MyDataObject
{
  public string FooProperty { get; set; }
}

void Bind() 
{
  var gridView = myListView.View as GridView;
  gridView.Columns.Clear();
  gridView.Columns.Add(
    new GridViewColumn() 
      { 
        Header = "FooHeader", 
        DisplayMember = new Binding("FooProperty")
      }
    );
  List<MyDataObject> source = GetData();
  myListView.ItemsSource = source;
}

So what if I rename the FooProperty on my data object to something else? The data binding will be invalid and I will not get a compile error since the binding was declared via text only. Is there a way to make the binding a little more refactor safe?

A: 

You could use reflection to determine the names of your property. Of course, this is problematic if you have more than one binding per class, so perhaps additionally using custom attributes (which are also available via reflection) to get 'hints' as to the correct binding field that the named property should be bound to.

That very well may end up just relocating the un-refactorable magic string to a different part of the application though, I can't say that I've tried this and had it work.

Mikeb
Refactoring is something that occurs at design time, whereas reflection occurs at run time. (Actually in the language 'io', refactoring can happen at run time, but that's another story!)
Drew Noakes
Of course, but as I understood the question, refactoring wouldn't catch the 'magic string' of FooProperty in the binding, so the property would change and the binding would not; by getting the right string at run time via reflection you are always looking at the most recent world and not dependent on making sure you changed all your constants.
Mikeb
@Mikeb - it depends what refactoring tool you use.
Drew Noakes
+1  A: 

Refactoring relies upon tool support recognising when a particular symbol in code (C#, XAML, config etc...) represents the identifier being renamed.

In the example you give, the string literal "FooProperty" could not be 100% construed as belonging to MyDataObject without special knowledge of the inner workings of GridView and by extension all other types in WPF and other frameworks.

However, in a DataTemplate, it's possible to be 99% sure:

<DataTemplate DataType="{x:Type local:MyDataObject}">
    <TextBlock Text="{Binding Path=FooProperty}" />
</DataTemplate>

I use (and swear by) an IDE plugin called ReSharper (aka R#) which is a very intelligent about these kinds of things. If you rename FooProperty, R# will rename the property for you automatically.

In your example, if you were to rename the property, R# would still be of use. It finds all instances of the string in literals (your case) and comments (very useful if you've commented out some code and might uncomment it later). You're provided with a tree view that shows each literal in context, and you can check/uncheck individual usages/files/folders/projects before proceeding.

If your budget allows, get R#. If your budget doesn't allow, download a trial and by the end of it, your budget will find room. Make sure you print out a copy of the shortcut keys to improve your learning experience.

Drew Noakes
Good recommendation.
Brian Gideon
A: 

There's been a big discussion on this topic in the WPF Disciples, so you may want to have a look around this topic there. It was kicked off by Karl Shifflett in this blog post here.

Pete OHanlon
+2  A: 

You could use a lambda expression to express the property name, rather than using the name directly :

    protected static string GetPropertyName<TSource, TResult>(Expression<Func<TSource, TResult>> expression)
    {
        if (expression.NodeType == ExpressionType.Lambda && expression.Body.NodeType == ExpressionType.MemberAccess)
        {
            PropertyInfo prop = (expression.Body as MemberExpression).Member as PropertyInfo;
            if (prop != null)
            {
                return prop.Name;
            }
        }
        throw new ArgumentException("expression", "Not a property expression");
    }

You would use it like that :

...
DisplayMember = new Binding(GetPropertyName((MyDataObject o) => o.FooProperty))
...

OK, it's a bit verbose... If you want something shorter, you could also create a helper method :

public Binding CreateBinding<TSource, TResult>(Expression<Func<TSource, TResult>> expression)
{
    return new Binding(GetPropertyName(expression))
}

...
DisplayMember = CreateBinding((MyDataObject o) => o.FooProperty)
...

That way, the refactoring should work fine if you rename the property (except in the XAML of course...)

Thomas Levesque
Very clever. Yeah, the xaml would still be a problem, but at least in my immediate scenario I have to build the columns dynamically anyway so that is of no concern at the moment. Thanks!
Brian Gideon