【问题标题】:Is there a way to listen that there are no events that are being raised in Reactive Extensions?有没有办法监听 Reactive Extensions 中没有引发任何事件?
【发布时间】:2014-05-20 16:05:39
【问题描述】:

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

如果没有响应式扩展,可以简单地使用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");
}

【问题讨论】:

    标签: c# system.reactive reactive-programming


    【解决方案1】:

    这里是显示如何将上述代码移植到 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 包中提取了代码所需的所有其他内容。这是 .NET 4.5。

    这是一个展示如何解决问题的示例。即我不建议使用.ObserveOnDispatcher(),我不建议编写代码隐藏,而且我知道在复选框上设置 IsEnabled 并不是实际上进行拼写检查。我希望这足以让观众重新创建他们的实际解决方案。

    我确实希望它有所帮助。

    【讨论】:

    • 感谢您的帮助,它确实有效!为什么不推荐使用ObserveOnDispatcher?但是,如果它是要使用的自定义控件,MVVM 与自定义控件的用法完全不同,因为SpellCheck 是我不拥有或不应该在ViewModel 中的附加属性。跨度>
    • 我建议使用ObserveOn(IScheduler) 并传入一个dispatcherScheduler(用于测试)。见introtorx.com/Content/v1.0.10621.0/16_TestingRx.html#TestingRx。接下来我将使用 MVVM,拥有一个名为 SuppressSpellCheck 的属性并将其绑定到您的 AttachedProperty。现在上面的 Rx 查询只是切换它来启用/禁用你的拼写检查。如果您不进行单元测试,请忽略所有这些:-)
    【解决方案2】:

    好问题。

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

    在这个示例中,我将使用Observable.Interval(TimeSpan.FromSeconds(0.25)).Take(4); 作为源序列的替代品(只是为了证明这个概念)。

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

    var disableSpellcheck = source.Select(_=&gt;false);

    然后我们需要决定何时需要重新启用该功能。这是在源序列上出现 1 秒的静默时间。实现这一点的一个技巧是为源中的每个事件创建一个一秒计时器。创建新的 Timer 时,取消之前的 Timer。你可以通过创建一个嵌套的 observable 序列来做到这一点,并在它产生一个新的序列时使用 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");
    }
    

    【讨论】:

    • 嗨,Lee,我尝试了您的代码并用 Keypress 替换了 Observable.Interval。我没有得到想要的结果,尽管当用户再次开始输入时,它不会发布给订阅者以重新启用或禁用拼写检查。它只做了一次(启用/禁用),就是这样
    • 我已经更新了代码以证明它在快速连续发生一系列事件时有效,一些沉默,然后快速连续发生更多事件。如果您显示您实际使用的代码,我可以指出错误。
    • 你能发布你正在使用的实际代码吗?看起来您从这个答案中剪切并粘贴代码,然后只是重命名了一些字符串?例如,在类之外声明了一个方法。您在类中有方法级代码。发布的代码甚至无法编译。如果您愿意至少尝试一下,我很乐意提供帮助。
    • 这是一个 WPF 应用程序,它有一个名为 meh 的 RichTextBox,我刚刚更改了您对 KeyPress 所说的内容。所以几乎它看起来和你的样本一样。那是我正在使用的实际代码:) 我用XAML 更新了它
    • 好的。显然沟通中断了;-)。我的示例是一个 LinqPad 脚本,我希望您能够看到它通过将事件与 slience 混合来模拟打字。我在下面为您发布了完整的解决方案。
    【解决方案3】:

    我相信this example 和Throttle 可以解决类似的问题。

    所以类似的东西也可能对你有用:

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

    【讨论】:

    • 说这是一个TextChanged,当用户开始输入时我关闭SpellCheck,如果用户没有输入任何内容,那么我需要打开SpellCheck。我不认为仅仅使用Throttle 就能达到我想要的效果。它只会关闭该功能,但我需要重新打开它
    • 另外,它会持续每 1 秒发射一次,这是我不想要的。我只想要最后一个。
    • @lll 我认为如果您限制相同的 TextChanged 并在打开 SpellCheck 的情况下订阅它,它将按预期工作。
    • 有没有办法让它像 >---------|(event stop)--|1000---> 毫秒发布给订阅者。
    • 我以为您想在每次输入停止时触发该事件。油门不应每 1 秒触发一次事件,它会过滤并等待事件,而不是生成新事件。您使用哪个版本的 Rx?
    【解决方案4】:

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

    【讨论】:

    • 这是否意味着它在观察时不断调用 UpdateEnableStatus(false)?
    • 不,正如我在回答中所说,我没有看到 UpdateEnableStatus 每秒被击中的证据(我坐在那里,上面有一个断点)
    • 试过你的解决方案,它一直在打 Do(_ => ...)。每 1 秒。 :s
    • @lll 您的应用程序与我的示例应用程序有何不同?
    • 我正在使用WPF,当用户停止输入时也尝试启用SpellCheck。反之亦然
    猜你喜欢
    • 2011-12-21
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多