Search code examples
c#wpfivalueconvertermarkup-extensionsdependencyobject

Improved IValueConverter -- MarkupExtension or DependencyObject?


I saw online 2 different approaches to enhancing an IValueConverter. One of them extended a ValueConverter from MarkupExtension, the other from DependencyObject. I can't extend from both, so I'm wondering if any one is better than the other?


Solution

  • Deriving from each gives you different kind of power and flexibility:

    • Deriving from MarkupExtension enables you to use the value converter without making it a static resource, as described below:

      public class DoubleMe : MarkupExtension, IValueConverter
      {
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
            return this;
         }
         public object Convert(object value, /*rest of parameters*/ )
         {
            if ( value is int )
               return (int)(value) * 2; //double it
            else
               return value.ToString() + value.ToString();
         }
        //...
      }
      

      In XAML, you can directly use it without creating a StaticResource:

      <TextBlock Text="{Binding Name, Converter={local:DoubleMe}}"/>
      <TextBlock Text="{Binding Age, Converter={local:DoubleMe}}"/>
      

      Such code is very handy when debugging, as you can just write local:DebugMe and then can debug the DataContext of the control on which you use it.

    • Deriving from DependencyObject enables you to configure the value converter with some preferences in a more expressive way, as described below:

      public class TruncateMe : DependencyObject, IValueConverter
      {
           public static readonly DependencyProperty MaxLengthProperty =
               DependencyProperty.Register("MaxLength",
                                            typeof(int),
                                            typeof(TruncateMe),
                                            new PropertyMetadata(100));
           public int MaxLength
           {
               get { return (int) this.GetValue(MaxLengthProperty); }
               set { this.SetValue(MaxLengthProperty, value); }
           }
      
           public object Convert(object value, /*rest of parameters*/ )
           {
              string s = value.ToString();
              if ( s.Length > MaxLength)
                return s.Substring(0, MaxLength) + "...";
            else
                return s;
           }
           //...
      }
      

      In XAML, you can directly use it as:

      <TextBlock>
         <TextBlock.Text>
             <Binding Path="FullDescription">
                 <Binding.Converter>
                   <local:TruncateMe MaxLength="50"/>
                 </Binding.Converter>
             </Binding>
         </TextBlock.Text> 
      

      What does it do? It truncates the string FullDescription if it is more than 50 characters!

    @crazyarabian commented that:

    Your statement "Deriving from DependencyObject enables you to configure the value converter with some preferences in a more expressive way" isn't exclusive to DependencyObject as you can create the same MaxLength property on a MarkupExtension resulting in <TextBlock Text="Binding Age, Converter={local:DoubleMe, MaxLength=50}}"/>. I would argue that a MarkupExtension is more expressive and less verbose.

    That is true. But then that is not bindable; that is, when you derive from MarkupExtension, then you cannot do :

    MaxLength="{Binding TextLength}"
    

    But if you derive your converter from DependencyObject, then you can do the above. In that sense, it is more expressive compared to MarkupExtension.

    Note that the target property must be a DependencyProperty for Binding to work. MSDN says,

    • Each binding typically has these four components: a binding target object, a target property, a binding source, and a Path to the value in the binding source to use. For example, if you want to bind the content of a TextBox to the Name property of an Employee object, your target object is the TextBox, the target property is the Text property, the value to use is Name, and the source object is the Employee object.

    • The target property must be a dependency property.