Hi Steve,
In Windows a window cannot have children created in different thread than
the parent istelf.
What you wanna do is in the thread pool call parent's Invoke method and pass
a delegate to the control creating routine. This will execute the creator in
the UI thread and then you can safely add those controls to the parent.
However you may not have a reference to the control which Invoke you need to
call (for example you may have more then one UI threads).
To solve this problem don't forget that it is not important, which thread
has been created the .NET control class it is important which thread has
executed the code that causes control's Handle to be created. So to solve
the prblem waht you need to do is when reveive the event with the created
control (the event hadnler is executed from the worker thread) to switch to
the UI thread (assuming that the event consumer is a control) and recreate
the control handler. Controls have Recreate method, but it is protected
unfortunately. You can use reflection to call that method or if the controls
are user controls you can expose a method for that.
The following is an example that do exacly that
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Reflection;
namespace ThreadingAndControls
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(120, 224);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void button1_Click(object sender, System.EventArgs e)
{
ControlCreator cc = new ControlCreator();
cc.ControlCreated +=new ControlEventHandler(cc_ControlCreated);
//Starting the creation method in a worker thread form the thread pool
ThreadPool.QueueUserWorkItem(new WaitCallback(cc.DoCreation));
}
private void cc_ControlCreated(object sender, ControlEventArgs e)
{
//This method is executed by the worker thread.
e.Control.Text = "TestButton";
e.Control.Location = new Point(10,10);
//Executes the RecreateControl method in the UI thread.
e.Control.CreateControl();
this.Invoke(new ControlEventHandler(RecreateControl), new
object[2]{sender, e});
}
void RecreateControl(object sender, ControlEventArgs e)
{
PropertyInfo pi = typeof(Control).GetProperty("CreateThreadId",
BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("Form created by TID: {0}", pi.GetValue(this, new
object[0]));
pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl created by TID: {0}", pi.GetValue(e.Control, new
object[0]));
MethodInfo mi = e.Control.GetType().GetMethod("RecreateHandle",
BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(e.Control, new object[0]);
pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl re-created by TID: {0}", pi.GetValue(e.Control,
new object[0]));
this.Controls.Add(e.Control);
}
}
class ControlCreator
{
public event ControlEventHandler ControlCreated;
public void DoCreation(object state)
{
Button btn = new Button();
Application.DoEvents();
ControlCreated(this, new ControlEventArgs(btn));
}
}
}
--
Stoitcho Goutsev (100) [C# MVP]
Steve said:
I'm trying to dynamically create a set controls in a Threadpool and then
add them to thte Controls collection of a Form (the main thread). The
controls seem to be created in the thread an get passed back successfully
via an derived EventArgs class, but I get "invalid handle" error messages
when trying to work them back on the main thread. I've seen a number of
articles about a control being "owned" by the thread that created it, so, my
question is, is what I'm trying to do even possible? Is there some other
way I should go about it?