views:

96

answers:

2

Is it possible to display the WinForms "font picker" dialog non-modally? Or is there another font picker other than the standard one that can be used non-modally?

Our application has many windows, and users who frequently need to interrupt what they are doing and switch to another window to look at something. If they use a taskbar button to switch windows, this tends to lead to "buried dialog" scenarios with modal dialogs, where the UI is unresponsive, but it isn't immediately apparent why, because the modal dialog that has captured the focus is behind another window.

+1  A: 

I don't know the exact code but you need to replace the Owner with the desktop. You can get the handle to the desktop using the GetDesktopWindow API method as described here:

http://www.pinvoke.net/default.aspx/user32.getdesktopwindow

One way to set the Owner would be to create your own custom class that inherits from the FontDialog and then set the owner via the protected CommonDialog.RunDialog method but might be other ways as well.

Edit: Actually, might work to just send in the desktop handle as a parameter to the ShowDialog...

ho1
A: 

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.

McKenzieG1