Friday, March 26, 2010

Owning a PrintDialog in WPF

The default settings when you create a new window in WPF make no sense at all. For starters, they all appear on the taskbar. Secondly, the ShowDialog method doesn't have an overload that takes the parent window as a parameter, nor does it default to Application.Current.MainWindow, so you end up with a ton of windows floating around that can pop behind each other, etc. However, it is typically easy to set the Owner of a window, set ShowInTaskbar to false, and then call ShowDialog. However, I have recently come across an issue in our software that at first glance was impossible to resolve.

We have our main application window, which shows image and video content. It's possible to show an image as full screen, and to do this, we create a new window, with no frame, make the main window it's owner, and show it to fill the screen area. In this window we have a print tool, which you can select, and then click on the form to print the image you can see. My issue was, when you click on the print dialog itself to select 'OK' or to change the printer, instead of it responding to your click, it disappears, and when you close the window, you find that it's floating above the main program window underneath. We did have one person work on this project who sometimes forgot to set the properties I mention above, and because the app fills the screen, we'd just not notice the item in the taskbar, nor would we notice that if someone were to click outside the dialog, it would disappear. But, there's something odd inside this control for it to disappear when I click *on* it. Nevertheless, I expected to set it's Owner and be done.

The PrintDialog control has no Owner property, no Parent property, nothing you can use to control the context in which it is shown. I simply had no options to tell it to stay visible when I click on it over a form that's not the main form of my app. How is this possible ? What complete lack of logical thought led to this situation ? I don't know.

The trick is that Application.Current.MainWindow is settable. This is the code I use to get around this bug in WPF - I set the MainWindow to be the window I am working in, and then set it back afterwards. I could perhaps put it in a try/catch, but I've never had this code throw any sort of error, so I've not done that. If it did throw an error, it would blow up the app, not change the main window and keep working, which would be my main possible cause for concern.

PrintDialog dlg = new PrintDialog();

// All of this is to get around the fact that WPF does not let us set a parent for the PrintDialog
Window currentMainWindow = Application.Current.MainWindow;

Application.Current.MainWindow = this;

if ((bool)dlg.ShowDialog().GetValueOrDefault())
Application.Current.MainWindow = currentMainWindow; // do it early enough if the 'if' is entered
PrintBitmapInternal(bitmap, dlg);

Application.Current.MainWindow = currentMainWindow; // in case the 'if' is not entered

One other thing, why does ShowDialog return a bool? ? Were they just so proud of themselves for coming up with nullable types ? This means I need to cast to bool in order to do a one line check, or I need to store the value to do two checks on it. Does it return null if the dialog is not shown ? Wouldn't an enum result in more easily read code ? Right now it's not clear to me when it could return null, except that I know it never has, in the years I have worked with WPF.


  1. CG,

    You don't have to cast to bool to do a one line check, FYI. Just tried this in VS2010, works fine:

    bool? b = true;
    if (b.GetValueOrDefault())

  2. So, I can do if (dlg.ShowDialog().GetValueOrDefault()) ? Not sure that I love it, but thanks for the info.

  3. I've been looking for a solution to a problem I'm having and ran across this page. Not sure if you would know what is going on. But I am using PrintDialog after clicking a print button to print a barcode I've generated in a BitmapImage. The image prints, but when the dialog goes away, the window it was run from is corrupted. You can resize it and the display repaints, but I can't seem to get it to repaint from the few methods I've tried. Any suggestions?