SHAutoComplete giveth, and SHAutoComplete taketh away


The SH­Auto­Complete function lets you attach autocomplete functionality to an edit control, and there are flags that describe what sources you want the autocomplete to draw from. If you call SH­Auto­Complete a second time, the second set of flags replace the original flags. The flags do not accumulate. For example, if you first call SH­Auto­Complete(SHACF_FILESYS_ONLY), and then you later call SH­Auto­Complete(SHACF_URLHISTORY), the result is that the autocompletion uses only the URL history.

This replacement behavior (as opposed to accumulation behavior) is handy if you want to remove an autocompletion that you previously added. You just call SH­Auto­Complete a second time and leave off the flags for autocomplete sources you don't want. There's a catch, though: If you want to turn off everything, then you cannot pass zero, because that gets interpreted as SHACF_DEFAULT. You have to pass a nonzero value, and fortunately there's a handy nonzero value which means Turn off everything: SHACF_AUTOSUGGEST_FORCE_OFF.

Let's illustrate this technique by disabling autocomplete in the common dialog, a problem which commenter Ian mistakenly solved by modifying a global setting.

#include <windows.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <commdlg.h>
#include <dlgs.h>

UINT_PTR CALLBACK HookProc(HWND hdlg, UINT uMsg,
                           WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_INITDIALOG:
        PostMessage(hdlg, WM_APP, 0, 0);
        break;
    case WM_APP:
        SHAutoComplete(
          (HWND)SendDlgItemMessage(GetParent(hdlg), cmb13,
                                   CBEM_GETEDITCONTROL, 0, 0),
          SHACF_AUTOSUGGEST_FORCE_OFF);
        break;
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    TCHAR szFile[MAX_PATH];
    szFile[0] = TEXT('\0');
    OPENFILENAME ofn = { sizeof(ofn) };
    ofn.hInstance = hinst;
    ofn.lpstrFilter = TEXT("All files\0*.*\0");
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_ENABLEHOOK | OFN_EXPLORER;
    ofn.lpfnHook = HookProc;
    GetOpenFileName(&ofn);
    return 0;
}

The hook procedure uses the SH­Auto­Complete function to turn off autocompletion on the file name edit control in the common dialog. There are a few annoying bits that I have to get through before I finally make that SH­Auto­Complete call: First I have to find the edit control, which means finding the combo box and then asking the combo box for the interior edit control. (Fortunately, this is already called out in the documentation for SH­Auto­Complete, so I didn't have to puzzle over it for long.) And second, I couldn't disable autocomplete directly in WM_INITDIALOG because that happens too early in the common file dialog initialization process. Instead, I post myself a message and do the "final initialization" later. (This I discovered by trial and error.)

And there you have it, a common dialog box with no autocomplete.

Update: Joylon Smith points out that the documentation for SHAutoComplete explicitly cautions against calling it more than once on the same window because it results in a memory leak.

That caution was written based on information I provided back in Windows XP. The memory leak was fixed in Windows Vista, but the documentation was not updated to match. So please mentally insert "On versions of Windows prior to Windows Vista (and versions of Windows Server prior to Windows Server 2008)" at the start of that paragraph. A doc change request has also been submitted, so hopefully the revised documentation will appear soon.

Comments (4)
  1. WndSks says:

    "If you call SH­Auto­Complete a second time" but MSDN says "SHAutoComplete should not be called more than once with the same HWND", so is it really safe to call again or not?

    [Whoa, slow down people, I have two weeks worth of updates/revisions to apply! I've updated the article to clarify that the solution requires Windows Vista. -Raymond]
  2. yuhong2 says:

    "That caution was written based on information I provided back in Windows XP. The memory leak was fixed in Windows Vista, but the documentation was not updated to match. So please mentally insert "On versions of Windows prior to Windows Vista (and versions of Windows Server prior to Windows Server 2008)" at the start of that paragraph. A doc change request has also been submitted, so hopefully the revised documentation will appear soon. "

    BTW, on the matter of this, I hate how often in recent versions of the Windows SDK the behavior in the latest version of Windows is mentioned first, then there is something like "Windows Server 2003 and earlier:" then the old behaviors is mentioned, often when the opposite is better.

  3. Ian says:

    Thanks so much for that, Raymond. I was never completely happy with my solution of temporarily modifying the global setting, as I indicated in my original comment.

    This is the kind of thing I really appreciate about this blog. Thanks again.

  4. WndSks says:

    I assume there is no undocumented WM_GETIAUTOCOMPLETE that can be used on pre Vista to get a pointer to IAutoComplete[2] to avoid the leak? I'm also guessing it uses SetWindowSubclass and friends to subclass the editbox, so getting the iface pointer ourself by using a offset from the subclassproc is doubly hard (I'm guessing (A lot of guessing here) this thing is in a class, called CAutoComplete or something like that probably?) Would be nice if MS documented how to do it, even if it is ugly and uses hardcoded offsets or whatever so legacy platforms don't leak.

Comments are closed.