WPF Data Binding with StringFormat when UpdateSourceTrigger is PropertyChanged

1.7k Views Asked by At

I want to have a textbox append a specific string once the user tabs out of the control, i.e. LostFocus, however I prefer the textbox to validate as the user types, so UpdateSourceTrigger is set to PropertyChanged.

Is there a way to get this to work in WPF?

Looked at this question which is similar but wondering if there is a cleaner solution?

My XAML is:

    <TextBox Text="{Binding Path=MyBindingPath, 
                            StringFormat='\{0} -HELLO',
                            TargetNullValue={x:Static sys:String.Empty},
                            ValidatesOnDataErrors=True,   
                            NotifyOnValidationError=True,    
                            UpdateSourceTrigger=PropertyChanged}"/>
2

There are 2 best solutions below

0
On BEST ANSWER

You can set UpdateSourceTrigger to Explicit and in the TextChanged event handler of the TextBox you can call UpdateSource explicitly after performing the things you want.

//write the code you want to run first and then the following code
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
0
On

This seems to be the cleanest approach I have come up with. This essentially sets events that will turn off the string formatting when the user has focus on the textbox, and restores the stringformat when the user leaves the textbox.

<TextBox Text="{Binding ValueToBind, UpdateSourceTrigger=PropertyChanged}" utilities:Formatting.StringFormat="N2" />
public class Formatting
{
    public static readonly DependencyProperty StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat", typeof(string), typeof(Formatting), new PropertyMetadata("", OnStringFormatPropertyChanged));

    private static void OnStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is FrameworkElement element && e.OldValue != e.NewValue)
        {
            element.Loaded += (sender, args) =>
            {
                // Debug.Print("Loaded");
                UpdateStringFormat(element.IsFocused ? null : (string)e.NewValue, element);
            };
            element.LostFocus += (sender, args) =>
            {
                // Debug.Print("Lost Focus");
                UpdateStringFormat((string)e.NewValue, element);
            };
            element.GotFocus += (sender, args) =>
            {
                // Debug.Print("Got Focus");
                UpdateStringFormat(null, element);
            };
        }
    }

    private static void UpdateStringFormat(string stringFormat, FrameworkElement element)
    {
        var bindingExpression = element.GetBindingExpression(TextBox.TextProperty);
        if (bindingExpression != null && bindingExpression.ParentBinding != null)
        {
            Binding parentBinding = bindingExpression.ParentBinding;

            if (parentBinding.StringFormat == stringFormat)
                return;

            // Debug.Print("Updating String Format");
            bindingExpression.UpdateSource();

            Binding newBinding = new Binding
            {
                Path = parentBinding.Path,
                Mode = parentBinding.Mode,
                UpdateSourceTrigger = parentBinding.UpdateSourceTrigger,
                StringFormat = stringFormat
            };
            element.SetBinding(TextBox.TextProperty, newBinding);
        }
    }

    [AttachedPropertyBrowsableForType(typeof(UIElement))]
    public static void SetStringFormat(DependencyObject obj, string stringFormat)
    {
        obj.SetValue(StringFormatProperty, stringFormat);
    }

    [AttachedPropertyBrowsableForType(typeof(UIElement))]
    public static string GetStringFormat(DependencyObject obj)
    {
       return (string)obj.GetValue(StringFormatProperty);
    }
}