Try this:
void btnClick(object sender, EventArgs e)
{
var t = new Thread(doStuff);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
void doStuff()
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.ShowDialog();
}
The SaveFileDialog is run in a separate thread (it has to have a single threaded apartment), and it doesn't block the UI thread of your application. However, be very careful about everything you do, it can be very unstable.
The basic issue is that your application is running a Windows Messaging loop, which is basically a loop that iterates over Windows Messages coming from the OS (and other applications). If the loop is stuck for some reason, the application becomes unresponsive (when you click the mouse, the OS sends a WM_MOUSEDOWN and many more methods to your message queue, which has to be depopulated by the message loop to do anything). Being in the ShowDialog
method is exactly one of the ways the loop gets stuck - your form can no longer process any messages, because it never gets a chance to.
Now, what Invoke
does is, that it adds the method you want to call to another queue on the target form. The next chance the form gets during the windows messaging loop, it executes all the items in the invoke queue - again, the messaging loop gets stuck.
Now, how does the dialog receive windows messages? Easy, in practice, it creates its own WM loop. This is very little but another while
loop, that only terminates as the modal dialog closes - blocking the messaging loop of the parent form (or rather, the application).
The problem with the code above is that it could steal the messaging loop from our parent window. The solution is to explicitly create a new window with a new messaging loop, by explicitly passing the owner to ShowDialog
:
void doStuff()
{
NativeWindow nw = null;
try
{
nw = new NativeWindow();
nw.CreateHandle(new CreateParams());
SaveFileDialog sfd = new SaveFileDialog();
sfd.ShowDialog(nw);
}
finally
{
if (nw != null)
nw.DestroyHandle();
}
}