tags:

views:

287

answers:

5

I have a C# Windows Form application that contains a menu with this event:

private void createMenuItem_Click(object sender, EventArgs e)
    {
        canvas.Layer.RemoveAllChildren();
        canvas.Controls.Clear();
        createDock();
    }

I would like to provide the user with the opportunity to fire this event through another menu option that pulls up a timer.

My timer looks like this:

private void transfer_timer()
 {
  System.Timers.Timer Clock = new System.Timers.Timer();
  Clock.Elapsed += new ElapsedEventHandler(createMenuItem_Click); 
  Clock.Interval = timer_interval;
  Clock.Start();
 }

When I do this the resulting error message is:

createDock - Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.

What am I doing wrong?

A: 

You need to use System.Windows.Forms.Timer. It will always fire its events on the UI thread, whereas System.Timers.Timer will not, causing the exception.

SLaks
+3  A: 

UI elements have thread affinity. In general, their methods can only be called from the thread that created.

You can use the Invoke method on a control to do a cross-thread call. Invoke will marshal the call and execute it on the UI thread.

Michael
This is usually the best and simplest way if you are comfortable using delegates.You can use SLaks or Joe's answer if your UI updating and event handling doesn't interfere with the Forms timer (i.e. if your UI updating is always fast).
Alan McBee
+2  A: 

That's because the System.Timers.Timer uses a different thread, and you can't manipulate GUI from a different thread.

The simplest solution is to use a System.Windows.Forms.Timer instead.

Joe White
+1  A: 

As the error message you received is trying to explain the Timer object you created uses another thread to perform the callback. Windows.Forms (or Win32 for that matter) can not handle that, this is explained in more detail here. One option as suggested is to use `System.Windows.Forms.Timer. Or use the Control.InvokeRequired, Control.Invoke pattern as explained in the provided link.

Bas Bossink
Thank Bas - great link.
John M
+2  A: 

Don't do that. :) Factor it out more like this ...

private void createMenuItem_Click(object sender, EventArgs e)
{
  DoCanvasWork(); // pick a good name :)        
}

private void transfer_timer() {
  System.Timers.Timer Clock = new System.Timers.Timer();
  Clock.Elapsed += new ElapsedEventHandler(Clock_Tick);
  Clock.Interval = timer_interval;  Clock.Start(); 
}

private void Clock_Tick( object sender, EventArgs e )
{
  BeginInvoke( new MethodInvoker(DoCanvasWork) );
}

private void DoCanvasWork()
{
  canvas.Layer.RemoveAllChildren();
  canvas.Controls.Clear();
  createDock();
}

That will give you much more clear and much more maintainable code. Your timer is not selecting a menu, it's doing the work that's the same as if the menu had been selected.

Per comment: You're welcome. Glad to help! Here's a good primer on cross thread marshaling (short read, good examples). But, basically BeginInvoke is taking a delegate (the MethodInvoker) and executing it asynchronously on the thread that the control was created on. In this case, it's a non-blocking, fire and forget message across threads (i.e. I don't wait for a return and I don't call EndInvoke to get a return). If you really want to get in-depth with this, Chris Sells book on Winforms is a great resource.

JP Alioto
thanks JP - the code sample really helped out.What exactly is the BeginInvoke doing?
John M