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 UpdateSourc开发者_开发技巧eTrigger=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.
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
.
<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.
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):
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.
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.
If the control has an inherited DataContext, setting that control's DataContext to null will block the inheritance. Then to resume binding updates, you use the ClearValue method to clear the DataContext DependencyProperty so inheritance kicks in again.
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.
My solution ended up as follows to prevent the text updating while the user is trying to change it.
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)
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. and when you are ready to start sending an receiving data again, reset the Views DataContext to the View Model.
精彩评论