Spawn a new thread to open a new window and close it from a different thread
Right now I have C# code to spawn a new window in a different thread, this works, but as soon as the new spawned window opens, it closes and the thread ends. How would I make it so the new spawned window can be closed from the first thread?
Here is a "tree" of how the spawning currently works:
Main thread
--Uses a function in the main thread to start another function in a separate thread to open w window, causing the window to use that thread.
Basically I just want the two windows to each have their own thread. And be able to control the spawned secondary window from the first window thread.
I bet what you're doing is something like this:
new Thread(() => new TestForm().Show()).Start();
because this makes the window disappear immediately, like you describe.
Try this instead:
new Thread(() => new TestForm().ShowDialog()).Start();
ShowDialog spins its own message pump, and only returns when the window is closed.
This is just a quick example. It's a little more robust than the first one I wrote. It eliminates the existing race condition by using p/invoke.
class MainUIThreadForm : Form
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainUIThreadForm());
}
private IntPtr secondThreadFormHandle;
public MainUIThreadForm()
{
Text = "First UI";
Button button;
Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
button.Click += (s, e) =>
{
if (secondThreadFormHandle == IntPtr.Zero)
{
Form form = new Form
{
Text = "Second UI",
Location = new Point(Right, Top),
StartPosition = FormStartPosition.Manual,
};
form.HandleCreated += SecondFormHandleCreated;
form.HandleDestroyed += SecondFormHandleDestroyed;
form.RunInNewThread(false);
}
};
Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false });
button.Click += (s, e) =>
{
if (secondThreadFormHandle != IntPtr.Zero)
PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
};
}
void EnableStopButton(bool enabled)
{
if (InvokeRequired)
BeginInvoke((Action)(() => EnableStopButton(enabled)));
else
{
Control stopButton = Controls["Stop"];
if (stopButton != null)
stopButton.Enabled = enabled;
}
}
void SecondFormHandleCreated(object sender, EventArgs e)
{
Control second = sender as Control;
secondThreadFormHandle = second.Handle;
second.HandleCreated -= SecondFormHandleCreated;
EnableStopButton(true);
}
void SecondFormHandleDestroyed(object sender, EventArgs e)
{
Control second = sender as Control;
secondThreadFormHandle = IntPtr.Zero;
second.HandleDestroyed -= SecondFormHandleDestroyed;
EnableStopButton(false);
}
const int WM_CLOSE = 0x0010;
[DllImport("User32.dll")]
extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam);
}
internal static class FormExtensions
{
private static void ApplicationRunProc(object state)
{
Application.Run(state as Form);
}
public static void RunInNewThread(this Form form, bool isBackground)
{
if (form == null)
throw new ArgumentNullException("form");
if (form.IsHandleCreated)
throw new InvalidOperationException("Form is already running.");
Thread thread = new Thread(ApplicationRunProc);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = isBackground;
thread.Start(form);
}
}
You can do it like this:
In Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;
namespace TwoWindows
{
static class Program
{
public static Form1 form1;
public static Form2 form2;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
form1 = new Form1();
form2 = new Form2();
form1.Form2Property = form2;
form2.Form1Property = form1;
form1.Show();
form2.Show();
Application.Run();
}
}
}
In Form1.cs:
namespace TwoWindows
{
public partial class Form1 : Form
{
public Form2 Form2Property { get; set; }
public Form1()
{
InitializeComponent();
}
protected override void OnClosed(EventArgs e)
{
if (Form2Property.IsDisposed)
Application.Exit();
}
}
}
And Form2.cs:
namespace TwoWindows
{
public partial class Form2 : Form
{
public Form1 Form1Property { get; set; }
public Form2()
{
InitializeComponent();
}
protected override void OnClosed(EventArgs e)
{
if (Form1Property.IsDisposed)
Application.Exit();
}
}
}
This way you can get two forms on the same thread and use one to control the other one. If you need to use threads I would suggest to use dedicated threads that are part of the classes an not spawn in a method that can be called more than once. Then use ManualResetEvent or AutoResetEvent to control the thread processing. I really like the approach of using something like this, because is safe and doesn't spend much resources initialising threads.
public class MyClassOrForm
{
Thread myProcessingThread;
public AutoResetEvent startProcessing = new AutoResetEvent(false);
public AutoResetEvent processingFinished = new AutoResetEvent(false);
public AutoResetEvent killProcessingThread = new AutoResetEvent(false);
public MyClassOrForm()
{
myProcessingThread = new Thread(MyProcess);
}
private void MyProcess()
{
while (true)
{
if (startProcessing.WaitOne())
{
// Do processing here
processingFinished.Set();
}
if (killProcessingThread.WaitOne(0))
return;
}
}
}
Then, once you have set the data to be processed, call form another class or method
MyClassOrMethodInstance.startProcessing.Set();
And if you need to wait for that processing to finish then insert:
MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms);
This is equivalent to a Thread.Join() call, only that you wont have to allocate another thread every time with the risks that threads entails if they depend on local data or unfinished child threads.