Improved IValueConverter -- MarkupExtension or DependencyObject?

10,071

Solution 1

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.

Solution 2

Since it's my library you're citing as an example of converters that extend DependencyObject, I think it fitting to explain myself.

I actually started out by simply implementing IValueConverter with Object as my base class. The only reason I switched to extending DependencyObject was to allow for a technique - pioneered by Josh Smith - called virtual branching. You can read about that technique here.

Suppose you want to do something like this:

<UserControl.Resources>
    <con:CaseConverter Casing="{Binding SomeProperty}"/>
</UserControl.Resources>

This won't work because resources are not part of the visual tree, and so the binding will fail. Virtual branching hacks around this little dilemma, enabling you to perform such a binding. However, it still relies - like any other WPF binding - on the target being a DependencyObject. Thus, if I simply implemented IValueConverter without extending DependencyObject, you would be precluded from using virtual branches.

Now, if I'm completely honest, I'm not sure I'd still do this if I had my time again. I've never actually had to use a virtual branch myself - I just wanted to enable the scenario. I may even change this in a future version of my library. So my advice would be stick to a base class of Object (or a simple derivative thereof) unless you really think you'll need virtual branching.

Share:
10,071
michael
Author by

michael

Updated on June 09, 2022

Comments

  • michael
    michael about 2 years

    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?

    • Fredrik Hedblad
      Fredrik Hedblad almost 13 years
      I guess it depends on what you're trying to achieve. Can you add some more details?
  • michael
    michael almost 13 years
    I saw Kent Boogaart's Converters project on Codeplex extending all the IValueConverter's he wrote from DependencyObject. wpfconverters.codeplex.com/SourceControl/changeset/view/6194‌​2#
  • sellmeadog
    sellmeadog almost 13 years
    @Nawaz 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.
  • Nawaz
    Nawaz almost 13 years
    @crazyarabian: Yes. You can do that, But then that is not bindable. You cannot do MaxLength="{Binding TextLength}".
  • Nawaz
    Nawaz almost 13 years
    @crazyarabian: Read the updated answer with more explanation.
  • sellmeadog
    sellmeadog almost 13 years
    @Nawaz: I was going to contribute my own answer illustrating that the only beneficial difference is that DependencyObject enables bindable properties (but I got sidetracked). While I like like and agree with the updates you've made, your code examples and initial answer still imply that DependencyObject allows for configuration with properties while MarkupExtension does not.
  • aroon65
    aroon65 almost 13 years
    Actually, the reason I extended DependencyObject is because it allows you to use a virtual branch so that you can bind properties of your converter. The stated examples won't work because the converter is not part of the visual tree, be it defined as a resource or inline. Read about virtual branching here: codeproject.com/KB/WPF/AttachingVirtualBranches.aspx
  • myermian
    myermian over 12 years
    @KentBoogaart: Care to throw in an answer to this? I love catching anything the WPF gurus throw their hat into...
  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 12 years
    @KentBoogaart @Nawaz, I'm trying to achieve the DependencyObject way in Silverlight without great success, am I missing anything?