有没有办法倾听在Reactive Extensions中没有引发任何事件?

时间:2014-05-20 16:05:39

标签: c# system.reactive reactive-programming

当用户开始输入时,我有要求关闭某个功能,这很简单。当用户停止输入时,我想重新打开该功能。

如果没有被动扩展程序,可以使用timer简单地实现此功能,该计数会将每次最后一次击键上的计时器重置为1 second,并在user stops typing and timer elapses功能已重新启用

我可以调用任何方法来实现与Reactive Extensions相同的效果吗?

ThrottleTimeout每隔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);
}

其中mehRichTextBox

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 个答案:

答案 0 :(得分:3)

很棒的问题。

可能有很多方法可以解决这个问题,但这里有一个你可以使用的方法。首先,我们需要源可观察序列。这可能是一个keypressed事件或属性更改事件,已使用FromEvent或其他工厂/转换或ReactiveUI转换为Observable序列。

在此示例中,我将使用Observable.Interval(TimeSpan.FromSeconds(0.25)).Take(4);代替源序列(仅用于证明概念)。

接下来,我们需要决定何时需要禁用该功能(SpellCheck?)。这是源序列产生值的时候。

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

但是,如上所述,每次源序列yileded一个值时,都会调用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");
}

答案 1 :(得分:3)

以下是显示上述代码如何移植到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并不是实际进行拼写检查。我希望这足以让观众重新创造他们的实际解决方案。

我希望它有所帮助。

答案 2 :(得分:0)

我相信this exampleThrottle解决了类似的问题。

所以一些事情也可能对你有用:

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

答案 3 :(得分:0)

我不是RX高手,但我已经创建了一个似乎有效的WinForms应用程序示例。在表单上,​​我有textBox1button1,就是这样。

以下是代码隐藏:

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方法,至少在我的测试中是这样。