Есть ли способ прослушать, что в Reactive Extensions не возникают события?

У меня есть требование отключать определенную функцию, когда пользователь начинает печатать, что очень просто. А когда пользователь перестает печатать, я хочу снова включить эту функцию.

Без реактивных расширений можно просто реализовать эту функцию с помощью timer, который сбрасывает таймер при каждом последнем нажатии клавиши на 1 second, а когда user stops typing and timer elapses функция включается снова.

Есть ли какой-нибудь метод, который я могу вызвать для достижения того же эффекта с Reactive Extensions?

Throttle или Timeout продолжает вызывать действие подписчика/исключения каждые 1 second

ОБНОВЛЕНИЕ

XAML

<RichTextBox MaxHeight="1000" VerticalScrollBarVisibility="Visible" x:Name="meh"/>

Класс расширения

public static IObservable<EventArgs> ObserveTextChanged(this RichTextBox rtb)
{
 return Observable.FromEventPattern<TextChangedEventHandler, EventArgs>(
            h => rtb.TextChanged += h,
            h => rtb.TextChanged -= h)
                         .Select(ep => ep.EventArgs);
}

Класс, где meh — это RichTextBox

public class MainWindow()
{
 //Change this to be the keypress/propertychagned event. The type T doesn't matter we ignore it
 var typing = meh.ObserveTextChanged().Take(4);
 var silence = meh.ObserveTextChanged().IgnoreElements();
 var source = typing.Concat(silence).Concat(typing);
 var disableSpellcheck = source.Select(_ => false);
 var enableSpellcheck = source.Select(_ => Observable.Timer(TimeSpan.FromSeconds(1)))
                                     .Switch()
                                     .Select(_ => true);

 disableSpellcheck.Merge(enableSpellcheck)
                  .DistinctUntilChanged()
                  .Subscribe(SetFlag);

 }

// Define other methods and classes here
public void SetFlag(bool flag)
{
 Dispatcher.Invoke(new Action(() => SpellCheck.SetIsEnabled(meh, flag)));
 Debug.Write("flag");
}

person 123 456 789 0    schedule 20.05.2014    source источник


Ответы (4)


Вот весь код, показывающий, как приведенный выше код можно перенести в WPF. Похоже, что здесь есть пробел в общении, поэтому я создал все приложение wpf, чтобы доказать это.

<Window x:Class="StackoverFlow_23764884.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <HeaderedContentControl Header="When checked, spellcheck is enabled (emulated)">
            <CheckBox x:Name="spellChecking" IsChecked="True" IsEnabled="False"/>
        </HeaderedContentControl>

        <HeaderedContentControl Header="Type here to see the Spellcheck enable and disable">
            <RichTextBox x:Name="meh" Width="400" Height="300" />
        </HeaderedContentControl>
    </StackPanel>
</Window>

И код позади:

using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;

namespace StackoverFlow_23764884
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var source = meh.ObserveTextChanged();
            var disableSpellcheck = source.Select(_ => false);
            var enableSpellcheck = source.Select(_ => Observable.Timer(TimeSpan.FromSeconds(1)))
                                         .Switch()
                                         .Select(_ => true);

            disableSpellcheck.Merge(enableSpellcheck)
                             .DistinctUntilChanged()
                             .ObserveOnDispatcher()
                             .Subscribe(isEnabled => spellChecking.IsChecked=isEnabled);
        }

    }

    public static class ObEx
    {
        public static IObservable<EventArgs> ObserveTextChanged(this RichTextBox rtb)
        {
            return Observable.FromEventPattern<TextChangedEventHandler, EventArgs>(
                h => rtb.TextChanged += h,
                h => rtb.TextChanged -= h)
                .Select(ep => ep.EventArgs);
        }
    }
}

Я вытащил пакет nuget Rx-WPF, чтобы вытащить все остальное, необходимое для кода. Это .NET 4.5.

Это пример, чтобы показать, как решить проблему. т. е. я не рекомендую использовать .ObserveOnDispatcher(), я не рекомендую писать код программной части, и я знаю, что установка IsEnabled для флажка фактически не выполняет проверку орфографии. Я надеюсь, что этого достаточно, чтобы аудитория воссоздала свое реальное решение.

Я надеюсь, что это поможет.

person Lee Campbell    schedule 22.05.2014
comment
Спасибо за помощь, это действительно сработало! Почему вы не рекомендуете использовать ObserveOnDispatcher? Что, если это настраиваемый элемент управления, который нужно использовать, MVVM — это совершенно другое использование пользовательского элемента управления, поскольку SpellCheck — это свойство присоединения, которым я не владею или не должен быть в ViewModel. - person 123 456 789 0; 22.05.2014
comment
Я бы предложил использовать ObserveOn(IScheduler) и передать диспетчер-планировщик (для тестирования). См. introtorx.com/Content/v1.0.10621.0/16_TestingRx.html. #TestingRx. Затем я бы использовал MVVM, имел свойство с именем SuppressSpellCheck и привязал его к вашему AttachedProperty. Теперь запрос Rx выше просто переключает это, чтобы включить/отключить проверку орфографии. Если вы не тестируете модуль, то игнорируйте все это :-) - person Lee Campbell; 22.05.2014

