我是Reactive Extensions的新手,这就是我用来做Popup Toaster Notification的方法。当鼠标越过烤面包机时,不透明度将恢复到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()是否正确完成?
Switch()是否为我配置了以前的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();
答案 0 :(得分:4)
好吧,首先,你在整个地方泄漏IDisposables
- 这些Subscribe
次调用中的每一次都会返回一个IDisposable
,这只是超出了范围,而不是处理得当。但是,使用Rx lib中的一些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
:=&gt; MouseEnter
活动mouseOut
:=&gt; 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;
}
}