wpfdata-bindingacceptbutton

Accept Button and Data Binding inconsistency


It is convenient to have an "Accept Button" (in WPF: IsDefault="True") on a Form.

In the Windows Forms world, I used to read the data from the UI to the object(s) in the corresponding Click event of the button.

But with WPF, data binding ought to be used. In the constructor of the Window, I set this.DataContext = test;

And here comes the problem: the user entered some text in TextBox2, and hits the Enter key. Now, the command bound to the OK button gets executed, the data are saved.

But it is not the correct data! Why? TextBox2 has not yet lost focus, and consequently the ViewModel has not yet been updated. Changing the UpdateSourceTrigger to PropertyChanged is not always appropriate (e.g. formatted numbers), I am looking for a general solution.

How do you overcome such a problem?


Solution

  • Typically I use a custom Attached Property to tell WPF to update the binding source when the Enter key is pressed

    It is used in the XAML like this:

    <TextBox Text="{Binding SomeProperty}" 
             local:TextBoxProperties.EnterUpdatesTextSource="True" />
    

    And the code for the attached property is below:

    public class TextBoxProperties
    {
        // When set to True, Enter Key will update Source
        public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
            DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof(bool),
                                                typeof(TextBoxProperties),
                                                new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
    
        // Get
        public static bool GetEnterUpdatesTextSource(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnterUpdatesTextSourceProperty);
        }
    
        // Set
        public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
        {
            obj.SetValue(EnterUpdatesTextSourceProperty, value);
        }
    
        // Changed Event - Attach PreviewKeyDown handler
        private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj,
                                                                  DependencyPropertyChangedEventArgs e)
        {
            var sender = obj as UIElement;
            if (obj != null)
            {
                if ((bool)e.NewValue)
                {
                    sender.PreviewKeyDown += OnPreviewKeyDownUpdateSourceIfEnter;
                }
                else
                {
                    sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
                }
            }
        }
    
        // If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
        private static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (GetEnterUpdatesTextSource((DependencyObject)sender))
                {
                    var obj = sender as UIElement;
                    BindingExpression textBinding = BindingOperations.GetBindingExpression(
                        obj, TextBox.TextProperty);
    
                    if (textBinding != null)
                        textBinding.UpdateSource();
                }
            }
        }
    }