views:

150

answers:

1

Looking at the Reactive Extensions for javascript demo on Jeff Van Gogh's blog, I thought I'd give it a try in C#/Winforms, but it doesn't seem to work so well.

I just threw this into the constructor of a form (with the Rx framework installed and referenced):

Observable.Context = SynchronizationContext.Current;
var mousemove = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");
var message = "Time flies like an arrow".ToCharArray();

for(int i = 0; i < message.Length; i++)
{
    var l = new Label() 
            { 
                Text = message[i].ToString(), 
                AutoSize = true, 
                TextAlign = ContentAlignment.MiddleCenter 
            };
    int closure = i;
    mousemove
        .Delay(closure * 150)
        .Subscribe(e => 
            {
                l.Left = e.EventArgs.X + closure * 15 + 10;
                l.Top = e.EventArgs.Y;
                //Debug.WriteLine(l.Text);
            });
    Controls.Add(l);
}

When I move the mouse, the letters seem to get moved in a random order, and if I uncomment the Debug line, I see multiple events for the same letter...

Any ideas? I've tried Throttle, but it doesn't seem to make any difference. Am I just asking too much of WinForms to move all those labels around?

(Cross posted on Rx Forum)

+1  A: 

Answer cross posted back from Rx Forum (if the author is here and wants to claim it, that's fine by me):

Problem #1: You are using old bits. Observable.Context was gone about 4 versions ago. Problem #2: Javascript has no concept of threads, and Rx likes to put stuff on other threads for you.

So, using the latest bits, the solution looks something like this:

void Form1_Load(object sender, EventArgs e)
{
   Reactive("Time flies like an arrow");
}

private void Reactive(string msg)
{
    var mousemove = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");
    var message = msg.ToCharArray();

    for(int i = 0; i < message.Length; i++)
    {
        var l = new Label()
        { 
            Text = message[i].ToString(), 
            AutoSize = true, 
            TextAlign = ContentAlignment.MiddleCenter 
        };
        int closure = i;
        mousemove
            .Delay(TimeSpan.FromSeconds(0.07 * i), Scheduler.Dispatcher)
            .Subscribe(e =>
            {
                l.Left = e.EventArgs.X + closure * 12 - 5;
                l.Top = e.EventArgs.Y + 15;
            });
        Controls.Add(l);
    }
}

Notice the ObserveOnDispatcher and SubscribeOnDispatcher. This brings us close to the Javascript version, but again threading is the real issue here.

UPDATE, added correction from Jeff Van Gogh in above code


Just for fun, here is a vague rendering of the same thing without Rx:

private void Unreactive(string msg)
{
    var message = msg.ToCharArray();

    for(int i = 0; i < message.Length; i++)
    {
        var l = new Label()
        { 
            Text = message[i].ToString(), 
            AutoSize = true, 
            TextAlign = ContentAlignment.MiddleCenter 
        };
        Controls.Add(l);
        int closure = i;
        this.MouseMove += (s, e) => LabelDelayMove(closure, e, l);
    }
}

private void LabelDelayMove(int i, MouseEventArgs e, Label l)
{
    Point p = new Point(e.X + i * 12 - 5, e.Y - 15);
    var timer = new System.Threading.Timer((_) => LabelMove(l, p), null, i * 70, System.Threading.Timeout.Infinite);
}

private void LabelMove(Label l, Point location)
{
    this.BeginInvoke(new Action(() => l.Location = location));
}
Benjol
Link to the original post? Ah, I see it in your question, but not this answer.
Richard Hein
@Richard, yup, I've added a link to the question, but can't for the life of me see where there might be a link to the answer. They really should drop their forums and come over here to join the party!
Benjol