I have to confess, the more I play with reactive extensions the more I enjoy the result I get and appreciate their power. With this post I would like to show how it is possible to take advantage of these extensions to easily obtain something that normally requires a complex code. The example is really simple and probably not so useful by itself but there are lot of uses I can imagine for the trail functions I will show in a few.
Using the Reactive Extensions make very simple to collect and manipulate the events coming from a source, like you are querying a database. The library itself define a number of methods that operate this way. The buffering functions for instance take a stream of events and collect them in groups based on count or timeout. BufferWithCount gives chunks of counted events and BufferWithTime collect the events generated in a specific timeout. For example, given this sequence:
1,2,3 - 4,5,6 - 7,8,9
In the following code I show an extension method that is able to collect the events creating a Trail based on count. I called it TrailWithCount; Given the previous collection the result will be
1,2,3 - 2,3,4 - 3,4,5 - 4,5,6 - etc...
1: public static IObservable<IEnumerable<T>> TrailWithCount<T>(this IObservable<T> observable, int count)
3: Queue<T> queue = new Queue<T>();
5: return observable.Select(
6: o =>
10: while (queue.Count > count) queue.Dequeue();
11: return queue.ToArray();
This method take in input an IObservable<T> and returns an IObservable<IEnumerable<T>>. This is very close to the BufferWithCount method that returns an IObservable<IList<T>>. I preferred the use of IEnumerable<T> instead of IList<T> because I do not need the Insert and Remove methods of this interface. In the first row of the method I create a Queue<T>. It is important to remember that this collection will be created once the first time the method is called and thanks to the usage of lambda expression it will be alive and available to the body of the Select method for each event we get from the stream.
So, every time we get an event, in the Select method I add it to the Queue and then I remove the ones that exceed the count. Finally I select the items in the queue a result of the lambda expression so they are forwarded to the next extension method in the chain.
The same way, but taking advantage od the Timestamped<T> class I can operate to collect the trail events by time. The Timestamped<T> class is useful to add a timestamp (the moment when the event was received) to the item I add to the queue. So instead of removing items from the queue when they exceed a count I compare the timestamp with the current time and I remove the ones that are outside the defined timeout. Here is the method:
1: public static IObservable<IEnumerable<T>> TrailWithTime<T>(this IObservable<T> observable, TimeSpan timeSpan)
3: Queue<Timestamped<T>> queue = new Queue<Timestamped<T>>();
5: return observable.Select(
6: o =>
8: DateTime now = DateTime.Now;
10: queue.Enqueue(new Timestamped<T>(o, now));
12: while (now - queue.Peek().Timestamp > timeSpan)
15: return queue.Select(v => v.Value).ToArray();
Ok, now that we got the Trail method it is time to use them. To visualize the resulting trail I will catch the MouseMove events and collect the resulting points to draw the trail on the plugin area. So first of all I attach the MouseMove using the Observable.FromEvent then I transform the output events to a stream of Point calling GetPosition for every EventArgs. Finally I call the TrailWithCount (or TrailWithTime) method just before subscribing to the stream.
1: Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
2: ev => new MouseEventHandler(ev),
3: ev => this.LayoutRoot.MouseMove += ev,
4: ev => this.LayoutRoot.MouseMove -= ev)
5: .Select(o => o.EventArgs.GetPosition(this.LayoutRoot))
The Subscribe method will call a Draw method that simply assign the points of the trail to a Polyline. This will show a trail that grow to the required length then is shortened at the end when the mouse moves. Here you can try by yourself moving the mouse on the following instance of the plugin:
In the attached code you can download you will find the methods I discussed above and an additional TrailWithTimeAndCount method that have an hybrid behavior that combines both the other methods. To make the download working you have to download the Reactive Extensions from Nuget and add a reference to them in the project.
Download: http://www.silverlightplayground.org/assets/sources/SLPG.Trailing.Silverlight.zip (17 KB)