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


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

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

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

Throttle или Timeout продолжает вызывать действие subscriber / exception каждые 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");
}
4 2

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);
        }
    }
}

Я вытащил из RX-WPF NuGet packeage все остальное, что требуется для кода. Это .NET 4.5.

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

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

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

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

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

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

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

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

var enableSpellcheck = source.Select(_=>Observable.Timer(TimeSpan.FromSeconds(1)))
      .Switch()
      .Select(_=>true);
Теперь мы хотим объединить эти две последовательности и передать результат методу, который включает / выключает функцию.
Observable.Merge(disableSpellcheck, enableSpellcheck)
          .Subscribe(isEnabled=>SetFlag(isEnabled));

Однако, как отмечалось выше, этот вызов SetFlag (false) каждый раз, когда исходная последовательность yileded значение. Это легко решается с помощью оператора 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");
}

Я считаю, что Этот пример с дроссельной заслонкой решает аналогичную задачу.

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

var throttled = observable.Throttle(TimeSpan.FromMilliseconds(1000));
using (throttled.Subscribe(x => RestoreThing())) {}

Я не мастер 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 Каждую секунду, по крайней мере в моем тестировании.