【问题标题】:Use Reactive Extensions to simulate Click on UIElement使用 Reactive Extensions 模拟点击 UIElement
【发布时间】:2014-03-20 14:28:21
【问题描述】:

在 WPF 中,我希望能够使用鼠标事件和响应式扩展来为 UIElement 创建一个可观察的对象,该 UIElement 的工作方式类似于 Click 事件。有很多使用它来创建拖放行为的示例,但我找不到任何只需单击一下的东西。

我预计它将涉及 MouseLeftButtonDown、MouseLeftButtonUp、MouseLeave 和 MouseEnter 上的 observables。但我不确定我需要使用 Merge、SelectMany、TakeUntil 或 TakeWhile 的哪种组合。在尝试将其全部包含在扩展中时,这是我目前所拥有的:

public static IDisposable GetClick(this UIElement item, Action clickAction)
        {
            var obs1 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeftButtonDown += h,
                    h => item.MouseLeftButtonDown -= h);

            var obs2 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeftButtonUp += h,
                    h => item.MouseLeftButtonUp -= h);

            var obs3 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeave += h,
                    h => item.MouseLeave -= h);

            var obs4 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseEnter += h,
                    h => item.MouseEnter -= h);

             var finalObs = ???

             return finalObs.Subscribe(x => clickAction.Invoke());

}

【问题讨论】:

标签: c# wpf system.reactive


【解决方案1】:

以下似乎可行,但我怀疑有可能以更简洁的方式做到这一点。

var click = mouseEnter
    .SelectMany(_ => mouseDown.TakeUntil(mouseLeave))
    .SelectMany(_ => mouseUp.TakeUntil(mouseLeave).Take(1));

我已将 finalObs 重命名为 clickobs1 重命名为 mouseDownobs2 重命名为 mouseUp...

编辑:添加 Take(1) 以修复 Enigmativity 指出的缺陷

编辑(2):

这是我更喜欢的另一种解决方案。

您需要在 mouseUp 的定义中添加 .Select(_ =&gt; "U"),在 mouseDown 中添加 .Select(_ =&gt; "D")...

var click = Observable.Merge(mouseDown, mouseUp, mouseLeave, mouseEnter)
    .Scan((s, c) => c == "L" ? "" : s + c)  // Create a string of the events, reset on mouseLeave
    .Where(s => s.Length >= 2 && s.Substring(s.Length - 2) == "DU");

经过考虑,在用户将鼠标放在项目上,然后移出项目,然后向后移动和鼠标向上的情况下,不可能获得完全正确的行为。这是因为当您不在项目上方时鼠标不会向上移动,因此您不能确定它们没有鼠标向上,然后在外面时鼠标向下。

【讨论】:

  • 我测试了你的代码,它很接近,但不太正确。第一次单击会产生一个OnNext,但下一次单击会产生两个连续的OnNext 调用。接下来给出三个,等等。每次点击都会增加OnNext 调用的算术级数。
  • 干杯,我没有为此测试过
  • 我确信答案将涉及JoinWindow 和/或Switch,但它们对我的大脑伤害太大了。
  • @Enigmativity 我添加了一个 Take(1) 似乎可以解决它。
  • 你是对的。哇。就这么简单。很棒的解决方案。 +1
【解决方案2】:

正确的方法是使用this.CaptureMousethis.ReleaseMouseCapture,这解决了公认答案中与检测鼠标离开和返回有关的一些问题。使用 ReactiveUI 绑定事件的完整(未经测试)解决方案是。

// Create a factory for capturing the mouse and and releasing it as an 
// IDisposable compatible with Observable.Using
Func<IDisposable> captureDisposable = () => {
        this.CaptureMouse(); 
        return Disposable.Create(()=>this.ReleaseMouseCapture());
};

// Capture the mouse and then release it on mouse up
var up = Observable.Using
    ( captureDisposable
    , capture => this.Events().PreviewMouseUp.Take(1)
    );

// Create the click event
var click = this.Events().PreviewMouseDown.Select(e=>up).Switch();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多