Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do default converters kick in?

Tags:

wpf

converters

With the following code, although Text property is bound to a DateTime source property, I noticed WPF seems to automatically convert the text to a DateTime, without me needing to write a ValueConverter. Can someone please shed some light on how this is done

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:WpfApplication1="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"
        >    
    <StackPanel>
        <DatePicker Height="25" Name="datePicker1" Width="213" Text="{Binding Path=DueDate,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
</Window>
public class P
    {
        private DateTime? dueDate = DateTime.Now;
        public DateTime? DueDate
        {
            get { return dueDate; }
            set 
            { 
                dueDate = value;
            }
        }
    }

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            P p = new P();
            this.DataContext = p;
        }
    }
like image 905
sturdytree Avatar asked Mar 08 '12 21:03

sturdytree


2 Answers

The DatePicker is a custom control that was initially part of the WPF Toolkit before being added as a standard control in .NET 4.

I just went to the source code repository for the control to find you the exact source code which is responsible for the conversion of the text to date:

#region Text

    /// <summary>
    /// Gets or sets the text that is displayed by the DatePicker.
    /// </summary>
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    /// <summary>
    /// Identifies the Text dependency property.
    /// </summary>
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(
        "Text",
        typeof(string),
        typeof(DatePicker),
        new FrameworkPropertyMetadata(string.Empty, OnTextChanged, OnCoerceText));

    /// <summary>
    /// TextProperty property changed handler.
    /// </summary>
    /// <param name="d">DatePicker that changed its Text.</param>
    /// <param name="e">DependencyPropertyChangedEventArgs.</param>
    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DatePicker dp = d as DatePicker;
        Debug.Assert(dp != null);

        if (!dp.IsHandlerSuspended(DatePicker.TextProperty))
        {
            string newValue = e.NewValue as string;

            if (newValue != null)
            {
                if (dp._textBox != null)
                {
                    dp._textBox.Text = newValue;
                }
                else
                {
                    dp._defaultText = newValue;
                }

                dp.SetSelectedDate();
            }
            else
            {
                dp.SetValueNoCallback(DatePicker.SelectedDateProperty, null);
            }
        }
    }

    private static object OnCoerceText(DependencyObject dObject, object baseValue)
    {
        DatePicker dp = (DatePicker)dObject;
        if (dp._shouldCoerceText)
        {
            dp._shouldCoerceText = false;
            return dp._coercedTextValue;
        }

        return baseValue;
    }

    /// <summary>
    /// Sets the local Text property without breaking bindings
    /// </summary>
    /// <param name="value"></param>
    private void SetTextInternal(string value)
    {
        if (BindingOperations.GetBindingExpressionBase(this, DatePicker.TextProperty) != null)
        {
            Text = value;
        }
        else
        {
            _shouldCoerceText = true;
            _coercedTextValue = value;
            CoerceValue(TextProperty);
        }
    }

    #endregion Text
like image 26
evasilchenko Avatar answered Sep 27 '22 17:09

evasilchenko


It is using the DateTimeTypeConverter from the Base Class Library (EDIT: Well, it could have used a TypeConverter however it appears that from @DeviantSeev's answer that they did not).

There 'default' converters you are talking about are actually TypeConverters (MSDN) and they have been a part of the .NET Framework since v2.0 and they are used through-out the Base Class Libraries. Another example of TypeConverters in WPF is the ThicknessTypeConverter for Padding, Margin, and BorderThickness properties. It converts a comma-delimited string to a Thickness object.

There are plenty of articles available if you want to understand them further.

There are two parts to using a TypeConverter - implementation of the class and then marking up your properties/types with TypeConverterAttribute.

For example, I recently had a custom control that required a char[] that I wanted to set from Xaml like so:

<AutoCompleteTextBox MultiInputDelimiters=",;. " />

Usage

[TypeConverter(typeof(CharArrayTypeConverter))]
public char[] MultiInputDelimiters
{
      get { return (char[])GetValue(MultiInputDelimitersProperty); }
      set { SetValue(MultiInputDelimitersProperty, value); }
}

Implementation

public class CharArrayTypeConverter : TypeConverter
{

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return (Type.GetTypeCode(sourceType) == TypeCode.String);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
            return ((string)value).ToCharArray();

        return value;
    }

}

When to use a TypeConverter?

You can only use TypeDescriptors if you are writing a custom control as you need to be able to mark-up the property with the TypeDescriptorAttribute. Also I would only use TypeConverter if the conversion is rather a straight-forward - as in the example above where I have a string and want a char[] - or if there are multiple possible formats that I want to convert from.

You write IValueConverter when you want more flexibility on how the value to converted by driving it by data or a passing a parameter. For example, a very common action in WPF is converting a bool to Visibility; there are three possible outputs from such a conversion (Visible, Hidden, Collapsed) and with only two inputs (true, false) it difficult to decide this in a TypeConverter.

In my applications, to achieve this two inputs to three output problem I have written a single BoolToVisibilityConverter with a TrueValue and FalseValue properties and then I instance it three times in my global ResourceDictionary. I'll post the code sample tomorrow morning, I don't it in front of me right now..

[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
    public Visibility FalseCondition { get; set; }
    public Visibility TrueCondition { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((bool)value) ? TrueCondition : FalseCondition;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((bool)value)
            return TrueCondition;

        return FalseCondition;
    }
}

<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" FalseCondition="Collapsed" TrueCondition="Visible"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityCollapsedConverter" FalseCondition="Visible" TrueCondition="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenConverter" FalseCondition="Visible" TrueCondition="Hidden"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenWhenFalseConverter" FalseCondition="Hidden" TrueCondition="Visible"/>
like image 120
Dennis Avatar answered Sep 27 '22 17:09

Dennis