Отличный вопрос.

Вероятно, есть много способов решить эту проблему, но вот один из них, с которым вы можете работать. Сначала нам нужна исходная наблюдаемая последовательность. Вероятно, это событие нажатия клавиши или изменение свойства, которое было преобразовано в наблюдаемую последовательность с использованием FromEvent или какой-либо другой фабрики/преобразования или, возможно, ReactiveUI.

В этом примере я буду использовать Observable.Interval(TimeSpan.FromSeconds(0.25)).Take(4); в качестве замены исходной последовательности (просто для подтверждения концепции).

Далее нам нужно решить, когда нам нужно отключить функцию (Проверка орфографии?). Это когда исходная последовательность дает значение.

var disableSpellcheck = source.Select(_=>false);

Затем нам нужно решить, когда нам нужно снова включить эту функцию. Это когда в исходной последовательности была 1 секунда тишины. Один трюк, который вы можете сделать, чтобы реализовать это, состоит в том, чтобы создать односекундный таймер для каждого события из источника. При создании нового таймера отмените предыдущий. Вы можете сделать это, создав вложенную наблюдаемую последовательность и используя Switch для отмены предыдущих внутренних последовательностей при создании новой.

var enableSpellcheck = source.Select(_=>Observable.Timer(TimeSpan.FromSeconds(1)))
      .Switch()
      .Select(_=>true);

Теперь мы хотим объединить эти две последовательности и отправить результат в метод, который включает/отключает эту функцию.

Observable.Merge(disableSpellcheck, enableSpellcheck)
          .Subscribe(isEnabled=>SetFlag(isEnabled));

Однако, как отмечалось выше, этот вызов SetFlag(false) каждый раз, когда исходная последовательность возвращает значение. Это легко решается с помощью оператора DistinctUntilChanged().

Окончательный пример кода (LinqPad) выглядит следующим образом:

void Main()
{
    //Change this to be the keypress/propertychagned event. The type T doesn't matter we ignore it
    var typing = Observable.Interval(TimeSpan.FromSeconds(0.25)).Take(4);
var silence = Observable.Timer(TimeSpan.FromSeconds(1)).IgnoreElements();
var source = typing.Concat(silence).Concat(typing);

    var disableSpellcheck = source.Select(_=>false);
    var enableSpellcheck = source.Select(_=>Observable.Timer(TimeSpan.FromSeconds(1)))
        .Switch()
        .Select(_=>true);

    Observable.Merge(disableSpellcheck, enableSpellcheck)
            .DistinctUntilChanged()
            .Subscribe(isEnabled=>SetFlag(isEnabled));

}

// Define other methods and classes here
public void SetFlag(bool flag)
{
    flag.Dump("flag");
}
person Lee Campbell    schedule 21.05.2014
comment
Привет, Ли, я попробовал твой код и заменил Observable.Interval на Keypress. Я не получаю желаемого результата, хотя, когда пользователь снова начинает печатать, он не публикуется для подписчиков, чтобы повторно включить или отключить проверку орфографии. Он сделал это только один раз (включить/отключить) и все. - person 123 456 789 0; 21.05.2014
comment
Я обновил код, чтобы доказать, что он работает, когда есть серия событий в быстрой последовательности, некоторое молчание, а затем еще несколько событий в быстрой последовательности. Если вы покажете код, который вы на самом деле используете, я могу указать на ошибку. - person Lee Campbell; 22.05.2014
comment
Можете ли вы опубликовать фактический код, который вы используете? Похоже, вы вырезали и вставили код из этого ответа, а затем просто переименовали некоторые строки? Например, есть метод, объявленный вне класса. У вас есть код уровня метода внутри класса. размещенный код даже не скомпилируется. Я рад помочь, если вы счастливы хотя бы попробовать. - person Lee Campbell; 22.05.2014
comment
Это приложение WPF, в котором RichTextBox называется meh, и я только что изменил то, что вы сказали, на KeyPress. Так что в значительной степени это будет выглядеть так же, как ваш образец. Это фактический код, который я использую :) Я обновил его с помощью XAML - person 123 456 789 0; 22.05.2014
comment
Ok. Явно обрыв связи ;-). Моим образцом был скрипт LinqPad, который, как я надеялся, вы сможете увидеть, эмулирует набор текста, смешивая события с молчанием. Я разместил полное решение для вас ниже. - person Lee Campbell; 22.05.2014

Я верю этому примеру с Throttle решает аналогичную проблему.

Так что что-то в этом роде может сработать и для вас:

