如何知道文本框是否被修改?

4
我正在使用 .Net 4.5 的延迟绑定标签,但我想在更改尚未“提交”时更改文本框的背景颜色。在延迟发生时,如何将 IsDirty 属性设置为 true 呢?
我尝试使用 TextChanged 事件来设置 IsDirty 标志,然后在绑定属性被设置时删除标志。问题是,TextChanged 事件在绑定属性更改时触发,而不仅仅是在用户修改文本时触发。
我通过监视 TextChanged 事件和绑定属性以一种非常笨拙和脆弱的方式使其“工作”。不用说这很容易出现错误,因此我希望有一个更简洁的解决方案。有没有办法知道文本框已被更改但尚未提交(由延迟引起)?
3个回答

6
我查看了源代码,BindingExpressionBase本身通过一个叫做NeedsUpdate的属性意识到了这一点。但是这个属性是内部的,所以你需要使用反射来获取它。
然而,你无法轻松地监视此属性。所以在我看来,您需要同时使用TextChangedSourceUpdated这两个事件,以了解何时可能已更改NeedsUpdate
更新: 我创建了一个附加行为来完成这项工作,可以用于监视任何DependencyProperty上的待处理更新。请注意,必须将NotifyOnSourceUpdated设置为true。
在此处上传了一个小示例项目:PendingUpdateExample.zip 示例
<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
}

另一种方法是使用MultiBinding,绑定源和目标并在转换器中比较它们的值。然后您可以在Style中更改Background属性。 这假设您不需要转换该值。以下是两个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();
    }
}

0

你可以尝试一些其他的事件,比如PreviewTextInput或者与按键相关的事件。(你可能需要使用tunneling版本,因为冒泡事件可能在内部处理)


它无法捕获文本框上的粘贴操作。 - Manuel

0

我不确定 Delay 绑定。然而,在 .Net 4.0 中,我会使用 BindingGroup。BindingGroup 具有 CanRestoreValues 属性,该属性将报告您想要的精确信息:如果 UpdateSourceTrigger 是显式的(如果存在 BindingGroup,默认值为显式),则从一个绑定控件值被更改到调用 BindingGroup.CommitEdit 并将值转发给绑定对象之前,CanRestoreValues 将为 true。但是,只要任何共享 BindingGroup 的控件具有未决值,CanRestoreValues 就会为 true,因此,如果要针对每个单独的属性进行反馈,则必须为每个控件使用一个 BindingGroup,这使得调用 CommitEdit 稍微不太方便。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接