February, 2007

  • The Old New Thing

    With what operations is LockWindowUpdate not meant to be used?

    • 28 Comments

    Okay, now that we know what operations LockWindowUpdate is meant to be used with, we can look at various ways people misuse the function for things unrelated to dragging.

    People see the "the window you lock won't be able to redraw itself" behavior of LockWindowUpdate and use it as a sort of lazy version of the WM_SETREDRAW message. Though sending the WM_SETREDRAW message really isn't that much harder than calling LockWindowUpdate. It's twenty more characters of typing, half that if you use the SetWindowRedraw macro in <windowsx.h>.

    Instead of LockWindowUpdate(hwnd)
    Use SendMessage(hwnd, WM_SETREDRAW, FALSE, 0) or
    SetWindowRedraw(hwnd, FALSE)
    Instead of LockWindowUpdate(NULL)
    Use SendMessage(hwnd, WM_SETREDRAW, TRUE, 0) or
    SetWindowRedraw(hwnd, TRUE)

    As we noted earlier, only one window in the system can be locked for update at a time. If your intention for calling LockWindowUpdate is merely to prevent a window from redrawing, say, because you're updating it and don't want the window continuously refreshing until your update is complete, then just disable redraw on that window. If you use LockWindowUpdate, you create a whole slew of subtle problems.

    First off, if some other program is misusing LockWindowUpdate in this same way, then one of you will lose. Whoever tries LockWindowUpdate first will get it, and the second program will fail. Now what do you do? Your window isn't locked any more.

    Second, if you have locked your window for update and the user switches to another program and tries to drag an item (or even just tries to move the window!), that attempt to LockWindowUpdate will fail, and the user is now in the position where drag/drop has stopped working for some mysterious reason. And then, ten seconds later, it starts working again. "Stupid buggy Windows," the user mutters.

    Conversely, if you decide to call LockWindowUpdate when a drag/drop or window-move operation is in progress, then your call will fail.

    This is just a specific example of the more general programming mistake of using global state to manage a local condition. When you want to disable redrawing in one of your windows, you don't want this to affect other windows in the system; it's a local condition. But you're using a global state (the window locked for update) to keep track of it.

    I can already anticipate people saying, "Well, the window manager shouldn't let somebody lock a window for update if they're not doing a drag/drop operation." But how does the window manager know? It knows what is happening, but it doesn't know why. Is that program calling LockWindowUpdate because it's too lazy to use the WM_SETREDRAW message? Or is it doing it in response to some user input that resulted in a drag/drop operation? Note that you can't just say, "Well, the mouse button has to be down," because the user might be performing a keyboard-based operation (such as resizing a window with the arrow keys) that has the moral equivalent of a drag/drop. Morality is hard enough to resolve as it is; expecting computers to be able to infer it is asking a bit much.

    Next time, a final remark on LockWindowUpdate.

  • The Old New Thing

    Mandarin Chinese gaining popularity in public schools

    • 31 Comments

    NPR reports that Mandarin Chinese is gaining popularity in public schools. (But please oh please don't take pronunciations lessons from the student at time code 3:25. His first tone was clearly a second—falling victim to the classic mistake of applying English tone shaping to Chinese syllables.) On the other hand, some of those students who chose to study Mandarin Chinese didn't, um, do their homework:

    Some say they are here by accident or because they thought it would be an easy "A". Oops.

    I do admire the argument of one parent against learning Chinese:

    Everybody is going to speak English. I'm sure the Chinese all speak English.

    That's right. The Chinese all speak English. The accent? That's just an affectation.

  • The Old New Thing

    With what operations is LockWindowUpdate meant to be used?

    • 19 Comments

    As I noted earlier, the intended purpose of LockWindowUpdate can be captured in one word: dragging.

    The simplest case of LockWindowUpdate is used by the window manager when you move or resize a window and "Show window contents while dragging" is disabled. When you start the move/size operation, the window manager locks the entire desktop so it can draw the dotted-rectangle feedback without risking interference from another window that happens to intersect the dotted-rectangle. When the move/size operation is complete, the desktop is unlocked, and the world returns to normal.

    A common case where an application uses LockWindowUpdate is if it wants to draw a custom image when offering drag feedback. In this case, the application locks its own window in order to draw the drag feedback. It then uses the DCX_LOCKWINDOWUPDATE flag to get a DC that it can use to draw the desired feedback, and it doesn't have to worry about the window procedure or any other code in the application accidentally drawing to the feedback window and messing up the drag image. For example, if the application is displaying drag feedback on a list view, and some asynchronous event caused the list view contents to change (say, an item got added), and the drag image just happens to be where the new item is about to appear, you wouldn't want the normal redraw behavior of the listview to overpaint (or worse, merge with) the drag image.

    A case where you would want to lock another application's window is if you are dragging an object across the screen. You might do this if you are a program like Spy that has an option to let the user select a window by dragging a "selection gadget" over it. Not only do you have to lock the window the use is selecting so that its own painting won't conflict with your "selection gadget", but also so that it doesn't conflict with the highlight effect you place around that window.

    By now, you've probably noticed a common thread to all of these LockWindowUpdate scenarios: They all involve dragging of some sort. Dragging a window caption to move it, dragging the edge of a window to resize it, dragging an item into a window, or dragging an item out of a window. This is not a coincidence. The LockWindowUpdate function was designed with these drag scenarios in mind. Since dragging an item uses the mouse button, and there's only one mouse, you can't have multiple drag operations in progress at once, and therefore, there's no need to lock more than one window for update at a time. The function should perhaps more accurately have been named LockDragWindow.

  • The Old New Thing

    Crush Finder experiment gets off the ground at Princeton

    • 25 Comments

    And who says these college students aren't getting anything done? The Princeton University student government announced, among other things, a web site wherein students could list up to five other people they have crushes on, and if two people list each other, the web site puts the two potential lovebirds in touch with each other. The site went online on (appropriately enough) Valentine's Day, and student reaction was swift, with over a thousand students signing up before the day was over.

    You can pay Crush Finder a visit, though since you're (probably) not a student you can't actually submit anything, but the gossip in you can check out the top ten most crushed-on students.

  • The Old New Thing

    How is LockWindowUpdate meant to be used?

    • 16 Comments

    Now that we know how LockWindowUpdate works, we can look at what it is for.

    Actually, the intended purpose of LockWindowUpdate can be captured in one word: dragging. But we'll get to that a little later.

    The purpose of LockWindowUpdate is to allow a program to temporarily take over the responsibility of drawing a window. Of course, in order to do this, you have to prevent the window procedure (or anybody else) from doing their normal drawing activities; otherwise, the two pieces of code (the code that normally draws the window and the code that's trying to take over) fight for control of the window and you get an ugly mess since neither knows what the other is doing.

    But if you've locked the window for updating, how do you draw to it? You use the DCX_LOCKWINDOWUPDATE flag to the GetDCEx function. This flag means, "Let me draw to the window even though it is locked for update." Only the code that locked the window for update should pass this flag, of course, since it would otherwise create the conflict that LockWindowUpdate was intended to avoid in the first place.

    Since people like tables so much, I've made one that summarizes what changes when a window is locked for update:

    Normal behavior Window locked for update
    BeginPaint, GetDC, etc. Drawing operations paint the window Drawing operations paint nothing, but the affected area is remembered for future invalidation
    GetDCEx with DCX_LOCKWINDOWUPDATE flag (do not use) Drawing operations paint the window

    In other words, when a window is locked for update, the ability to draw to the window is taken away from the normal DC-acquisition functions (BeginPaint and friends) and given to GetDC(DCX_LOCKWINDOWUPDATE). Note that if no window is locked for update, you should not pass the DCX_LOCKWINDOWUPDATE flag; the purpose of that flag is to indicate "I'm the guy who called LockWindowUpdate. Let me in!"

    It's sort of the window manager equivalent of the old comedy routine where you tell the guard, "Nobody is allowed into this room." And then you come back an hour later and the guard won't let you in.

    "I'm sorry, sir, but I'm not allowed to let anyone into this room."

    "But I'm the one who told you not to let anyone into the room."

    "That's right sir, and I'm following your orders. Nobody is allowed into the room."

    The mistake was in the initial order to the guard. You should have said, "Nobody except me is allowed into this room." And DCX_LOCKWINDOWUPDATE is how you tell the window manager, "It's me. Let me in."

    If you go back and look at the way the LockWindowUpdate function works, you'll see that if the window that was locked doesn't try to draw, then when the window is unlocked, nothing is invalidated. Whereas the CS_SAVEBITS class style automatically saves the original pixels and restores them when the window is removed from the screen, the LockWindowUpdate doesn't do any such thing. It is your responsibility to ensure that any pixels you changed while the window was locked for update have been restored to their original values when you call LockWindowUpdate(NULL). This is typically done by saving the original pixels into an off-screen bitmap before you do your custom painting and putting them back when you're done.

    Okay, so here's the intended usage pattern:

    • When you want to take over drawing from another window, call LockWindowUpdate on that window.
    • Save the pixels from that window that you're going to overwrite.
    • Draw your new pixels. (These pixels are often a modification of the original pixels. For example, you might add the image of an object that is being dragged over that window.)
    • Repeat as necessary as long as your operation is still in progress. (Doing so may require you to "back up" more pixels of the screen if the region of the screen you're modifying is different from the region you modified originally. You can do this backup/restore incrementally. For example, instead of accumulating the set of pixels you need to restore, you can restore all the original pixels, compute the new position of the drag image, save those new pixels, and draw the new image. That way, you have only one set of "backup pixels" to deal with.)
    • When the operation is complete, restore the original pixels and call LockWindowUpdate(NULL).

    Next time, we'll look more at that one word "dragging" and how it is closely tied to the whole concept of LockWindowUpdate.

    Even though we've only started talking about LockWindowUpdate, you should already know enough to answer this question.

    (Note: The purpose of this series is to describe the way the LockWindowUpdate was intended to be used, not to discuss whether it was a good design in the first place.)

  • The Old New Thing

    What does LockWindowUpdate do?

    • 28 Comments

    Poor misunderstood LockWindowUpdate.

    This is the first in a series on LockWindowUpdate, what it does, what it's for and (perhaps most important) what it's not for.

    What LockWindowUpdate does is pretty simple. When a window is locked, all attempt to draw into it or its children fail. Instead of drawing, the window manager remembers which parts of the window the application tried to draw into, and when the window is unlocked, those areas are invalidated so that the application gets another WM_PAINT message, thereby bringing the screen contents back in sync with what the application believed to be on the screen.

    This "keep track of what the application tried to draw while Condition X was in effect, and invalidate it when Condition X no longer hold" behavior you've seen already in another guise: CS_SAVEBITS. In this sense, LockWindowUpdate does the same bookkeeping that would occur if you had covered the locked window with a CS_SAVEBITS window, except that it doesn't save any bits.

    The documentation explicitly calls out that only one window (per desktop, of course) can be locked at a time, but this is implied by the function prototype. If two windows could be locked at once, it would be impossible to use LockWindowUpdate reliably. What would happen if you did this:

    LockWindowUpdate(hwndA); // locks window A
    LockWindowUpdate(hwndB); // also locks window B
    LockWindowUpdate(NULL); // ???
    

    What does that third call to LockWindowUpdate do? Does it unlock all the windows? Or just window A? Or just window B? Whatever your answer, it would make it impossible for the following code to use LockWindowUpdate reliably:

    void BeginOperationA()
    {
     LockWindowUpdate(hwndA);
     ...
    }
    
    void EndOperationA()
    {
     ...
     LockWindowUpdate(NULL);
    }
    
    void BeginOperationB()
    {
     LockWindowUpdate(hwndB);
     ...
    }
    
    void EndOperationB()
    {
     ...
     LockWindowUpdate(NULL);
    }
    

    Imagine that the BeginOperation functions started some operation that was triggered by asynchronous activity. For example, suppose operation A is drawing drag/drop feedback, so it begins when the mouse goes down and ends when the mouse is released.

    Now suppose operation B finishes while a drag/drop is still in progress. Then EndOperationB will clean up operation B and call LockWindowUpdate(NULL). If you propose that that should unlock all windows, then you've just ruined operation A, which expects that hwndA still be locked. Similarly, if you argue that it should unlock only hwndA, then only only is operation A ruined, but so too is operation B (since hwndB is still locked even though the operation is complete). On the other hand, if you propose that LockWindowUpdate(NULL) should unlock hwndB, then consider the case where operation A completes first.

    If LockWindowUpdate were able to lock more than one window at a time, then the function prototype would have to have been changed so that the unlock operation knows which window is being unlocked. There are many ways this could have been done. For example, a new parameter could have been added or a separate function created.

    // Method A - new parameter
    // fLock = TRUE to lock, FALSE to unlock
    BOOL LockWindowUpdate(HWND hwnd, BOOL fLock);
    
    // Method B - separate function
    BOOL LockWindowUpdate(HWND hwnd);
    BOOL UnlockWindowUpdate(HWND hwnd);
    

    But neither of these is the case. The LockWindowUpdate function locks only one window at a time. And the reason for this will become more clear as we learn what LockWindowUpdate is for.

  • The Old New Thing

    News flash: Professional athletes do it for the money

    • 33 Comments

    This weekend is NBA All-Star Weekend 2007, because just having a game isn't enough; you need to make it a weekend-long festival.

    Tyrus Thomas, invited to paricipate in the slam dunk contest, which comes with a top prize of $35,000, was quoted as saying

    I'm just going to go out there, get my check and call it a day. ... I'm just into the free money. That's it. I'll just do whatever when I get out there.

    And yet, there was a huge uproar over the shocking revelation that (news flash!) professional athletes are in it for the money. Thomas was fined $10,000 by his team for his remarks—rather ironic that the way to teach him that it's not about the money is to impose a monetary penalty—and issued an apology some days later:

    I truly feel honored to be invited to participate in this year's slam dunk contest during next week's NBA All-Star Weekend in Las Vegas. The opportunity to represent the Bulls and the city of Chicago on a global stage is a privilege that I do not take lightly. I regret the extent to which my comments indicate otherwise.

    He explained that "it was a miscommunication and understanding of words." Huh? Under what possible rules of interpretation was "I'm just into the free money" intended to be understood as "The opportunity to appear on a global stage is a privilege that I do not take lightly"?

    Only a Game's Bill Littlefield took up the matter in his Weekly Warm-up podcast.

  • The Old New Thing

    Why don't I use any class libraries in my sample code?

    • 28 Comments

    As a general rule, I avoid using any class libraries in my sample code. This isn't because I'm opposed to class libraries, but rather because I don't want to narrow my audience to "people who use MFC" (to choose one popular class library). If I were to start using MFC for all of my samples, I'd probably lose all the people who don't use MFC.

    "Oh, but those people can just translate the MFC code into whatever class library they use."

    Well, sure, they could do that, but first they would have to learn MFC. I wouldn't be talking about HWNDs and HDCs any more but rather CWnds and CDCs. I would write "Add this to your OnDropEx handler", and all the non-MFC people would say, "What are you talking about? I'm not using MFC. What is the Win32 equivalent to OnDropEx?" (Suppose my article on using accessibility to read the text under the mouse cursor were titled "How to use MFC to retrieve text under the mouse cursor." Would you have read it?)

    "Well, fine, don't use MFC, but still it wouldn't kill you to use a smart pointer library."

    But which one? There's MFC's CIP, ATL's CComPtr, STL^H^H^Hthe C++ standard library's auto_ptr, the Microsoft compiler's built-in _com_ptr_t (which you get automatically if you use the nonstandard #import directive), and boost's grab bag of smart pointer classes scoped_ptr, shared_ptr, weak_ptr, intrusive_ptr... And they all behave differently. Sometimes subtly incompatibly. For example, MFC's CIP::CreateObject method uses CLSCTX_INPROC_SERVER, whereas ATL's CComPtr::CreateInstance method uses CLSCTX_ALL. When you're chasing down a nasty COM marshalling problem, these tiny details matter, and if you're an ATL programmer looking at MFC code, these tiny details are also something you're going to miss simply due to lack of familiarity. (And woe unto you if your preferred language is VB or C# or some other popular non-C++ language. Now you have double the translation work ahead of you.)

    Instead of hiding the subtleties behind a class library, I put them right out on the table. Those of you who have a favorite class library can convert the boring error-prone plain C++ code into your beautiful class library.

    In fact, I almost expect you to do it.

    (On a related note, some people are horrified at the rather dense code presentation I use here. I don't write code like that in real life; I'd be just as horrified as you if I saw that code in a real program. I just use that style here because of the nature of the medium. A great way to lose people's interest is to make them plow through 100 lines of boring code before they reach the good stuff.)

  • The Old New Thing

    There's not much luggage space, unless you dump the snow

    • 7 Comments

    Every year, Road and Track magazine reviews a strange vehicle in their April edition. Last year, it was the 1949 MG TC. But my favorite is their 2005 review of the Zamboni 500.

    [T]he Zamboni leaves the line with alacrity (revved to its 3000-rpm redline), rockets up to 9.7 mph and then stays at exactly that speed for the full 200-ft. length of the rink. Those old myths that you will "black out" or "be unable to breathe" at speeds above 9.5 mph proved to be completely untrue. I was perfectly comfortable, once I got over the excitement, and felt no ill effects then or later.

    There's not much luggage space, unless you dump the snow, and $50,000 is a lot of money for any sport ute with a top speed of only 9.7 mph.

    (The page also contains links to previous April Fool's stories.)

  • The Old New Thing

    Why can't you set the command prompt's current directory to a UNC?

    • 53 Comments

    If you try to set the current directory of a command prompt, you get the error message "CMD does not support UNC paths as current directories." What's going on here?

    It's MS-DOS backwards compatibility.

    If the current directory were a UNC, there wouldn't be anything to return to MS-DOS programs when they call function 19h (Get current drive). That function has no way to return an error code, so you have to return a drive letter. UNCs don't have a drive letter.

    You can work around this behavior by using the pushd command to create a temporary drive letter for the UNC. Instead of passing script.cmd to the CreateProcess function as the lpCommandLine, you can pass cmd.exe /c pushd \\server\share && script.cmd.

    (Griping that seems to happen any time I write about batch files, so I'll gripe them pre-emptively: Yes, the batch "language" sucks because it wasn't designed; it just evolved. I write this not because I expect you to enjoy writing batch files but because you might find yourself forced to deal with them. If you would rather abandon batch files and use a different command interpreter altogether, then more power to you.)

Page 2 of 4 (39 items) 1234