1

我是 Reactive Extensions 的新手,这就是我想出的做弹出烤面包机通知的方法。当鼠标经过烤面包机时,不透明度恢复到 100%。否则,它会逐渐淡出。

该代码有效,但我并不完全确信我没有泄漏资源,尤其是在 mouseOut 订阅中。此外,我不确定这是否是实现此功能的最佳方式。

任何批评,提示将不胜感激。

    private void rxPop()
    {
        Rectangle toaster = (Rectangle)this.FindName("toaster1");
        Thickness newToasterPosition = new Thickness(
             toaster.Margin.Left, toaster.Margin.Top,
             toaster.Margin.Right, toaster.Margin.Bottom + toaster.Height);

        /* Animations */
        Storyboard slideInAnimation = slide(toaster,
            newToasterPosition,
            TimeSpan.FromMilliseconds(450));

        Storyboard fadeInAnimation = animateOpacity(toaster, 1.0, TimeSpan.FromMilliseconds(150));

        Storyboard fadeOutAnimation = animateOpacity(toaster, 0.0, TimeSpan.FromSeconds(3));

        /* Events */
        var mouseEnter = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
            (h => toaster.MouseEnter += h,
             h => toaster.MouseEnter -= h);

        var mouseOut = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
            (h => toaster.MouseLeave += h,
             h => toaster.MouseLeave -= h);

        var slideInCompleted = Observable.FromEventPattern<EventHandler, EventArgs>(
            h => slideInAnimation.Completed += h,
            h => slideInAnimation.Completed -= h);

        var fadeOutCompleted = Observable.FromEventPattern<EventHandler, EventArgs>(
            h => fadeOutAnimation.Completed += h,
            h => fadeOutAnimation.Completed -= h);

        // slideIn then fadeOut 
        slideInCompleted.Subscribe(e => fadeOutAnimation.Begin());

        var mouseEnterSubscription = mouseEnter
            .ObserveOnDispatcher()
            .Do(a =>
                {
                    fadeOutAnimation.Pause();
                    fadeInAnimation.Begin();
                    slideInAnimation.Pause();
                    mouseOut.Do(
                        b =>
                        {
                            fadeOutAnimation.Begin();
                            fadeInAnimation.Stop();
                            slideInAnimation.Resume();
                        }).Subscribe();
                })
            .Subscribe();

        fadeOutCompleted.Subscribe((e) => mouseEnterSubscription.Dispose());

        slideInAnimation.Begin();
    }

理想情况下,我希望以下列方式表达这些事件:

   slideIn then fadeOut
   unless mouseEnter 
        then fadeIn , slideIn.Pause
        until mouseLeave 
           then fadeOut.Begin and slideIn.Resume

在 RX 中最接近的方法是什么?

更新 #1 *更新 #2 *(清理订阅())

这是一个有点干净的尝试。

protected CompositeDisposable _disposables = new CompositeDisposable();

private void rxPop()
{

IDisposable mouseEnterSubscription = null;

/* Business logic: slideIn then fadeOut then remove from visual tree */
_disposables.Add(
  slideInAnimation
    .BeginUntilDone()
    .Select(slideInCompletedEvent =>
        fadeOutAnimation.BeginUntilDone())
    .Switch()
    .Subscribe(fadeOutAnimationCompletedEvent =>
    {
        mouseEnterSubscription.Dispose();

        // remove from visual tree
        (toaster.Parent as Panel).Children.Remove(toaster);
    }));

/* Business logic: mouseEnter/mouseLeave should pause/restart animations */
mouseEnterSubscription = mouseEnter
    .ObserveOnDispatcher()
    .Do(mouseEnterEventArgs =>
    {
        fadeOutAnimation.Pause();
        fadeInAnimation.Begin();
        slideInAnimation.Pause();
    })
    .Select(e => mouseOut)
    .Switch()
    .Do(mouseLeaveEventArgs =>
    {
        fadeOutAnimation.Begin();
        fadeInAnimation.Stop();
        slideInAnimation.Resume();
    })
    .Subscribe();

}

public static class RxExtensions
{
    public static IObservable<EventPattern<EventArgs>> BeginUntilDone(this Storyboard sb)
    {
        var tmp = Observable.FromEventPattern(
            h => sb.Completed += h,
            h => sb.Completed -= h);
        sb.Begin();
        return tmp;
    }
}

我的问题是:

  1. ObserveOnDispatcher() 是否正确完成?

  2. Switch() 是否为我处理了以前的 IObservable?

  3. 我很难将以上内容翻译成 LINQ 查询语法

        /* Business Logic */
        var showToast =
            // Start by sliding in
            from slideInComplete in slideIn.BeginObservable()
            where slideInComplete
            // Then in parallel, fadeOut as well as wait for mouseEnter
            from fadeOutComplete in fadeOut.BeginObservable()
            from enter in mouseEnter
            // ... I'm lost here.
            // ... how do I express 
            // ..... mouseEnter should pause fadeOut?
            select new Unit();
    
