Getting UI right requires obsessive attention to detail, particularly if you're building on a platform that doesn't provide substantial help for common UI patterns. I've recently been designing and implementing a minor feature in a Windows client application, and can't believe how long it's taking to get this feature right. The feature? Remembering the position of an application window across sessions.
Users like applications that remember the state and position of windows across application sessions. They can pick a window arrangement they like, close the app whenever they want, and next time have everything just the way they like it. In the case of the Microsoft Windows APIs (both Win32 and .NET), the platform doesn't provide any built-in support for remembering window position. The platform documentation blithely tells you to do this work yourself, and sort of implies it won't be very hard. Hah.
Here's what the learning curve looks like during a sequence of design/implement/test iterations:
- Designer: This feature is easy to deliver: when the user closes the window, we'll save the window's current state (on Windows, this is either maximized, minimized, or normal) and the window's current position.
User: Huh. This kind of works, but if I close the application while the window is minimized (sometimes this happens if I log off while I've got a bunch of apps minimized), the next time I start the application, the app comes up minimized. That's dumb.
- Designer: Okay, we'll only save the window state and position if the window's maximized or normal.
User: That's better, but sometimes it still doesn't do the right thing. If I position the window, then minimize it, then close it, the app doesn't remember my window position.
- Designer: That's easy to fix: we'll always save the window position, but we'll only save the window state if the window is normal or maximized.
User: This doesn't work.
- Designer: Ugh. It appears that, if a window is minimized, it's "window position" data is essentially junk, and not the window's last meaningful position like we'd expected. To fix this, whenever the user positions the window in its normal state, we'll remember that position as the user's preferred position for the window. Then, when the user closes the window, we save this preferred position (not the current position, which could be junk). Additionally, if the window is maximized or normal, we save the window state too.
User: Not bad! This seems to work most of the time. How about this, though: I position the window where I want it, then maximize it, then minimize it, then close it. The next time I open the app, the window is in the normal state—not the maximized state the I last saw it in. That's odd.
- Designer: You are one tough customer. Fine: whenever the user puts the window into the normal or maximized state, we'll remember this as the preferred state. Then, when the user closes the window, we save this preferred state (regardless of what state the window is currently in). Satisifed?
User: Not by a long shot. You see, I have a laptop. I've also got this external monitor on my desk—a monitor whose dimensions are different than those of the laptop's built-in screen. If I close the application when the laptop is docked, then reopen the application when the laptop is undocked, the application tries to come up in a position that no longer makes sense. Sometimes I can't even get to the window with the mouse because the window comes up off-screen.
- Designer: Crap. Okay, whenever we're recording the user's preferred position for the window, we'll also save the current dimensions of the monitor itself. Then, if the app is opening and the monitor's dimensions have changed, we'll do our best to interpolate a meaningful position for the window in the new monitor dimensions.
User: This helps a bit, but it's not perfect. Each time I move between the docked and undocked state, the window position shifts a bit. I really want the window to be one size when I'm undocked, and a different size when I'm docked.
- Designer: Urg. To do this right, we'll have to save a window's position in a list that stores a monitor's size and the user's preferred size for the window whenever the window is opened on a monitor of that size. Over time, this list will grow to encompass all monitor sizes the user likes to use, and their preferred window size for each of these monitor sizes. Satisfied now?
User: Mostly. Did I mention I recently bought a high DPI monitor? I was thinking that your window size interpolation routine should take into account physical screen inches instead of assuming a fixed pixel size...
- Designer: Please, please go away.
And so it goes. Since the OS doesn't provide any help, every ISV rolls their own solution for this, with the unsurprising result that they all stumble in different ways at some point along this path. Most seem get to step three or so. (Internet Explorer 7.x, for example, has the bug described by the user after step four.) If someone were willing to bake support for saving window state into Windows, the work would be leveraged across enough apps that it'd be worth the time to implement a deep solution. Even then, there'd still be room for improvement.
There is no magical point where perfection is reached. Good design is a fractally hard problem: the more closely you focus on any given feature, the more rough edges you find to polish. The only sane approach is to iterate in an area until you've produced a solid user experience for a substantial portion of the cases you care about, then move on.
Heh, I never thought of the laptop/multimonitor one... damn, now I have to go fix my app :P
Posted by: Mike Weller | October 04, 2005 at 10:17 AM
I love it when I play a game at 640x480 and then after I quit, all of my windows are resized, my IM app is moved into the center of the screen, etc, etc. Add that to the list of things you have to keep track of...
Posted by: Ed | October 04, 2005 at 03:43 PM
Interesting...
For IE6, whenever you max it, quit and comeback, it will become a "full screen" size and not a max state. As in the app occupy the whole screen minus the taskbar but still can be drag around unlike the max state.
For me, I wasn't that detail in getting to step 6, my app just detect whether any part of its window is out of the current monitor resolution, if yes, the app will be centered to the current resolution. I skip step 7 and go direct to step 8. Ha!!!
Posted by: ChiaFong Choo | October 05, 2005 at 03:07 AM
Why not always maximize? This would seem to be the 90% solution since most people do 1 thing at a time, it maximizes visible information for the primary task at hand, and the taskbar makes it easy to switch to another window when you want to do something else. There are times I have multiple windows visible, like copying from Excel to Word, or using the Calculator, but these are not the common case. I don't use IM but I could see why this type of app would be good to run in a small window somewhere while you're doing something else.
Posted by: Justin Magaram | October 05, 2005 at 08:03 PM
you forgot one; i loose windows all the time due to tablet orientation issues. :) add docking into the mix, and its game over.
Most frequently, the outlook logon screen ends up lost in some non existant, off screen rotated out region of my desktop... killing outlook (the hard way) and possibly logging out of windows are my only options.
Posted by: chris hollander | October 06, 2005 at 01:31 PM
I've finally gotten around to posting my answer to this: a C# class that, as far as I can tell, handles everything but the high-DPI monitor issue. Check it out at http://brh.numbera.com/components/winforms/windowstatemanager/ .
Posted by: Ben Hollis | November 07, 2005 at 08:35 PM
Why not always maximize?
I used to think that until I got a 22" monitor. Run at 1600x1200 on a 22" monitor and you quickly find that you'd rather change apps by clicking in their windows. Also MDI=bad.
Posted by: Richard Gadsden | November 29, 2005 at 08:32 AM
You forgot step 2a:
Well, I launched the app twice in a row but it looks like the app only opened once. I discovered a week later that the second window opened directly on top of the first one. Can you make them shift a little?
Posted by: Ben | November 29, 2005 at 11:37 AM
Heh - I always thought window positioning was a "personal obsession", now I know I'm not alone :)
I followed up to step 6, then flanked the whole resolution / DPI issue by storing a *relative* position.
Posted by: peterchen | November 30, 2005 at 09:17 AM
You also need to take in account the location and size of the TaskBar. I've seen several programs position themselves underneath the TaskBar if I move it to the top of my screen or increase it's size. My positioning routine converts from Screen to Workspace coordinates and will move the Window down and right and shrink it if necessary to ensure the entire window is on the visible part of the screen. This was such a widespread problem that, according to Raymond's blog http://blogs.msdn.com/oldnewthing/archive/2003/09/12/54896.aspx, it is why the TaskBar is at the bottom of the screen and not the top.
Posted by: Chris Radke | December 01, 2005 at 05:09 PM
The UI problem is a difficult one, but I can't help but think that most of the problems you describe are an intrinsic problem in the Windows development model as a whole. Since Windows needs to maintain backwards compatability, there's no chance that it's going to completely revamp itself to clear out the things that create problems.
So what do we get in the end, kids? A 10-year old operating system, inundated in nasty little hacks that make it look new and shiny.
Posted by: greg | April 03, 2006 at 07:50 AM
What about saving the window position and size as a percentage of the current monitor's dimensions? That way, assuming we're not minimized or maximized, re-opening the application on a different display will always be _proportional_ to the current display rather than explicitly dictated?
Just a thought :)
Posted by: Richard Harlos | April 05, 2006 at 08:30 AM
Richard, that's waht I did :) It works well if you have scalable information (such a view port, charts, etc.). I'm even usign t for MDI childs. If you have fixed-size forms, it wouldn't work so well obviously.
greg - I don't think that's a Windows history problem, but illustrates a more fundamental aspect of user interfaces, and the complex interactions between presumably unrelated features.
Posted by: peterchen | March 29, 2009 at 06:21 AM