UPDATE: Just to summarize what my question has boiled down to:
I was hoping that constructing .NET forms and controls did NOT create any window handles -- hoping tha
Creating a control in a background thread is possible but only on an STA thread.
I created an extension method in order to use this with the async/await pattern
private async void treeview1_AfterSelect(object sender, TreeViewEventArgs e)
{
var control = await CreateControlAsync(e.Node);
if (e.Node.Equals(treeview1.SelectedNode)
{
panel1.Controls.Clear();
panel1.Controls.Add(control);
}
else
{
control.Dispose();
}
}
private async Control CreateControlAsync(TreeNode node)
{
return await Task.Factory.StartNew(() => CreateControl(node), ApartmentState.STA);
}
private Control CreateControl(TreeNode node)
{
// return some control which takes some time to create
}
This is the extension method. Task does not allow to set the apartment so I use a thread internally.
public static Task<T> StartNew<T>(this TaskFactory t, Func<T> func, ApartmentState state)
{
var tcs = new TaskCompletionSource<T>();
var thread = new Thread(() =>
{
try
{
tcs.SetResult(func());
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.IsBackground = true;
thread.SetApartmentState(state);
thread.Start();
return tcs.Task;
}
I think your understanding is a little off. Controls must be touched from the thread that created them, not the main UI thread. You could have numerous UI threads in a application, each with its own set of controls. Thus creating a control on a different thread will not allow you to work with it from the main thread without marshalling all of the calls over using Invoke or BeginInvoke.
EDIT Some references for multiple UI threads:
MSDN on Message Loops MSDN social discussion Multiple threads in WPF
The answer is no.
If you create a window handle on any thread other than the GUI thread you can never show it.
Edit: It is completely possible to create Forms and controls and display them in a thread other than the main GUI thread. Of course if you do this you can only access the multi threaded GUI from the thread that created it, but it is possible. – Ashley Henderson
You need to perform any heavy lifting on a bg thread and then load the data into you GUI widget
In general, properties of the form need to be accessed from the same thread running the message loop. That means, in order to construct the form on another thread, you would need to marshal any calls to actually set properties using BeginInvoke. This is true for property sets from the constructor, too, if they end up generating a message that needs to be processed (as is happening to you now).
Even if you get that to work, what does it buy you? It will be a bit slower, not faster, overall.
Perhaps just show a splash screen while this form is loading?
Alternatively, review why your form takes so long to construct in the first place. It's not common for this to take seconds.
While it is not possible to create a form on one thread, and display it using another thread, it is certainly possible to create a form in a non main GUI thread. The current accepted answer seems to say this is not possible.
Windows Forms enforces the Single Threaded Apartment model. In summary this means that there can only be one Window message loop per thread and vice versa. Also, if for example threadA wants to interact with the message loop of threadB, it must marshal the call through mechanisms such as BeginInvoke.
However, if you create a new thread and provide it with it's own message loop, that thread will happily process events independently until it is told to end the message loop.
So to demonstrate, below is Windows Forms code for creating and displaying a form on a non GUI thread:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart ts = new ThreadStart(OpenForm);
Thread t = new Thread(ts);
t.IsBackground=false;
t.Start();
}
private void OpenForm()
{
Form2 f2 = new Form2();
f2.ShowDialog();
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
label1.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;
}
}
The OpenForm method runs in a new thread and creates an instance of Form2.
Form2 is actually given it's own separate message loop by calling ShowDialog(). If you were to call Show() instead, no message loop would be provided and Form2 would close immediately.
Also, if you try accessing Form1 within OpenForm() (such as using 'this') you will receive a runtime error as you are trying to do cross-thread UI access.
The t.IsBackground=false
sets the thread as a foreground thread. We need a foreground thread because background threads are killed immediately when the main form is closed without first calling FormClosing or FormClosed events.
Apart from these points, Form2 can now be used just like any other form. You'll notice that Form1 is still happily running as usual with it's own message lopp. This means you can click on the button and create multiple instances of Form2, each with their own separate message loop and thread.
You do need to be careful about cross Form access which is now actually cross-thread. You also need to ensure that you handle closing of the main form to ensure any non main thread forms are closed correctly.
I believe it is possible to add the components created on the non-UI thread to the main UI, I've done it.
So there are 2 threads, 'NewCompThread' and 'MainThread'.
You spin off NewCompThread and it creates components for you - all ready to be displayed on the MainUI (created on MainThread).
But ... you WILL get an exception if you try something like this on NewCompThread:
ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;
But you can add this:
if (ComponentCreatedOnMainThread.InvokeRequired) {
ComponentCreatedOnMainThread.Invoke(appropriate delegate...);
} else {
ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;
}
And it will work. I've done it.
The strange thing (to me) is that then the ComponentCreatedOnNewCompTHread 'thinks' it was created on the MainThread.
If you do the following from the NewCompThread:
ComponentCreatedOnNewCompTHread.InvokeRequired
it will return TRUE, and you'll need to create a delegate and use Invoke to get back to the MainThread.