Lexa

WPF, C#, Objective C and a little Math

WPF Binding double to TextBox Text Property

Binding to double property of some object with UpdateSourceTrigger set to PropertyChanged allows dynamically update ViewModel while user enters the number. Application become more responsive and user friendly.

Unfortuntely simple binding has frustrating editing behaviors – portion of the text are automatically erased in some cases (usually decimal separator and leading or trailing zeroes) – “cleaning up of the input”.

As an example I made a small application, converting millimeters to inches. This is how the problem looks like (please note I have German keyboard layout, decimal separator is comma):

input_problems

The reason of this is quite simple as soon as string is converted into a double value and it is assigned to ViewModel dependency property, ViewModel notifies performed update, and double value transformed back to string without “unneded” separators and zeroes. This is very annoying while entering text, so decided to fix it.

The solution is quite simple – own converter which saves user input string and returns it instead of rebuilding it every time. Something like this:

class ProxyConverterTest : IValueConverter
{
    private string user_string = null;

    public object Convert(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        if (user_string != null)
        {
            return user_string;
        }
            
        double number = (double)value;
        return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}", number);
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        string s_value = value.ToString();
        double result = 0;

        if (!double.TryParse(s_value, System.Globalization.NumberStyles.Number, 
            System.Globalization.CultureInfo.CurrentCulture, out result))
            return null;

        user_string = s_value;

        return result;
    }        
}

Unfortunately this will not work in all cases – in example, if bound value of ViewModel will be changed from some other place of the application (in my example – changed from the other TextBox), converter will continue delivering old string value. So when TextBox looses its focus, it makes sense to set user_string to null. Also on lost focus we can format output string – clean it up (actually the same behavior which we had at the beginning, but only on Lost Focus). To do so, an attached property is introduced:

class ProxyConverterTest : IValueConverter
{
    private string user_string = null;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (user_string != null)
        {
            return user_string;
        }
            
        double number = (double)value;
        return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}", number);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string s_value = value.ToString();
        double result = 0;

        if (!double.TryParse(s_value, System.Globalization.NumberStyles.Number, 
            System.Globalization.CultureInfo.CurrentCulture, out result))
            return null;

        user_string = s_value;

        return result;
    }
        
    public static DependencyProperty GetUpdateOnLostFocus(DependencyObject obj)
    {
        return (DependencyProperty)obj.GetValue(UpdateOnLostFocusProperty);
    }

    public static void SetUpdateOnLostFocus(DependencyObject obj, DependencyProperty value)
    {
        obj.SetValue(UpdateOnLostFocusProperty, value);
    }

    public static readonly DependencyProperty UpdateOnLostFocusProperty =
        DependencyProperty.RegisterAttached("UpdateOnLostFocus", typeof(DependencyProperty), 
        typeof(ProxyConverterTest), new UIPropertyMetadata(null, UpdateOnLostFocusChanged));

    private static void UpdateOnLostFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = (FrameworkElement)d;

        if (e.NewValue != null)
        {
            element.LostFocus += new RoutedEventHandler(element_LostFocus);
        }

        if (e.OldValue != null)
        {
            element.LostFocus -= new RoutedEventHandler(element_LostFocus);
        }
    }

    static void element_LostFocus(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement)sender;
        BindingExpression bindingExpression = element.GetBindingExpression(GetUpdateOnLostFocus(element));
        Binding parentBinding = bindingExpression.ParentBinding;
        var converter = parentBinding.Converter as ViewModel.ProxyConverterTest;
        if (converter != null)
        {
            converter.user_string = null;
            bindingExpression.UpdateTarget();
        }
    }
}

Xaml code:

<TextBox view_model:ProxyConverterTest.UpdateOnLostFocus="TextBox.Text">
    <TextBox.Text>
        <Binding Path="Bar" Source="{StaticResource Foo}" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
            <Binding.Converter>
                <view_model:ProxyConverterTest/>
            </Binding.Converter>
        </Binding>                                 
    </TextBox.Text>
</TextBox>

Input looks much better now:
input_fixed

Here is the complete source code of the project: StringConversionTest. Note that ProxyConverter is extended with Scale Dependency property to implement conversion of input value from inches to millimeters.


Categorised as: UI, WPF


2 Comments

  1. Alex says:

    Алексей, это потрясающе. То что нужно. Искал решение именно этой пробелемы

  2. Alex says:

    Спасибо

Leave a Reply

Your email address will not be published. Required fields are marked *