Filtering the Browse for Folder dialog so it shows only drive letters


Today, we're going to customize the Browse for Folder dialog so it shows only drive letters.

Start with our previous Browse for Folder customization program, and make these changes:

// Lazy global variable
PIDLIST_ABSOLUTE g_pidlMyComputer;

class CFunnyFilter :
    public RuntimeClass<
    RuntimeClassFlags<RuntimeClassType::ClassicCom>,
    IFolderFilter>
{
public:
  // *** IFolderFilter ***
  IFACEMETHODIMP ShouldShow(
        IShellFolder* psf,
        PCIDLIST_ABSOLUTE pidlFolder,
        PCUITEMID_CHILD pidlItem)
  {
    int compare = CompareDepth(pidlFolder);
    if (compare < 0) return S_OK;
    if (compare > 0) return S_FALSE;

    STRRET str;
    psf->GetDisplayNameOf(pidlItem, SHGDN_FORPARSING, &str);
    wchar_t buf[4];
    if (SUCCEEDED(StrRetToBuf(&str, pidlItem, buf, ARRAYSIZE(buf))) &&
        PathIsRoot(buf)) return S_OK;
    return S_FALSE;
  }

  IFACEMETHODIMP GetEnumFlags(
      IShellFolder* psf,
      PCIDLIST_ABSOLUTE pidlFolder,
      HWND *phwnd,
      DWORD *pgrfFlags) {        
    if (CompareDepth(pidlFolder) > 0) *pgrfFlags = 0;
    return S_OK;
  }

private:
  static int CompareDepth(PCIDLIST_ABSOLUTE pidl)
  {
    if (pidl == nullptr) return -1;
    if (ILIsEqual(pidl, g_pidlMyComputer)) return 0;
    if (ILIsParent(pidl, g_pidlMyComputer, FALSE)) return -1;
    return 1;
  }
};

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
  CCoInitialize init;
  BROWSEINFO bi = { };
  TCHAR szDisplayName[MAX_PATH];
  SHGetSpecialFolderLocation(nullptr, CSIDL_DRIVES, &g_pidlMyComputer);
  bi.pidlRoot = g_pidlMyComputer;
  bi.pszDisplayName = szDisplayName;
  bi.lpfn = BrowseCallbackProc;
  bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS;
  PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi);
  CoTaskMemFree(pidl);
  CoTaskMemFree(g_pidlMyComputer);
  return 0;
}

Okay, let's see what we've got.

First, we declare a global variable to remember the location of what was once called My Computer but nowadays goes by the name This PC. Whatever it is, it's the thing that contains your drive letters.

The real work happens in the filter. Starting at the bottom, we have a method called Check­Depth which determines whether the passed-in folder is an ancestor of, equal to, or a descendant of My Computer. Actually, we treat anything that isn't a parent or equal to My Computer as if it were a descendant.

The Check­Depth method is method is a bit tricky for a few reasons. First, it treats the null pointer as equivalent to the desktop, so that it is the ancestor of everything. For whatever reason, that's what IFolder­Filter gives you, so we accommodate it.

Second, if you pass FALSE to ILIs­Parent, it means that the function will return a nonzero value if the first ID list is an ancestor of or is equal to the second ID list. Therefore, we have to do the equality test first.

Okay, working upward, the next method is Get­Enum­Flags. This is called when the Browse for Folder dialog wants to enumerate the children of a folder, and it's our chance to influence what gets enumerated. We don't want to expand the drives themselves, so if we have something that is a child of My Computer, we set the enumeration flags to zero, which means that nothing gets enumerated.

The first method is Should­Show. This is where most of the excitement is. You are given a folder and an item in that folder, and your job is to decide whether that item should be shown in the Browse for Folder dialog.

First, we say that folders which are ancestors of My Computer can show all of their children. This ensures that the Browse for Folder dialog can reach My Computer in the first place.

Second, we say that descendants of My Computer do not show any children. This is technically redundant because our Get­Enum­Flags prevented those children from being enumerated, but we'll block them here just to be sure they don't show up.

Finally, if we are showing children of My Computer itself, we ask for the parsing name of the item and see if a drive root comes back. If the parsing name is longer than four characters, then the Str­Ret­To­Buf function will fail with an insufficient-buffer error, in which case we know that we don't have a drive root.

The handy Str­Ret­To­Buf function deals with the kooky STRRET structure so we don't have to.

