Super size me

Having fixed all the outstanding “Oh my [deity/dictator of choice], we have to fix this now or people will be really rather unhappy” issues (as they’re referred to under our bug tracking system. Or is that my imagination? You know, I think it probably is) against DTS Package Compare 2.0, it was time to turn attention to some more of those niggling little issues which don’t really make much of a difference to the masses, but you know are really going to irritate somebody at some point.

Remember, Remember…

Applications should remember the state they were in when you last ran them, goes an unwritten tenet. The old version of DTS Compare 1.5 didn’t do this (very naughty of it), and so we’d duly carried this bug over to the new DTS Package Compare 2.0 to make sure we fixed it. For some time, including our private beta, the application had gotten away with simply maximising itself to the primary monitor on the user’s system, and this was generally splendid, if extraordinarily cheap. But since lots of other applications do recall where they were when you were last using them, we shouldn’t really be the exception.

Now I don’t like working in this little simple looking area of user interface programming, as it rarely gets done properly. Take well known spreadsheet and word processing applications, for example: I’ve on several occasions had to help out colleagues or family members who started the application only to have it appear on the Windows taskbar but not exhibit any notable window in any way. Now those of us In The Know can often fix these problems by activating the (rather non-visible) window, pressing Alt-Space then M (for the system menu’s “Move” option on that window, which you could of course pick with the mouse…if you could see the window in question), and pressing cursor keys for a while until the window appears. In these cases the application just got confused (or you’ve dropped your screen resolution rather substantially since last running the app) and positioned its window offscreen. So moving it back on screen tends to help. Occasionally of course some well known applications get themselves into such a pickle that you have to start mumbling mantras like “Aich kee current user, Software, Vendorname, ApplicationName, ah let’s just delete everything” in order to fix the problem. And should you invoke such voodoo at your workstation, expect your computer to notice if you have a microphone attached, and a booming voice to warn you that messing with the registry can cause infertility, global warming, and seriously damage the economy of small countries. Well, not really. But in any case it’s not a fun way to spend a morning when you haven’t had your second coffee.

I also have to mention, as an aside, a well known brand of developer tool which can occasionally encounter fatal problems loading projects. It also has a setting to load the last project when you open the tool. Windows of course now has a setting whereby if it encounters a fatal problem it will automatically restart the troubled application. These three work together in an amusing fashion: sepuku.mpg on a loop.

The Registry

The point being that simply dumping out window positions as reported by Windows and stuffing such dumps into the registry is not generally clever. This situation is not helped if you’re using third party user interface libraries, which helpfully provide mechanisms to do exactly this, thus saving you the bother of considering whether it’s a good idea since you can simply call them, rebuild, and close any bugs you might have in this area, before cheerfully strolling down to the boozer.

There are two principal reasons why this approach is, IMHO, not a wise plan. The first is that stuffing user-related data into the registry is now frowned upon in many circles. Let’s be honest, it wasn’t a great place to put things in anyway. The registry was a wonderful new way of crossing interstellar distances without all that tedious messing about with INI files, but INI files were handy as you could usually just delete them without breaking applications. Deleting things from the registry is not an activity for the faint hearted, at least before they’ve had a few drinks, and they’re likely to regret it in the morning anyway. (At least one headache will result. For some reason I have a fleeting image of a PC booting, looking in its registry and discovering an unexpected traffic cone planted in HKEY_LOCAL_MACHINE, and then promptly BSOD’ing with an 0x80040200 NoIdeaWhatHappenedLastNightError. But then I may just need a holiday.) INI files were accessible and user-visible; the registry is neither nor. Luckily, Windows nowadays provides applications with an Application Data folder for each user (and a choice whether this should accompany roaming profiles, or stay doggedly attached to the current workstation) in which they can place their own settings files. And, hopefully, react well if a user comes along and gleefully trashes them all.

Lies, Damned Lies, and Window Positions

The second reason, which was originally by way of being the subject of this article, is that Windows lies about window positions. This is hard for non-programmers to see (but probably easier to believe).

