1

I've recently started experimenting with DataBinding and implementing DependencyProperties for my custom classes. It all works fine and the possibilities are exciting, however, I came across a problem that may be only solvable by slightly modifying the overall class design. And I want to make sure this is the only option and I'm not missing anything.

So, my class stores information about video files the user imports into the application. Among other properties, it contains:

public class VideoFile {

    public string FilePath { get; protected set; }
    public uint ID { get; protected set; ]
    public string Extension { get { return Path.GetExtension(FilePath); } }
    public string FileName { get { return Path.GetFilename(FilePath); } }

}

So, I've successfully replaced FilePath with an DependencyProperty. However, in the UI, I mostly want to display just the filename, which uses some logic to provide its value. As far as I know, here are my options:

  1. I could simply create DependencyProperties for FileName and Extension, and set their value in the constructor, but that's redundant; I already have that information in the FilePath, so I want to avoid this option.
  2. Create ValueConverters, one for displaying Filename and one for displaying Extension, and use them in my bindings.

I've only met ValueConverters briefly, so I'm not sure about it. Can I use them for this purpose? Or, have I just encountered one of the main reasons they exist? :)

And last but not least, can anyone think of a situation similar to this, when a ValueConverter is not the right way to go? I want to avoid jumping straight into them, only to realize it will not work because "that one" property just can't be expressed in this way.

H.B.
  • 166,899
  • 29
  • 327
  • 400
oli.G
  • 1,300
  • 2
  • 18
  • 24
  • 1
    Why are you converting these to `DependencyProperty`? I would suspect from the code posted that you don't need to. – Dan Puzey Sep 17 '12 at 12:50
  • @DanPuzey because I want do display them in the UI. To be more specific, a listbox where each item represents an instance of this class. With a thumbnail, filename, size in MB, and duration in seconds. Although these properties are not expected to change, I want to display them in various places and I got tired of writing for loops and creating controls in code. I want a nice, clean XAML with a rocking DataTemplate :) – oli.G Sep 17 '12 at 13:15
  • You don't need a `DependencyProperty` to be able to display them in the UI. In fact, it's an unnecessary overhead that I suggest you should avoid. (See my answer for an alternative.) – Dan Puzey Sep 17 '12 at 13:17

3 Answers3

1

You don't need DependencyProperties for this. You only need a DependencyProperty when you're going to set into a property using a MarkupExtension, and I doubt you're doing that with a model class (because you won't be declaring this class in Xaml!).

A much more lightweight way would be to use INotifyPropertyChanged. Here's a .NET 3.5-style implementation:

public class VideoFile : INotifyPropertyChanged
{
    private string _filePath;

    public string FilePath
    {
        get
        {
            return _filePath;
        }
        protected set
        {
            _filePath = value;
            OnPropertyChanged("FilePath");
            OnPropertyChanged("Extension");
            OnPropertyChanged("FileName");
        }
    }

    public uint ID { get; protected set; }
    public string Extension { get { return Path.GetExtension(FilePath); } }
    public string FileName { get { return Path.GetFileName(FilePath); } }

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

    public event PropertyChangedEventHandler PropertyChanged;
}

(In .NET 4.5 this can be simplified somewhat thanks to the new [CallerMemberName] attribute.)

The only downside is that you require backing fields for your properties. However, there's a VS extension called NotifyPropertyWeaver that can automate part of this work and remove the need for explicit backing properties, too.

Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
  • This is nice until you have several properties raising PropertyChanged on other properties, then you can't tell head or tail on what setting something does, and it's a nightmare to debug. Which is why I advised against it. – Louis Kottmann Sep 17 '12 at 13:30
  • Thanks for the detailed implementation, and backing fields are not a problem. However, I still think I need a dependency property. As far as I understand, implementing it this way will keep the UI autoupdated, so when I say Label1.Content = VideoFile1.FileName, the label will get updated once the property changes. But I declared a listbox in XAML, and in that ListBox, I want to write a custom DataTemplate for list items. In that template I want to create labels and images, and bind them to those properties... And I assume this would not help me in this scenario. Am I wrong? – oli.G Sep 17 '12 at 13:31
  • 1
    You are wrong. `INotifyProperyChanged` is all you need to auto-update your UI, including in a custom `DataTemplate`. A `DependencyProperty` will work, but is overkill for this use. And @Baboon: how is `PropertyChanged` harder to debug than a `DependencyProperty`? – Dan Puzey Sep 17 '12 at 14:09
  • I advised against raising PropertyChanged for multiple properties in a single setter, I never implied what you said. – Louis Kottmann Sep 17 '12 at 14:25
  • 1
    What is overkill with DependencyProperties? they are faster than a INotifyPropertyChanged implementation when it comes to binding. [See this SO thread](http://stackoverflow.com/questions/570684/inotifypropertychanged-vs-dependencyproperty) . – Louis Kottmann Sep 17 '12 at 14:35
0

Don't duplicate data.

Prefer Binding and an IValueConverter, because that way, whenever FilePath changes, Extension and FileName will be updated in the UI as well.

You could also, of course, raise PropertyChanged for them in FilePath's setter but that's bad practice since FilePath should not have to care about who/what uses it.

The class then looks like:

public class VideoFile : INotifyPropertyChanged {

    string m_FilePath;
    public string FilePath 
    { 
       get { return m_FilePath; } 
       protected set
       {
          if(value != m_FilePath)
          {
             m_FilePath = value;
             RaisePropertyChanged(() => this.FilePath);
          }
       }
    }
    public uint ID { get; protected set; }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged<T>(Expression<Func<T>> _PropertyExpression)
    {
        RaisePropertyChanged(PropertySupport.ExtractPropertyName(_PropertyExpression));
    }

    protected void RaisePropertyChanged(String _Prop)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(_Prop));
        }
    }

    #endregion
}