So that's the filtering. The last changes are to Win­Main: We obtain the item ID list for My Computer and set it as the root for the Browse for Folder dialog. (Remember that Little Programs do little to no error checking.) We also tell the Browse for Folder dialog that we require the user to select a file system object. That ensures that the OK button is disabled when the user is sitting at My Computer. And after the excitement is done, we clean up.

There you have it. A Browse for Folder dialog that shows only drive letters.

I'm not sure how useful this is, but I never claimed that this was useful.

Comments (16)
  1. Nick says:

    Combine this with April 12th’s article and you can build a program to get the current directory of a user-selected drive. Is this the best way to accomplish that? 🤷 But at least you didn’t have to write your own drive picker dialog.

  2. Ted M says:

    The arbitrary size filter in the previous linked article, combined with the drive filter would be useful for programs that dump large amounts of data to a folder just off of the root of a scratch drive (Video capture utilities, for example)

  3. Muzer says:

    Could a network location of “//a” somehow linked from My Computer (my brain’s a little rusty on how you make things show up in My Computer so I don’t know entirely if this is possible) show up as a false positive here? (Not that I suspect anyone would have such a computer name linked into My Computer like this…)

    1. \\a is not considered a root (it has to be \\a\b), but the underlying point is well-taken. I’ll add a PathGetDriveNumber.

  4. Kevin says:

    What happens if your end user has more than 26 drives and is using Volume Mount Points? Are they just unable to specify all of their devices?

    1. You’ll have to ask the people who want to do this. (And yes, people want to do this. That’s why I wrote this blog entry. So that when the next person wants to do this, I can just point them at the blog entry.)

    2. cheong00 says:

      That’s valid question, but in reality if you need that much space, usually you’ll want to run them in RAID set (either RAID 0 or JBOD if you don’t need redundancy) or even a SAN array. So drive letters rarely runs out.

      1. cheong00 says:

        And for companies that has lots of file servers, they usually use DFS to manage the mount points.

      2. Danish guy says:

        Not necessary. I have 4 cardreaders in my machine, which eat 4 drive letters each. I guarantee you that it’s not a good idea to put them in a read array.

  5. William says:

    That’s pretty cool and believe it or not, I’ve seen a programme actually do this before. I don’t recall what it was, though I thought it was quite clever at the time. I believe it used a fixed path off the root of the drive so you only specified the root drive to create the hierarchy on. Yes, a dropdown list would’ve achieved the same result but it was a different take on that UI design.

  6. GL says:

    It is quite surprising that Raymond is continuing such an aged article… Is this usual for this blog?

    1. Muzer says:

      Very common. Raymond writes the posts in his queue months/years in advance, and quite often has a tendency to remember even older articles he’s written and refer to them in future ones. I’ve seen articles from a couple of years ago that follow up on articles from 2003…

      1. GL says:

        Could it be the case that Raymond wrote the entire series of “choose folder dialog” long time ago and put the entries on random days in the following years? Or on a random day, like today, Raymond randomly picks a “continuable” entry in the archive and extend it? Or is it such a coincidence that Raymond came up with the idea that happened to have had its place in the blog since years ago this morning?!

        Series like “choose folder dialog” are quite different from, for example, “The wisdom of seventh graders”, because the content of the latter arises/is “born” from time to time. This blog is like shuffled reading notes of the complete book of history, which I certainly enjoy a lot. BTW I’ve planned to read the archive from the very beginning but haven’t got time to do that.

        1. cheong00 says:

          Well… Use already posted code and modify it a little to suit a new purpose is both allow him use less effort to write, and easier for us to read (because presumably those who really read it have also read the old program and understand how it works already, they just need to focus on the modified parts which is highlighted.) So why not?

    2. pc says:

      One can also occasionally get (lightly) chastised for asking a question which he answered in a blog post over a decade ago.

      The only solution really is to read through the entire archives. Don’t worry, they’re worth it.

      ;)

  7. Someone says:

    “The Check­Depth method is method is a bit tricky for a few reasons. First, it treats the null pointer as equivalent to the desktop, so that it is the ancestor of everything. For whatever reason, that’s what IFolder­Filter gives you, so we accommodate it. ”

    My tests under Windows 7 shows that pidlFolder == nullptr happens at the ShouldShow() call when and only for the item “Computer”. For whatever reason, even GetEnumFlags() is never called for the “Desktop” item (is this correct behavior?).

    You can get around the special case (or bug) for the “Computer” item when you use ILCombine(pidlFolder, pidlItem) and then work with the result of this call.

Comments are closed.

Skip to main content