Generate Strongly Typed Observable Events for the Reactive Extensions for .NET (Rx)
- by Bobby Diaz
I must have tried reading through the various explanations and introductions to the new Reactive Extensions for .NET before the concepts finally started sinking in. The article that gave me the ah-ha moment was over on SilverlightShow.net and titled Using Reactive Extensions in Silverlight. The author did a good job comparing the "normal" way of handling events vs. the new "reactive" methods.
Admittedly, I still have more to learn about the Rx Framework, but I wanted to put together a sample project so I could start playing with the new Observable and IObservable<T> constructs. I decided to throw together a whiteboard application in Silverlight based on the Drawing with Rx example on the aforementioned article. At the very least, I figured I would learn a thing or two about a new technology, but my real goal is to create a fun application that I can share with the kids since they love drawing and coloring so much!
Here is the code sample that I borrowed from the article:
var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");
var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");
var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");
var draggingEvents = from pos in mouseMoveEvent
.SkipUntil(mouseLeftButtonDown)
.TakeUntil(mouseLeftButtonUp)
.Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
new
{
X2 = cur.EventArgs.GetPosition(this).X,
X1 = prev.EventArgs.GetPosition(this).X,
Y2 = cur.EventArgs.GetPosition(this).Y,
Y1 = prev.EventArgs.GetPosition(this).Y
})).Repeat()
select pos;
draggingEvents.Subscribe(p =>
{
Line line = new Line();
line.Stroke = new SolidColorBrush(Colors.Black);
line.StrokeEndLineCap = PenLineCap.Round;
line.StrokeLineJoin = PenLineJoin.Round;
line.StrokeThickness = 5;
line.X1 = p.X1;
line.Y1 = p.Y1;
line.X2 = p.X2;
line.Y2 = p.Y2;
this.LayoutRoot.Children.Add(line);
});
One thing that was nagging at the back of my mind was having to deal with the event names as strings, as well as the verbose syntax for the Observable.FromEvent<TEventArgs>() method. I came up with a couple of static/helper classes to resolve both issues and also created a T4 template to auto-generate these helpers for any .NET type. Take the following code from the above example:
var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");
var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");
var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");
Turns into this with the new static Events class:
var mouseMoveEvent = Events.Mouse.Move.On(this);
var mouseLeftButtonDown = Events.Mouse.LeftButtonDown.On(this);
var mouseLeftButtonUp = Events.Mouse.LeftButtonUp.On(this);
Or better yet, just remove the variable declarations altogether:
var draggingEvents = from pos in Events.Mouse.Move.On(this)
.SkipUntil(Events.Mouse.LeftButtonDown.On(this))
.TakeUntil(Events.Mouse.LeftButtonUp.On(this))
.Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
new
{
X2 = cur.EventArgs.GetPosition(this).X,
X1 = prev.EventArgs.GetPosition(this).X,
Y2 = cur.EventArgs.GetPosition(this).Y,
Y1 = prev.EventArgs.GetPosition(this).Y
})).Repeat()
select pos;
The Move, LeftButtonDown and LeftButtonUp members of the Events.Mouse class are readonly instances of the ObservableEvent<TTarget, TEventArgs> class that provide type-safe access to the events via the On() method. Here is the code for the class:
using System;
using System.Collections.Generic;
using System.Linq;
namespace System.Linq
{
/// <summary>
/// Represents an event that can be managed via the <see cref="Observable"/> API.
/// </summary>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event args.</typeparam>
public class ObservableEvent<TTarget, TEventArgs> where TEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="ObservableEvent"/> class.
/// </summary>
/// <param name="eventName">Name of the event.</param>
protected ObservableEvent(String eventName)
{
EventName = eventName;
}
/// <summary>
/// Registers the specified event name.
/// </summary>
/// <param name="eventName">Name of the event.</param>
/// <returns></returns>
public static ObservableEvent<TTarget, TEventArgs> Register(String eventName)
{
return new ObservableEvent<TTarget, TEventArgs>(eventName);
}
/// <summary>
/// Creates an enumerable sequence of event values for the specified target.
/// </summary>
/// <param name="target">The target.</param>
/// <returns></returns>
public IObservable<IEvent<TEventArgs>> On(TTarget target)
{
return Observable.FromEvent<TEventArgs>(target, EventName);
}
/// <summary>
/// Gets or sets the name of the event.
/// </summary>
/// <value>The name of the event.</value>
public string EventName { get; private set; }
}
}
And this is how it's used:
/// <summary>
/// Categorizes <see cref="ObservableEvents"/> by class and/or functionality.
/// </summary>
public static partial class Events
{
/// <summary>
/// Implements a set of predefined <see cref="ObservableEvent"/>s
/// for the <see cref="System.Windows.System.Windows.UIElement"/> class
/// that represent mouse related events.
/// </summary>
public static partial class Mouse
{
/// <summary>Represents the MouseMove event.</summary>
public static readonly ObservableEvent<UIElement, MouseEventArgs> Move =
ObservableEvent<UIElement, MouseEventArgs>.Register("MouseMove");
// additional members omitted...
}
}
The source code contains a static Events class with prefedined members for various categories (Key, Mouse, etc.). There is also an Events.tt template that you can customize to generate additional event categories for any .NET type. All you should have to do is add the name of your class to the types collection near the top of the template:
types = new Dictionary<String, Type>()
{
//{ "Microsoft.Maps.MapControl.Map, Microsoft.Maps.MapControl", null }
{ "System.Windows.FrameworkElement, System.Windows", null },
{ "Whiteboard.MainPage, Whiteboard", null }
};
The template is also a bit rough at this point, but at least it generates code that *should* compile. Please let me know if you run into any issues with it. Some people have reported errors when trying to use T4 templates within a Silverlight project, but I was able to get it to work with a little black magic...
You can download the source code for this project or play around with the live demo. Just be warned that it is at a very early stage so don't expect to find much today. I plan on adding alot more options like pen colors and sizes, saving, printing, etc. as time permits. HINT: hold down the ESC key to erase!
Enjoy!
Additional Resources
Using Reactive Extensions in Silverlight
DevLabs: Reactive Extensions for .NET (Rx)
Rx Framework Part III - LINQ to Events - Generating GetEventName() Wrapper Methods using T4