5

PropertyChanged我想知道当用户在输入文本时暂停时是否可以引发事件TextBox?或者更具体地说,我想X在用户停止输入文本框后几秒钟运行一个方法。

例如,我有一个带有 TextBox 的表单,没有别的。用户在 TextBox 中键入 1-9 位的 Id 值,一个相当消耗资源的后台进程加载记录。

我不想使用,UpdateSouceTrigger=PropertyChanged因为这会导致在输入字符时运行资源密集型后台进程,因此 9 位 ID 号从这些进程中的 9 个开始。

我也不想使用UpdateSourceTrigger=LostFocus,因为表单上没有其他内容可以使 TextBox 失去焦点。

那么有没有办法让我的后台进程仅在用户在输入 ID 号时暂停后才运行?

4

5 回答 5

10

设置UpdateSourceTrigger=PropertyChanged,然后每次属性更改时,为您想要的延迟启动一个计时器。如果在计时器滴答之前再次更改属性,则取消旧计时器并启动新计时器。如果计时器确实滴答作响,那么您知道该属性在 X 秒内没有更改,您可以启动后台进程。

于 2011-07-15T18:01:25.690 回答
8

准备代码转储。

我已经使用 WPF 假行为(一个附加的 DP,其行为类似于行为)来完成此操作。这段代码有效,但它并不漂亮,并且可能导致泄漏。可能需要用弱引用等替换所有引用。

这是行为类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Data;
using System.ComponentModel;

namespace BehaviorForDelayedTrigger
{
    public static class DelayedUpdateBehavior
    {
        #region TargetProperty Attached DependencyProperty
        /// <summary>
        /// An Attached <see cref="DependencyProperty"/> of type <see cref="DependencyProperty"/> defined on <see cref="DependencyObject">DependencyObject instances</see>.
        /// </summary>
        public static readonly DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
          TargetPropertyPropertyName,
          typeof(DependencyProperty),
          typeof(DelayedUpdateBehavior),
          new FrameworkPropertyMetadata(null, OnTargetPropertyChanged)
        );

        /// <summary>
        /// The name of the <see cref="TargetPropertyProperty"/> Attached <see cref="DependencyProperty"/>.
        /// </summary>
        public const string TargetPropertyPropertyName = "TargetProperty";

        /// <summary>
        /// Sets the value of the <see cref="TargetPropertyProperty"/> on the given <paramref name="element"/>.
        /// </summary>
        /// <param name="element">The <see cref="DependencyObject">target element</see>.</param>
        public static void SetTargetProperty(DependencyObject element, DependencyProperty value)
        {
            element.SetValue(TargetPropertyProperty, value);
        }

        /// <summary>
        /// Gets the value of the <see cref="TargetPropertyProperty"/> as set on the given <paramref name="element"/>.
        /// </summary>
        /// <param name="element">The <see cref="DependencyObject">target element</see>.</param>
        /// <returns><see cref="DependencyProperty"/></returns>
        public static DependencyProperty GetTargetProperty(DependencyObject element)
        {
            return (DependencyProperty)element.GetValue(TargetPropertyProperty);
        }

