Proper disposal of RX subscriptions - silverlight

I'm new to Reactive Extensions, and this is what I've come up with to do a Popup Toaster Notification. When the mouse goes over the toaster, the opacity is brought back to 100%. Otherwise, it fade out gradually.
The code works, but I'm not entirely confident I'm not leaking resources, especially in the mouseOut subscription. In addition, I'm not sure if this is the best way to implement this functionality.
Any critiques, tips would be appreciated.
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();
}
Ideally, I would have liked to express the events in the following manner:
slideIn then fadeOut
unless mouseEnter
then fadeIn , slideIn.Pause
until mouseLeave
then fadeOut.Begin and slideIn.Resume
What's the closest way to do this in RX?
UPDATE #1
*UPDATE #2* (Cleaning up Subscribe())
Here's a somewhat cleanear attempt.
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;
}
}
My questions are:
Is ObserveOnDispatcher() done correctly?
Does the Switch() dispose the previous IObservable for me?
I struggle to translate the above into LINQ query syntax
/* 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();

Well, first off, you're leaking IDisposables all over the place - each one of those Subscribe calls returns an IDisposable, which are just going out of scope, not being disposed properly. That part is easily fixed, however, using some of the IDisposable containers in the Rx lib:
(snippets from the test harness I threw together around your example code)
// 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();
};
Then later on in your queries, wrap all Subscribe calls (except one, see below) in something like this:
// slideIn then fadeOut
_disposables.Add(slideInCompleted.Subscribe(e => fadeOutAnimation.Begin()));
The one exception being the MouseOut "canceller":
.Do(a =>
{
fadeOutAnimation.Pause();
fadeInAnimation.Begin();
slideInAnimation.Pause();
_resubscriber.Disposable = mouseOut.Do(
b =>
{
fadeOutAnimation.Begin();
fadeInAnimation.Stop();
slideInAnimation.Resume();
}).Subscribe();
})
Now...as for this:
slideIn then fadeOut
unless mouseEnter
then fadeIn , slideIn.Pause
until mouseLeave
then fadeOut.Begin and slideIn.Resume
That I'll have to think about...I think there's a more...rx way of doing it, but I'd have to ponder it a bit. Definitely handle the IDisposable cleanup, tho!
(will edit this if I can come up with something for the second bit)
EDIT: Ooh, think I've got something promising...
First off, let's rig up a way to translate Storyboard start/completes into an 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;
}
}
Basically, this sets up a "Start Animation, and signal us true when it's done" sequence...on to the good part!
So let's assume you've got the following Storyboards defined:
fadeIn: Animates Opacity to 1.0
fadeOut: Animates Opacity to 1.0
slideIn: Animates Margin to "in" value
slideOut: Animates Margin to "out" value
And these observables (slight name tweaks from your code):
mouseEnter: => MouseEnter event
mouseOut: => MouseLeave event
You can set up an IObservable that carries the actual sequence thusly:
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();
EDIT EDIT: Here's the full Test rig I used, maybe you can port to your needs:
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;
}
}

Related

WPF Blur effect causing high CPU even when collapsed

We have to show a legal pop-up in our WPF app. When the pop-up is shown, we use blur effect on the view below.
Recently we recognized that this is causing high GPU usage. Because of the spinner control in the background. The more active content, the more GPU usage.
We collapse this spinner when the pop-up is shown based on a property. But this doesn't help. Only when we set it to collapsed in MainWindow.xaml it works.
We tried multiple things e.g. BitmapCache and other techniques but with no success so far.
Here an example:
https://github.com/rmoergeli/BlurEffectTest.git
I investigated that the problem in demo code hides in your animation: it doesn't stop on changing it visibility from visible to collapsed.
So I found a solution to stop animation on being collapsed with the help of MSDN resource.
public partial class Spinner : UserControl
{
private Canvas _content;
private Storyboard _rotationStoryboard;
public Spinner()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
DefineContent();
SizeChanged += Spinner_SizeChanged;
IsVisibleChanged += Spinner_IsVisibleChanged;
Loaded += Spinner_Loaded;
}
private void Spinner_Loaded(object sender, RoutedEventArgs e)
{
_rotationStoryboard.Begin(this, isControllable: true);
}
private void Spinner_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isVisible && isVisible)
_rotationStoryboard.Resume(this);
else
_rotationStoryboard.Pause(this);
}
private void DefineContent()
{
_content = new Canvas();
//set content render transform origin point to center
_content.RenderTransformOrigin = new Point(0.5 , 0.5);
_content.RenderTransform = new RotateTransform(angle: 0);
// Assign the canvas a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
RegisterName("animatableCanvas", _content);
Content = _content;
DefineAnimatableContent();
// Create an animation and a storyboard to animate the
// canvas.
DoubleAnimation doubleAnimation = new DoubleAnimation
{
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(3)),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetName(doubleAnimation, "animatableCanvas");
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RenderTransform.Angle"));
_rotationStoryboard = new Storyboard();
_rotationStoryboard.Children.Add(doubleAnimation);
}
private void DefineAnimatableContent() //renamed Refresh method
{
int n = Balls;
double size = BallSize;
_content.Children.Clear();
double x = ActualWidth / 2;
double y = ActualHeight / 2;
double r = Math.Min(x, y) - size / 2;
double doubleN = Convert.ToDouble(n);
for (int i = 1; i <= n; i++)
{
double doubleI = Convert.ToDouble(i);
double x1 = x + Math.Cos(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
double y1 = y + Math.Sin(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
var e = new Ellipse
{
Fill = BallBrush,
Opacity = doubleI / doubleN,
Height = size,
Width = size
};
Canvas.SetLeft(e, x1);
Canvas.SetTop(e, y1);
_content.Children.Add(e);
};
}
#region Event Handlers
private void Spinner_SizeChanged(object sender, SizeChangedEventArgs e)
{
//we dont need this anymore as we set content render transform origin point to content center
//Transform.CenterX = ActualWidth / 2;
//Transform.CenterY = ActualHeight / 2;
DefineAnimatableContent();
}
#endregion
//other logic
}
This code stops animation on changing Spiner control visibility. But sure you can pause and resume animation with any trigger you need.
N.B! Using this approach you define all the content (not only animarable parts) in code behind so you don't need Spinner.xaml resource anymore.

How to parallelize 3d model creation?

Bit of a special case here, because I'm using a library called helix-toolkit but bear with me.
The thing is I would like to parallelize the creation of model objects in my code using a backgroundworker.
I know that there is a big issue with mutlithreading and working on UI elements but maybe somehow there is a workaround.
Here is the rough structure of my code:
First file in which I create the Backgroundwoker, splitting the workload for creating Geometry3D objects and finally calling SetModelGeometry to bind the geometries to the viewport. The second file shows how the binding is done.
MainWindow.xaml.cs
private void Draw_Building()
{
_worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
_worker.DoWork += Draw_Building_DoWork;
_worker.ProgressChanged += DrawBuilding_ProgressChanged;
_worker.RunWorkerCompleted += DrawBuilding_RunWorkerCompleted;
_worker.RunWorkerAsync(10000);
}
private void Draw_Building_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// returns a list containing Geometry3D objects ( created by meshBuilder.ToMeshGeometry3D() )
Geometryhandler.Draw_Building(sender, e);
}
private void DrawBuilding_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
StatusProgressBar.Value = e.ProgressPercentage;
var i = (int)e.UserState;
var actualComponent = MyBuildingComponents.First(c => c.Id == i);
LblStatusbarInfo.Text = "Currently meshing element #" + actualComponent.Globalid + " (" +
actualComponent.Objectname + ")";
StatusProgressbarMsg.Text = "Meshing (" + e.ProgressPercentage + " %)";
}
private void DrawBuilding_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusProgressbarMsg.Text = "-";
StatusProgressBar.Value = 0;
LblStatusbarInfo.Text = "Meshing completed.";
Geometry = e.Result as List<MeshIdandGeometry>;
// creates MeshGeometryModel3D objects and binds them to the viewport using the List of Geometries
MainViewModel.SetModelGeometry(Geometry);
}
MainViewModel.cs
public void SetModelGeometry(List<MeshIdandGeometry> geometry)
{
MyModelGeometry = new Element3DCollection();
if (geometry != null)
{
foreach (var mygeometry in geometry)
{
var s = new MeshGeometryModel3D
{
Geometry = mygeometry.Geometry,
Material = mygeometry.Material,
};
this.MyModelGeometry.Add(s);
s.Attach(MyModelViewport.RenderHost);
}
}
this.OnPropertyChanged("MyModelGeometry");
}
My problem at the moment is the following error message:
The calling thread cannot access this object because a different
thread owns it.
which is thrown in the SetModelGeometry function when trying to attach the ModelGeometry to the viewport.
I guess the compiler is complaining about the fact that the geometries were created in different threads, to which he has no access now.
Is there any workaround/solution without destroying the parallel execution of the DrawBuilding function?
EDIT:
EDIT 2: posted the wrong version of the Draw_Building method
The Draw_Building method in the Geometryhandler:
public void Draw_Building(object sender, DoWorkEventArgs e)
{
var geometry = new List<MeshIdandGeometry>();
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(#"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(#"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
var max = _mainWindow.MyBuildingComponents.Count;
var i = 1;
// Loop over building components
foreach (var component in _mainWindow.MyBuildingComponents)
{
//if (i == 5) break;
var component1 = component;
var componentTriangles = _mainWindow.MyTriangles.Where(triangle => triangle.ComponentId == component1.Id);
var meshBuilder = new MeshBuilder(true, true, true);
// Loop over triangles in building element
foreach (var triangle in componentTriangles)
{
var triangle1 = triangle;
var p1 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId1);
var p2 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId2);
var p3 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId3);
if (p1 != null && p2 != null && p3 != null)
{
//meshBuilder.AddTriangle(new Vector3((float)p1.X, (float)p1.Y, (float)p1.Z),
// new Vector3((float)p2.X, (float)p2.Y, (float)p2.Z),
// new Vector3((float)p3.X, (float)p3.Y, (float)p3.Z));
// coordination are switched to match the coordinate system in SharpDX viewport
meshBuilder.AddTriangle(new Vector3(-(float)p1.Y, (float)p1.Z, -(float)p1.X),
new Vector3(-(float)p2.Y, (float)p2.Z, -(float)p2.X),
new Vector3(-(float)p3.Y, (float)p3.Z, -(float)p3.X));
}
}
var mesh = meshBuilder.ToMeshGeometry3D();
var meshandtriangle = new MeshIdandGeometry
{
Id = component1.Id,
Geometry = mesh,
Material = standardMaterial,
};
geometry.Add(meshandtriangle);
i++;
var progressPercentage = Convert.ToInt32(((double)i / max) * 100);
var backgroundWorker = sender as BackgroundWorker;
backgroundWorker?.ReportProgress(progressPercentage, component1.Id);
}
e.Result = geometry;
}
Big thanks to #egse for finding a solution.
Part of the code that causes the problem:
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(#"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(#"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
Basically the problem with the above code is that the material is created as a local variable inside the scope of the backgroundworker. This causes problems with ownership when the UI thread tries to enter the material objects.
The solution to this problem is to make sure that the material is created by the UI thread (e.g in this case, the constructor of the Geometryhandler)
TL;DR: Do not create instances of classes which inherit from DependencyObject in another thread than the UI.

When to start the WPF Progress Bar

I want my app to show a running progress bar while doing some components checking. However, due to my lack of knowledge in Desktop app programming and WPF, I cannot find suitable place for it.
I tried to show the incrementing the progress Bar during the Window_Loaded(), ContentRendered() but with no luck.
Instead of showing the progressBar increases, it just show the final state of the progress Bar.
Here is the code
public partial class Loading : Window
{
public Loading()
{
InitializeComponent();
SetProgressBar();
this.Show();
CheckComponents();
}
private void CheckComponents()
{
System.Threading.Thread.Sleep(3000);
CheckProductionDBConnection();
pgrsBar.Value = 30;
System.Threading.Thread.Sleep(3000);
CheckInternalDBConnection();
pgrsBar.Value = 60;
System.Threading.Thread.Sleep(3000);
CheckProductionPlanning();
pgrsBar.Value = 90;
//MainWindow mainWindow = new MainWindow();
//mainWindow.Show();
}
private void SetProgressBar()
{
pgrsBar.Minimum = 0;
pgrsBar.Maximum = 100;
pgrsBar.Value = 0;
}
//more code down here...
Where should I put the CheckComponents() method?
You could put this code in an event handler subscribed to the Activated event. The one catch with this is that the Activated event is fired every time the window receives focus after having lost it. To get around this, the first thing you can do in your event handler is unsubscribe from the Activated event so that your code is executed only the first time the window is activated.
You also need to offload this work to a worker thread if you don't want the delay to block the main thread. If you do that, you'll have to invoke your calls to update the progess bar's value.
Here's some sample code to get you started:
public Loader()
{
InitializeComponent();
SetProgressBar();
this.Activated += OnActivatedFirstTime;
}
private void OnActivatedFirstTime(object sender, EventArgs e)
{
this.Activated -= this.OnActivatedFirstTime;
ThreadPool.QueueUserWorkItem(x =>
{
System.Threading.Thread.Sleep(3000);
CheckProductionDBConnection();
this.Dispatcher.BeginInvoke(new Action(() => pgrsBar.Value = 30));
System.Threading.Thread.Sleep(3000);
CheckInternalDBConnection();
this.Dispatcher.BeginInvoke(new Action(() => pgrsBar.Value = 60));
System.Threading.Thread.Sleep(3000);
CheckProductionPlanning();
this.Dispatcher.BeginInvoke(new Action(() => pgrsBar.Value = 90));
});
}
private void SetProgressBar()
{
pgrsBar.Minimum = 0;
pgrsBar.Maximum = 100;
pgrsBar.Value = 0;
}

Wpf Reactive Extensions react when mouse is not over two elements

I'm trying to learn more about reactive extensions but I find it quite hard to find a real world example so i can train myself.
I few days ago i found myself writing some ToggleButton Mouse Enter, Leave Checked Unchecked events, and now i am wondering if i could simplify it using reactive extensions.
Here is the goal:
Given a ToggleButton, when hovering over and it's not checked, a popup should show, the popup should close if the mouse is not over the button or the popup
If I press the toggle button (Checked) the popup should stay open until the button is unchecked (ignoring mouse enter leave events) after which the mouse hover behavior should kick in again.
And if the Popup is closed externaly the toggle button should be automatically unchecked. (I know that this could be implemented using a few bindings and data triggers but i want to exercise my reactive extensions logic)
Right now i have the following:
private void ToggleButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!ToggleButton.IsChecked ?? false)
Popup.IsOpen = true;
}
private void ToggleButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!Popup.Child.IsMouseOver && !(TaskManagerTab.IsChecked ?? false))
{
Popup.IsOpen = false;
return;
}
popup.Child.MouseLeave += Popup_MouseLeave;
}
void Popup_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Popup.Child.MouseLeave -= Popup_MouseLeave;
if (!ToggleButton.IsMouseOver && !(ToggleButton.IsChecked ?? false))
{
Popup.IsOpen = false;
return;
}
}
private void ToggleButton_CheckedChanged(object sender, System.Windows.RoutedEventArgs e)
{
Popup.IsOpen = ToggleButton.IsChecked ?? false;
if (Popup.IsOpen)
Popup.Closed += Popup_Closed;
}
void Popup_Closed(object sender, System.EventArgs e)
{
Popup.Closed -= Popup_Closed;
ToggleButton.IsChecked = false;
}
}
I would a rough version but i really don't know how to begin.
Thank you!
Update:
I did come up with this but I'm not sure about performance, and i can't seem to repeat if the toggle button is unchecked.
var mouseEnterSaveBtn = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(h => SaveBtn.MouseEnter += h,
h => SaveBtn.MouseEnter -= h);
var mouseLeaveList = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(h => popup.MouseLeave += h,
h => popup.MouseLeave -= h);
var toggleBtnChecked =
Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(h => SaveBtn.Checked += h,
h => SaveBtn.Checked -= h);
var allCloseEvents = mouseLeaveList.Merge(mouseLeaveList);
mouseEnterSaveBtn.TakeUntil(toggleBtnChecked).Subscribe(pattern =>
{
popup.Visibility = Visibility.Visible;
allCloseEvents.TakeUntil(toggleBtnChecked).
Subscribe(eventPattern =>
{
if (!popup.IsMouseOver && !popup.IsMouseOver)
popup.Visibility = Visibility.Collapsed;
});
});
That's pretty close to what I would do. One thing I would suggest, though, is that anytime you feel like you should subscribe to an observable within an observer (notice your nested Subscribe calls), you can probably combine those observables and then subscribe.
var mouseIn = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
(
h => SaveBtn.MouseEnter += h,
h => SaveBtn.MouseEnter -= h
);
var mouseOut = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>
(
h => popup.MouseLeave += h,
h => popup.MouseLeave -= h
);
var isMouseOver = mouseIn
.Select((o,e) => true)
.Merge(mouseOut
.Select((o,e) => false));
var toggleBtnChecked = Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>
(
h => SaveBtn.Checked += h,
h => SaveBtn.Checked -= h
)
.Select((o,e) => (o as ToggleButton).Checked);
var shouldShow = isMouseOver
.DistinctUntilChanged()
.CombineLatest(toggleBtnChecked, (mouse, pressed) => pressed || mouse);
shouldShow.Subscribe
(
shouldBeVisible =>
{
popup.Visibility = shouldBeVisible ? Visibility.Visible : Visibility.Collapsed;
}
);

Silverlight Mouse Events: MouseEnter and MouseLeave conflicts

I have a collection of buttons in a grid. For each one of these buttons, I want to handle the MouseEnter and MouseLeave events to animate the height of the button (and do some other interesting stuff). It all works good until I start moving my mouse too fast over and off the buttons which eventually cause the events to take place at before the other is complete. What's the best way of making sure the events wait for eachother before being triggered?
UPDATE:
Going by x0r's advice, I refactored my code into an internal class which inherits from Button and has the required methods to perform the animations. Unfortunately, this did not really solve the problem because - I think - I'm handling the Completed event of the first animation in two separate places. (correct me if I'm wrong). Here's my code:
internal class MockButton : Button
{
#region Fields
private Storyboard _mouseEnterStoryBoard;
private Storyboard _mouseLeaveStoryBoard;
private Double _width;
#endregion
#region Properties
internal Int32 Index { get; private set; }
#endregion
#region Ctors
internal MockButton(Int32 index) : this(index, 200)
{
}
internal MockButton(Int32 index, Double width)
{
this.Index = index;
this._width = width;
}
#endregion
#region Event Handlers
internal void OnMouseEnter(Action action, Double targetAnimationHeight)
{
if (_mouseEnterStoryBoard == null)
{
_mouseEnterStoryBoard = new Storyboard();
DoubleAnimation heightAnimation = new DoubleAnimation();
heightAnimation.From = 10;
heightAnimation.To = targetAnimationHeight;
heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
_mouseEnterStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
Storyboard.SetTarget(heightAnimation, this);
_mouseEnterStoryBoard.Children.Add(heightAnimation);
}
_mouseEnterStoryBoard.Completed += (s, e) =>
{
action.Invoke();
};
_mouseEnterStoryBoard.Begin();
}
internal void OnMouseLeave()
{
if (_mouseLeaveStoryBoard == null)
{
_mouseLeaveStoryBoard = new Storyboard();
DoubleAnimation heightAnimation = new DoubleAnimation();
heightAnimation.To = 10;
heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
_mouseLeaveStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
Storyboard.SetTarget(heightAnimation, this);
_mouseLeaveStoryBoard.Children.Add(heightAnimation);
}
if (_mouseEnterStoryBoard.GetCurrentState() != ClockState.Stopped)
{
_mouseEnterStoryBoard.Completed += (s, e) =>
{
_mouseLeaveStoryBoard.Begin();
};
}
else
{
_mouseLeaveStoryBoard.Begin();
}
}
#endregion
}
UPDATE 2:
Some events are getting triggered multiple times. An example of that is the Click event on the close button of my Rule object...
public Rule(Action<Int32> closeAction)
{
this.Style = Application.Current.Resources["RuleDefaultStyle"] as Style;
this.CloseAction = closeAction;
this.Loaded += (s, e) =>
{
if (_closeButton != null)
{
_closeButton.Click += (btn, args) =>
{
if (this.CloseAction != null)
{
this.CloseAction.Invoke(this.Index);
}
};
if (_closeButtonShouldBeVisible)
{
_closeButton.Visibility = System.Windows.Visibility.Visible;
}
}
};
}
And below is the Action<Int32> I'm passing to the Rule object as the CloseAction:
private void RemoveRule(Int32 ruleIndex)
{
Rule ruleToRemove = Rules.FirstOrDefault(x => x.Index.Equals(ruleIndex));
Storyboard sb = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
sb.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Opacity"));
animation.Duration = TimeSpan.FromMilliseconds(300);
animation.From = 1;
animation.To = 0;
sb.Children.Add(animation);
Storyboard.SetTarget(animation, ruleToRemove);
sb.Completed += (s, e) =>
{
if (Rules.FirstOrDefault(x => x.Index.Equals(ruleIndex)) != null)
{
this.Rules.RemoveAt(ruleIndex);
}
};
sb.Begin();
}
UPDATE 3:
In order to avoid the animations running too early, I thought I could delay the MouseEnter event, so if the user just scrolls over the item too fast, it doesn't kick off. But I have a problem now: Say the user mouses over the item and then mouses out. If I use the Storyboard.BeginTime property, that won't safe guard against that behavior because eventhough the animation gets delayed, it's still going to start eventually... So is there a way I could prevent that from happening?
Any suggestions?
check in your mouseleave eventhandler if the first storyboard is still running and if that is the case attach the starting of the second storyboard to the Completed event of the first storybaord:
private void OnOddRowMouseLeave(object sender, MouseEventArgs e)
{
...
if(_firstStoryboard.GetCurrentState() != ClockState.Stopped)
_firstStoryboard.Completed += (s,e) => _secondStoryboard.Begin();
else
_secondStoryboard.Begin()
Everything that Silverlight does is asyncronous and so most likely what is happening is that because you are moving quickly in and out of the box the mouse leave is being fired before the mouseenter has a chance to finish. You could setup your two events so thay they have an indicator of whether or not the other is in process. For example you could do this
bool mouseOut =false;
bool mouseIn =false;
void OnMouseEnter(Action action, Double targetAnimationHeight)
{
if(!this.mouseOut)
{
this.mouseIn = true;
if (_mouseEnterStoryBoard == null)
{
_mouseEnterStoryBoard = new Storyboard();
DoubleAnimation heightAnimation = new DoubleAnimation();
heightAnimation.From = 10;
heightAnimation.To = targetAnimationHeight;
heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
_mouseEnterStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
Storyboard.SetTarget(heightAnimation, this);
_mouseEnterStoryBoard.Children.Add(heightAnimation);
}
_mouseEnterStoryBoard.Completed += (s, e) =>
{
action.Invoke();
};
_mouseEnterStoryBoard.Begin();
if(this.mouseOut)
{
this.OnMouseLeave();
}
this.mouseIn = false;
}
}
void OnMouseLeave()
{
if(!this.mouseIn)
{
this.mouseOut = false;
if (_mouseLeaveStoryBoard == null)
{
_mouseLeaveStoryBoard = new Storyboard();
DoubleAnimation heightAnimation = new DoubleAnimation();
heightAnimation.To = 10;
heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
_mouseLeaveStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
Storyboard.SetTarget(heightAnimation, this);
_mouseLeaveStoryBoard.Children.Add(heightAnimation);
}
if (_mouseEnterStoryBoard.GetCurrentState() != ClockState.Stopped)
{
_mouseEnterStoryBoard.Completed += (s, e) =>
{
_mouseLeaveStoryBoard.Begin();
};
}
else
{
_mouseLeaveStoryBoard.Begin();
}
}
else
{
this.mouseOut = true;
}
}
I haven't actually checked this code but this should help you to at least get closer to what you want. This should be quick enough that your user doesn't realize that it is not firing exactly on exit if they go over it quickly. But this should help to keep you from getting overlap.
Another way you could do this is setup the initial events as null, and have the mouse in event set the mouse in event when it is complete but the problem with that is that if the mouse out fires before the event is set then you don't event get the event firing.

Resources