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.

Tuesday, March 16, 2010

Refactoring does not work

So, I wrote a method, with every intention of refactoring it to make it reusable at the end. Now I needed to change some stuff by hand because it passes the top level object in, if I have an object heirarchy. I don't mind that. However, because I had to change several values, it added a number of out parameters. The only issue with this is, all my values are only optionally modified. Therefore, the autogenerated code does not compile. The solution ? Just changing 'out' to 'ref'. Given that 'ref' code will compile where 'out' will not, why not just use 'ref' ?

Friday, March 12, 2010

Windows Explorer Integration

I love how when I go to save or open a file, I can right click and I've got access to all sorts of other stuff. But, try this. Open a Word file. Let's call it letter.doc. Now, click on 'Save As'. The dialog opens. Click on letter.doc and press F2. Rename it to Letter Old.Doc. Now, the text will still say Letter.Doc, but when you click the 'Save' button, it says 'The document Letter Old.Doc already exists'. So, if I click save, does the file get overwritten, or not ? One way or the other, it's a bug.

Thursday, March 11, 2010

Combo boxes in WPF

So, I want to give a user a list of PDFs that they can email, in a combo box to make it fit with some existing UI. The PDF data is in a class, so I bind the checkboxes in my template to a property, and it works in both directions. It's working well, that's a great feature in WPF. Except, when I click outside the text of my item, the item is selected, which is fine, but then I added code to set my property so it becomes checked. I've done this, but, no matter what I do, the checked item does not get checked in the actual list, so I open my combobox, and I have an item that is in my list, unchecked, but it's checked in the textbox. If I open the list, and check/uncheck the item in the list, the copy in the textbox stays in synch. It's just when I use code to check it, the UI does not catch up. Seems to me like a case where the two way binding just plain does not work - the same object is rendered in a combobox in two places, and in one place it does not render properly.

Did I mention that intellisense and the list of controls to add to a WPF form all do not work for me in visual studio, I suspect because of the bogus error message I get for every form in my project ?

The solution to this is the following code:

ComboBoxItem itm = PDFList.ItemContainerGenerator.ContainerFromItem(i) as ComboBoxItem;
ContentPresenter myContentPresenter = FindVisualChild(itm);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
CheckBox cb = myDataTemplate.FindName("chkSelect", myContentPresenter) as CheckBox;

if (cb != null)
cb.IsChecked = true;

Simple, right ? But you also need to add this method. You need to add a METHOD to give yourself the reusable ability to find a control inside a template in only 5 lines of code. Insane....

Here's that method

private childItem FindVisualChild(DependencyObject obj) where childItem : DependencyObject
{

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is childItem) return (childItem)child; else { childItem childOfChild = FindVisualChild(child);

if (childOfChild != null)

return childOfChild;

}

}

return null;

}

Wednesday, March 10, 2010

Splitters and anchors

I am not sure if this issue comes back to the fact that I am using a splitter, because I can tell you that in the first instance, using a splitter in winforms is so counter-intuitive and convoluted that I had to look up an article online, and it gave me some code to use on the basis that they also felt that trying to add a splitter using the design view is an exercise in frustration and futility. The concept is simple, why is it so hard ?

So, I have my splitter. On one side is a listbox, on the other is a panel. On that panel I have two pictureboxes, which are supposed to be evenly spaced across the panel. No matter what I try with the anchor properties, I cannot get them to resize themselves so they keep their layout. If I make the first one anchor to the right and the left, it will fill the entire panel left to right, with the second one appearing on top of it. The second one does not resize at all, when I apply the same properties.

At least, I think that is true. If you click the property sheet for the Anchor property, a little box pops up. If you click what you want on that box and then hit build, when the app closes, you'll see an 'invalid property' message box and your changes will be lost, you need to explicity close that popup first. I believe I've done that and tested every combination I can think of, twice. I can't believe I'm writing a winforms app and resizing controls by hand.

**update

It seems that if it's in a splitter, the panel never fires a size changed event. I can catch the event on the main form, and then manually resize my controls, but I can't get the framework to position them for me. For anyone reading this, ignore the SizeChanged and ClientSizedChanged events ( and also the SplitterMoving event if you draw any sort of image or video inside your splitter ), the SplitterMoved event is the way to resize your form after the splitter has been moved.

Wednesday, March 3, 2010

More Weven hell

I have one other user ( my boss ) reporting that our program plays 15 out of 19 video thumbnails and plays them out of order. Now, this is different to the user who says it fails, or it works but with 19 out of order. The issue I have is, my code reads the files from a folder ( one line of code ), sorts them ( one line of code ), then assigns them as a datasource ( one line of code ). For this to fail, the list needs to lose it's sort order during the binding process, and the binding process needs to silently fail in mid stream. Of course, I cannot reproduce this. I am about to stress test my app by running an app at the same time that eats processor and HDD. So I wrote this app and copied it across on a memory stick, in a zip. I tried to use the unzip wizard, and I put my app inside program files, b.c I know there's all sorts of permissions issues I might hit otherwise.

The wizard tells me that it cannot create the folder and asks 'might it exist already ?'. Meaning, if it did, it would be an error, right ? So I try to create the folder myself to find out the error. Except after I gave admin permission it worked, and then the unzip wizard worked fine.

So, let's recap. Weven gave me an error and falsely told me what could have caused it. I created the exact situation that Weven told me would stop it from working, and then it worked. Anyone think they have this whole secure OS/permissions required thing worked out yet ?