November, 2006

  • The Old New Thing

    I bet somebody got a really nice bonus for that feature


    I often find myself saying, "I bet somebody got a really nice bonus for that feature."

    "That feature" is something aggressively user-hostile, like forcing a shortcut into the Quick Launch bar or the Favorites menu, like automatically turning on a taskbar toolbar, like adding an icon to the notification area that conveys no useful information but merely adds to the clutter, or (my favorite) like adding an extra item to the desktop context menu that takes several seconds to initialize and gives the user the ability to change some obscure feature of their video card.

    Allow me to summarize the guidance:

    The Quick Launch bar and Favorites menu belong to the user. There is intentionally no interface to manipulate shortcuts in the Quick Launch bar. We saw what happened to the Favorites menu and learned our lesson: Providing a programmatic interface to high-valued visual real estate results in widespread abuse. Of course, this doesn't stop people from hard-coding the path to the Quick Launch directory—too bad the name of the directory isn't always "Quick Launch"; the name can change based on what language the user is running. But that's okay, I mean, everybody speaks English, right?

    There is no programmatic interface to turn on a taskbar toolbar. Again, that's because the taskbar is a high-value piece of the screen and creating a programmatic interface can lead to no good. Either somebody is going to go in and force their toolbar on, or they're going to go in and force a rival's toolbar off. Since there's no programmatic interface to do this, these programs pull stunts like generating artificial user input to simulate the right-click on the taskbar, mousing to the "Toolbars" menu item, and then selecting the desired toolbar. The taskbar context menu will never change, right? Everybody speaks English, right?

    The rule for taskbar notifications is that they are there to, well, notify the user of something. Your print job is done. Your new hardware device is ready to use. A wireless network has come into range. You do not use a notification icon to say "Everything is just like it was a moment ago; nothing has changed." If nothing has changed, then say nothing.

    Many people use the notification area to provide quick access to a running program, which runs counter to the guidance above. If you want to provide access to a program, put a shortcut on the Start menu. Doesn't matter whether the program is running already or not. (If it's not running, the Start menu shortcut runs it. If it is already running, the Start menu shortcut runs the program, which recognizes that it's already running and merely activates the already-running copy.)

    While I'm here, I may as well remind you of the guidance for notification balloons: A notification balloon should only appear if there is something you want the user to do. It must be actionable.

    Your print job is complete. Go pick it up.
    Your new hardware device is ready to use. Start using it.
    A wireless network has come into range. Connect to it.

    The really good balloons will tell the user what the expected action is. "A wireless network has come into range. Click here to connect to it." (Emphasis mine.)

    Here are some bad balloons:

    Bad BalloonAction?
    Your screen settings have been restored. So what do you want me to do about it?
    Your virtual memory swap file has been automatically adjusted. If it's automatic, what do I need to do?
    Your clock has been adjusted for daylight saving time. Do you want me to change it back?
    Updates are ready for you to install. So?

    One of my colleagues got a phone call from his mother asking him what she she should do about a new error message that wouldn't go away. It was the "Updates are ready for you to install" balloon. The balloon didn't say what she should do next.

    The desktop context menu extensions are the worst, since the ones I've seen come from video card manufacturers that provide access to something you do maybe once when you set up the card and then don't touch thereafter. I mean, do normal users spend a significant portion of their day changing their screen resolution and color warmth? (Who on a laptop would even want to change their screen resolution?) What's worse is that one very popular such extension adds an annoying two second delay to the appearance of the desktop context menu, consuming 100% CPU during that time. If you have a laptop with a variable-speed fan, you can hear it going nuts for a few seconds each time you right-click the desktop. Always good to chew up battery life initializing a context menu that nobody on a laptop would use anyway.

    The thing is, all of these bad features were probably justified by some manager somewhere because it's the only way their feature would get noticed. They have to justify their salary by pushing all these stupid ideas in the user's faces. "Hey, look at me! I'm so cool!" After all, when the boss asks, "So, what did you accomplish in the past six months," a manager can't say, "Um, a bunch of stuff you can't see. It just works better." They have to say, "Oh, check out this feature, and that icon, and this dialog box." Even if it's a stupid feature.

    As my colleague Michael Grier put it, "Not many people have gotten a raise and a promotion for stopping features from shipping."

  • The Old New Thing

    On the importance of backwards compatibility for large corporations


    Representatives from the IT department of a major worldwide corporation came to Redmond and took time out of their busy schedule to give a talk on how their operations are set up. I was phenomenally impressed. These people know their stuff. Definitely a world-class operation.

    One of the tidbits of information they shared with us is some numbers about the programs they have to support. Their operations division is responsible for 9,000 different install scripts for their employees around the world.

    That was not a typo.

    Nine thousand.

    This highlighted for me the fact that backwards compatibility is crucial for adoption in the corporate world. Do the math. Suppose they could install, test and debug ten programs each business day, in my opinion, a very optimistic estimate. Even at that rate, it would take them three years to get through all their scripts.

    This isn't a company that bought some software ten years ago and don't have the source code. They have the source code for all of their scripts. They have people who understand how the scripts work. They are not just on the ball; they are all over the ball. And even then, it would take them three years to go through and check (and possibly fix) each one.

    Oh, did I mention that four hundred of those programs are 16-bit?

  • The Old New Thing

    It's not surprising at all that people search for Yahoo


    Earlier this year, one columnist was baffled as to why "Yahoo" was the most searched-for term on Google. I wasn't baffled at all. Back in 2001, Alexa published the top ten most searched-for terms on their service, and four of the top ten were URLs:,,, and

    A lot of people simply don't care to learn the difference between the search box and the address bar. "If I type what I want into this box here, I sometimes get a strange error message. But if I type it into that box there, then I get what I want. Therefore, I'll use that box there for everything." And you know what? It doesn't bother me that they don't care. In fact, I think it's good that they don't care. Computers should adapt to people, not the other way around.

    You can try to explain to these people, "You see, this is a URL, so you type it into the address box. But that is a search phrase, so you type it into the search box."

    "You-are-what? Look, I don't care about your fancy propeller-beanie acronyms. You computer types are always talking about how computers are so easy to use, and then you make up these arbitrary rules about where I'm supposed to type things. If I want something, I type into this box and click 'Search'. And it finds it. Watch. I want Yahoo, so I type 'yahoo' into the box, and boom, there it is. I have a system that works. Why are you trying to make my life more confusing?"

    I remember attending a presentation by the MSN Explorer team on what they learned about how people use a web browser. They found many situations where people failed to accomplish their desired task because they typed the right thing into the wrong box. But instead of trying to teach people which box to type it in, they just expanded the definition of "right". You typed your query into the wrong box? No problem, we'll just pretend you typed it into the correct box. In fact, let's just get rid of all these special-purposes boxes. Whatever you want, just type it into this box, and we'll get it for you.

    I wish the phone company would learn this. Sometimes I'll dial a telephone number and I'll get an automated recording that says, "I'm sorry. You must dial a '1' before the number. Please hang up and try again." Or "I'm sorry. You must not dial a '1' before the number. Please hang up and try again." That's because in the state of Washington, there are complicated rules about when you have to dial a "1" in front of the number and when you don't. (Fortunately, the rule on when you have to dial the area code is easier to remember: If the area code you are calling is the same as the area code you are dialing from, then you can omit the area code.) For example, suppose your home number is 425-882-xxxx. Here's how you have to dial the following numbers:

    To call this numberyou dial

    If you get it wrong, the voice comes on the line to tell you. Hey, since you know what I did wrong and you know what I meant to do, why not just fix it? If I dial a number and forget the "1", just insert the 1 and connect the call. If I dial a number and include the "1" when I didn't need to, just delete the 1 and connect the call. Don't make me have to look up in the book whether I need a 1 or not. (In the front of the phone book are tables showing which numbers need a "1" and which don't. I hate those tables.)

    (Yes, I know there are weird technical/legal reasons for why I have to dial the phone in four different ways depending on whom I want to call. But it's still wrong that these technical/legal reasons mean that the rules for dialing a telephone are impossibly complicated.)

  • The Old New Thing

    How do I convert an HRESULT to a Win32 error code?


    Everybody knows that you can use the HRESULT_FROM_WIN32 macro to convert a Win32 error code to an HRESULT, but how do you do the reverse?

    Let's look at the definition of HRESULT_FROM_WIN32:

    #define HRESULT_FROM_WIN32(x) \
      ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) \
    : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000)))

    If the value is less than or equal to zero, then the macro returns the value unchanged. Otherwise, it takes the lower sixteen bits and combines them with FACILITY_WIN32 and SEVERITY_ERROR.

    How do you reverse this process? How do you write the function WIN32_FROM_HRESULT?

    It's impossible to write that function since the mapping provided by the HRESULT_FROM_WIN32 function is not one-to-one. I leave as an execise to draw the set-to-set mapping diagram from DWORD to HRESULT. (Original diagram removed since people hate VML so much, and I can't use SVG since it requies XHTML.) If you do it correctly, you'll have a single line which maps 0 to S_OK, and a series of blocks that map blocks of 65536 error codes into the same HRESULT space.

    Notice that the values in the range 1 through 0x7FFFFFFFF are impossible results from the HRESULT_FROM_WIN32 macro. Furthermore, values in the range 0x80070000 through 0x8007FFFF could have come from quite a few original Win32 codes; you can't pick just one.

    But let's try to write the reverse function anyway:

      // Could have come from many values, but we choose this one
      *pdwWin32 = HRESULT_CODE(hr);
      return TRUE;
     if (hr == S_OK) {
      *pdwWin32 = HRESULT_CODE(hr);
      return TRUE;
     // otherwise, we got an impossible value
     return FALSE;

    Of course, we could have been petulant and just written

    BOOL WIN32_FROM_HRESULT_alternate(HRESULT hr, OUT DWORD *pdwWin32)
     if (hr < 0) {
      *pdwWin32 = (DWORD)hr;
      return TRUE;
     // otherwise, we got an impossible value
     return FALSE;

    because the HRESULT_FROM_WIN32 macro is idempotent: HRESULT_FROM_WIN32(HRESULT_FROM_WIN32(x)) == HRESULT_FROM_WIN32(x). Therefore you would be technically correct if you declared that the "inverse" function was trivial. But in practice, people want to try to get "x" back out, so that's what we give you.

    Now that you understand how the HRESULT_FROM_WIN32 macro works, you can answer this question, based on an actual customer question:

    Sometimes, when I import data from a scanner, I get the error "The directory cannot be removed." What does this mean?

    You will have to use some psychic powers, but I think you're up to it.

    One unfortunate aspect of both HRESULTs and Win32 error codes is that there is no single header file that contains all the errors. This is understandable from a logistical point of view: Multiple teams need to make up new error codes for their components, but the winerror.h file is maintained by the kernel team. If winerror.h were selected to be the master repository for all error codes, it means that any team that wanted to add a new error code or change an existing one would have to pester the kernel team to make the change for them. Things get even more complicated if those teams have their own SDK. For example, suppose both the DirectX and Windows Media teams wanted to include the new winerror.h in their corresponding SDKs. If you install the SDKs in the wrong order (and how are you supposed to know which should be installed first, DirectX 8 or WMSDK 6?), you can end up regressing your winerror.h file. It's the version conflict problem, but without the benefit of version resources.

    Many teams have prevailed upon the kernel team to reserve a chunk of error codes just for them.

    Traffic Control7500–7999
    Active Directory8000–8999
    Side By Side14000–14999

    There is room for only 65535 Win32 error codes, and over an eighth of them have already been carved out by these "block assignments". I wonder if we will eventually run out of error codes prematurely due to having given away error codes in too-large chunks. (Some sort of analogy with IPv4 could be made here but I'm not going to try.)

  • The Old New Thing

    How do I test that return value of ShellExecute against 32?


    We discussed earlier the history behind the the return value of the ShellExecute function, and why its value in Win32 is meaningless aside from testing it against the value 32 to determine whether an error occurred.

    How, then, should you check for errors?

    Let's turn the question around. How would you, the implementor of the ShellExecute function, report success? The ShellExecute is a very popular function, so you have to prepared for the ways people check the return code incorrectly yet manage to work in spite of themselves. The goal, therefore, is to report success in a manner that breaks as few programs as possible.

    (Now, there may be those of you who say, "Hang compatibility. If programs checked the return value incorrectly, then they deserve to stop working!" If you choose to go in that direction, then be prepared for the deluge of compatibility bugs to be assigned to you to fix. And they're going to come from a grumpy compatibility testing team because they will have spent a long time just finding out that the problem was that the program was checking the return value of ShellExecute incorrectly.)

    Since there is still 16-bit code out there that may thunk up to 32-bit code, you probably don't want to return a value greater than 0xFFFF. Otherwise, when that value gets truncated to a 16-bit HINSTANCE will lose the high word. If you returned a value like 0x00010001, this would truncate to 0x0001, which would be treated as an error code.

    For similar reasons, the 64-bit implementation of the ShellExecute function had better not use the upper 32 bits of the return value. Code that casts the return value to int will lose the high 32 bits.

    Furthermore, you shouldn't return a value that, when cast to an integer, results in a negative number. Some people will use a signed comparison against 32; others will use an unsigned comparison. If you returned a value like -5, then the people who used a signed comparison would think the function failed, whereas those who used an unsigned comparison would think it succeeded.

    By the same logic, the value you choose as the return value should not result in a negative number when cast to a 16-bit integer. If the return value is passed to a 16-bit caller that casts the result to an integer and compares against 32, you want consistent results independent of whether the 16-bit caller used a signed or unsigned comparison.

    Edge conditions are tricky, so you don't want to return the value 32 exactly. If you look at code that checks the return value from ShellExecute, you'll probably find that the world is split as to whether 32 is an error code or not. So it'd be in your best interest not to return the value 32 exactly but rather a value larger than 32.

    So far, you're constrained to choosing a value in the range 33–32767.

    Finally, you might be a fan of Douglas Adams. (Most geeks are.) The all-important number 42 fits into this range. Your choice of return value, therefore, might be (HINSTANCE)42.

    Going back to the original question: How should I check the return value of ShellExecute for errors? MSDN says you can cast the result to an integer and compare the result against 32. That'll work fine. You could cast in the other direction, comparing the return value against (HINSTANCE)32. That'll work fine, too. Or you could cast the result to an INT_PTR and compare the result against 32. That's fine, too. They'll all work, because the implementor of the ShellExecute function had to plan ahead for you and all the other people who call the ShellExecute function.

  • The Old New Thing

    Converting an HRESULT to a Win32 error code: Diagram and answer to exercise


    Here's the diagram from How do I convert an HRESULT to a Win32 error code?. If you are offended by VML, cover your ears and hum for a while.

    Win32 HRESULT

    The little sliver at the top is the mapping of zero to zero. The big white box at the bottom is the mapping of all negative numbers to corresponding negative numbers. And the rainbow represents the mapping of all the positive values, mod 65536, into the range 0x80070000 through 0x8007FFFF.

    Now let's take a look at that puzzle I left behind:

    Sometimes, when I import data from a scanner, I get the error "The directory cannot be removed." What does this mean?

    My psychic powers told me that the customer was doing something like this (error checking deleted):

    ReportError(HWND hwnd, HRESULT hr)
     DWORD dwError = HRESULT_CODE(hr);
     TCHAR szMessage[256];
                   dwError, 0, szMessage, 256, NULL);
     MessageBox(hwnd, szMessage, TEXT("Error"), MB_OK);

    and that the actual HRESULT was WIA_ERROR_COVER_OPEN, which is defined as


    Passing this value to HRESULT_CODE would yield 16, which maps to

    // MessageText:
    //  The directory cannot be removed.
    #define ERROR_CURRENT_DIRECTORY          16L

    And that would explain why the customer reported this strange error when reading data from a scanner.

  • The Old New Thing

    Placebo setting: QoS bandwidth reservation


    A placebo setting that has been getting a lot of play in recent years is that of QoS bandwidth reservation. The setting in question sets a maximum amount of bandwidth that can be reserved for QoS. I guess one thing people forgot to notice is the word "maximum". It doesn't set the amount of reserved bandwidth, just the maximum.

    Changing the value will in most cases have no effect on your download speed, since the limit kicks in only if you have an application that uses QoS in the first place. QoS, which stands for "quality of service", is a priority scheme for network bandwidth. A program can request a certain amount of bandwidth, say for media streaming, and when the program accesses the network, up to that much bandwidth is guaranteed to be available to the program. The setting in question controls how much bandwidth can be claimed for high priority network access. If no program is using QoS, then all your bandwidth is available to non-QoS programs. What's more, even if there is a QoS reservation active, if the program that reserved the bandwidth isn't actually using it, then the bandwidth is available to non-QoS programs.

    Consider this analogy: A restaurant seats 100 people, and it has a policy of accepting reservations for at most twenty percent of those seats. This doesn't mean that twenty seats are sitting empty all the time. If ten people have made reservations for dinner at 8pm, then ninety seats are available for drop-in customers at that time. The twenty percent policy just means that once twenty people have made reservations for dinner at 8pm, the restaurant won't accept any more reservations.

    Here's an example with made-up numbers: Suppose you are downloading a large file over your 720kbps connection. Since there is nothing else using the network, your download proceeds at 720kbps. Now suppose you fire up a program that uses QoS, say, for streaming media. (I don't know whether Windows Media Player uses QoS.) You connect to a streaming media source, and the media player does some math and determines that in order to give you smooth playback, it needs a minimum of 100kbps. (If it gets more, then great, but it needs at least that much to avoid dropouts.) The program places a reservation of that amount through QoS. With a default maximum reservation of 20% = 144kbps, this reservation request is granted. Playback of the streaming media begins, and your bandwidth is now split, with 100kbps going to your media player and the remaining 620kbps going to your download.

    Now you hit pause on the media player to answer the phone. Even though the media player has a 100kbps reservation, it's not using it, so all 720kbps of bandwidth is devoted to your download. You get off the phone and unpause the media player. Bandwidth is once again divided 100kbps for the media player and 620kbps for the download.

    Now, sure, you can set your QoS maximum reservation to zero. This means that when the media player asks for a guarantee of 100kbps, QoS will tell it, "Sorry, no can do." The media player will still play the streaming media, but since it no longer has a guarantee of bandwidth, there may be stretches where the download consumes most of the network bandwidth and the streaming media gets only 50kbps. Result: dropped frames, stuttering, or pauses for buffering.

    So tweak this value all you want, but understand what you're tweaking.

  • The Old New Thing

    The quiet dream of placebo settings


    Back in the Windows 95 days, people swore that increasing the value of MaxBPs in the system.ini file fixed application errors. People usually made up some pseudo-scientific explanation for why this fixed crashes. These explanations were complete rot.

    These breakpoints had nothing to do with Windows applications. They were used by 32-bit device drivers to communicate with code in MS-DOS boxes, typically the 16-bit driver they are trying to take over from or are otherwise coordinating their activities with. A bunch of these are allocated at system startup when drivers settle themselves in, and on occasion, a driver might patch a breakpoint temporarily into DOS memory, removing it when the breakpoint is hit (or when the breakpoint is no longer needed). Increasing this value had no effect on Windows application.

    I fantasized about adding a "Performance" page to Tweak UI with an option to increase the number of "PlaceBOs". I would make up some nonsense text about this setting controlling how high in memory the system should place its "breakpoint opcodes". Placing them higher will free up memory for other purposes and reduce the frequency of "Out of memory" errors. Or something like that.

    I was reminded of this story by my pals in products support who were trying to come up with a polite way of explaining to their customer that there is no /7GB boot.ini switch. In other situations, they sometimes dream of shipping placebo.dll to a customer to solve their problem.

    (And by the way, the technical reason why the user-mode address space is limited to eight terabytes was given by commenter darwou: The absence of a 16-byte atomic compare-and-exchange instruction means that bits need to be sacrificed to encode the sequence number which avoids the ABA problem.)

  • The Old New Thing

    It takes only one program to foul an upgrade


    "Worst software ever." That was Aaron Zupancic's cousin's reaction to the fact that Windows XP was incompatible with one program originally designed for Windows 98.

    Then again, commenter Aargh! says "The bad code should be fixed, period. If it can't be fixed, it breaks, too bad." Perhaps Aargh! can send a message to Aaron's cousin saying, "Too bad." I'm sure that'll placate Aaron's cousin.

  • The Old New Thing

    What is the process by which the cursor gets set?


    Commenter LittleHelper asked, "Why is the cursor associated with the class and not the window?" This question makes the implicit assumption that the cursor is associated with the class. While there is a cursor associated with each window class, it is the window that decides what cursor to use.

    The cursor-setting process is described in the documentation of the WM_SETCURSOR message:

    The DefWindowProc function passes the WM_SETCURSOR message to a parent window before processing. If the parent window returns TRUE, further processing is halted. Passing the message to a window's parent window gives the parent window control over the cursor's setting in a child window. The DefWindowProc function also uses this message to set the cursor to an arrow if it is not in the client area, or to the registered class cursor if it is in the client area.

    That paragraph pretty much captures the entire cursor-setting process. all I'm writing from here on out is just restating those few sentences.

    The WM_SETCURSOR goes to the child window beneath the cursor. (Obviously it goes to the child window and not the parent, because the documentation says that DefWindowProc forward the message to its parent. if the message went to the parent originally, then there would be nobody to forward the message to!) At this point, your window procedure can trap the WM_SETCURSOR message, set the cursor, and return TRUE. Thus, the window gets the first priority on deciding what the cursor is.

    If you don't handle the WM_SETCURSOR message, then DefWindowProc forwards the message to the parent, who in turn gets to decide whether to handle the message or forward to its parent in turn. One possibility is that one of the ancestor windows will handle the message, set the cursor, and return TRUE. In that case, the TRUE return value tells DefWindowProc that the cursor has been set and no more work needs to be done.

    The other, more likely, possibility is that none of the ancestor windows cared to set the cursor. At each return to DefWindowProc, the cursor will be set to the class cursor for the window that contains the cursor.

    Here it is in pictures. Suppose we have three windows, A, B, and C, where A is the top-level window, B a child, and C a grandchild, and none of them do anything special in WM_SETCURSOR. Suppose further that the mouse is over window C:

    SendMessage(hwndC, WM_SETCURSOR, ...)
     C's window procedure does nothing special
     DefWindowProc(hwndC, WM_SETCURSOR, ...)
      DefWindowProc forwards to parent:
       SendMessage(hwndB, WM_SETCURSOR, ...)
       B's window procedure does nothing special
       DefWindowProc(hwndB, WM_SETCURSOR, ...)
        DefWindowProc forwards to parent:
         SendMessage(hwndA, WM_SETCURSOR, ...)
         A's window procedure does nothing special
          DefWindowProc(hwndA) cannot forward to parent (no parent)
          DefWindowProc(hwndA) sets the cursor to C's class cursor
          DefWindowProc(hwndA) returns FALSE
         A's window procedure returns FALSE
        SendMessage(hwndA, WM_SETCURSOR, ...) returns FALSE
        DefWindowProc(hwndB) sets the cursor to C's class cursor
        DefWindowProc(hwndB) returns FALSE
       B's window procedure returns FALSE
      SendMessage(hwndB, WM_SETCURSOR, ...) returns FALSE
      DefWindowProc(hwndC) sets the cursor to C's class cursor
      DefWindowProc(hwndC) returns FALSE
     C's window procedure returns FALSE
    SendMessage(hwndC, WM_SETCURSOR, ...) returns FALSE

    Observe that the WM_SETCURSOR started at the bottom (window C), bubbled up to the top (window A), and then worked its way back down to window C. On the way up, it asks each window if it wants to set the cursor, and if it makes it all the way to the top with nobody expressing an opinion, then on the way down, each window sets the cursor to C's class cursor.

    Now, of course, any of the windows along the way could have decided, "I'm setting the cursor!" and returned TRUE, in which case the message processing would have halted immediately.

    So you see, the window really does decide what the cursor is. Yes, there is a cursor associated with the class, but it is used only if the window decides to use it. If you want to associate a cursor with the window, you can do it by handling the WM_SETCURSOR message explicitly instead of letting DefWindowProc default to the class cursor.

    LittleHelper's second question: "Many programs call SetCursor on every WM_MOUSEMOVE. Is this not recommended?"

    Although there is no rule forbidding you from using WM_MOUSEMOVE to set your cursor, it's going to lead to some problems. First, and much less serious, you won't be able to participate in the WM_SETCURSOR negotiations since you aren't doing your cursor setting there. But the real problem is that you're going to get cursor flicker. WM_SETCURSOR will get sent to your window to determine the cursor. Since you didn't do anything, it will probably turn into your class cursor. And then you get your WM_MOUSEMOVE and set the cursor again. Result: Each time the user moves the mouse, the cursor changes to the class cursor and then to the final cursor.

    Let's watch this happen. Start with the scratch program and make these changes:

    OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
     Sleep(10); // just to make the flicker more noticeable
     SetCursor(LoadCursor(NULL, IDC_CROSS));
     // Add to WndProc
     HANDLE_MSG(hwnd, WM_MOUSEMOVE, OnMouseMove);

    Run the program and move the mouse over the client area. Notice that it flickers between an arrow (the class cursor, set during WM_SETCURSOR) and the crosshairs (set during WM_MOUSEMOVE).

Page 1 of 3 (28 items) 123