var throttled = observable.Throttle(TimeSpan.FromMilliseconds(1000));
using (throttled.Subscribe(x => RestoreThing())) {}
person Gábor Bakos    schedule 20.05.2014
comment
Скажем, это TextChanged, где я отключаю SpellCheck, когда пользователь начинает печатать, и если пользователь ничего не набирает, мне нужно снова включить SpellCheck. Я не думаю, что простое использование Throttle приведет к тому, чего я хочу. Это просто отключит функцию, но мне нужно включить ее снова - person 123 456 789 0; 20.05.2014
comment
Кроме того, он будет срабатывать каждую секунду, чего я не хочу. Я просто хочу последний. - person 123 456 789 0; 20.05.2014
comment
@lll Я думаю, что если вы отрегулируете тот же TextChanged и подпишетесь на него, включив проверку правописания, он будет работать, как и ожидалось. - person Gábor Bakos; 20.05.2014
comment
Есть ли способ сделать что-то вроде ›---------|(остановка события)--|1000---›опубликовать мс для подписчиков. - person 123 456 789 0; 20.05.2014
comment
Я думал, вы хотите запускать событие каждый раз, когда набор текста прекращается. Throttle не должен запускать событие каждую 1 секунду, он как бы фильтрует и ожидает события, а не генерирует новые события. Какую версию Rx вы используете? - person Gábor Bakos; 20.05.2014
comment
Дроссель будет продолжать публиковать сообщения для подписчиков. Я использую последнюю версию Rx. Он продолжает стрелять, если я удерживаю клавишу нажатой, каждую секунду он публикует - person 123 456 789 0; 20.05.2014
comment
Я должен признать, что я невежда. Не могли бы вы поделиться, как вы пытаетесь его использовать? (Это изображение с версией Java предполагает, что оно запускается через определенный интервал времени и только последний элемент из этого интервала: github.com/Netflix/RxJava/wiki/) The Distinct/DistinctUntilChanged (introtorx.com/content/v1.0.10621.0/05_Filtering.html#Distinct) может решить проблему повторяющихся событий. - person Gábor Bakos; 20.05.2014
comment
Distinct будет продолжать слушать и срабатывать каждый раз, это похоже на Event.Given(1 second).Last().Subscribe(), это то, что я хочу: P - person 123 456 789 0; 20.05.2014

Я не мастер RX, но я создал пример приложения WinForms, которое, кажется, работает. На форме у меня есть textBox1 и button1 и все.

Вот код:

public Form1()
        {
            InitializeComponent();

            var observable = Observable.FromEventPattern(
                s => textBox1.TextChanged += s, s => textBox1.TextChanged -= s)
                //make sure we are on the UI thread
                .ObserveOn(SynchronizationContext.Current)
                //immediately set it to false
                .Do(_ => UpdateEnabledStatus(false))
                //throttle for one second
                .Throttle(TimeSpan.FromMilliseconds(1000))
                //again, make sure on UI thread
                .ObserveOn(SynchronizationContext.Current)
                //now re-enable
                .Subscribe(_ => UpdateEnabledStatus(true));
        }

        private void UpdateEnabledStatus(bool enabled)
        {
            button1.Enabled = enabled;
        }

Это работает так, как вы хотите, и не вызывает метод UpdateEnabledStatus каждую секунду, по крайней мере, в моем тестировании.

person Sven Grosen    schedule 20.05.2014
comment
Значит ли это, что пока он наблюдает, он продолжает вызывать UpdateEnableStatus(false)? - person 123 456 789 0; 20.05.2014
comment
Нет, как я сказал в своем ответе, я не видел доказательств того, что UpdateEnableStatus срабатывает каждую секунду (я сидел там с точкой останова) - person Sven Grosen; 20.05.2014
comment
Попробовал ваше решение, оно продолжает нажимать Do (_ =› ...). Каждую 1 секунду. :с - person 123 456 789 0; 20.05.2014
comment
@llll, чем ваше приложение отличается от моего примерного приложения? - person Sven Grosen; 20.05.2014
comment
Я использую WPF, также попробуйте включить SpellCheck, когда пользователь перестанет печатать. Наоборот - person 123 456 789 0; 20.05.2014
comment
@lll попробую с WPF - person Sven Grosen; 20.05.2014
comment
@lll Сделано в WPF, такое же поведение, как я видел раньше. UpdateEnabledStatus не попадает каждую секунду. Есть ли какие-либо другие детали вашего приложения, которые имеют отношение к этому? - person Sven Grosen; 20.05.2014
comment
Продолжайте нажимать клавишу и не отпускайте ее. Он продолжает стрелять по наблюдателям UpdateEnableStatus - person 123 456 789 0; 20.05.2014
comment
@lll Я не знаю, как объяснить эту морщину, если такое поведение настолько вредно для вашего приложения, вам придется надеяться, что кто-то, кто знает больше о RX, ответит - person Sven Grosen; 20.05.2014