我是反应式扩展的新手,这就是我想出的做弹出式烤面包机通知的方法。当鼠标越过烤面包机时,不透明度恢复到 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;
}
}
我的问题是:
ObserveOnDispatcher(( 是否正确完成?
开关((是否为我处置了以前的IObservable?
我努力将上述内容转换为 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;
}
}