简体   繁体   English

暂停控件的数据绑定

[英]Suspend Databinding of Controls

I have a series of controls that are databound to values that change every second or so. 我有一系列控件,它们数据绑定到每秒钟左右变化的值。 From time to time, I need to "pause" the controls, so that they do not update their databindings (in either direction). 有时,我需要“暂停”控件,这样他们就不会更新数据绑定(在任何一个方向上)。 I then later need to "unpause" the controls, so that they can update the datasource with their values, and receive future updates from the source as normal. 然后,我需要“取消暂停”控件,以便他们可以使用其值更新数据源,并正常接收来自源的未来更新。 How do I accomplish this? 我该如何做到这一点?

Sample Binding: 样本绑定:

<TextBox Text="{Binding UpdateSourceTrigger=LostFocus, Mode=TwoWay, Path=myData}">

You don't necessarily have to suspend binding. 您不一定要暂停绑定。 Another, and possibly simpler, way to do this is to suspend change notification in the view model. 另一种可能更简单的方法是在视图模型中暂停更改通知。 For instance: 例如:

private HashSet<string> _ChangedProperties = new HashSet<string>();

private void OnPropertyChanged(string propertyName)
{
   if (_Suspended)
   {
      _ChangedProperties.Add(propertyName);
   }
   else
   {
      PropertyChangedEventHandler h = PropertyChanged;
      if (h != null)
      {
         h(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

private bool _Suspended;

public bool Suspended
{
   get { return _Suspended; }
   set
   {
      if (_Suspended == value)
      {
         return;
      }
      _Suspended = value;
      if (!_Suspended)
      {
         foreach (string propertyName in _ChangedProperties)
         {
            OnPropertyChanged(propertyName);
         }
         _ChangedProperties.Clear();
      }
   }
}

This will (if it's debugged and tested, which I haven't done) stop raising PropertyChanged events when Suspended is set to true , and when Suspended is set to false again it will raise the event for every property that changed while it was suspended. 这将(如果它已经过调试和测试,我还没有完成)在Suspended设置为true时停止引发PropertyChanged事件,并且当Suspended再次设置为false时,它将为暂停时更改的每个属性引发事件。

This won't stop changes to bound controls from updating the view model. 这不会阻止更改绑定控件以更新视图模型。 I submit to you that if you're letting the user edit properties on the screen at the same time that you're changing them in the background, there's something you need to take a closer look at, and it's not binding. 我向你提交,如果你让用户在屏幕上编辑属性,同时你在后台更改它们,那么你需要仔细查看一些内容,而且它不具有约束力。

To deal with the source set the UpdateSourceTrigger to be Explicit . 要处理源,请将UpdateSourceTrigger设置为Explicit

<TextBox Name="myTextBox" Text="{Binding UpdateSourceTrigger=Explicit, Mode=TwoWay, Path=myData}">

Then in code behind reference a service which can deal with the actual updating as defined by your conditions. 然后在代码后面引用一个服务,它可以处理由你的条件定义的实际更新。

BindingExpression be = myTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();

This will allow you to specify at which point the data goes back to the source from the target. 这将允许您指定数据从目标返回源的点。

The target can be addressed by making a call to the same referenced service which has the knowledge on when to call the INotifyPropertyChanged.PropertyChanged event within your ViewModel. 可以通过调用相同的引用服务来解决目标,该服务具有何时在ViewModel中调用INotifyPropertyChanged.PropertyChanged事件的知识。

    class Data : INotifyPropertyChanged
    {
        Manager _manager;

        public Data(Manager manager)
        {
            _manager = manager;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        String _info = "Top Secret";
        public String Information
        {
            get { return _info; }
            set 
            {
                _info = value;

                if (!_manager.Paused)
                {
                    PropertyChangedEventHandler handler = PropertyChanged;
                    if (handler != null)
                        handler(this, new PropertyChangedEventArgs("Information"));
                }
            }
        }
    }

First of all you need create explicit binding: 首先,您需要创建显式绑定:

Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);

Then when you need pause twoway binding you need destroy old binding and create new oneway binding with explicit trigger(in this case you binding source will not be updated when some property has been changed): 然后,当你需要暂停twoway绑定时,你需要销毁旧绑定并使用显式触发器创建新的单向绑定(在这种情况下,当某些属性被更改时,绑定源将不会更新):

BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
binding.Mode = BindingMode.OneWay;
txtContent.SetBinding(TextBox.TextProperty, binding);

When you need to resume twoway binding you can explicit update source(if you need it) than destroy oneway binding and create twoway binding. 当你需要恢复twoway绑定时,你可以显式更新源(如果你需要),而不是破坏单向绑定并创建双向绑定。

BindingExpression be = txtContent.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);

Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);

If the control you want to suspend has a DataContext (ViewModel) you own, just save it off and null out the DataContext. 如果要挂起的控件具有您拥有的DataContext(ViewModel),则只需将其保存并将DataContext置空。

If the control has an inherited DataContext, setting that control's DataContext to null will block the inheritance. 如果控件具有继承的DataContext,则将该控件的DataContext设置为null将阻止继承。 Then to resume binding updates, you use the ClearValue method to clear the DataContext DependencyProperty so inheritance kicks in again. 然后,为了恢复绑定更新,您使用ClearValue方法清除DataContext DependencyProperty,以便继承再次启动。

You can get fancy and use a VisualBrush to take a screen shot of the control you are suspending before clearing its DataContext, so the user doesn't see the control go blank. 在清除DataContext之前,您可以使用VisualBrush来截取您要挂起的控件的屏幕截图,这样用户就不会看到控件变为空白。

My solution ended up as follows to prevent the text updating while the user is trying to change it. 我的解决方案最终结果如下,以防止在用户尝试更改文本时更新文本。

XAML: XAML:

<TextBox Grid.Row="0" Grid.Column="1" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MinimumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
<TextBox Grid.Row="0" Grid.Column="2" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MaximumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>

Code behind: 代码背后:

    private void TextBox_OnGotFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
    {
        TextBox tb = sender as TextBox;
        if (tb == null) return;
        BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
        if (expression == null) return;
        // disable updates from source
        BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
        tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.OneWayToSource, UpdateSourceTrigger = UpdateSourceTrigger.Explicit , FallbackValue = tb.Text});
    }

    private void TextBox_OnLostFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
    {
        TextBox tb = sender as TextBox;
        if (tb == null) return;
        BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
        if (expression == null) return;
        // send current value to source
        expression.UpdateSource();
        // enable updates from source
        BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
        tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus });
    }

Note that I assign the current Text as the fallback value of the OneWayToSource binding to have a start value (otherwise the text field would be empty once focused) 请注意,我将当前文本指定为OneWayToSource绑定的回退值以具有起始值(否则文本字段在聚焦后将为空)

If you keep a reference to the view in the controller class you could fire an event from the view model when you want to suspend databinsing that has the controller clear the DataContext of the view. 如果您在控制器类中保留对视图的引用,则可以在希望暂停数据绑定时从视图模型触发事件,该数据包使控制器清除视图的DataContext。 and when you are ready to start sending an receiving data again, reset the Views DataContext to the View Model. 当您准备好再次开始发送接收数据时,请将Views DataContext重置为View Model。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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