Вызываете событие PropertyChanged во время записи паузы в TextBox? - PullRequest
5 голосов
/ 15 июля 2011

Мне было интересно, возможно ли вызвать событие PropertyChanged, когда пользователь делает паузу при наборе текста в TextBox? Или, более конкретно, я хочу запустить метод X секунд после того, как пользователь перестанет вводить текстовое поле.

Например, у меня есть форма с TextBox и больше ничего. Пользователь вводит 1-9-значное значение Id в TextBox, довольно ресурсоемкий фоновый процесс загружает запись.

Я не хочу использовать UpdateSouceTrigger=PropertyChanged, потому что это приведет к запуску ресурсоемкого фонового процесса всякий раз, когда набирается символ, поэтому 9-значный идентификационный номер начинает 9 из этих процессов.

Я также не хочу использовать UpdateSourceTrigger=LostFocus, потому что в форме больше ничего не нужно, чтобы TextBox терял фокус.

Так есть ли способ заставить мой фоновый процесс запускаться только после того, как пользователь сделал паузу при наборе номера Id?

Ответы [ 5 ]

9 голосов
/ 15 июля 2011

Установите UpdateSourceTrigger=PropertyChanged, а затем каждый раз, когда свойство изменяется, запускайте таймер для желаемой задержки. Если свойство снова изменяется до отметки таймера, отмените старый таймер и запустите новый. Если таймер делает отметку , то вы знаете, что свойство не изменилось за X секунд, и вы можете запустить фоновый процесс.

7 голосов
/ 15 июля 2011

Подготовка к дампу кода.

Я сделал это с поддельным поведением 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.

4 голосов
/ 15 июля 2011

Я думаю, это именно то, что вы ищете: DelayBinding для WPF

Это пользовательская привязка, которая делает именно то, что предлагают два ответа выше. Это максимально просто, как запись <TextBox Text="{z:DelayBinding Path=SearchText}" /> или указать интервал задержки <TextBox Text="{z:DelayBinding Path=SearchText, Delay='00:00:03'}" />

3 голосов
/ 16 апреля 2018

Если вы используете .NET 4.5 или выше, вы можете использовать свойство Delay Binding.Это действительно просто:

<TextBox Text="{Binding Name, Delay=500, UpdateSourceTrigger=PropertyChanged}"/>
2 голосов
/ 15 июля 2011

Почему бы не использовать UpdateSouceTrigger=PropertyChanged, но вместо непосредственного запуска фонового процесса он сбросит таймер, который запустит этот процесс, скажем, через 3 секунды. Таким образом, если они наберут что-то еще до истечения 3 секунд, таймер будет сброшен, и фоновый процесс будет происходить на +3 секунды больше.

...