简体   繁体   中英

How to suppress a NotifyPropertyChanged event on a focused Input Control?

My application features a periodic database synchronisation. Whenever a synchronisation occurs, the values of all input controls are reset to the current values from the database.

However, when a synchronisation event occurs while typing a long text in a TextBox this is more than inconvenient.

The desired behaviour is that the value of an input control is not set to the value of the bound property in case that control currently has focus. As soon as the keyboard focus is lost, the current value should be synchronized back to the bound property and thereby to the database (which would be the default behaviour).

My first idea was to modify my controls so that the binding mode is automatically set to OneWayToSource while the input has keyboard focus. Currently I do not see another option but to derive all sorts of input controls I have in my application which would be a lot of work.

Do you see a way to implement this kind of behaviour in a central location, so that it is used by all UI controls, preferably without subclassing TextBox , ComboBox etc.?

You can create a helper that will track the IsKeyboardFocusWithin property and update the target value only if it does not contain keyboard focus. In example, you have specific source that changes every second:

    public partial class MainWindow {
    public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
        "Data", typeof(string), typeof(MainWindow), new PropertyMetadata(default(string), OnDataChanged));

    static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        Debug.WriteLine((string)e.NewValue);
    }

    public string Data {
        get { return (string)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }
    public MainWindow() {
        InitializeComponent();
        DispatcherTimer dt = new DispatcherTimer();
        dt.Interval = TimeSpan.FromMilliseconds(1000);
        dt.Tick += Dt_Tick;
        dt.Start();
    }

    void Dt_Tick(object sender, EventArgs e) {
        Data = new Random().Next(0, 100).ToString();
    }
}

In this case this is the Data property. Now, helper. I decided to create it as a MarkupExtension to simplify XAML:

 public class SynchronizationHelperExtension : MarkupExtension {
    public Binding Binding { get; set; }
    class Helper {            
        static int index = 0;
        bool locked = false;
        public static readonly DependencyProperty HelperProperty = DependencyProperty.RegisterAttached(
            "Helper", typeof(Helper), typeof(Helper), new PropertyMetadata(default(Helper)));

        public static void SetHelper(DependencyObject element, Helper value) {
            element.SetValue(HelperProperty, value);
        }

        public static Helper GetHelper(DependencyObject element) {
            return (Helper)element.GetValue(HelperProperty);
        }
        public static readonly DependencyProperty FocusProperty = DependencyProperty.RegisterAttached("FocusProperty", typeof(bool), typeof(Helper), new PropertyMetadata(false, (o, args) => GetHelper(o)?.OnFocusPropertyChanged(o, (bool)args.OldValue, (bool)args.NewValue)));            
        public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("SourceProperty", typeof(object), typeof(Helper), new PropertyMetadata(false, (o, args) => GetHelper(o)?.OnSourcePropertyChanged(o, args.OldValue, args.NewValue)));
        public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("TargetProperty", typeof(object), typeof(Helper), new PropertyMetadata(false, (o, args) => GetHelper(o)?.OnTargetPropertyChanged(o, args.OldValue, args.NewValue)));

        void OnTargetPropertyChanged(DependencyObject o, object oldValue, object newValue) {
            o.SetValue(SourceProperty, newValue);
        }
        void OnSourcePropertyChanged(DependencyObject o, object oldValue, object newValue) {
            if (locked)
                return;
            o.SetValue(TargetProperty, newValue);
        }
        void OnFocusPropertyChanged(DependencyObject o, bool oldValue, bool newValue) {
            locked = newValue;
        }
    }

    public override object ProvideValue(IServiceProvider serviceProvider) {
        var helper = new Helper();
        var ipwt = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        var dObj = ipwt.TargetObject as DependencyObject;
        BindingOperations.SetBinding(dObj, Helper.FocusProperty, new Binding() {Path = new PropertyPath(FrameworkElement.IsKeyboardFocusWithinProperty), RelativeSource = RelativeSource.Self});
        Binding.Mode = BindingMode.TwoWay;
        BindingOperations.SetBinding(dObj, Helper.SourceProperty, Binding);
        Helper.SetHelper(dObj, helper);
        return new Binding() {Path = new PropertyPath(Helper.TargetProperty), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus, RelativeSource = RelativeSource.Self }.ProvideValue(serviceProvider);
    }
}

We have 3 properties: one stores actual value from source ( Helper.SourceProperty ) second - stores the target value ( Helper.TargetProperty ) and the third used to lock synchronization between these properties (Helper.FocusProperty) Also we have the Binding property - this is the binding that you use to bind your Target property with the source (eg TextBox.Text with the MainWindow.Data ) In the ProvideValue method we:

  1. binding the FrameworkElement.IsKeyboardFocusWithinProperty with the Helper.FocusProperty to lock updets
  2. updating the original binding to make it TwoWay (to update the Data property after change)
  3. returning BindingExpression to the Helper.TargetProperty

The XAML will look like this:

<StackPanel>
    <TextBox Text="{local:SynchronizationHelper Binding={Binding Data, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}}"/>
    <TextBox Text="{local:SynchronizationHelper Binding={Binding Data, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}}"/>
    <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Data}"></TextBlock>
</StackPanel>

And the short video demonstrating the result

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM