The key feature of the behavior of FontDialog
here is not the parent (owner) relationship, but fact that you can only use it by calling ShowDialog
, and there is no apparent way to do that without blocking the GUI thread.
Fortunately, there is a way around that problem. I used a BackgroundWorker
to shunt the call to ShowDialog
onto a worker thread, allowing the GUI thread to proceed. The async FontDialog
wrapper class looks like this:
public class FontDialogAsync
{
public event EventHandler<NewFontChosenEventArgs> NewFontChosen;
private readonly IWin32Window parentHandle;
private readonly BackgroundWorker fontDialogWorker = new BackgroundWorker();
private class WindowWrapper : IWin32Window
{
public WindowWrapper(IntPtr hWnd)
{
Handle = hWnd;
}
public IntPtr Handle { get; private set; }
}
public FontDialogAsync(IWin32Window parent)
{
parentHandle = new WindowWrapper(parent.Handle);
fontDialogWorker.DoWork += FontDialogWorkerDoWork;
fontDialogWorker.RunWorkerCompleted += FontDialogWorkerRunWorkerCompleted;
}
private class FontDialogAsyncArgs
{
public readonly IWin32Window ParentForm;
public readonly Font InitialFont;
public FontDialogAsyncArgs(IWin32Window parent, Font initFont)
{
ParentForm = parent;
InitialFont = initFont;
}
}
public void Show(Font font)
{
if (!fontDialogWorker.IsBusy) fontDialogWorker.RunWorkerAsync(new FontDialogAsyncArgs(parentHandle, font));
}
private static void FontDialogWorkerDoWork(object sender, DoWorkEventArgs e)
{
try
{
var args = (FontDialogAsyncArgs)e.Argument;
var fontDialog = new FontDialog { Font = args.InitialFont };
var result = fontDialog.ShowDialog(args.ParentForm);
e.Result = (result == DialogResult.Cancel) ? null : fontDialog.Font;
}
catch (Exception ex)
{
UtilitiesAndConstants.ReportExceptionToCommonLog(ex);
}
}
private void FontDialogWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e1)
{
if (e1.Result == null) return;
if (NewFontChosen != null) NewFontChosen(this, new NewFontChosenEventArgs((Font)e1.Result));
}
}
[Note that you need to hide the parent window handle inside the WindowWrapper
instance to keep the runtime from raising a cross-thread exception.]
The EventArgs
class looks like this:
public class NewFontChosenEventArgs : EventArgs
{
public readonly Font FontChosen;
public NewFontChosenEventArgs(Font newFont)
{
FontChosen = newFont;
}
}
...and you use it like this:
private FontDialogAsync nonBlockingFontDialog;
public void SetFont()
{
if (nonBlockingFontDialog == null)
{
nonBlockingFontDialog = new FontDialogAsync(ParentForm);
nonBlockingFontDialog.NewFontChosen += NonBlockingFontDialogNewFontChosen;
}
nonBlockingFontDialog.Show(Font);
}
Where ParentForm
is the Windows.Form
instance to which you want to tie the dialog. The resulting dialog will be modal with respect to the parent (i.e. you will not be able to do anything with the parent without first dismissing the dialog), but the rest of the appliation's UI will work normally.