        /// <summary>
        /// Called when <see cref="TargetPropertyProperty"/> changes
        /// </summary>
        /// <param name="d">The <see cref="DependencyObject">event source</see>.</param>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs">event arguments</see></param>
        private static void OnTargetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var prop = e.NewValue as DependencyProperty;
            if(prop == null)
                return;
            d.Dispatcher.BeginInvoke(
                (Action<DependencyObject, DependencyProperty>)
                    ((target, p) => new PropertyChangeTimer(target, p)), 
                DispatcherPriority.ApplicationIdle, 
                d, 
                prop);

        }
        #endregion
        #region Milliseconds Attached DependencyProperty
        /// <summary>
        /// An Attached <see cref="DependencyProperty"/> of type <see cref="int"/> defined on <see cref="DependencyObject">DependencyObject instances</see>.
        /// </summary>
        public static readonly DependencyProperty MillisecondsProperty = DependencyProperty.RegisterAttached(
          MillisecondsPropertyName,
          typeof(int),
          typeof(DelayedUpdateBehavior),
          new FrameworkPropertyMetadata(1000)
        );

        /// <summary>
        /// The name of the <see cref="MillisecondsProperty"/> Attached <see cref="DependencyProperty"/>.
        /// </summary>
        public const string MillisecondsPropertyName = "Milliseconds";

        /// <summary>
        /// Sets the value of the <see cref="MillisecondsProperty"/> on the given <paramref name="element"/>.
        /// </summary>
        /// <param name="element">The <see cref="DependencyObject">target element</see>.</param>
        public static void SetMilliseconds(DependencyObject element, int value)
        {
            element.SetValue(MillisecondsProperty, value);
        }

        /// <summary>
        /// Gets the value of the <see cref="MillisecondsProperty"/> as set on the given <paramref name="element"/>.
        /// </summary>
        /// <param name="element">The <see cref="DependencyObject">target element</see>.</param>
        /// <returns><see cref="int"/></returns>
        public static int GetMilliseconds(DependencyObject element)
        {
            return (int)element.GetValue(MillisecondsProperty);
        }
        #endregion
        private class PropertyChangeTimer
        {
            private DispatcherTimer _timer;
            private BindingExpression _expression;
            public PropertyChangeTimer(DependencyObject target, DependencyProperty property)
            {
                if (target == null)
                    throw new ArgumentNullException("target");
                if (property == null)
                    throw new ArgumentNullException("property");
                if (!BindingOperations.IsDataBound(target, property))
                    return;
                _expression = BindingOperations.GetBindingExpression(target, property);
                if (_expression == null)
                    throw new InvalidOperationException("No binding was found on property "+ property.Name + " on object " + target.GetType().FullName);
                DependencyPropertyDescriptor.FromProperty(property, target.GetType()).AddValueChanged(target, OnPropertyChanged);
            }

            private void OnPropertyChanged(object sender, EventArgs e)
            {
                if (_timer == null)
                {
                    _timer = new DispatcherTimer();
                    int ms = DelayedUpdateBehavior.GetMilliseconds(sender as DependencyObject);
                    _timer.Interval = TimeSpan.FromMilliseconds(ms);
                    _timer.Tick += OnTimerTick;
                    _timer.Start();
                    return;
                }
                _timer.Stop();
                _timer.Start();
            }

            private void OnTimerTick(object sender, EventArgs e)
            {
                _expression.UpdateSource();
                _expression.UpdateTarget();
                _timer.Stop();
                _timer = null;
            }
        }
    }
}

这是一个如何使用它的例子:

<Window
    x:Class="BehaviorForDelayedTrigger.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:t="clr-namespace:BehaviorForDelayedTrigger">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition
                Height="auto" />
        </Grid.RowDefinitions>
        <Viewbox>
            <TextBlock
                x:Name="TargetTextBlock"
                Background="Red" />
        </Viewbox>
        <TextBox
            t:DelayedUpdateBehavior.TargetProperty="{x:Static TextBox.TextProperty}"
            t:DelayedUpdateBehavior.Milliseconds="1000"
            Grid.Row="1"
            Text="{Binding Text, ElementName=TargetTextBlock, UpdateSourceTrigger=Explicit}" />
    </Grid>
</Window>

这件事的主旨是...

您在绑定的 UIElement 上设置附加属性,传入您希望延迟的 DP。至此,我有了附加属性的目标和要延迟的属性,就可以进行设置了。我必须等到绑定可用,所以我必须在数据绑定设置后使用 Dispatcher 实例化我的观察程序类。如果不这样做,您将无法获取绑定表达式。

观察者类获取绑定并向 DependencyProperty 添加更新侦听器。在监听器中,我设置了一个计时器(如果我们还没有更新)或重置计时器。一旦计时器滴答作响,我就会触发绑定表达式。

同样,它可以工作,但它肯定需要清理。此外,您可以通过其名称使用 DP,并使用以下代码片段:

FieldInfo fieldInfo = instance.GetType()
                             .GetField(name, 
                                 BindingFlags.Public | 
                                 BindingFlags.Static | 
                                 BindingFlags.FlattenHierarchy);
return (fieldInfo != null) ? (DependencyProperty)fieldInfo.GetValue(null) : null;

您可能必须在 . 上添加“属性” name,但与使用x:Static.

于 2011-07-15T19:06:24.757 回答
4

我认为这正是您正在寻找的:DelayBinding for WPF

它是自定义绑定,完全符合上述两个答案的建议。它可以像编写<TextBox Text="{z:DelayBinding Path=SearchText}" />或指定延迟间隔一样简单<TextBox Text="{z:DelayBinding Path=SearchText, Delay='00:00:03'}" />

于 2011-07-15T19:00:24.570 回答
4

如果您使用的是 .NET 4.5 或更高版本,则可以使用绑定的延迟属性。这真的很容易:

<TextBox Text="{Binding Name, Delay=500, UpdateSourceTrigger=PropertyChanged}"/>
于 2018-04-16T15:34:57.170 回答
2

为什么不使用UpdateSouceTrigger=PropertyChanged,而不是直接启动后台进程,而是让它重置一个计时器,该计时器将在 3 秒后启动该进程。这样,如果他们在 3 秒前输入其他内容,计时器就会重置,后台进程将从现在开始 +3 秒。

于 2011-07-15T18:02:22.007 回答