Here I am posting the full solution to my own problem. Maybe this will help out some beginners. And no, it didn't take me a year and a half to figure this one out, thanks to MSDN. Its just that I didn't bother to look into this once I had the answer. Anyways, here goes it.
As said by Muhammad, the trick is in using the
Control.BeginInvoke (or
Control.Invoke) method to force the method to be executed in the same thread as that of the control. The difference between Invoke and BeginInvoke is that the former is synchronous while the latter is asynchronous. Using BeginInvoke makes more sense as the whole idea is to create efficient multi-threaded application. BeginInvoke starts a new thread passing along a delegate object of the method to be invoked and the parameters to be passed to it.
In this case, the BeginInvoke method of the owner Form has to be called from the test class. So, we include an
ISynchronizeInvoke parameter in the constructor of the class which is then stored in a member variable. The ISynchronizeInvoke Interface must be implemented in-order to execute a delegate asynchronously. Thankfully, this has been done in the System.Windows.Forms.Control class, so it doesn't require any work on our part.
CODE
protected ISynchronizeInvoke SyncObject;
public test(ISynchronizeInvoke SyncObject)
{
this.SyncObject = SyncObject;
TestTimer.Elapsed += new ElapsedEventHandler(Tick);
TestTimer.Interval = 1000;
}
We add a new delegate with the same signature as that of the method Tick (the event handler for the System.Timers.Timer's elapsed event).
CODE
protected delegate void TickDelegate(object source, ElapsedEventArgs e);
The Tick method makes sure that it is running in the same thread as that of the Synchronizing object passed to the class's constructor using the
Control.InvokeRequired property. If it is not, it calls the BeginInvoke method of the SyncObject passing along a delegate to itself and an object array of it's parameters. No further work is done in this call to the Tick method. After a while, the Tick method is invoked again, only this time on the owner form's thread. The InvokeRequired property returns false and the actual work of the method is done.
CODE
private void Tick(object source, ElapsedEventArgs e)
{
if (SyncObject.InvokeRequired)
SyncObject.BeginInvoke(new TickDelegate(Tick), new object[] { source, e });
else
TestEvent();
}
Only one change needs to be made in the Form class - a reference to itself must be passed to the contstructor of the test class. For this, the instantiation of the object is now done inside the constructor (this keyword is only valid inside a non-static property, method, or constructor).
CODE
test A;
public Form1()
{
InitializeComponent();
A = new test(this);
A.TestEvent += new test.TestEventHandler(A_TestEvent);
}
Full code for the two classes:-
Test classCODE
using System;
using System.Timers;
using System.ComponentModel;
namespace Test
{
class test
{
public delegate void TestEventHandler();
public event TestEventHandler TestEvent;
protected Timer TestTimer = new Timer();
protected ISynchronizeInvoke SyncObject;
public test(ISynchronizeInvoke SyncObject)
{
this.SyncObject = SyncObject;
TestTimer.Elapsed += new ElapsedEventHandler(Tick);
TestTimer.Interval = 1000;
}
protected delegate void TickDelegate(object source, ElapsedEventArgs e);
private void Tick(object source, ElapsedEventArgs e)
{
if (SyncObject.InvokeRequired)
SyncObject.BeginInvoke(new TickDelegate(Tick), new object[] { source, e });
else
TestEvent();
}
public void Go()
{
TestTimer.Enabled = true;
}
}
}
Form ClassCODE
using System;
using System.Windows.Forms;
namespace Test
{
public partial class Form1 : Form
{
test A;
public Form1()
{
InitializeComponent();
A = new test(this);
A.TestEvent += new test.TestEventHandler(A_TestEvent);
}
void A_TestEvent()
{
this.Text = "Test Text"; // This is where the Exception "Cross Threaded Operation not valid" used to be generated
}
private void Form1_Load(object sender, EventArgs e)
{
A.Go();
}
}
}
Comment/Reply (w/o sign-up)