Holy cow, I wrote a book!
I dreamed that one of my friends had made the U.S. cycling team. (Perhaps because everybody else got busted for doping.) Even more implausibly, I also made the team.
To celebrate, he challenged me to a short race. The path ran along a river, in which a medium-sized boat was setting sail. Our bicycles somehow could fly (which we considered perfectly normal) and we were flying over the boat, just about keeping pace with it.
The boat reversed direction many times, and we reversed along with it. At one of the reversals, I thought, "I could take a shortcut if I kept going straight," but I must've lost even/odd count because I flew off the boat... heading back to the starting line.
Bonus weirdness: For some reason, we were in Sweden, and the race commentator saw a school labeled Gymnasium and made some remark about repurposing buildings left over from the Olympics.
Today's Little Program takes a fully-qualified file name from the command line and puts that file onto the clipboard. Once there, you can paste it into an Explorer window, or into an email message, or a word processing document, or anybody else who understands shell data objects.
#include <windows.h> #include <shlobj.h> #include <atlbase.h> #include <shlobj.h> class COleInitialize { public: COleInitialize() : m_hr(OleInitialize(NULL)) { } ~COleInitialize() { if (SUCCEEDED(m_hr)) OleUninitialize(); } operator HRESULT() const { return m_hr; } HRESULT m_hr; }; // GetUIObjectOfFile incorporated by reference int __cdecl wmain(int argc, PWSTR argv[]) { COleInitialize init; CComPtr<IDataObject> spdto; if (SUCCEEDED(init) && argc == 2 && SUCCEEDED(GetUIObjectOfFile(nullptr, argv[1], IID_PPV_ARGS(&spdto))) && SUCCEEDED(OleSetClipboard(spdto)) && SUCCEEDED(OleFlushClipboard())) { // success } return 0; }
The COleInitialize class is just the OLE counterpart to the CCoInitialize class we saw some time ago.
COleInitialize
CCoInitialize
All the program does is take the file name on the command line, asks the shell for the corresponding data object, then puts that object onto the clipboard, erasing what was there before.
Once the data is on the clipboard, our job is done so we exit.
No, wait! If you exit while your application has data on the clipboard, that clipboard data may be lost. The documentation for OleSetClipboard notes:
OleSetClipboard
If you need to leave the data on the clipboard after your application is closed, you should call OleFlushClipboard rather than calling OleSetClipboard with a NULL parameter value.
Therefore, we stick in a call to OleFlushClipboard before exiting. This forces any delay-rendered content to be rendered immediately, because we ain't gonna be around to delay-render it no more.
OleFlushClipboard
Note that the file on the command line must be fully-qualified, because we pass it straight to GetUIObjectOfFile, which expects a fully-qualified path. Fixing the program to allow relative paths (and to actually print error messages and stuff) is left as an exercise, because Little Programs don't deal with annoying details like error checking and reporting.
GetUIObjectOfFile
Last time, we looked at the confusingly-named WM_UPDATEUISTATE and WM_CHANGEUISTATE messages. But how does the whole indicator thingie get off the ground?
WM_UPDATEUISTATE
WM_CHANGEUISTATE
The default state for a window is to show all indicators. But as a special trick, the dialog manager will send a WM_UPDATEUISTATE message with UIS_INITIALIZE after the dialog has been initialized, which turns off the indicators if the last input event was a mouse event. This is its way of inferring whether the dialog box was triggered by a mouse or keyboard action and setting the initial indicators accordingly. (Note that if the user checked Underline keyboard shortcuts and access keys, then the dialog manager leaves the indicators enabled regardless of the last input event.)
UIS_INITIALIZE
That special WM_UPDATEUISTATE message is what gives dialog boxes the extra special feature of hiding the keyboard accelerators until you use the keyboard.
But notice that only the dialog manager does this. If you want this behavior in your own non-dialog windows, you will need to send the message yourself.
BOOL MyWindow::OnCreate(...) { ... create and initialize any child windows ... // initialize indicators BOOL fAlwaysUnderline = FALSE; SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &fAlwaysUnderline, 0); if (!fAlwaysUnderline) { SendMessage(this->m_hwnd, WM_UPDATEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0); } }
Exercise: Why is it important to create and initialize the child windows before sending the WM_UPDATEUISTATE message?
Exercise: Why can't the window manager do this automatically after WM_CREATE returns?
WM_CREATE
Exercise: Explain the behavior this customer observes.
We have a dialog box with three buttons. Sometimes the dialog displays underlines for the hotkeys, and sometimes it doesn't. I know about the feature which hides keyboard accelerators by default, but that doesn't explain why the setting gets ignored sometimes. The first time I show the dialog in my program, I get the underlines, but the second and subsequent times, I do not.
In Korea, it is generally believed that leaving a fan on in an enclosed room can be fatal. Ken Jennings looks at cultural superstitions and wrote a Slate article focusing on the scourge of Korean fan death.
My mother told me that handling cellophane tape makes you sterile. Though that may have just been her way of getting me to stop playing with cellophane tape.
What strange cultural superstitions exist in your part of the world? (Of course, this is a bit of an unfair question, because if you genuinely believe it, then you won't recognize it as a strange cultural superstition!)
Clarification: Please reply in the spirit of the article. Keep it fun.
I always get confused by the WM_UPDATEUISTATE and WM_CHANGEUISTATE messages, and I have to go figure them out each time I need to mess with them. So this time, I'm going to write it down so I don't forget. Because the act of writing it down helps me to remember.
It's like in school, where the teacher says, "This is a closed-book, closed-notes exam, but you are allowed to bring one piece of standard 8½″×11″ paper with you, on which you can write anything you like. No funny business." You work really hard to create the ultimate sheet of paper to bring to the exam, and then it turns out that during the exam, you barely refer to it at all. Because the act of deciding what to put on the cheat sheet made you remember the material.
Part of the problem with the messages WM_UPDATEUISTATE and WM_CHANGEUISTATE is their confusing names, because to most people update and change are basically the same concept. The difference is the direction the message travels. Before we look at that, let's look at the mysterious WPARAM.
WPARAM
The WPARAM specifies what action you want to perform (initialize, set, or clear) and the target of the action (focus, accelerators, or both).
UIS_SET
UIS_CLEAR
Setting a flag hides the corresponding indicator. For example, if you have a UIS_SET for UISF_HIDEFOCUS, that means that you want to hide focus indicators.
UISF_HIDEFOCUS
Clearing a flag shows the corresponding indicator. For example, if you have a UIS_CLEAR for UISF_HIDEFOCUS, that means that you want to show focus indicators.
Yes, it's a bit of a double-negative situation.
Each window has its own internal state that remembers which indicators have been hidden for that window. You can query this state by sending the window a WM_QUERYUISTATE message.
WM_QUERYUISTATE
The WM_UPDATEUISTATE message travels down the tree: When a window receives the WM_UPDATEUISTATE message, it updates its state according to the WPARAM and then forwards the message to its children. Therefore, if you want to change the state for an entire window tree, you can send the WM_UPDATEUISTATE message to the top-level window, and the message will be delivered to that window and all its children.
It's called update because it says, "Okay, listen up everybody, this is what we're going to do."
The WM_CHANGEUISTATE message is more like a change request. It travels up the tree: When a window receives the message, it sees if the state being requested matches the window's current state. If so, then processing stops since there is nothing to change. Otherwise, the window forwards the message to its parent. The idea here is to push the change request up the tree until it finds the top-level window.
If a top-level window receives a WM_CHANGEUISTATE message for a state change that actually changes something, it turns around and sends itself a WM_UPDATEUISTATE message, which as we saw before, tells the entire window tree to set its indicator state to the value specified.
Okay, let's draw a picture. Suppose we have a top-level window with two children, and suppose that everybody starts out with all indicators hidden.
Window B decides that it wants to show accelerators, say because the user tapped the Alt key. It sends itself a WM_CHANGEUISTATE message with a wParam of MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL).
wParam
MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL)
The WM_CHANGEUISTATE message handler for Window B sees that the UISF_HIDEACCEL flag is set, so the clear action is meaningful. It forwards the request to its parent, Window A.
UISF_HIDEACCEL
The WM_CHANGEUISTATE message handler for Window A also sees that the UISF_HIDEACCEL flag is set, so the clear action is meaningful. Since it has no parent, Window A converts the WM_CHANGEUISTATE message to a WM_UPDATEUISTATE message and sends it to itself.
The WM_UPDATEUISTATE message handler for Window A sees that it is being told to clear the UISF_HIDEACCEL flag, so it clears the flag and then forwards the mesage to both its children.
Each of the child windows B and C receive the WM_UPDATEUISTATE message and see that they are also being told to clear the UISF_HIDEACCEL flag, so they do so. Those windows have no children of their own, so message processing stops. By this mechanism, Window B has managed to convince all the other windows in the hierarchy to clear the UISF_HIDEACCEL flag.
Now, suppose that Window C also decides to clear the accelerator indicator. It does the same thing as Window B and sends itself a WM_CHANGEUISTATE message with a wParam of MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL). This time, the WM_CHANGEUISTATE message handler for Window C sees that the UISF_HIDEACCEL flag is already clear, so the clear action is redundant. Message processing stops.
These two examples show the flow of the UI state change messages. When somebody wants to suggest a change to the UI state, they send themselves a WM_CHANGEUISTATE message with a description of what they want to change. The above algorithm then kicks in to decide whether the change is meaningful, and if so, it notifies all the other windows in the hierarchy about the new state.
Next time, we'll look at how this whole indicator state thing gets off the ground.
It looks like the Visio blog populated a sample organizational chart with pictures of Microsoft employees, and I am now Oliver Lee, Director of Strategic Planning.
My secret identity has been revealed. I'm moonlighting at Contoso.
What does GDI use BITMAPINFOHEADER.biXPelsPerMeter and SetBitmapDimensionEx for?
BITMAPINFOHEADER.biXPelsPerMeter
SetBitmapDimensionEx
Nothing.
The BITMAPINFOHEADER.biXPelsPerMeter and BITMAPINFOHEADER.biYPelsPerMeter are completely ignored by GDI when loading a bitmap. The values are there for the benefit of image-editing programs who want to record additional information about the bitmap, but GDI ignores them.
BITMAPINFOHEADER.biYPelsPerMeter
Similarly, the SetBitmapDimensionEx and GetBitmapDimensionEx functions update a SIZE structure associated with each bitmap, but GDI does nothing with the values, aside from initializing them to zero when the bitmap is created.
GetBitmapDimensionEx
SIZE
The value is there so that, for example, a program which puts a bitmap on the clipboard can specify the recommended physical dimensions of the bitmap, in order to help another program that reads the bitmap from the clipboard (for example, a word processor) decide how big to resize the bitmap when it is pasted.
Whether any programs actually do this sort of thing I do not know.
But that's what it's there for, in case anybody wanted to do it.
Remember, the term Microspeak is not tightly scoped to mean jargon used only at Microsoft. It's jargon used at Microsoft more often than in general usage. Today, it's a term that you really need to master if you want to talk with others about project planning.
To book a feature is to commit to implementing the feature, including assigning resources to get it done. This means finding designers to design the feature, developers to implement it, and testers to test it, as well as (the hardest part) finding time in the schedule to do it. The resource that is in shortest supply is usually time, since there is no way to create more of it.
More generally, a resource is booked when it is committed to doing something. This is a natural extension of the concept of booking a room in a hotel or a seat on a train.
Here are some citations.
We will be using the Widget framework that Bob is booked to finish in July.
There are no resources booked for enhancing the Widget framework this release cycle.
The Widget team knows that we need this feature from them, but they haven't booked it yet, so we need to develop a fallback plan.
The term is in general use, but for some reason, Microspeak uses it almost exclusively to describe the commitment to completing a particular piece of work by a particular date. Instead of saying that the work is committed or scheduled or confirmed, we say that it is booked. (If you use one of the other words, people may ask for clarification. For example, if you say that it is committed, some people might think you mean that the change has already been submitted to the source code repository.)
Curiously the antonym of booked is not unbooked. If a feature has no resources assigned to it, the preferred term is unfunded.
I dreamed that a colleague and I were looking for a copy of a TN3270 emulator in order to investigate a bug. The search took us into an abandoned-looking Building 5. But upon entering, we discovered that Building 5 was actually the secret lair of all the Administrative Assistants.
Oh, and we eventually found the bug in TN3270. It was an application bug that caused it to leave the laser on for too long in one spot, causing it to burn your thumb.
Today's Little Program takes a rectangular portion of another application and continuously replicates it in its own client area. You might want to do this if you want to monitor a portion of an application like a custom progress bar, and the application doesn't use the Windows 7 taskbar progress indicator feature. (Maybe it's an old application.)
Take our scratch program and make the following changes:
#define STRICT #include <windows.h> #include <windowsx.h> #include <ole2.h> #include <commctrl.h> #include <shlwapi.h> #include <stdio.h> #include <dwmapi.h> HINSTANCE g_hinst; /* This application's HINSTANCE */ HWND g_hwndChild; /* Optional child window */ HTHUMBNAIL g_hthumb; BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { DWM_THUMBNAIL_PROPERTIES props = {}; HWND hwndTarget; if (sscanf(reinterpret_cast<PCSTR>(lpcs->lpCreateParams), "%p %ld %ld %ld %ld", &hwndTarget, &props.rcSource.left, &props.rcSource.top, &props.rcSource.right, &props.rcSource.bottom) == 5) { DwmRegisterThumbnail(hwnd, hwndTarget, &g_hthumb); props.dwFlags = DWM_TNP_VISIBLE | DWM_TNP_RECTSOURCE | DWM_TNP_RECTDESTINATION; props.rcDestination = props.rcSource; OffsetRect(&props.rcSource, -props.rcSource.left, -props.rcSource.top); props.fVisible = TRUE; DwmUpdateThumbnailProperties(g_hthumb, &props); } return TRUE; } void OnDestroy(HWND hwnd) { if (g_hthumb) DwmUnregisterThumbnail(g_hthumb); PostQuitMessage(0); } int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nShowCmd) { ... hwnd = CreateWindow( "Scratch", /* Class Name */ "Scratch", /* Title */ WS_OVERLAPPEDWINDOW, /* Style */ CW_USEDEFAULT, CW_USEDEFAULT, /* Position */ CW_USEDEFAULT, CW_USEDEFAULT, /* Size */ NULL, /* Parent */ NULL, /* No menu */ hinst, /* Instance */ lpCmdLine); ... }
Our Little Program passes its command line through to the WM_CREATE message, which parses it as a pointer (for Visual C++, a hex value with no 0x prefix) and four integers representing the left, top, right, and bottom coordinates a rectangle within that window. (For example, to get the upper left 100 pixels of the window, pass 0 0 100 100.) It creates a thumbnail from that window and positions it inside the scratch window.
0x
0 0 100 100
Use Spy or whatever program to get a window handle and run the progarm with the window handle and four integers (described above). A live slice of the window will appear in the scratch program.
Making it easier to select the target window and a rectangle from it is left as an exercise. This is just a Little Program.