• The Old New Thing

    People lie on surveys and focus groups, often unwittingly

    • 81 Comments

    Philip Su's discussion of early versions of Microsoft Money triggered a topic that had been sitting in the back of my mind for a while: That people lie on surveys and focus groups, often unwittingly. I can think of three types of lies offhand. (I'm not counting malicious lying; that is, intentional lying for the purpose of undermining the results of the survey or focus group.)

    First, people lie about the reasons why they do things.

    The majority of consumers who buy computers claim that personal finance management is one of the top three reasons they are purchasing a PC. They've been claiming this for more than a decade. But only somewhere around 2% of consumers end up using a personal finance manager.

    This is one of those unconscious lies. People claim that they want a computer to do their personal finances, to organize their recipes, to mail-merge their Christmas card labels.

    They are lying.

    Those are the things people wish they would use their computer for. That's before the reality hits them of how much work it is to track every expenditure, transcribe every recipe, type in every address. In reality, they end up using the computer to play video games, surf the web, and email jokes to each other.

    Just because people say they would do something doesn't mean they will. That leads to the second class of focus group lying: The polite lie, also known as "say what the sponsor wants to hear".

    The following story is true, but the names have been changed.

    A company conducted focus groups for their Product X, which had as its main competitor Product Q. They asked people who were using Product Q, "Why do you use Product Q instead of Product X?" The respondents gave their reasons: "Because Product Q has feature F," "Because Product Q performs G faster," "Because Product Q lets me do activity H." They added, "If Product X did all that and was cheaper, we'd switch to it."

    Armed with this valuable insight, the company expended time, effort, and money in adding feature F to Product X, making Product X do G faster, and adding the ability to do activity H. They lowered the price and sat back and waited for the customers to beat a path to their door.

    But the customers didn't come.

    Why not?

    Because the customers were lying. In reality, they had no intention of switching from Product Q to Product X at all. They grew up with Product Q, they were used to the way Product Q worked, they simply liked Product Q. Product Q had what in the hot bubble-days was called "mindshare", but what in older days was called "brand loyalty" or just "inertia".

    When asked to justify why they preferred Product Q, the people in the focus group couldn't say, "I don't know; I just like it." That would be perceived as an "unhelpful" answer, and besides it would be subconsciously admitting that they were being manipulated by Product Q's marketing! Instead, they made up reasons to justify their preference to themselves and consequently to the sponsor of the focus group.

    Result: Company wastes tremendous effort on the wrong thing.

    (Closely related to this is the phenomenon of saying—and even believing—"I'd pay ten bucks for that!" Yet when the opportunity arises to buy it for $10, you decline. I do this myself.)

    The third example of lying that occurred to me is the one where you don't even realize that you are contradicting yourself. My favorite example of this was a poll on the subject of congestion charging on highways in the United States. The idea behind congestion charging is to create a toll road and vary the cost of driving on the road depending on how heavy traffic is. Respondents were asked two questions:

    1. "If congestion charging were implemented in your area, do you think it would reduce traffic congestion?"
    2. "If congestion charging were implemented in your area, would you be less likely to drive during peak traffic hours?"

    Surprisingly, most people answered "No" to the first question and "Yes" to the second. But if you stop and think about it, if people avoid driving during peak traffic hours, then congestion would be reduced because there are fewer cars on the road. An answer of "Yes" to the second question logically implies an answer of "Yes" to the first question.

    (One may be able to explain this by arguing that, "Well, sure congestion charging would be effective for influencing my driving behavior, but I don't see how it would affect enough other people to make it worthwhile. I'm special." Sort of how most people rate themselves as above-average drivers.)

    What I believe happened was that people reacted by saying to themselves, "I am opposed to congestion charging," and concluding, "Therefore, I must do what I can to prevent it from happening." Proclaiming on surveys that it would never work is one way of accomplishing this.

    When I shared my brilliant theories with some of my colleagues, one of them, a program manager on the Office team, added his own observation (which I have edited slightly):

    A variation of two of the above observations that often shows up in the usability lab:

    A user has spent an hour battling with the software. At some point the user's expectation of how the software should behave (the "user model") diverged from the actual behavior. Consequently, the user couldn't predict what will happen next and is therefore having a horrible time making any progress on the task. (Usually, this is the fault of the software design unintentionally misleading the user—which is why we test things.) After many painful attempts, the user finally succeeds, gets hints, or is flat-out told how the feature works. Often, the user stares mutely at the monitor for five seconds, then says: "I suppose that makes sense."

    It's an odd combination of people wanting to give a helpful answer with people wanting to feel special. In this case, the user wants to say something nice about the software that any outside observer could clearly tell was broken. Additionally, the users (subconsciously) don't want to admit that they were wrong and don't understand the software.

    Usability participants also have a tendency to say "I'm being stupid" when those of us on the other side of the one-way glass are screaming "No you're not, the software is broken!" That's an interesting contrast—in some cases, pleading ignorance is a defense. In other cases, pleading mastery is. At the end of the day, you must ignore what the user said and base any conclusions on what they did.

    I'm sure there are other ways people subconsciously lie on surveys and focus groups, but those are the ones that came to mind.

    [Insignificant typos fixed, October 13.]

  • The Old New Thing

    What's the atom returned by RegisterClass useful for?

    • 8 Comments

    The RegisterClass and RegisterClassEx functions return an ATOM. What is that ATOM good for?

    The names of all registered window classes is kept in an atom table internal to USER32. The value returned by the class registration functions is that atom. You can also retrieve the atom for a window class by asking a window of that class for its class atom via GetClassWord(hwnd, GCW_ATOM).

    The atom can be converted to an integer atom via the MAKEINTATOM macro, which then can be used by functions that accept class names in the form of strings or atoms. The most common case is the lpClassName parameter to the CreateWindow macro and the CreateWindowEx function. Less commonly, you can also use it as the lpClassName parameter for the GetClassInfo and GetClassInfoEx functions. (Though why you would do this I can't figure out. In order to have the atom to pass to GetClassInfo in the first place, you must have registered the class (since that's what returns the atom), in which case why are you asking for information about a class that you registered?)

    To convert a class name to a class atom, you can create a dummy window of that class and then do the aforementioned GetClassWord(hwnd, GCW_ATOM). Or you can take advantage of the fact that the return value from the GetClassInfoEx function is the atom for the class, cast to a BOOL. This lets you do the conversion without having to create a dummy window. (Beware, however, that GetClassInfoEx's return value is not the atom on Windows 95-derived operating systems.)

    But what good is the atom?

    Not much, really. Sure, it saves you from having to pass a string to functions like CreateWindow, but all it did was replace a string with with an integer you now have to save in a global variable for later use. What used to be a string that you could hard-code is now an atom that you have to keep track of. Unclear that you actually won anything there.

    I guess you could use it to check quickly whether a window belongs to a particular class. You get the atom for that class (via GetClassInfo, say) and then get the atom for the window and compare them. But you can't cache the class atom since the class might get unregistered and then re-registered (which will give it a new atom number). And you can't prefetch the class atom since the class may not yet be registered at the point you prefetch it. (And as noted above, you can't cache the prefetched value anyway.) So this case is pretty much a non-starter anyway; you may as well use the GetClassName function and compare the resulting class name against the class you're looking for.

    In other words, window class atoms are an anachronism. Like replacement dialog box classes, it's one of those generalities of the Win32 API that never really got off the ground, but which must be carried forward for backwards compatibility.

    But at least now you know what they are.

    [Typos fixed October 12.]

  • The Old New Thing

    Why is there a separate GetSystemDirectory function?

    • 18 Comments

    If the system directory is always %windir%\SYSTEM32, why is there a special function to get it?

    Because it wasn't always that.

    For 16-bit programs on Windows NT, the system directory is %windir%\SYSTEM. That's also the name of the system directory for Windows 95-based systems and all the 16-bit versions of Windows.

    But even in the 16-bit world, if it was always %windir%\SYSTEM, why have a function for it?

    Because even in the 16-bit world, it wasn't always %windir%\SYSTEM.

    Back in the old days, you could run Windows directly over the network. All the system files were kept on the network server, and only the user's files were kept on the local machine. What's more, every single computer on the network used the same system directory on the server. There was only one copy of USER.EXE, for example, which everybody shared.

    Under this network-based Windows configuration, the system directory was a directory on a server somewhere (\\server\share\somewhere) and the Windows directory was a directory on the local machine (C:\WINDOWS). Clients did not have write permission into the shared system directory, but they did have permission to write into the Windows directory.

    That's why GetSystemDirectory is a separate function.

  • The Old New Thing

    Cooking for engineers

    • 22 Comments

    All geeks who enjoy cooking (and who have cable television) worship Alton Brown's program Good Eats. It's the cooking show for engineers.

    And recently I discovered a cooking blog that is designed for engineers, named, not surprisingly, Cooking for Engineers. Marvel at the elegance and beauty of the recipe diagrams. I have no idea whether the dishes are any good, but the recipes themselves are works of art.

    (And don't miss the analysis of orange juice shelf life.)

    Side remark: I found it somewhat odd that many Slashdotters responded to my spam graph of a few days ago with remarks like "This guy needs to get a hobby." I guess they don't consider cooking, knitting, or studying German, Swedish, or Mandarin to be valid hobbies.

  • The Old New Thing

    How to host an IContextMenu, part 11 - Composite extensions - composition

    • 6 Comments

    Okay, now that we have two context menu handlers we want to compose (namely, the "real" one from the shell namespace and a "fake" one that contains bonus commands we want to add), we can use merge them together by means of a composite context menu handler.

    The kernel of the composite context menu is to multiplex multiple context menus onto a single context menu handler, using the menu identifer offsets to route the commands.

    Everything else is just typing.

    class CCompositeContextMenu : public IContextMenu3
    {
    public:
      // *** IUnknown ***
      STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
      STDMETHODIMP_(ULONG) AddRef();
      STDMETHODIMP_(ULONG) Release();
    
      // *** IContextMenu ***
      STDMETHODIMP QueryContextMenu(HMENU hmenu,
                              UINT indexMenu, UINT idCmdFirst,
                              UINT idCmdLast, UINT uFlags);
      STDMETHODIMP InvokeCommand(
                              LPCMINVOKECOMMANDINFO lpici);
      STDMETHODIMP GetCommandString(
                              UINT_PTR    idCmd,
                              UINT        uType,
                              UINT      * pwReserved,
                              LPSTR       pszName,
                              UINT        cchMax);
    
      // *** IContextMenu2 ***
      STDMETHODIMP HandleMenuMsg(
                              UINT uMsg,
                              WPARAM wParam,
                              LPARAM lParam);
    
      // *** IContextMenu3 ***
      STDMETHODIMP HandleMenuMsg2(
                              UINT uMsg,
                              WPARAM wParam,
                              LPARAM lParam,
                              LRESULT* plResult);
    
      // Constructor
      static HRESULT Create(IContextMenu **rgpcm, UINT cpcm,
                            REFIID riid, void **ppv);
    
    private:
    
      HRESULT Initialize(IContextMenu **rgpcm, UINT cpcm);
      CCompositeContextMenu() : m_cRef(1), m_rgcmi(NULL), m_ccmi(0) { }
      ~CCompositeContextMenu();
    
      struct CONTEXTMENUINFO {
        IContextMenu *pcm;
        UINT cids;
      };
    
      HRESULT ReduceOrdinal(UINT_PTR *pidCmd, CONTEXTMENUINFO **ppcmi);
    
    private:
      ULONG m_cRef;
      CONTEXTMENUINFO *m_rgcmi;
      UINT m_ccmi;
    };
    

    The local structure CONTEXTMENUINFO contains information about each of the context menus that are part of our composite. We need to have the context menu pointer itself, as well as the number of menu identifiers consumed by that context menu by its IContextMenu::QueryContextMenu handler. We'll see why as we implement this class.

    HRESULT CCompositeContextMenu::Initialize(
        IContextMenu **rgpcm, UINT cpcm)
    {
      m_rgcmi = new CONTEXTMENUINFO[cpcm];
      if (!m_rgcmi) {
        return E_OUTOFMEMORY;
      }
    
      m_ccmi = cpcm;
      for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
        CONTEXTMENUINFO *pcmi = &m_rgcmi[icmi];
        pcmi->pcm = rgpcm[icmi];
        pcmi->pcm->AddRef();
        pcmi->cids = 0;
      }
    
      return S_OK;
    }
    

    Since a C++ constructor cannot fail, there are various conventions for how one handles failure during construction. One convention, which I use here, is to put the bulk of the work in an Initialize method, which can return an appropriate error code if the initialization fails.

    (Note that here I am assuming a non-throwing new operator.)

    Our initialization function allocates a bunch of CONTEXTMENUINFO structures and copies the IContextMenu pointers (and AddRefs them) for safekeeping. (Note that the m_ccmi member is not set until after we know that the memory allocation succeeded.)

    The destructor therefore undoes these operations.

    CCompositeContextMenu::~CCompositeContextMenu()
    {
      for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
        m_rgcmi[icmi].pcm->Release();
      }
      delete[] m_rgcmi;
    }
    

    (If you don't understand the significance of the [], here's a refresher.)

    The Create pattern you saw last time, so this shouldn't be too surprising.

    HRESULT CCompositeContextMenu::Create(IContextMenu **rgpcm, UINT cpcm,
                                          REFIID riid, void **ppv)
    {
      *ppv = NULL;
    
      HRESULT hr;
      CCompositeContextMenu *self = new CCompositeContextMenu();
      if (self) {
        if (SUCCEEDED(hr = self->Initialize(rgpcm, cpcm)) &&
            SUCCEEDED(hr = self->QueryInterface(riid, ppv))) {
          // success
        }
        self->Release();
      } else {
        hr = E_OUTOFMEMORY;
      }
      return hr;
    }
    

    And then the standard COM bookkeeping.

    HRESULT CCompositeContextMenu::QueryInterface(REFIID riid, void **ppv)
    {
      IUnknown *punk = NULL;
      if (riid == IID_IUnknown) {
        punk = static_cast<IUnknown*>(this);
      } else if (riid == IID_IContextMenu) {
        punk = static_cast<IContextMenu*>(this);
      } else if (riid == IID_IContextMenu2) {
        punk = static_cast<IContextMenu2*>(this);
      } else if (riid == IID_IContextMenu3) {
        punk = static_cast<IContextMenu3*>(this);
      }
    
      *ppv = punk;
      if (punk) {
        punk->AddRef();
        return S_OK;
      } else {
        return E_NOINTERFACE;
      }
    }
    
    ULONG CCompositeContextMenu::AddRef()
    {
      return ++m_cRef;
    }
    
    ULONG CCompositeContextMenu::Release()
    {
      ULONG cRef = --m_cRef;
      if (cRef == 0) delete this;
      return cRef;
    }
    

    Now we reach our first interesting method: IContextMenu::QueryContextMenu:

    HRESULT CCompositeContextMenu::QueryContextMenu(
        HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
        UINT idCmdLast, UINT uFlags)
    {
      UINT idCmdFirstOrig = idCmdFirst;
      UINT cids = 0;
    
      for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
        CONTEXTMENUINFO *pcmi = &m_rgcmi[icmi];
        HRESULT hr = pcmi->pcm->QueryContextMenu(hmenu,
                        indexMenu, idCmdFirst, idCmdLast, uFlags);
        if (SUCCEEDED(hr)) {
          pcmi->cids = (USHORT)hr;
          cids += pcmi->cids;
          idCmdFirst += pcmi->cids;
        }
      }
    
      return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cids);
    }
    

    We ask each contained context menu in turn to add its commands to the context menu. Here is where you see one of the reasons for the return value of the IContextMenu::QueryContextMenu method. By telling tells the container how many menu identifiers you used, the container knows how many are left for others. The container then returns the total number of menu identifiers consumed by all of the context menus.

    Another reason for the return value of the IContextMenu::QueryContextMenu method is seen in the next helper method:

    HRESULT CCompositeContextMenu::ReduceOrdinal(
        UINT_PTR *pidCmd, CONTEXTMENUINFO **ppcmi)
    {
      for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
        CONTEXTMENUINFO *pcmi = &m_rgcmi[icmi];
        if (*pidCmd < pcmi->cids) {
          *ppcmi = pcmi;
          return S_OK;
        }
        *pidCmd -= pcmi->cids;
      }
      return E_INVALIDARG;
    }
    

    This method takes a menu offset and figures out which of the contained context menus it belongs to, using the return value from IContextMenu::QueryContextMenu to decide how to divide up the identifier space. The pidCmd parameter is in/out. On entry, it's the menu offset for the composite context menu; on exit, it's the menu offset for the contained context menu that is returned via the ppcmi parameter.

    The IContextMenu::InvokeCommand is probably the most complicated, since it needs to support the four different ways of dispatching the command.

    HRESULT CCompositeContextMenu::InvokeCommand(
                                LPCMINVOKECOMMANDINFO lpici) {
    
      CMINVOKECOMMANDINFOEX* lpicix =
                    reinterpret_cast<CMINVOKECOMMANDINFOEX*>(lpici);
      BOOL fUnicode = lpici->cbSize >= sizeof(CMINVOKECOMMANDINFOEX) &&
                      (lpici->fMask & CMIC_MASK_UNICODE);
      UINT_PTR idCmd = fUnicode ? reinterpret_cast<UINT_PTR>(lpicix->lpVerbW)
                                : reinterpret_cast<UINT_PTR>(lpici->lpVerb);
    
      if (!IS_INTRESOURCE(idCmd)) {
        for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
          HRESULT hr = m_rgcmi->pcm->InvokeCommand(lpici);
          if (SUCCEEDED(hr)) {
            return hr;
          }
        }
        return E_INVALIDARG;
      }
    
      CONTEXTMENUINFO *pcmi;
      HRESULT hr = ReduceOrdinal(&idCmd, &pcmi);
      if (FAILED(hr)) {
          return hr;
      }
    
      LPCWSTR pszVerbWFake;
      LPCWSTR *ppszVerbW = fUnicode ? &lpicix->lpVerbW : &pszVerbWFake;
      LPCSTR pszVerbOrig = lpici->lpVerb;
      LPCWSTR pszVerbWOrig = *ppszVerbW;
    
      lpici->lpVerb = reinterpret_cast<LPCSTR>(idCmd);
      *ppszVerbW = reinterpret_cast<LPCWSTR>(idCmd);
    
      hr = pcmi->pcm->InvokeCommand(lpici);
    
      lpici->lpVerb = pszVerbOrig;
      *ppszVerbW = pszVerbWOrig;
    
      return hr;
    }
    

    After some preliminary munging to find the command identifier, we dispatch the invocation in three steps.

    First, if the command is being dispatched as a string, then this is the easiest case. We loop through all the contained context menus asking each one if it recognizes the command. Once one does, we are done. And if nobody does, then we shrug and say we don't know either.

    Second, if the command being dispatched is an ordinal, we ask ReduceOrdinal to figure out which contained context menu handler it belongs to.

    Third, we rewrite the CMINVOKECOMMANDINFO structure so it is suitable for use by the contained context menu handler. This means changing the lpVerb member and possibly the lpVerbW member to contain the new menu offset relative to the contained context menu handler rather than being relative to the container. This is complicated slightly by the fact that the Unicode verb lpVerbW might not exist. We hide that behind a pszVerbWFake local variable which stands in if there is no genuine lpVerbW.

    Okay, now that you see the basic idea behind distributing the method calls to the appropriate contained context menu, the rest should be comparatively easy.

    HRESULT CCompositeContextMenu::GetCommandString(
                                UINT_PTR    idCmd,
                                UINT        uType,
                                UINT      * pwReserved,
                                LPSTR       pszName,
                                UINT        cchMax)
    {
      HRESULT hr;
      if (!IS_INTRESOURCE(idCmd)) {
        for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
          hr = m_rgcmi[icmi].pcm->GetCommandString(idCmd,
                            uType, pwReserved, pszName, cchMax);
          if (hr == S_OK) {
            return hr;
          }
        }
        if (uType == GCS_VALIDATEA || uType == GCS_VALIDATEW) {
          return S_FALSE;
        }
        return E_INVALIDARG;
      }
    
      CONTEXTMENUINFO *pcmi;
      if (FAILED(hr = ReduceOrdinal(&idCmd, &pcmi))) {
        return hr;
      }
    
      return pcmi->pcm->GetCommandString(idCmd, uType,
                            pwReserved, pszName, cchMax);
    }
    

    The GetCommandString method follows the same three-step pattern as InvokeCommand.

    First, dispatch any string-based commands by calling each contained context menu handler until somebody accepts it. If nobody does, then reject the command. (Note the special handling of GCS_VALIDATE, which expects S_FALSE rather than an error code.)

    Second, if the command is specified by ordinal, ask ReduceOrdinal to figure out which contained context menu handler it belongs to.

    Third, pass the reduced command to the applicable contained context menu handler.

    The last methods are made easier by a little helper function:

    HRESULT IContextMenu_HandleMenuMsg2(
                IContextMenu *pcm, UINT uMsg, WPARAM wParam,
                LPARAM lParam, LRESULT* plResult)
    {
      IContextMenu2 *pcm2;
      IContextMenu3 *pcm3;
      HRESULT hr;
      if (SUCCEEDED(hr = pcm->QueryInterface(
                        IID_IContextMenu3, (void**)&pcm3))) {
        hr = pcm3->HandleMenuMsg2(uMsg, wParam, lParam, plResult);
        pcm3->Release();
      } else if (SUCCEEDED(hr = pcm->QueryInterface(
                        IID_IContextMenu2, (void**)&pcm2))) {
        if (plResult) *plResult = 0;
        hr = pcm2->HandleMenuMsg(uMsg, wParam, lParam);
        pcm2->Release();
      }
      return hr;
    }
    

    This helper function takes an IContextMenu interface pointer and tries to invoke IContextMenu3::HandleMenuMsg2; if that fails, then it tries IContextMenu2::HandleMenuMsg; and if that also fails, then it gives up.

    With this helper function, the last two methods are a piece of cake.

    HRESULT CCompositeContextMenu::HandleMenuMsg(
                UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      LRESULT lres;   // thrown away
      return HandleMenuMsg2(uMsg, wParam, lParam, &lres);
    }
    

    The IContextMenu2::HandleMenuMsg method is just a forwarder to the IContextMenu3::HandleMenuMsg2 method:

    HRESULT CCompositeContextMenu::HandleMenuMsg2(
                UINT uMsg, WPARAM wParam, LPARAM lParam,
                LRESULT* plResult)
    {
      for (UINT icmi = 0; icmi < m_ccmi; icmi++) {
        HRESULT hr;
        if (SUCCEEDED(hr = IContextMenu_HandleMenuMsg2(
                        m_rgcmi[icmi].pcm, uMsg, wParam, lParam,
                        plResult))) {
          return hr;
        }
      }
      return E_NOTIMPL;
    }
    

    And the IContextMenu3::HandleMenuMsg2 method merely walks through the list of context menu handlers, asking each one whether it wishes to handle the command, stopping when one finally does.

    Armed with this composite menu class, we can show it off in our sample program by compositing the "real" context menu with our CTopContextMenu, thereby showing how you can combine multiple context menus into one big context menu.

    HRESULT GetCompositeContextMenuForFile(HWND hwnd,
                LPCWSTR pszPath, REFIID riid, void **ppv)
    {
      *ppv = NULL;
      HRESULT hr;
    
      IContextMenu *rgpcm[2] = { 0 };
      if (SUCCEEDED(hr = GetUIObjectOfFile(hwnd, pszPath,
                            IID_IContextMenu, (void**)&rgpcm[0])) &&
          SUCCEEDED(hr = CTopContextMenu::Create(
                            IID_IContextMenu, (void**)&rgpcm[1])) &&
          SUCCEEDED(hr = CCompositeContextMenu::Create(rgpcm, 2, riid, ppv))) {
          // yay
      }
      if (rgpcm[0]) rgpcm[0]->Release();
      if (rgpcm[1]) rgpcm[1]->Release();
    
      return hr;
    }
    

    This function builds the composite by creating the two contained context menu handlers, then creating a composite context menu that contains both of them. We can use this function by making the same one-line tweak to the OnContextMenu function that we tweaked last time:

    void OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)
    {
      POINT pt = { xPos, yPos };
      if (pt.x == -1 && pt.y == -1) {
        pt.x = pt.y = 0;
        ClientToScreen(hwnd, &pt);
      }
    
      IContextMenu *pcm;
      if (SUCCEEDED(GetCompositeContextMenuForFile(
                        hwnd, L"C:\\Windows\\clock.avi",
                        IID_IContextMenu, (void**)&pcm))) {
        ...
    

    Notice that with this composite context menu, the menu help text that we update in our window title tracks across both the original file context menu and our "Top" context menu. Commands from either half are also invoked successfully.

    The value of this approach over the method from part 9 is that you no longer have to coordinate the customization of the context menu between two pieces of code. Under the previous technique, you had to make sure that the code that updated the menu help text was in sync with the code that added the custom commands.

    Under the new method, all the customizations are kept in one place (in the "Top" context menu which is inside the composite context menu), so that the window procedure doesn't need to know what customizations have taken place. This becomes more valuable if there are multiple points at which context menus are displayed, some uncustomized, others customized in different ways. Centralizing the knowledge of the customizations simplifies the design.

    Okay, I think that's enough on context menus for now. I hope you've gotten a better understanding of how they work, how you can exploit them, and most importantly, how you can perform meta-operations on them with techniques like composition.

    There are still some other things you can do with context menus, but I'm going to leave you to experiment with them on your own. For example, you can use the IContextMenu::GetCommandString method to walk the menu and obtain a language-independent command mame for each item. This is handy if you want to, say, remove the "delete" option: You can look for the command whose language-independent name is "delete". This name does not change when the user changes languages; it will always be in English.

    As we've noticed before, you need to be aware that many context menu handlers don't implement the IContextMenu::GetCommandString method fully, so there will likely be commands that you simply cannot get a name for. Them's the breaks.

    [Editing errors corrected, 11am.]

  • The Old New Thing

    How to host an IContextMenu, part 10 - Composite extensions - groundwork

    • 11 Comments

    You might wonder why the IContextMenu interface operates on menu identifier offsets so much rather than with the menu identifiers themselves.

    The reason is to support something which I will call "compositing".

    You may have multiple context menu extensions that you want to combine into one giant context menu extension. The shell does this all over the place. For example, the context menu we have been playing with all this time is really a composite of several individual context menu extensions: the static registry verbs plus all the COM-based extensions like "Send To", "Open With", and anything else that may have been added by a program you installed (like a virus checker).

    So before we can write a compositor, we need to have a second context menu to composite. Here's a quickie that implements two commands, let's call them "Top" and "Next" for lack of anything interesting to do.

    class CTopContextMenu : public IContextMenu
    {
    public:
      // *** IUnknown ***
      STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
      STDMETHODIMP_(ULONG) AddRef();
      STDMETHODIMP_(ULONG) Release();
    
      // *** IContextMenu ***
      STDMETHODIMP QueryContextMenu(HMENU hmenu,
                              UINT indexMenu, UINT idCmdFirst,
                              UINT idCmdLast, UINT uFlags);
      STDMETHODIMP InvokeCommand(
                              LPCMINVOKECOMMANDINFO lpici);
      STDMETHODIMP GetCommandString(
                              UINT_PTR    idCmd,
                              UINT        uType,
                              UINT      * pwReserved,
                              LPSTR       pszName,
                              UINT        cchMax);
    
      static HRESULT Create(REFIID riid, void **ppv);
    
    private:
      CTopContextMenu() : m_cRef(1), m_cids(0) { }
    
    private:
      HRESULT ValidateCommand(UINT_PTR idCmd, BOOL fUnicode,
                              UINT *puOffset);
      HRESULT Top(LPCMINVOKECOMMANDINFO lpici);
      HRESULT Next(LPCMINVOKECOMMANDINFO lpici);
    
    private:
      ULONG m_cRef;
      UINT  m_cids;
    };
    

    The class declaration isn't particularly interesting. We are not owner-draw so we don't bother implementing IContextMenu2 or IContextMenu3.

    First, some basic paperwork for getting off the ground.

    HRESULT CTopContextMenu::Create(REFIID riid, void **ppv)
    {
      *ppv = NULL;
      HRESULT hr;
      CTopContextMenu *self = new CTopContextMenu();
      if (self) {
        hr = self->QueryInterface(riid, ppv);
        self->Release();
      } else {
        hr = E_OUTOFMEMORY;
      }
      return hr;
    }
    

    We have two commands. Instead of hard-coding the numbers 0 and 1, let's give them nice names.

    #define TOPCMD_TOP      0
    #define TOPCMD_NEXT     1
    #define TOPCMD_MAX      2
    

    And here's a table that we're going to use to help us manage these two commands.

    const struct COMMANDINFO {
      LPCSTR  pszNameA;
      LPCWSTR pszNameW;
      LPCSTR  pszHelpA;
      LPCWSTR pszHelpW;
    } c_rgciTop[] = {
      { "top",  L"top",
        "The top command",  L"The top command", }, // TOPCMD_TOP
      { "next", L"next",
        "The next command", L"The next command", },// TOPCMD_NEXT
    };
    

    Our TOPCMD_* values conveniently double as indices into the c_rgciTop array.

    Next come the boring parts of a COM object:

    HRESULT CTopContextMenu::QueryInterface(REFIID riid, void **ppv)
    {
      IUnknown *punk = NULL;
      if (riid == IID_IUnknown) {
        punk = static_cast<IUnknown*>(this);
      } else if (riid == IID_IContextMenu) {
        punk = static_cast<IContextMenu*>(this);
      }
    
      *ppv = punk;
      if (punk) {
        punk->AddRef();
        return S_OK;
      } else {
        return E_NOINTERFACE;
      }
    }
    
    ULONG CTopContextMenu::AddRef()
    {
      return ++m_cRef;
    }
    
    ULONG CTopContextMenu::Release()
    {
      ULONG cRef = --m_cRef;
      if (cRef == 0) delete this;
      return cRef;
    }
    

    Finally, we get to something interesting: IContextMenu::QueryContextMenu. Things to watch out for in the code below:

    • Checking whether there is room between idCmdFirst and idCmdLast is complicated by the fact that idCmdLast is endpoint-inclusive, which forces a strange +1. Another reason to prefer endpoint-exclusive ranges.
    • If the CMF_DEFAULTONLY flag is set, then we don't bother adding our menu items since none of our options is the default menu item.
    HRESULT CTopContextMenu::QueryContextMenu(
        HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
        UINT idCmdLast, UINT uFlags)
    {
      m_cids = 0;
    
      if ((int)(idCmdLast - idCmdFirst + 1) >= TOPCMD_MAX &&
        !(uFlags & CMF_DEFAULTONLY)) {
        InsertMenu(hmenu, indexMenu + TOPCMD_TOP, MF_BYPOSITION,
                   idCmdFirst + TOPCMD_TOP, TEXT("Top"));
        InsertMenu(hmenu, indexMenu + TOPCMD_NEXT, MF_BYPOSITION,
                   idCmdFirst + TOPCMD_NEXT, TEXT("Next"));
        m_cids = TOPCMD_MAX;
      }
    
      return MAKE_HRESULT(SEVERITY_SUCCESS, 0, m_cids);
    }
    

    In order to implement the next few methods, we need to have some culture-invariant comparison functions.

    int strcmpiA_invariant(LPCSTR psz1, LPCSTR psz2)
    {
      return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,
                            psz1, -1, psz2, -1) - CSTR_EQUAL;
    }
    
    int strcmpiW_invariant(LPCWSTR psz1, LPCWSTR psz2)
    {
      return CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE,
                            psz1, -1, psz2, -1) - CSTR_EQUAL;
    }
    

    These are like the strcmpi functions except that they use the invariant locale since they will be used to compare canonical strings rather than strings that are meaningful to an end user. (More discussion here in MSDN.)

    Now we have enough to write a helper function which is central to the context menu: Figuring out which command somebody is talking about.

    Commands can be passed to the IContextMenu interface either (a) by ordinal or by name, and either (b) as ANSI or as Unicode. This counts as either three ways or four ways, depending on whether you treat "ANSI as ordinal" and "Unicode as ordinal" as the same thing or not.

    HRESULT CTopContextMenu::ValidateCommand(UINT_PTR idCmd,
                            BOOL fUnicode, UINT *puOffset)
    {
      if (!IS_INTRESOURCE(idCmd)) {
        if (fUnicode) {
          for (idCmd = 0; idCmd < TOPCMD_MAX; idCmd++) {
            if (strcmpiW_invariant((LPCWSTR)idCmd,
                                   c_rgciTop[idCmd].pszNameW) == 0) {
              break;
            }
          }
        } else {
          for (idCmd = 0; idCmd < TOPCMD_MAX; idCmd++) {
            if (strcmpiA_invariant((LPCSTR)idCmd,
                                   c_rgciTop[idCmd].pszNameA) == 0) {
              break;
            }
          }
        }
      }
    
      if (idCmd < m_cids) {
        *puOffset = (UINT)idCmd;
        return S_OK;
      }
    
      return E_INVALIDARG;
    }
    

    This helper function takes a "something" parameter in the form of a UINT_PTR and a flag that indicates whether that "something" is ANSI or Unicode. The function itself checks whether the "something" is a string or an ordinal. If a string, then it converts that string into an ordinal by looking for it in the table of commands in the appropriate character set and using a locale-insensitive comparison. Notice that if the string is not found, then idCmd is left equal to TOPCMD_MAX, which is an invalid value (and therefore is neatly handled by the fall-through).

    After the (possibly failed) conversion to an ordinal, the ordinal is checked for validity; if valid, then the ordinal is returned back for further processing.

    With this helper function the implementation of the other methods of the IContextMenu interface are a lot easier.

    We continue with the IContextMenu::InvokeCommand method:

    HRESULT CTopContextMenu::InvokeCommand(
                                LPCMINVOKECOMMANDINFO lpici) {
    
      CMINVOKECOMMANDINFOEX* lpicix = (CMINVOKECOMMANDINFOEX*)lpici;
      BOOL fUnicode = lpici->cbSize >= sizeof(CMINVOKECOMMANDINFOEX) &&
                      (lpici->fMask & CMIC_MASK_UNICODE);
      UINT idCmd;
      HRESULT hr = ValidateCommand(fUnicode ? (UINT_PTR)lpicix->lpVerbW
                                            : (UINT_PTR)lpici->lpVerb,
                                   fUnicode, &idCmd);
      if (SUCCEEDED(hr)) {
        switch (idCmd) {
        case TOPCMD_TOP: hr = Top(lpici); break;
        case TOPCMD_NEXT: hr = Next(lpici); break;
        default: hr = E_INVALIDARG; break;
        }
      }
      return hr;
    }
    

    Here is a case where the "Are there three cases or four?" question lands squarely on the side of "four". There are two forms of the CMINVOKECOMMANDINFO structure, the base structure (which is ANSI-only) and the extended structure CMINVOKECOMMANDINFOEX which adds Unicode support.

    If the structure is CMINVOKECOMMANDINFOEX and the CMIC_MASK_UNICODE flag is set, then the Unicode fields of the CMINVOKECOMMANDINFOEX structure should be used in preference to the ANSI ones.

    This means that there are indeed four scenarios:

    1. ANSI string in lpVerb member.
    2. Ordinal in lpVerb member.
    3. Unicode string in lpVerbW member.
    4. Ordinal in lpVerbW member.

    After figuring out whether the parameter is ANSI or Unicode, we ask ValidateCommand to do the work of validating the verb and converting it to an ordinal, at which point we use the ordinal in a switch statement to dispatch the actual operation.

    Failing to implement string-based command invocation is an extremely common oversight in context menu implementations. Doing so prevents people from invoking your verbs programmatically.

    "Why should I bother to let people invoke my verbs programmatically?"

    Because if you don't, then people won't be able to write programs like the one we are developing in this series of articles! For example, suppose your context menu extension lets people "Frob" a file. If you don't expose this verb programmability, then it is impossible to write a program that, say, takes all the files modified in the last twenty-four hours and Frobs them.

    (I'm always amused by the people who complain that Explorer doesn't expose enough customizability programmatically, while simultaneously not providing the same degree of programmatic customizability in their own programs.)

    Oh wait, I guess I should implement those two operations. They don't do anything particularly interesting.

    HRESULT CTopContextMenu::Top(LPCMINVOKECOMMANDINFO lpici)
    {
      MessageBox(lpici->hwnd, TEXT("Top"), TEXT("Title"), MB_OK);
      return S_OK;
    }
    
    HRESULT CTopContextMenu::Next(LPCMINVOKECOMMANDINFO lpici)
    {
      MessageBox(lpici->hwnd, TEXT("Next"), TEXT("Title"), MB_OK);
      return S_OK;
    }
    

    The remaining method is IContextMenu::GetCommandString, which is probably the one people most frequently get wrong since the consequences of getting it wrong are not immediately visible to the implementor. It is the people who are trying to access the context menu programmatically who most likely to notice that the method isn't working properly.

    HRESULT CTopContextMenu::GetCommandString(
                                UINT_PTR    idCmd,
                                UINT        uType,
                                UINT      * pwReserved,
                                LPSTR       pszName,
                                UINT        cchMax)
    {
      UINT id;
      HRESULT hr = ValidateCommand(idCmd, uType & GCS_UNICODE, &id);
      if (FAILED(hr)) {
        if (uType == GCS_VALIDATEA || uType == GCS_VALIDATEW) {
          hr = S_FALSE;
        }
        return hr;
      }
    
      switch (uType) {
      case GCS_VERBA:
        lstrcpynA(pszName, c_rgciTop[id].pszNameA, cchMax);
        return S_OK;
    
      case GCS_VERBW:
        lstrcpynW((LPWSTR)pszName, c_rgciTop[id].pszNameW, cchMax);
        return S_OK;
    
      case GCS_HELPTEXTA:
        lstrcpynA(pszName, c_rgciTop[id].pszHelpA, cchMax);
        return S_OK;
    
      case GCS_HELPTEXTW:
        lstrcpynW((LPWSTR)pszName, c_rgciTop[id].pszHelpW, cchMax);
        return S_OK;
    
      case GCS_VALIDATEA:
      case GCS_VALIDATEW:
        return S_OK;    // all they wanted was validation
      }
    
      return E_NOTIMPL;
    }
    

    Here again we use the ValidateCommand method to do the hard work of validating the command, which is passed in the idCmd parameter, with interpretive assistance in the GCS_UNICODE flag of the uType parameter.

    If the command is not valid, then we propagate the error code, except in the GCS_VALIDATE cases, where the documentation says that we should return S_FALSE to indicate that the command is not valid.

    If the command is valid, we return the requested information, which is handled by a simple switch statement.

    Okay, now that we have this context menu, we can even test it out a little bit. Throw out the changes from part 9 and return to the program as it was in part 6, making the following change to the OnContextMenu function:

    void OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)
    {
      POINT pt = { xPos, yPos };
      if (pt.x == -1 && pt.y == -1) {
        pt.x = pt.y = 0;
        ClientToScreen(hwnd, &pt);
      }
    
      IContextMenu *pcm;
      if (SUCCEEDED(CTopContextMenu::Create(
                        IID_IContextMenu, (void**)&pcm))) {
        ...
    

    We now obtain our context menu not by calling the GetUIObjectOfFile function but rather by constructing a CTopContextMenu object. Since our CTopContextMenu implements IContextMenu, all the remaining code can be left unchanged.

    When you run this program, observe that even the help text works.

    Ah, one of the powers of operating with interfaces rather than objects: You can swap out the object and the rest of the code doesn't even realize what happened, so long as the interface stays the same.

    Okay, today was a long day spent just laying groundwork, just writing what has to be written. No breakthroughs, no "aha" moments, just typing. Read the method, understand what you have to do, and do it.

    Next time, we're going to see context menu composition, using this context menu as one of the components.

  • The Old New Thing

    The macros for declaring and implementing COM interfaces

    • 16 Comments

    There are two ways of declaring COM interfaces, the hard way and the easy way.

    The easy way is to use an IDL file and let the MIDL compiler generate your COM interface for you. If you let MIDL do the work, then you also get __uuidof support at no extra charge, which is a very nice bonus.

    The hard way is to do it all by hand. If you choose this route, your interface will look something like this:

    #undef  INTERFACE
    #define INTERFACE   ISample2
    
    DECLARE_INTERFACE_(ISample2, ISample)
    {
        BEGIN_INTERFACE
    
        // *** IUnknown methods ***
        STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppv) PURE;
        STDMETHOD_(ULONG,AddRef)(THIS) PURE;
        STDMETHOD_(ULONG,Release)(THIS) PURE;
    
        // ** ISample methods ***
        STDMETHOD(Method1)(THIS) PURE;
        STDMETHOD_(int, Method2)(THIS) PURE;
    
        // *** ISample2 methods ***
        STDMETHOD(Method3)(THIS_ int iParameter) PURE;
        STDMETHOD_(int, Method4)(THIS_ int iParameter) PURE;
    
        END_INTERFACE
    };
    

    What are the rules?

    • You must set the INTERFACE macro to the name of the interface being declared. Note that you need to #undef any previous value before you #define the new one.
    • You must use the DECLARE_INTERFACE and DECLARE_INTERFACE_ macros to generate the preliminary bookkeeping for an interface. Use DECLARE_INTERFACE for interfaces that have no base class and DECLARE_INTERFACE_ for interfaces that derive from some other interface. In our example, we derive the ISample2 interface from ISample. Note: In practice, you will never find the plain DECLARE_INTERFACE macro because all interfaces derive from IUnknown if nothing else.
    • You must list all the methods of the base interfaces in exactly the same order that they are listed by that base interface; the methods that you are adding in the new interface must go last.
    • You must use the STDMETHOD or STDMETHOD_ macros to declare the methods. Use STDMETHOD if the return value is HRESULT and STDMETHOD_ if the return value is some other type.
    • If your method has no parameters, then the argument list must be (THIS). Otherwise, you must insert THIS_ immediately after the open-parenthesis of the parameter list.
    • After the parameter list and before the semicolon, you must say PURE.
    • Inside the curly braces, you must say BEGIN_INTERFACE and END_INTERFACE.

    There is a reason for each of these rules. They have to do with being able to use the same header for both C and C++ declarations and with interoperability with different compilers and platforms.

    • You must set the INTERFACE macro because its value is used by the THIS and THIS_ macros later.
    • You must use one of the DECLARE_INTERFACE* macros to ensure that the correct prologue is emitted for both C and C++. For C, a vtable structure is declared, whereas for C++ the compiler handles the vtable automatically; on the other hand, since C++ has inheritance, the macros need to specify the base class so that upcasting will work.
    • You must list the base class methods in exactly the same order as in the original declarations so that the C vtable structure for your derived class matches the structure for the base class for the extent that they overlap. This is required to preserve the COM rule that a derived interface can be used as a base interface.
    • You must use the STDMETHOD and STDMETHOD_ macros to ensure that the correct calling conventions are declared for the function prototypes. For C, the macro creates a function pointer in the vtable; for C++, the macro creates a virtual function.
    • The THIS and THIS_ macros are used so that the C declaration explicitly declares the "this" parameter which in C++ is implied. Different versions are needed depending on the number of parameters so that a spurious trailing comma is not generated in the zero-parameter case.
    • The word PURE ensures that the C++ virtual function is pure, because one of the defining characteristics of COM interfaces is that all methods are pure virtual.
    • The BEGIN_INTERFACE and END_INTERFACE macros emit compiler-specific goo which the compiler vendor provides in order to ensure that the generated interface matches the COM vtable layout rules. Different compilers have historically required different goo, though the need for goo is gradually disappearing over time.

    And you wonder why I called it "the hard way".

    Similar rules apply when you are implementing an interface. Use the STDMETHODIMP and STDMETHODIMP_ macros to declare your implementations so that they get the proper calling convention attached to them. We'll see examples of this next time.

  • The Old New Thing

    Those partisan non-partisan groups

    • 31 Comments

    Just because they say they're non-partisan doesn't mean that they're non-partisan.

    Friday night, I got a phone call from "Victor" at "Washington Counts" who came right out and asked me whom I was going to vote for.

    I asked him to repeat the name of the organization he represents, and he said, "Washington Counts, a non-partisan organization, working in cooperation with Emily's List."

    I couldn't find any information about "Washington Counts", but I did find Emily's List, which is a blatantly partisan group. Their own About Page say that they are "dedicated to... electing pro-choice Democratic women".

    I pointed out to "Victor" that Emily's List is a partisan group. He ignored me and repeated his question, asking me whom I was going to vote for.

    I told him that I was unlikely to be inclined to assist an organization that start out by lying to me.

    And then I heard a click and a recorded voice saying, "This survey was sponsored by Emily's List. On the web at www.emilyslist.org."

    Let's see what happened here. Somebody claimed to be from "Washington Counts", but in fact they were from "Emily's List". [Corrected identification 9:38am.] That person claimed to be representing a non-partisan group, but in fact the group is highly partisan.

    I guess if you're going to lie, you may as well go all-out.

    Of course, this could have been a double-fake-out. Perhaps it was really a pro-life Republican group pretending to be a pro-choice Democratic group?

    These sorts of double-fake-outs are not unheard of. In California, anybody who pays the requisite fee can get a statement printed in the voter's guide. It has been known to occur that somebody who holds one position on an issue submits an incoherent or absurd statement in support of the opposition position, thereby making the opposition look stupid. During the 1996 U.S. presidential election primary season, Candidate X sponsored a telephone survey asking voters "If Candidate Y took <controversial position>, would your opinion of Candidate Y go up, down, or stay the same?" The intent here was to start the rumor that Candidate Y was actually considering taking said controversial position (which would have undermined Candidate Y's traditional support).

    You can never tell where the dirty tricks are coming from in politics.

  • The Old New Thing

    How to host an IContextMenu, part 9 - Adding custom commands

    • 5 Comments

    The indexMenu, idCmdFirst and idCmdLast parameters to the IContextMenu::QueryContextMenu method allow you, the host, to control where in the context menu the IContextMenu will insert its commands. To illustrate this, let's put two bonus commands on our context menu, with the boring names "Top" and "Bottom".

    We need to reserve some space in our menu identifiers, so let's carve some space out for our private commands:

    #define SCRATCH_QCM_FIRST 1
    #define SCRATCH_QCM_LAST  0x6FFF
    #define IDM_TOP           0x7000
    #define IDM_BOTTOM        0x7001
    

    We reserved 0x1000 commands for ourselves, allowing the IContextMenu to play with commands 1 through 0x6FFF. (We could have carved our space out of the low end, too, by increasing SCRATCH_QCM_FIRST instead of decreasing SCRATCH_QCM_LAST.)

    Go back to the program we had in part 6 and make these changes:

    void OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)
    {
      POINT pt = { xPos, yPos };
      if (pt.x == -1 && pt.y == -1) {
        pt.x = pt.y = 0;
        ClientToScreen(hwnd, &pt);
      }
    
      IContextMenu *pcm;
      if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                       IID_IContextMenu, (void**)&pcm))) {
        HMENU hmenu = CreatePopupMenu();
        if (hmenu) {
          if (InsertMenu(hmenu, 0, MF_BYPOSITION,
                         IDM_TOP, TEXT("Top")) &&
              InsertMenu(hmenu, 1, MF_BYPOSITION,
                         IDM_BOTTOM, TEXT("Bottom")) &&
              SUCCEEDED(pcm->QueryContextMenu(hmenu, 1,
                                 SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                                 CMF_NORMAL))) {
            pcm->QueryInterface(IID_IContextMenu2, (void**)&g_pcm2);
            pcm->QueryInterface(IID_IContextMenu3, (void**)&g_pcm3);
            int iCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD,
                                        pt.x, pt.y, hwnd, NULL);
            if (g_pcm2) {
              g_pcm2->Release();
              g_pcm2 = NULL;
            }
            if (g_pcm3) {
              g_pcm3->Release();
              g_pcm3 = NULL;
            }
            if (iCmd == IDM_TOP) {
              MessageBox(hwnd, TEXT("Top"), TEXT("Custom"), MB_OK);
            } else if (iCmd == IDM_BOTTOM) {
              MessageBox(hwnd, TEXT("Bottom"), TEXT("Custom"), MB_OK);
            } else if (iCmd > 0) {
              CMINVOKECOMMANDINFOEX info = { 0 };
              info.cbSize = sizeof(info);
              info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE;
              if (GetKeyState(VK_CONTROL) < 0) {
                info.fMask |= CMIC_MASK_CONTROL_DOWN;
              }
              if (GetKeyState(VK_SHIFT) < 0) {
                info.fMask |= CMIC_MASK_SHIFT_DOWN;
              }
              info.hwnd = hwnd;
              info.lpVerb  = MAKEINTRESOURCEA(iCmd - SCRATCH_QCM_FIRST);
              info.lpVerbW = MAKEINTRESOURCEW(iCmd - SCRATCH_QCM_FIRST);
              info.nShow = SW_SHOWNORMAL;
              info.ptInvoke = pt;
              pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
            }
          }
          DestroyMenu(hmenu);
        }
        pcm->Release();
      }
    }
    

    [Corrected insertion location for "Bottom" 9:42am.]

    Before calling IContextMenu::QueryContextMenu, we added our own custom commands (with menu identifiers outside the range we offer to IContextMenu::QueryContextMenu so they won't conflict), and then call IContextMenu::QueryContextMenu passing the new reduced range as well as specifying that the insertion position is 1 instead of 0.

    When we pass the context menu to to IContextMenu::QueryContextMenu, the menu looks like this:

    Top
    Bottom

    By passing 1 as the insertion point, we are telling the context menu handler that it should insert its commands at position 1 (pushing out what is currently at positions 1 and onwards).

    Top

    ... new stuff ...
     
    Bottom

    After displaying this enhanced context menu, we check which command the user picked, whether it's one of ours (which we handle directly) or one from the inserted portion of the context menu (which we dispatch to the handler).

  • The Old New Thing

    How to host an IContextMenu, part 8 - Optimizing for the default command

    • 5 Comments

    There is a small improvement that can be made to to the program we wrote last time. It involves taking advantage of the last parameter to the IContextMenu::QueryContextMenu method:

    CMF_DEFAULTONLY
    This flag is set when the user is activating the default action, typically by double-clicking. This flag provides a hint for the shortcut menu extension to add nothing if it does not modify the default item in the menu. A shortcut menu extension or drag-and-drop handler should not add any menu items if this value is specified. A namespace extension should add only the default item (if any).

    As the text from MSDN indicates, this flag is a hint to the IContextMenu implementation that it should worry only about the default command.

    void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
    {
      IContextMenu *pcm;
      if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                       IID_IContextMenu, (void**)&pcm))) {
        HMENU hmenu = CreatePopupMenu();
        if (hmenu) {
          if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                                 SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                                 CMF_DEFAULTONLY))) {
            UINT id = GetMenuDefaultItem(hmenu, FALSE, 0);
            if (id != (UINT)-1) {
              CMINVOKECOMMANDINFO info = { 0 };
              info.cbSize = sizeof(info);
              info.hwnd = hwnd;
              info.lpVerb = MAKEINTRESOURCEA(id - SCRATCH_QCM_FIRST);
              pcm->InvokeCommand(&info);
            }
          }
          DestroyMenu(hmenu);
        }
        pcm->Release();
      }
    }
    

    With this change on my machine, the time taken by the call to IContextMenu::QueryContextMenu dropped from 100ms to 50ms. Your mileage may vary. It depends on how many context menu extensions you have and how well they respect the CMF_DEFAULTONLY flag.

    (And this exercise highlights how important it is that people who implement the IContextMenu interface pay attention to the flags. If your context menu handler doesn't respect the CMF_DEFAULTONLY flag, then you're part of the problem.)

Page 380 of 434 (4,335 items) «378379380381382»