Reputation: 11489
I'm using the Delay binding tag of .Net 4.5 but I want to change the textbox's background color while the changes are not "committed". How can I set an IsDirty property to true while the delay is happening?
I tried using the TextChanged event to set an IsDirty flag and then remove the flag when the bound property got set. The problem is that the TextChanged fires whenever the bound property changes and not just when the user modifies the text.
I got it "working" in a very clunky and fragile way by monitoring the TextChanged event and the bound property. Needless to say this is very prone to bugs so I would like a cleaner solution. Is there any way to know that the textbox has been changed but not committed yet (by the Delay)?
Upvotes: 4
Views: 2923
Reputation: 84684
I had a look through the source code and the BindingExpressionBase
itself is aware of this through a property called NeedsUpdate
. But this property is internal so you would have to use reflection to get it.
However, you won't be able to monitor this property in any easy way. So the way I see it, you would need to use both of the events TextChanged
and SourceUpdated
to know when NeedsUpdate
might have changed.
Update
I created an attached behavior that does this, it can be used to monitor pending updates on any DependencyProperty
. Note that NotifyOnSourceUpdated
must be set to true.
Uploaded a small sample project here: PendingUpdateExample.zip
Example
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
NotifyOnSourceUpdated=True,
UpdateSourceTrigger=PropertyChanged,
Delay=1000}"
ab:UpdatePendingBehavior.MonitorPendingUpdates="{x:Static TextBox.TextProperty}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="ab:UpdatePendingBehavior.HasPendingUpdates"
Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
UpdatePendingBehavior
public class UpdatePendingBehavior
{
#region MonitorPendingUpdates
public static DependencyProperty MonitorPendingUpdatesProperty =
DependencyProperty.RegisterAttached("MonitorPendingUpdates",
typeof(DependencyProperty),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(null, MonitorPendingUpdatesChanged));
public static DependencyProperty GetMonitorPendingUpdates(FrameworkElement obj)
{
return (DependencyProperty)obj.GetValue(MonitorPendingUpdatesProperty);
}
public static void SetMonitorPendingUpdates(FrameworkElement obj, DependencyProperty value)
{
obj.SetValue(MonitorPendingUpdatesProperty, value);
}
public static void MonitorPendingUpdatesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DependencyProperty property = e.NewValue as DependencyProperty;
if (property != null)
{
FrameworkElement element = target as FrameworkElement;
element.SourceUpdated += elementProperty_SourceUpdated;
if (element.IsLoaded == true)
{
SubscribeToChanges(element, property);
}
element.Loaded += delegate { SubscribeToChanges(element, property); };
element.Unloaded += delegate { UnsubscribeToChanges(element, property); };
}
}
private static void SubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.AddValueChanged(element, elementProperty_TargetUpdated);
}
private static void UnsubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.RemoveValueChanged(element, elementProperty_TargetUpdated);
}
private static void elementProperty_TargetUpdated(object sender, EventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
UpdatePendingChanges(element);
}
private static void elementProperty_SourceUpdated(object sender, DataTransferEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (e.Property == GetMonitorPendingUpdates(element))
{
UpdatePendingChanges(element);
}
}
private static void UpdatePendingChanges(FrameworkElement element)
{
BindingExpressionBase beb = BindingOperations.GetBindingExpressionBase(element, GetMonitorPendingUpdates(element));
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
PropertyInfo needsUpdateProperty = beb.GetType().GetProperty("NeedsUpdate", bindingFlags);
SetHasPendingUpdates(element, (bool)needsUpdateProperty.GetValue(beb));
}
#endregion // MonitorPendingUpdates
#region HasPendingUpdates
public static DependencyProperty HasPendingUpdatesProperty =
DependencyProperty.RegisterAttached("HasPendingUpdates",
typeof(bool),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(false));
public static bool GetHasPendingUpdates(FrameworkElement obj)
{
return (bool)obj.GetValue(HasPendingUpdatesProperty);
}
public static void SetHasPendingUpdates(FrameworkElement obj, bool value)
{
obj.SetValue(HasPendingUpdatesProperty, value);
}
#endregion // HasPendingUpdates
}
Another way could be to use a MultiBinding
that binds both to the source and the target and compares their values in a converter. Then you could change the Background
in the Style
. This assumes that you don't convert the value. Example with two TextBoxes
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
UpdateSourceTrigger=PropertyChanged,
Delay=2000}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsTextEqualConverter}">
<Binding RelativeSource="{RelativeSource Self}"
Path="Text"/>
<Binding ElementName="textBoxSource" Path="Text"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="textBoxSource"/>
IsTextEqualConverter
public class IsTextEqualConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString() == values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Upvotes: 6
Reputation: 2944
I'm not sure about the Delay binding. However, in .Net 4.0 I'd use a BindingGroup. BindingGroup has a CanRestoreValues property which will report exactly what you want: If the UpdateSourceTrigger is Explicit (which is default if there is a BindingGroup), CanRestoreValues will be true from the time one bound control value has been changed until BindingGroup.CommitEdit is called and the values are forwarded to the bound object. However, CanRestoreValues will be true as soon as any control that shares the BindingGroup has a pending value, so if you want feedback for each separate property you will have to use one BindingGroup per control, which makes calling CommitEdit a little bit less convenient.
Upvotes: 0