Please note that PropertySupport is part of Prism, but you can do without it by calling RaisePropertyChanged("FilePath"), it's just neat to have type safety because if you change the property's name you will have a compile-time error.

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
  • Calculated properties are not duplication of data, and converters are expensive. I would suggest that using `PropertyChanged` makes more sense, if this class is intended as a UI-bound model. In particular, when `FilePath` changes, the UI won't be rebound for *any* of the properties unless you also implement `INotifyPropertyChanged` or a `DependencyProperty`. Of the two, `NotifyPropertyChanged` is much more lightweight. – Dan Puzey Sep 17 '12 at 13:08
  • Yes, he does need to implement INotifyPropertyChanged, but making FilePath a DependencyProperty works as well. And calculatd properties **definitely** are data duplication: it's the same data presented differently. – Louis Kottmann Sep 17 '12 at 13:17
  • Presented differently but stored once: the very reason such a class exists. This is why, for example, `FileInfo` has multiple properties to return portions of the filename. It's no more duplication of data than using the converter is: it's the same code, encapsulated differently. And if you are suggesting that he makes `FilePath` a `DependencyProperty`, you should include that implementation in your code. – Dan Puzey Sep 17 '12 at 13:20
  • @DanPuzey thanks, this is very helpful. I'm going to dig a bit deeper into INotifyPropertyChanged and see how it works in my scenario. – oli.G Sep 17 '12 at 13:21
  • Then why have you accepted the answer that recommends against it? – Dan Puzey Sep 17 '12 at 13:23
  • @DanPuzey To clarify things a bit, I already MADE FilePath a DependencyProperty. I was just asking about those calculated properties. – oli.G Sep 17 '12 at 13:23
  • @DanPuzey And I accepted it because it is the best of the 2 answers so far. Your comment shed a different light on it, and made me consider some things I have never thought of, but I can't accept a comment, can I? :) – oli.G Sep 17 '12 at 13:26
  • @oli.G I added implementation of INotifyPropertyChanged. – Louis Kottmann Sep 17 '12 at 13:33
0

As far as I understand you would like to just display File Name on UI. Then you may consider updating FileName property whenever FilePath dependency property is changed (OnChangedFilePath method). You can also check if FilePath is OK in ValidateFilePath method. Please note that FileName must be also a dependency property or supporting IPropertyChanged, otherwise UI will not be updated when you change it. You do not need to use a converter for this purpose.

public string FilePath
{
    get { return (string)GetValue(FilePathProperty); }
    set { SetValue(FilePathProperty, value); }
}

private static object CoerceFilePath(DependencyObject d, object value)
{
    return value;
}

private static bool ValidateFilePath(object Value)
{
    return true;
}

private static void OnChangedFilePath(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}

public static readonly DependencyProperty FilePathProperty =
    DependencyProperty.Register("FilePath", typeof(string), typeof(ClassName),
    new PropertyMetadata(@"C:\File.avi", OnChangedFilePath, CoerceFilePath),
    new ValidateValueCallback(ClassName.ValidateFilePath));
Demir
  • 1,787
  • 1
  • 29
  • 42
  • This is not exactly what I was looking for, but it gave me some ideas for some other things unrelated to this question, so thanks anyway :) – oli.G Sep 17 '12 at 13:17