Is it possible to display the WinForms "font picker" dialog non-modally? Or is there another font pi开发者_如何学运维cker 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.
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
...
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.
精彩评论