• The Old New Thing

    Why does SystemParametersInfo hang when I pass the SPIF_SENDCHANGE flag?

    • 6 Comments

    If you pass the SPIF_SENDCHANGE flag to the SystemParametersInfo function, it will broadcast the WM_SETTINGCHANGE message with the wParam equal to the system parameter code you passed. For example, if you call

    SystemParametersInfo(SPI_SETDOUBLECLICKTIME,
          500, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
    

    then the system will broadcast the message

    SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0);
    

    If there is a window that isn't responding to messages, then this broadcast will hang until that unresponsive window finally resumes responding to messages or is killed.

    If you'd rather not be victimed by unresponsive windows, you have a few options, but it also may affect your program's expectations.

    You could issue the SystemParametersInfo call on a background thread. Then your background thread is the one that blocks instead of your UI thread.

    With this message, the background thread can notify the main thread when the broadcast finally completes, at which point your program now knows that all windows have received their notifications and are on board with the new setting.

    You could issue the SystemParametersInfo call without the SPIF_SENDCHANGE flag, then manually broadcast the change via

    DWORD dwResult;
    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0,
                SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG,
                5000, &dwResult);
    

    This does mean that unresponsive windows will not receive the notification that a system parameter has changed. This is acceptable if you decide that your change in settings was minor enough that a program missing the notification is no big deal. In other words, when the unresponsive program finally wakes up, it will not know that the setting has changed since it missed the notification.

    You can combine the above two methods: Use a background thread and send the message with a timeout.

    Perhaps the best technique is to use the SendNotifyMessage function. As we learned earlier, the SendNotifyMessage function is like SendMessage except that it doesn't wait for a response. This lets your program get back work while not messing up programs that were momentarily unresponsive when you decided to broadcast the notification.

    SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0);
    

    The downside is that you don't know when all windows have finally received and processed the notification. All you know is that someday, they will eventually find out. Usually you don't care about this aspect of the broadcast, so this lack of information is not an impediment.

  • The Old New Thing

    Using SystemParametersInfo to access user interface settings

    • 54 Comments

    The SystemParametersInfo function gives you access to a whole slew of user interface settings, and it is the only supported method for changing those settings.

    I'm not going to list every single setting; go read the list yourself. Here are some highlights:

    • SPI_GETICONTITLELOGFONT lets you query the font that is used for icon labels; SPI_SETICONTITLELOGFONT lets you change it.
    • SPI_GETNONCLIENTMETRICS lets you query the fonts that are used for window captions, menus, status bars, and message boxes; SPI_SETNONCLIENTMETRICS lets you change them.

    Here are some control panel settings.

    • SPI_SETKEYBOARDDELAY and SPI_SETKEYBOARDSPEED let you set the keyboard autorepeat parameters.
    • SPI_SETDOUBLECLICKTIME lets you set the mouse double-click speed.
    • SPI_SETMENUFADE lets you enable or disable the menu fade animation. [Typo fixed, 4pm.]
    • There is a whole series of SPI_SETxxxANIMATION settings that let you control which screen elements animate.

    Notice that when using the SPI_SET* commands, you also have to choose whether the setting changes are temporary (lost at logoff) or persistent. The historically-named SPIF_UPDATEINIFILE flag causes the changes to be saved to the user profile; if you leave it off, then the changes are not saved and are lost when the user logs off. You should also set the SPIF_SENDCHANGE flag so that programs which want to refresh themselves in response to changes in the settings can do so.

    The fact that there exist both temporary and persistent changes highlights the danger of accessing the registry directly to read or write the current settings. If the current settings are temporary, then they are not saved in the registry. The SystemParametersInfo function retrieves the actual current settings, including temporary ones. For example, if you want to query whether menus are being animated, and the user has temporarily disabled animation, reading the registry will tell you that they are being animated when in fact they are not.

    Also, changes written to the registry don't take effect untll the next logon, because that is the only time the values are consulted. To make a change take effect immediately, you must use SystemParametersInfo.

    It still puzzles me why people go to the undocumented registry keys to change these settings when there is a perfectly good documented function for doing it. Especially when the documented function works and the undocumented registry key is unreliable.

    I remember one application that went straight for the undocumented registry keys (to get the icon title font, I think). Unfortunately for the application, the format of the registry key is different between Windows 95 and Windows 2000, and it ended up crashing. (It expected the Windows 95 format.) If it had used the documented method of retrieving the icon title font, it would have worked fine. In other words, this program went out of its way to go around the preferred way of doing something and got hoist by its own petard.

  • The Old New Thing

    Keep your eye on the code page

    • 47 Comments

    Remember that there are typically two 8-bit code pages active, the so-called "ANSI" code page and the so-called "OEM" code page. GUI programs usually use the ANSI code page for 8-bit files (though utf-8 is becoming more popular lately), whereas console programs usually use the OEM code page.

    This means, for example, when you open an 8-bit text file in Notepad, it assumes the ANSI code page. But if you use the TYPE command from the command prompt, it will use the OEM code page.

    This has interesting consequences if you switch between the GUI and the command line frequently.

    The two code pages typically agree on the first 128 characters, but they nearly always disagree on the characters from 128 to 255 (so-called "extended characters"). For example, on a US-English machine, character 0x80 in the OEM code page is Ç, whereas in the ANSI code page it is €.

    Consider a directory which contains a file named Ç. If you type "dir" at a command prompt, you see a happy Ç on the screen. On the other hand, if you do "dir >files.txt" and open files.txt in a GUI editor like Notepad, you will find that the Ç has changed to a €, because the 0x80 in the file is being interpreted in the ANSI character set instead of the OEM character set.

    Stranger yet, if you mark/select the file name from the console window and paste it into Notepad, you get a Ç. That's because the console window's mark/select code saves text on the clipboard as Unicode; the character saved into the clipboard is not 0x80 but rather U+00C7, the Unicode code point for "Latin Capital Letter C With Cedilla". When this is pasted into Notepad, it gets converted from Unicode to the ANSI code page, which on a US-English system encodes the Ç character as 0xC7.

    But wait, there's more. The command processor has an option (/U) to generate all piped and redirected output in Unicode rather than the OEM code page.

    (Note that the built-in documentation for the command processor says that the /A switch produces ANSI output; this is incorrect. /A produces OEM output. This is one of those bugs that you recognize instantly if you are familiar with what is going on. It's so obviously OEM that when I see the documentation say "ANSI", my mind just reads it as "OEM". In the same way native English speakers often fail to notice misspellings or doubled words.)

    If you run the command

    cmd /U /C dir ^>files.txt
    

    then the output will be in Unicode and therefore will record the Ç character as U+00C7, which Notepad will then be able to read back.

    This has serious consequences for batch files.

    Batch files are 8-bit files and are interpreted according to the OEM character set. This means that if you write a batch file with Notepad or some other program that uses the ANSI character set for 8-bit files, and your batch file contains extended characters, the results you get will not match the what you see in your editor.

    Why the discrepancy between GUI programs and console programs over how 8-bit characters should be interpreted?

    The reason is, of course, historical.

    Back in the days of MS-DOS, the code page was what today is called the OEM code page. For US-English systems, this is the code page with the box-drawing characters and the fragments of the integral signs. It contained accented letters, but not a very big set of them, just enough to cover the German, French, Spanish, and Italian languages. And Swedish. (Why Swedish yet not Danish and Norwegian I don't know.)

    When Windows came along, it decided that those box-drawing characters were wasting valuable space that could be used for adding still more accented characters, so out went the box-drawing characters and in went characters for Danish, Norwegian, Icelandic, and Canadian French. (Yes, Canadian French uses characters that European French does not.)

    Thus began the schism between console programs (MS-DOS) and GUI programs (Windows) over how 8-bit character data should be interpreted.

  • The Old New Thing

    A timed context menu

    • 25 Comments

    This is sort of in the same spirit as our previous exercise in writing a timed message box, but this is much easier. Here, we use the handy-dandy WM_CANCELMODE message to get us out of menu mode.

    void CALLBACK
    MenuTooLateProc(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
    {
      SendMessage(hwnd, WM_CANCELMODE, 0, 0);
    }
    
    BOOL
    TimedTrackPopupMenuEx(HMENU hMenu, UINT uFlags, int x, int y,
        HWND hwnd, LPTPMPARAMS pTpm, DWORD dwTimeout)
    {
        UINT idTimer = SetTimer(NULL, IDT_TOOLATE, dwTimeout, MenuTooLateProc);
        BOOL fResult = TrackPopupMenuEx(hMenu, uFlags, x, y, hwnd, pTpm);
        if (idTimer) KillTimer(NULL, idTimer);
        return fResult;
    }
    

    Before displaying the menu, we set a timer. (And we use a thread timer because we don't own the hwnd window and therefore don't know what timer IDs are safe to use.) If the timer fires, we send ourselves a WM_CANCELMODE message to cancel menu mode. This causes the system to act as if the user had dismissed the menu without selecting anything, either by hitting ESC or clicking outside the menu. The call to the TrackPopupMenuEx function returns once the user has selected something (or the timeout has elapsed), at which point we clean up by destroying our timer before returning.

  • The Old New Thing

    Modality, part 8: A timed MessageBox, the better version

    • 16 Comments

    A few days ago, we saw a simple version of a timed message box which had a limitation that it could be used from only one thread at a time. Today we'll work to remove that limitation.

    As you may recall, the reason why it could be used from only one thread at a time was that we kept the "Did the message box time out?" flag in a global. To fix it, we will move the flag to a per-instance location, namely a helper window.

    Start with the scratch program, add the code for the scratch window class, change the name of the scratch window class so it doesn't conflict with the class name of the scratch program (thanks to reader Adrian for pointing this out), then add the following:

    #define IDT_TOOLATE     1
    
    typedef struct TOOLATEINFO {
     BOOL fTimedOut;
     HWND hwndReenable;
    } TOOLATEINFO;
    
    void CALLBACK
    MsgBoxTooLateProc(HWND hwnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime)
    {
      TOOLATEINFO *ptli = reinterpret_cast<TOOLATEINFO*>(
        GetWindowLongPtr(hwnd, GWLP_USERDATA));
      if (ptli) {
        ptli->fTimedOut = TRUE;
        if (ptli->hwndReenable) {
           EnableWindow(ptli->hwndReenable, TRUE);
        }
        PostQuitMessage(42);
      }
    }
    
    int TimedMessageBox(HWND hwndOwner, LPCTSTR ptszText,
        LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout)
    {
      TOOLATEINFO tli;
      tli.fTimedOut = FALSE;
      BOOL fWasEnabled = hwndOwner && IsWindowEnabled(hwndOwner);
      tli.hwndReenable = fWasEnabled ? hwndOwner : NULL;
    
      HWND hwndScratch = CreateScratchWindow(hwndOwner, DefWindowProc);
      if (hwndScratch) {
          SetWindowLongPtr(hwndScratch, GWLP_USERDATA,
                           reinterpret_cast<LPARAM>(&tli));
          SetTimer(hwndScratch, IDT_TOOLATE, dwTimeout, MsgBoxTooLateProc);
      }
      int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType);
      if (hwndScratch) {
        KillTimer(hwndScratch, IDT_TOOLATE);
        if (tli.fTimedOut) { // We timed out
          MSG msg;
          // Eat the fake WM_QUIT message we generated
          PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
          iResult = -1;
        }
        DestroyWindow(hwndScratch);
      }
      return iResult;
    }
    
    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        TimedMessageBox(hwnd, TEXT("text"), TEXT("caption"),
                        MB_OK, 2000);
        break;
      }
    }
    
    // add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    
    // add to InitApp
        RegisterScratchWindowClass();
    

    This is basically the same as the previous cheap version, just with slightly different bookkeeping.

    The state of the timed message box is kept in the structure TOOLATEINFO. But how to pass this state to the timer callback? You can't pass any parameters to timer callbacks.

    Aha, but timer callbacks do get a window handle. But as we discovered a few days ago, we can't just hang the callback off the hwndOwner window because we don't know how to pick a timer ID that doesn't conflict with an existing one.

    The solution: Hang it on a window of our own window creation. That way, we get a whole new space of timer IDs to play in, separate from the timer IDs that belong to hwndOwner. The scratch window is a convenient window to use. We don't pass an interesting window procedure to CreateScratchWindow because there is no need; all we wanted was a window to own our timer.

  • The Old New Thing

    Raymond's random walk, from Swedish designers to Mr. Monkey

    • 9 Comments

    My random walk began at Svenska Dagbladet and the article Svensk designer upprör skottar, about Swedish designer Johanna Larson whose T-shirt depicting the traffic-cone-wearing Duke of Wellington has sparked a debate in the city.

    The article mentions that the tradition of placing a traffic cone on the head of the Duke is twenty years old, and my search for further information on this ongoing prank led me to Richard Leyton's musings on whether the traffic cone should be glued in place permanently.

    At the end of his entry, he links to the wonderful web site of Mr Monkey and in particular Mr Monkey's visit to the statue to see it for himself.

    The Mr Monkey web site is one of the hidden treasures of the Internet. Sure, it may not be entirely original, but Mr Monkey himself is so goofy-looking, the pictures are for the most part quite well-composed, and the subtitles are the icing on the cake.

  • The Old New Thing

    The bonus window bytes at GWLP_USERDATA

    • 35 Comments

    The window manager provides a pointer-sized chunk of storage you can access via the GWLP_USERDATA constant. You pass it to the GetWindowLongPtr function and the SetWindowLongPtr function to read and write that value. Most of the time, all you need to attach to a window is a single pointer value anyway, so the free memory in GWLP_USERDATA is all you need.

    Note that this value, like the other window extra bytes and the messages in the WM_USER range, belongs to the window class and not to the code that creates the window.

  • The Old New Thing

    Curling proves itself much more exciting than hockey this year

    • 14 Comments

    NPR reports on an amazing last-rock shot at the Canadian women's curling championship (includes link to CBC video). It's odd hearing Melissa Block discussing curling in decidedly non-curling terms (for the benefit of the decidedly non-curling US listenership). One of the advantages of living in the Seattle area is that you can get all the curling coverage you want from the CBC just over the border.

    The National Post has a report loaded with curling jargon, for those who prefer their curling coverage filled with discussion of draw weight, stolen deuces, and in-turns.

  • The Old New Thing

    The scratch window

    • 14 Comments

    Sometimes you need a quick and dirty window and you don't want to go through all the hassle of registering a class for it. For example, you might need a window to do a brief snippet of DDE, or you just need a window to own a message box.

    To save yourself the trouble of registering a class for every single weenie thing you might need a window for, you can get lazy and register a single "scratch window" class and simply subclass it on an as-needed basis.

    ATOM RegisterScratchWindowClass(void)
    {
      WNDCLASS wc = {
            0,                              // style
            DefWindowProc,                  // lpfnWndProc
            0,                              // cbClsExtra
            0,                              // cbWndExtra
            g_hinst,                        // this file's HINSTANCE
            NULL,                           // hIcon
            LoadCursor(NULL, IDC_ARROW),    // hCursor
            (HBRUSH)(COLOR_BTNFACE+1),      // hbrBackground
            NULL,                           // lpszMenuName
            TEXT("Scratch"),                // lpszClassName
      };
    
      return RegisterClass(&wc);
    }
    
    HWND
    CreateScratchWindow(HWND hwndParent, WNDPROC wp)
    {
      HWND hwnd;
      hwnd = CreateWindow(TEXT("Scratch"), NULL,
                          hwndParent ? WS_CHILD : WS_OVERLAPPED,
    	              0, 0, 0, 0, hwndParent, NULL, NULL, NULL);
      if (hwnd) {
        SubclassWindow(hwnd, wp);
      }
      return hwnd;
    }
    

    Now if you need a quick one-off window, you can just create a scratch window instead of creating a custom window class just to handle that specific task.

    We'll see the scratch window in action soon.

  • The Old New Thing

    Suggestion Box 2

    • 191 Comments

    Post suggestions for future topics here instead of posting off-topic comments. Note that the suggestion box is emptied and read periodically so don't be surprised if your suggestion vanishes. (Note also that I am under no obligation to accept any suggestion.)

    Topics I are more inclined to cover:

    • Windows history (particularly the Windows 95 era).
    • Windows user interface programming in Win32, and shell programming in particular.
    • General programming topics (selectively).
    • Issues of general interest.
    • My personal hobbies.

    Topics I am not inclined to cover:

    • The blog software itself.  You can send feedback about .Text to its author, Scott Watermasysk.
    • Internet Explorer. You can try the IE folks.
    • Visual Studio.  You can try one of the Visual Studio blogs.
    • Managed code. This is not a .NET blog. I do not work on .NET technologies. As far as .NET is concerned, I'm just another programmer like you. Occasionally I touch a .NET-related topic, but I do not bring any expertise to the subject.
    • Non-software Microsoft topics, such as product support policies, marketing tactics, and hiring policy.
    • Microsoft software that isn't Windows. (Exchange, Office, ...)
    • Windows topics outside user interface programming. (Plug and Play, Terminal Services, Windows Messenger, Outlook Express, SQL, IIS, remoting, SOA...)
    • User interface programming in anything other than Win32. (Because I know nothing about it.)
    • Debugging a specific problem. (Not of general interest.)
    • Legal issues.
    • Predictions for the future. (What's the title of this blog again?)

    (Due to the way the blog server is set up, a new suggestion box gets set up every 30 days, assuming I don't forget to create a new one. If I forget, you can send me a reminder via the Contact page. You can also peek at the previous suggestion box.)

Page 371 of 439 (4,381 items) «369370371372373»