4

1 回答 1

4

好吧,首先,您IDisposables到处都在泄漏-这些Subscribe调用中的每一个都返回一个IDisposable,它只是超出了范围,没有被正确处理。然而,这部分很容易修复,使用IDisposableRx 库中的一些容器:

(我在您的示例代码周围拼凑的测试工具的片段)

// new fields

// A serial disposable lets you wrap one disposable
// such that changing the wrapped disposable autocalls
// Dispose on the previous disposable
protected SerialDisposable _resubscriber;

// A composite disposable lets you track/dispose a whole
// bunch of disposables at once
protected CompositeDisposable _disposables;

// no real need to do this here, but might as well
protected void InitializeComponent()
{
    _disposables = new CompositeDisposable();
    _resubscriber = new SerialDisposable();
    // misc
    this.Unloaded += (o,e) => 
    {
        if(_disposables != null) _disposables.Dispose();
        if(_resubscriber != null) _resubscriber.Dispose();
    };

然后稍后在您的查询中,将所有Subscribe调用(除了一个,见下文)包装成如下所示:

    // slideIn then fadeOut 
    _disposables.Add(slideInCompleted.Subscribe(e => fadeOutAnimation.Begin()));

一个例外是MouseOut“取消者”:

        .Do(a =>
            {
                fadeOutAnimation.Pause();
                fadeInAnimation.Begin();
                slideInAnimation.Pause();
                _resubscriber.Disposable = mouseOut.Do(
                    b =>
                    {
                        fadeOutAnimation.Begin();
                        fadeInAnimation.Stop();
                        slideInAnimation.Resume();
                    }).Subscribe();
            })

现在......至于这个:

slideIn then fadeOut
unless mouseEnter 
    then fadeIn , slideIn.Pause
    until mouseLeave 
        then fadeOut.Begin and slideIn.Resume

我必须考虑一下...我认为还有更多... rx的方法,但我必须考虑一下。一定要进行IDisposable清理,寿!

(如果我能想出第二点的话,我会编辑这个)

编辑:哦,我想我有一些有希望的东西......

首先,让我们想办法将Storyboardstart/completes 转换为IObservable

public static class Ext
{
    public static IObservable<bool> BeginObservable(this Storyboard animation)
    {
        var sub = new BehaviorSubject<bool>(false);
        var onComplete = Observable.FromEventPattern<EventHandler, EventArgs>(
            h => animation.Completed += h,
            h => animation.Completed -= h);

        IDisposable subscription = null;
        subscription = onComplete.Subscribe(e => 
        {
            Console.WriteLine("Animation {0} complete!", animation.Name);
            sub.OnNext(true);
            if(subscription != null)
                subscription.Dispose();
        });

        Console.WriteLine("Starting animation {0}...", animation.Name);
        animation.Begin();
        return sub;
    }
}

基本上,这设置了一个“开始动画,并true在它完成时向我们发出信号”序列......到了好的部分!

因此,假设您已经Storyboards定义了以下内容:

  • fadeIn: 动画Opacity到 1.0
  • fadeOut: 动画Opacity到 1.0
  • slideIn: 动画Margin到“in”值
  • slideOut: 动画Margin到“out”值

还有这些 observables(代码中的轻微名称调整):

  • mouseEnter: =>MouseEnter事件
  • mouseOut: =>MouseLeave事件

因此,您可以设置一个IObservable携带实际序列的:

var showToast = 
    // Start this mess on a "mouse enter" event
    from enter in mouseEnter
    // Start (in parallel) and wait until the fadeIn/slideIn complete
    from fadeInComplete in fadeIn.BeginObservable()
    from slideInComplete in slideIn.BeginObservable()
    where fadeInComplete && slideInComplete
    // Until you see a "mouse out" event
    from exit in mouseOut
    // Then start (in parallel) and wait until the fadeOut/slideOut complete
    from fadeOutComplete in fadeOut.BeginObservable()
    from slideOutComplete in slideOut.BeginObservable()
    where fadeOutComplete && slideOutComplete
    // And finally signal that this sequence is done
    // (we gotta select something, but we don't care what, 
    // so we'll select the equivalent of "nothing" in Rx speak)
    select new Unit();

编辑编辑:这是我使用的完整测试台,也许你可以移植到你的需要:

void Main()
{
    var window = new Window();
    var control = new MyControl();
    window.Content = control;
    window.Show();
}

public class MyControl : UserControl
{
    protected DockPanel _root;
    protected Rectangle _toaster;
    protected CompositeDisposable _disposables;
    protected Thickness _defaultToasterPosition = new Thickness(10, -60, 10, 10);

    public MyControl()
    {
        this.InitializeComponent();
    }

    protected void InitializeComponent()
    {
        _disposables = new CompositeDisposable();
        _root = new DockPanel();
        _toaster = new Rectangle();
        _toaster.SetValue(Rectangle.NameProperty, "toaster1");
        _toaster.Fill = Brushes.Red;
        _toaster.Stroke = Brushes.Black;
        _toaster.StrokeThickness = 3;
        _toaster.Width = 50;
        _toaster.Height = 50;
        _toaster.Opacity = 0.1;
        DockPanel.SetDock(_toaster, Dock.Bottom);
        _toaster.Margin = _defaultToasterPosition;
        rxPop();

        _root.Children.Add(_toaster);        
        this.Content = _root;

        this.Unloaded += (o,e) => 
        {
            if(_disposables != null) _disposables.Dispose();
        };
    }

    private void rxPop()
    {
        var toaster = (Rectangle)this.FindName("toaster1") ?? _toaster;
        var defaultToasterPosition = toaster.Margin;
        var newToasterPosition = new Thickness(
             defaultToasterPosition.Left, defaultToasterPosition.Top,
             defaultToasterPosition.Right, defaultToasterPosition.Bottom + toaster.Height);

        /* Animations */
        var slideIn = slide(toaster, newToasterPosition, TimeSpan.FromMilliseconds(450));
        var slideOut = slide(toaster, defaultToasterPosition, TimeSpan.FromMilliseconds(450));

        var fadeIn = animateOpacity(toaster, 1.0, TimeSpan.FromMilliseconds(150));
        var fadeOut = animateOpacity(toaster, 0.1, TimeSpan.FromSeconds(3));

        /* Events */
        var mouseEnter = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
            (h => toaster.MouseEnter += h,
             h => toaster.MouseEnter -= h);

        var mouseOut = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
            (h => toaster.MouseLeave += h,
             h => toaster.MouseLeave -= h);

        var showToast = 
            // Start this mess on a "mouse enter" event
            from enter in mouseEnter
            // Start (in parallel) and wait until the fadeIn/slideIn complete
            from fadeInComplete in fadeIn.BeginObservable()
            from slideInComplete in slideIn.BeginObservable()
            where fadeInComplete && slideInComplete
            // Until you see a "mouse out" event
            from exit in mouseOut
            // Then start (in parallel) and wait until the fadeOut/slideOut complete
            from fadeOutComplete in fadeOut.BeginObservable()
            from slideOutComplete in slideOut.BeginObservable()
            where fadeOutComplete && slideOutComplete
            // And finally signal that this sequence is done
            // (we gotta select something, but we don't care what, 
            // so we'll select the equivalent of "nothing" in Rx speak)
            select new Unit();

        _disposables.Add(showToast.Subscribe());
    }

    private Storyboard slide(Rectangle rect, Thickness newPosition, TimeSpan inTime)
    {
        var sb = new Storyboard();
        sb.Duration = inTime;
        Storyboard.SetTarget(sb, rect);        
        Storyboard.SetTargetProperty(sb, new PropertyPath(Rectangle.MarginProperty));
        var path = new ThicknessAnimation(newPosition, inTime);
        sb.Children.Add(path);
        return sb;
    }

    private Storyboard animateOpacity(Rectangle rect, double newOpacity, TimeSpan inTime)
    {
        var sb = new Storyboard();
        sb.Duration = inTime;
        Storyboard.SetTarget(sb, rect);        
        Storyboard.SetTargetProperty(sb, new PropertyPath(Rectangle.OpacityProperty));
        var path = new DoubleAnimation(newOpacity, inTime);
        sb.Children.Add(path);
        return sb;
    }
}

public static class Ext
{
    public static IObservable<bool> BeginObservable(this Storyboard animation)
    {
        var sub = new BehaviorSubject<bool>(false);
        var onComplete = Observable.FromEventPattern<EventHandler, EventArgs>(
            h => animation.Completed += h,
            h => animation.Completed -= h);

        IDisposable subscription = null;
        subscription = onComplete.Subscribe(e => 
        {
            Console.WriteLine("Animation {0} complete!", animation.Name);
            sub.OnNext(true);
            if(subscription != null)
                subscription.Dispose();
        });

        Console.WriteLine("Starting animation {0}...", animation.Name);
        animation.Begin();
        return sub;
    }
}
于 2013-02-26T06:30:21.570 回答