If you minimise a window, for instance, and then ask Windows where it is (the Win32 API GetWindowRect), it will tell you that it’s top left corner is at coordinates (-32000, -32000). Skeptics, realising that the top left of the screen is (0,0), and the bottom right usually say (1024,768) or similar, may doubt the verisimilitude of this report. And with good reason. What Windows means is that the Window is minimised, and so it isn’t going to tell you where the Window actually is. That’s no longer any of your concern. If you wanted to know where the Window was when it wasn’t minimised, you should have asked before the user went and minimised it. Silly application programmer.

And indeed if you maximise a window, and then ask Windows where it is, it will tell you that its top left corner is at coordinates are of the order of (0,-4).  Skeptics might be willing to let that pass, as they might deduce that Windows has, in order to make the window take up the whole screen, moved the top of it slightly off the screen.

Now one could ignore these little foibles. But of course, just because a minimised window has coordinates (-32000,-32000) doesn’t mean that setting a window’s coordinates to (-32000,-32000) will make it minimised. That would be silly. No, windows have a fairly sensible state that can be detected, amongst other things, via the Win32 API GetWindowPlacement. This window state (also referred to as its ShowWindow flags or Show Command) is either SW_SHOW, SW_HIDE, SW_MINIMIZE, SW_MAXIMIZE, or other variations on the theme. This, not the window rectangle, is your true indicator of whether a window is minimised, maximised or regular. And in order to super size (or sub size) your window, you’ll have to set this window state directly.

So. One could save this state with the application; save the window coordinates as well; and restore them both when the application is next started. Correct? Er, no. Because as previously mentioned, the coordinates returned when a window is minimsed or maximised are not the coordinates you should be saving. Those are the coordinates imposed on the window due to its minimised/maximised state, which do not reflect the actual window position prior to this occurring. A window might be 100 by 100 pixels in the bottom right of the screen, for example, but be maximised to take up the full screen. If you save its position after it’s been maximised, and then when the app is run set the maximised state and position, then when the user un-maximises (restores) the window it will not return to its diminutive location in the bottom right, but stay taking up the full screen.

So in terms of remembering window position to save on application exit, one has to watch when window sizes and positions change. One then has to record the true position of the window before the user minimises or maximises it, and ignore the window position (not record it) if that window is currently minimised or maximised. Then, on exit, you save the last recorded position, along with whether the window is minimised or maximised. On startup, you restore this information, and Bob is a close relative.

In C#, you do this via the Form.WindowState property (which accepts Maximized, Minimized or Normal) and the Form.Location and Form.Size properties. This maps to calling the Win32 APIs GetWindowRect() (for coordinates) and GetWindowPlacement() (for window state).

Screening for Problems

Of course it’s not as easy as that. This is software. Just because you’re trying to do something so trivial that it should be possible whilst drinking coffee and eating doughnuts, doesn’t mean that you’ve got a hope of getting it done swiftly.

The above works pretty well, but then what if the user has multiple monitors? Under .NET, saving the position works fine, but restoring it doesn’t. Specifically, if the window is maximised on the second monitor when you quit the application, when you restore it it will be maximised on the first monitor. However, if you then restore (de-maximise) it, it will return to its non-maximised position on the second monitor. Clear? Jolly good.

The reason is, it turns out, that .NET sets the window rectangle through the Win32 SetWindowPos API(). This should be fine, as it accepts all the relevant parameters, and in most cases works well. Sadly, this API doesn’t deal well with muliple monitors: the above bizarrety occurs.

Of course there is a solution: the GetWindowPlacement() API, previously referred to, can retrieve window coordinates as well as window state. And there is a corresponding SetWindowPlacement() API which accepts both pieces of information, and behaves correctly. (Indeed, coordinates returned from GetWindowPlacement() can only be provided to SetWindowPlacement(), and not eg. SetWindowPos(): they’re special in some irritating fashion.) The only minor downside (apart from having spent valuable beer time figuring this out) is that from .NET one has to invoke native code, and DllImport GetWindowPlacement and SetWindowPlacement from user32.dll. No big deal. DTS Package Compare has to use a whole slew of native functions for cases such as this, so a couple more is not, as it were, a biggy.

So that’s another completed tour through the mundane and slightly vexing. At some stage I may blog about something that was actually difficult to solve…but there seems to be so much more milage in life’s little irritations.