• The Old New Thing

    Dragging a shell object, part 1: Getting the IDataObject

    • 20 Comments

    The shell gives you the IDataObject; all you have to do is drag it around. (This is the first of a five-part series.)

    Start with the scratch program, and add the function GetUIObjectOfFile from an earlier article. Also, change the calls to CoInitialize and CoUninitialize to OleInitialize and OleUninitialize, respectively, since we're now going to be using full-on OLE and not just COM.

    In order to initiate a drag/drop operation, we need a drop source:

    class CDropSource : public IDropSource
    {
    public:
      // *** IUnknown ***
      STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
      STDMETHODIMP_(ULONG) AddRef();
      STDMETHODIMP_(ULONG) Release();
    
      // *** IDropSource ***
      STDMETHODIMP QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState);
      STDMETHODIMP GiveFeedback(DWORD dwEffect);
    
      CDropSource() : m_cRef(1) { }
    private:
      ULONG m_cRef;
    };
    
    HRESULT CDropSource::QueryInterface(REFIID riid, void **ppv)
    {
      IUnknown *punk = NULL;
      if (riid == IID_IUnknown) {
        punk = static_cast<IUnknown*>(this);
      } else if (riid == IID_IDropSource) {
        punk = static_cast<IDropSource*>(this);
      }
    
      *ppv = punk;
      if (punk) {
        punk->AddRef();
        return S_OK;
      } else {
        return E_NOINTERFACE;
      }
    }
    
    ULONG CDropSource::AddRef()
    {
      return ++m_cRef;
    }
    
    ULONG CDropSource::Release()
    {
      ULONG cRef = --m_cRef;
      if (cRef == 0) delete this;
      return cRef;
    }
    
    HRESULT CDropSource::QueryContinueDrag(
              BOOL fEscapePressed, DWORD grfKeyState)
    {
      if (fEscapePressed) return DRAGDROP_S_CANCEL;
    
      // [Update: missing paren repaired, 7 Dec]
      if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON)))
        return DRAGDROP_S_DROP;
    
      return S_OK;
    }
    
    HRESULT CDropSource::GiveFeedback(DWORD dwEffect)
    {
      return DRAGDROP_S_USEDEFAULTCURSORS;
    }
    

    As you can see, this drop source is extraordinarily boring. Even the interesting methods are uninteresting.

    The IDropSource::QueryContinueDrag method is pretty much boilerplate. If the Escape key was pressed, then cancel the drag/drop operation. If the mouse buttons are released, then complete the operation. Otherwise, continue the operation.

    The IDropSource::GiveFeedback method is even less interesting. It merely returns DRAGDROP_S_USEDEFAULTCURSORS to indicate that it wants default drag feedback.

    Believe it or not, we now have everything we need to drag a file.

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
    {
      IDataObject *pdto;
      // In a real program of course
      // you wouldn't use a hard-coded path.
      // [comment added 11am because apparently some
      // people thought this wasn't self-evident.]
      if (SUCCEEDED(GetUIObjectOfFile(hwnd,
                        L"C:\\Windows\\clock.avi",
    		    IID_IDataObject, (void**)&pdto))) {
        IDropSource *pds = new CDropSource();
        if (pds) {
          DWORD dwEffect;
          DoDragDrop(pdto, pds, DROPEFFECT_COPY | DROPEFFECT_LINK,
                     &dwEffect);
          pds->Release();
        }
        pdto->Release();
      }
    }
    
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
    

    To drag an object, you need two things, a data object and a drop source. We created our drop source above, and the data object comes from the shell. All that's left to do is start the drag/drop operation by calling the DoDragDrop function.

    Notice that we specify that the permitted operations are DROPEFFECT_COPY and DROPEFFECT_LINK. We specifically disallow DROPEFFECT_MOVE because this program doesn't present a folder-like window; the user has no expectation that the drag/drop will result in a Move operation.

    Next time, adding Move support, just to see how it works.

  • The Old New Thing

    Why are documents printed out of order when you multiselect and choose "Print"?

    • 26 Comments

    If you select say five files and then right-click them and choose "Print", they tend to print in a random order. Why is that?

    The shell invokes the Print verb on each file in turn, and depending on how the program responsible for printing the document is registered, one of several things can happen.

    • Most commonly, the program that prints the document registered a simple command line under the shell\print\command registry key. In this case, the program is launched five times, each with a different file. All these print commands are now racing to the printer and it's a question of which copy of the program submits its print job first that determines the order in which they come out of the printer. (You're probably going to see the shortest and simplest documents come out first since they take less time to render.)
    • Occasionally, the program that prints the document registered a DDE verb under the shell\print\ddeexec registry key. In this case, one copy of the program is launched and it is given each filename one at a time. What it does with those filenames is now up to the program. If the program supports background printing, it will probably shunt the printing of the document onto a background thread, and now you're roughly in the same fix as the previous scenario: Five background threads each racing to see who can submit their print job first.
    • Extremely rarely, the program that prints the document registered a drop handler under the shell\print\DropTarget key. In this case, the drop target is instantiated and is given the list of files. It is then up to the drop target to decide what to do with the documents.

    These three ways of registering print actions are described in the MSDN documentation on verbs and file associations.

    [Update: 7:30am, fixed title.]

  • The Old New Thing

    Why did Windows 95 run the timer at 55ms?

    • 32 Comments

    The story behind the 55ms timer tick rate goes all the way back to the original IBM PC BIOS. The original IBM PC used a 1.19MHz crystal, and 65536 cycles at 1.19MHz equals approximately 55ms. (More accurately, it was more like 1.19318MHz and 54.92ms.)

    But that just pushes the question to another level. Why 1.19...MHz, then?

    With that clock rate, 216 ticks equals approximately 3600 seconds, which is one hour. (If you do the math it's more like 3599.59 seconds.) [Update: 4pm, change 232 to 216; what was I thinking?]

    What's so special about one hour?

    The BIOS checked once an hour to see whether the clock has crossed midnight. When it did, it needed to increment the date. Making the hourly check happen precisely when a 16-bit tick count overflowed saved a few valuable bytes in the BIOS.

    Another reason for the 1.19MHz clock speed was that it was exactly one quarter of the original CPU speed, namely 4.77MHz, which was in turn 4/3 times the NTSC color burst frequency of 3.5MHz. Recall that back in these days, personal computers sent their video output to a television set. Monitors were for the rich kids. Using a timer related to the video output signal saved a few dollars on the motherboard.

    Calvin Hsia has another view of the story behind the 4.77MHz clock.

    (Penny-pinching was very common at this time. The Apple ][ had its own share of penny-saving hijinks.)

  • The Old New Thing

    What is the purpose of the bmPlanes member of the BITMAP structure?

    • 21 Comments

    Many bitmap-related structures in Windows have a field called "planes". For example the BITMAPINFOHEADER structure has a biPlanes member (which must be set to 1). The BITMAP structure has a field called bmPlanes. What's the deal with that field?

    The EGA video adapter supported 16 simultaneous colors. This was an enormous improvement over the CGA, which supported only four colors. If you have 16 colors, then you need four bits per pixel. You would think that the encoding would be to have the each byte of video memory encode two pixels, one in the bottom four bits and one in the top four. But for technical reasons, the structure of video memory was not that simple.

    Instead of putting the bits for a single pixel next to each other, the color channels were each split into their own monochrome bitmap. In other words, the pixels were sliced "the other way":

    0 0 0 0 1 1 1 1 | 0F
    0 0 1 1 0 0 1 1 | 33
    0 1 0 1 0 1 0 1 | 55
    0 1 1 0 0 1 1 0 | 66

    0 3 5 6 8 B D E

    Suppose you wanted to display eight pixels, with colors { 0, 3, 5, 6, 8 B, D, E } above. Instead of storing the nibbles in that order, slice the nibbles apart into their component bits and collect all the bits from the same position together. In other words, read the bits across rather than down.

    In the default 16-color palette, the colors were assigned so that bit 0 was the blue channel, bit 1 was the green channel, bit 2 was the red channel, and bit 3 was the intensity channel. With this interpretation, the four slices can be interpreted as the "intensity plane", the "red plane", the "green plane" and the "blue plane". For the last three planes, you can imagine that each one represents what you would see if only the corresponding electron gun were firing.

    Since this was the native color format for EGA, there needed to be a way to express this color format in the BITMAP structure so that device-dependent bitmaps could be represented by Windows.

    Thus was born the planar color format. For 16-color planar bitmaps, the number of planes is four and the number of bits per pixel is one.

  • The Old New Thing

    What's the difference between GetKeyState and GetAsyncKeyState?

    • 14 Comments

    I've seen some confusion over the difference between the GetKeyState function and the GetAsyncKeyState function.

    GetKeyState returns the virtual key state. In other words, GetKeyState reports the state of the keyboard based on the messages you have retrieved from your input queue. This is not the same as the physical keyboard state:

    • If the user has typed ahead, GetKeyState doesn't report those changes until you use the PeekMessage function or the GetMessage function to retrieve the message from your input queue.

    • If the user has switched to another program, then the GetKeyState function will not see the input that the user typed into that other program, since that input was not sent to your input queue.

    When should you use GetKeyState and when should you use GetAsyncKeyState?

    For user interface work, you nearly always want GetKeyState.

    If you are responding to an input message and want to know what keys were pressed at the time that input was generated, then you want to use GetKeyState. For example, if you want to distinguish a left-click of the mouse from an Alt+LeftClick, you must use GetKeyState to query the state of the Alt key (known as VK_MENU for historical reasons). That's because you want to know whether the Alt key was down when the user clicked the mouse, not whether the key is down this very instant. Whether the user released the Alt key between the time they clicked and the time you processed the message is irrelevant. You care that the Alt key was down at the time of the click.

    Note that if you are implementing a context menu handler, then you shouldn't be using either GetKeyState or GetAsyncKeyState, because the context menu can be invoked programmatically without any user action. For IContextMenu::QueryContextMenu, you should test for the CMF_EXTENDEDVERBS flag to detect whether you should display extended commands rather than querying the keyboard directly. Similarly, for IContextMenu::InvokeCommand, you should be testing the CMIC_MASK_CONTROL_DOWN and CMIC_MASK_SHIFT_DOWN flags if you want to alter your behavior based on the shift states.

    Given this primer on the difference between GetKeyState and GetAsyncKeyState, you can now explain the behavior this user is seeing.

    [Updated: 1 Dec 2004, minor typo.]

  • The Old New Thing

    Why doesn't the RunAs program accept a password on the command line?

    • 57 Comments

    The RunAs program demands that you type the password manually. Why doesn't it accept a password on the command line?

    This was a conscious decision. If it were possible to pass the password on the command line, people would start embedding passwords into batch files and logon scripts, which is laughably insecure.

    In other words, the feature is missing to remove the temptation to use the feature insecurely.

    If this offends you and you want to be insecure and pass the password on the command line anyway (for everyone to see in the command window title bar), you can write your own program that calls the CreateProcessWithLogonW function.

    (I'm told that there is a tool available for download which domain administrators might find useful, though it solves a slightly different problem.)

  • The Old New Thing

    Simple things you can do with the ShellExecuteEx function

    • 29 Comments

    Here's a tiny little program:

    #include <windows.h>
    #include <shellapi.h>
    
    int __cdecl main(int argc, char **argv)
    {
      if (argc == 3) {
        SHELLEXECUTEINFO sei = { sizeof(sei) };
        sei.fMask = SEE_MASK_FLAG_DDEWAIT;
        sei.nShow = SW_SHOWNORMAL; // added 27 Nov
        sei.lpVerb = argv[1];
        sei.lpFile = argv[2];
        ShellExecuteEx(&sei);
      }
      return 0;
    }
    

    This is a little program that takes two parameters, the first being the verb and the second the file upon which to execute the verb. Notice that since we exit immediately, we need to set the SEE_MASK_FLAG_DDEWAIT flag: Normally, the ShellExecuteEx function assumes that there will be a message pump running after it returns. This allows it to return quickly and continue any necessary DDE conversations as the responses arrive from the DDE server. But if the thread is exiting or if the thread is not a GUI thread (both of which are true here), you want to suppress this behavior because there is no message pump around to complete the DDE conversation. Setting the SEE_MASK_FLAG_DDEWAIT flag indicates that the ShellExecuteEx function should finish its DDE conversation before it returns.

    Anyway, I wrote this little program to illustrate two of the canonical verbs that you can use. It seems the people don't realize that ShellExecuteEx can be used to perform these actions, since it gets asked a lot...

    • shex find %windir%
      Opens the search window with a specified folder as the default "Search in" location.
    • shex openas C:\AUTOEXEC.BAT
      Displays the "Open with" dialog for a file.
  • The Old New Thing

    A sample of desktop icon text effects

    • 66 Comments

    It seems everybody and his brother has an obvious solution to the desktop background problem. Of course, none of these people actually tested their solution to see if it actually was usable. Because geniuses don't need to test their grand pronouncements. That's why they're called geniuses.

    Let's see how well these geniuses fared. I sat down and implemented their brilliant suggestions since I am myself not a genius.

    From left to right, the effects are as follows:

    • Solid background + text. (This is what Windows uses.)
    • Black text, no effects. (As a baseline.)
    • Xor.
    • Simple drop shadow, drawing black at (+1,+1), black at (0,0) then white at (0,0).
    • One-pixel wide outline.
    • Two-pixel wide outline.
    • 50% alpha.

    To my untrained eye, the only readable ones are the first one and the "two-pixel wide outline" (which nobody suggested but which I just made up). The enormously popular Xor is completely useless.

    Of course, all but the first three are expensive operations, requiring multiple drawing passes, so they are unsuitable for the "high performance" drawing scenario that I described in the original article.

    Therefore, the only drawing method that looks good and is also fast is the first one. And it so happens that's what Windows uses when it needs to be fast.

  • The Old New Thing

    Why can't you drop directly onto a taskbar button?

    • 73 Comments

    If you drag a object and drop it onto a taskbar button, you get an error message that says,

    You cannot drop an item onto a button on the taskbar.

    However, if you drag the item over a button without releasing the mouse button, the window will open after a moment, allowing you to drop the item inside the window.

    Why doesn't the taskbar let you drop directly onto a taskbar button?

    Ideally, if the taskbar receives a IDropTarget::Drop, it could do something like this:

    // imaginary code
    IDropTarget *pdt;
    if (SUCCEEDED(GetDropTargetFromWindow(hwndButton, &pdt))) {
      pdt->Drop(...);
      pdt->Release();
    }
    

    (Warning: I said "something like" this. Forwarding a drop is actually more complicated than this.)

    The reason why the taskbar doesn't do this is that there is no such function GetDropTargetFromWindow function. The taskbar can't forward the drop operation even if it wanted to.

    Why is there no GetDropTargetFromWindow function? I have no idea. You'll have to ask the OLE folks. If I had to guess (and I know I have to because you folks will just keep badgering me until I come up with a guess), it's because that would create the problem of how to prevent somebody from screwing with a program by grabbing its drop target and never releasing it.

    Now of course people will criticize my explanation, so I'm going to say it again: I don't know the answer. I'm just guessing. My guess is probably wrong.

  • The Old New Thing

    Why do folders like "My Pictures" come back after I delete them?

    • 47 Comments

    Some people are offended by the special folders like "My Pictures" and "My Music" and delete them, only to find them being re-created. What's going on?

    Windows itself is okay with you deleting those folders. Some corporations, for example, remove those folders from their employees' machines because they don't want the employees looking at pictures and listening to music while on the job. (Go figure.) And if the folder is deleted, Windows doesn't try to bring it back.

    However, any program can ask for the folder to be re-created. For example, if a program passes the CSIDL_FLAG_CREATE to the SHGetFolderPath function, then Windows will create the specified folder if it doesn't exist. Similarly if it passes pass fCreate = TRUE to the SHGetSpecialFolderPath function.

    If it really troubles you, you can set a creation audit on the My Pictures folder and see which program is re-creating it.

Page 378 of 437 (4,361 items) «376377378379380»