正确处置 RX 订阅



我是反应式扩展的新手,这就是我想出的做弹出式烤面包机通知的方法。当鼠标越过烤面包机时,不透明度恢复到 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. 开关((是否为我处置了以前的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();
    

好吧,首先,您到处泄漏IDisposables - 每个Subscribe调用都返回一个IDisposable,这些调用只是超出范围,没有正确处理。但是,使用Rx库中的一些IDisposable容器可以轻松修复该部分:

(我围绕您的示例代码拼凑在一起的测试工具片段(

// 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清理,呵呵!

(如果我能想出第二点东西,将对此进行编辑(

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

首先,让我们设置一种将Storyboard开始/完成转换为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"值

这些可观察量(代码中的轻微名称调整(:

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

相关内容

  • 没有找到相关文章