Статьи

Немедленное регулирование привязки TextBox для Windows Phone

Кривая обучения разработчика Windows Phone

При разработке автономного веб-браузера для Windows Phone мне пришлось создать небольшую инфраструктуру MVVM, чтобы не допустить логики в представления.

Одним из первых, что я обнаружил пропущенным, был способ принудительного немедленного распространения текста, введенного в элемент управления TextBox, в свойство привязки данных моей ViewModel.

По умолчанию привязка TextBox срабатывает только тогда, когда элемент управления теряет фокус, и это выглядит неубедительно.

Повторное использование кода не миф!

Это действительно старая проблема. Он существовал в настольном Silverlight с самого начала. И он пробился на платформу Windows Phone.

К счастью, я вспомнил, что в те дни, когда экспериментировал с платформой Silverlight MVVM, я уже решил эту проблему, создав собственный элемент управления TextBox для Silverlight, поэтому я решил просто повторно использовать этот код и создать поведение, которое заставит обновление привязки срабатывать при каждом нажатии клавиши в TextBox. ,

В этом прелесть разработки приложения для Windows Phone — вы можете повторно использовать большую часть кода Silverlight, созданного за эти годы :)

Но потом я заметил еще одну проблему. Поскольку теперь каждое нажатие клавиши распространяло изменения в моей ViewModel, если пользователь печатает быстро, и я, например, выполняю какой-то вызов веб-службы при каждом изменении свойства — тогда у меня может быть слишком много запросов к веб-службе (по одному на каждое нажатие клавиши), но на самом деле Я хочу сделать звонок только тогда, когда пользователь перестал печатать на некоторое время.

К счастью, решение очень простое — мы будем использовать Reactive Extensions (Rx), поскольку он идеально подходит и уже встроен в Windows Phone.

Нам просто нужно сослаться на Microsoft.Phone.Reactive.dll из GAC (дополнительная загрузка не требуется), и мы на полпути.

Хорошо, но покажи нам код!

Да, давайте посмотрим, как выглядит код для ThrottledImmediateBindingBehavior:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using Microsoft.Phone.Reactive;

namespace Roboblob.Mvvm.Behaviors
{
    public class ThrottledImmediateBindingBehavior : Behavior
    {
        private BindingExpression _expression;

        public bool Throttle { get; set; }

        private double _throttleDelayInSeconds = 0.5;
        private IDisposable _currentObservable;

        public double ThrottleDelayInSeconds
        {
            get { return _throttleDelayInSeconds; }
            set { _throttleDelayInSeconds = value; }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            if (Throttle)
            {
                this._expression = this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
                var keys = Observable.FromEvent(AssociatedObject, "TextChanged").Throttle(TimeSpan.FromSeconds(ThrottleDelayInSeconds));
                _currentObservable = keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt => OnTextChanged(evt.Sender, evt.EventArgs));
            }
            else
            {
                this._expression = this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
                this.AssociatedObject.TextChanged += this.OnTextChanged;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (Throttle)
            {
                if (_currentObservable != null)
                {
                    _currentObservable.Dispose();
                    this._expression = null;
                }
            }
            else
            {
                this.AssociatedObject.TextChanged -= this.OnTextChanged;
                this._expression = null;
            }
        }

        private void OnTextChanged(object sender, EventArgs args)
        {
            if (_expression != null)
            {
                this._expression.UpdateSource();
            }
        }
    }
}

Просто красиво

Наш класс Binding имеет свойство bool Throttle, где мы можем включить / отключить регулирование, а также ThrottleDelayInSeconds, где мы указываем, через сколько секунд после того, как пользователь прекратит вводить, наше свойство обновляется.

В OnAttached — мы просто подключаемся к TextChanged и каждый раз, когда пользователь вводит что-то, мы обновляем источник привязки.

Но если для Throttle установлено значение true, мы подключаемся к событию TextChanged через Reactive Extensions и настраиваем регулирование так, чтобы привязка не запускалась, если изменения слишком быстрые.

При этом только после того, как изменения прекратят работу на время, определенное в ThrottleDelayInSeconds, только тогда будет инициировано обновление привязки.

Конечно, мы делаем небольшую очистку в OnDetaching, так как все хорошие кодеры :)

А как насчет Xaml ???

Да, в Xaml мы можем использовать это так:

<TextBox Text="{Binding SearchCriteriaText, Mode=TwoWay}">
                <i:Interaction.Behaviors>
                    <RoboblobBehaviors:ThrottledImmediateBindingBehavior Throttle="True" ThrottleDelayInSeconds="1" />
                </i:Interaction.Behaviors>
            </TextBox>

Для тех, кому лень набирать текст, я прилагаю сжатый исходный код простого проекта Windows Phone Mango, который демонстрирует использование поведения.

Кстати, кто-нибудь помнит, как это было печатать исходный код из печатных компьютерных журналов на вашем 8-битном компьютере?

Возможно нет : D

Ну, по крайней мере, я надеюсь, что кто-то найдет это полезным.

До следующего раза, счастливого кодирования!