Search code examples
visual-c++viewwindows-shellwindows-explorer

Hiding visible columns in Windows Explorer via IColumnManager of Windows Shell doesn't work


Background: Expert in C#/.NET, Basic knowledge in C++/MFC/Windows Shell

Environment: Windows 10 (German), Visual Studio 2017 (15.9.56), C++, Windows SDK 10.0.19041.0

Task: Showing/Hiding columns in Windows Explorer (current open window/current active folder) via code: C++/Windows Shell

Approach: IShellWindows -> IShellBrowser -> active IShellView -> IColumnManager -> SetColumnInfo of column in Windows Explorer

Result: Showing columns (which are hidden) works! Hiding columns (which are visible) doesn't work: Windows Explorer just does nothing / no error!

The first two functions of the following code show the way to get IColumnManager (compact code without any HRESULT handling) and the columns. The third function shows the code for GetColumnInfo and SetColumnInfo.

void BOShell::processActiveShellView()
{
    CoInitialize(NULL);
    IShellWindows* shwnds;
    CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&shwnds));

    VARIANT index;
    index.vt = VT_I4;
    index.lVal = 0;

    // opened window (there is only one)
    IDispatch* disp;
    shwnds->Item(index, &disp);

    IShellBrowser* shbrowser;
    IUnknown_QueryService(disp, SID_STopLevelBrowser, IID_PPV_ARGS(&shbrowser));

    // active shell view (selected folder)
    IShellView* shview;
    shbrowser->QueryActiveShellView(&shview);

    processColumns(shview);

    shbrowser->Release();
    disp->Release();
    shwnds->Release();
    CoUninitialize();
}

void BOShell::processColumns(IShellView* shview)
{
    IColumnManager* colmgr;
    shview->QueryInterface(IID_PPV_ARGS(&colmgr));

    UINT cols = 0;
    colmgr->GetColumnCount(CM_ENUM_ALL, &cols);

    PROPERTYKEY* propkeys = new PROPERTYKEY[cols];
    colmgr->GetColumns(CM_ENUM_ALL, propkeys, cols);

    for (UINT ii = 0; ii < cols; ii++)
    {
        setColumnInfo(colmgr, propkeys[ii]);
    }

    delete propkeys;
    colmgr->Release();
}

void BOShell::setColumnInfo(IColumnManager* colmgr, PROPERTYKEY& propkey)
{
    CM_COLUMNINFO ci;
    ci.cbSize = sizeof(ci);
    ci.dwMask = CM_MASK_NAME | CM_MASK_STATE | CM_MASK_WIDTH;
    colmgr->GetColumnInfo(propkey, &ci);
    // dwState is 1 (CM_STATE_VISIBLE)

    // hide column Typ
    if (wcscmp(ci.wszName, L"Typ") == 0)
    {
        ci.dwMask = CM_MASK_STATE;
        ci.dwState = CM_STATE_NONE;
        HRESULT hr = colmgr->SetColumnInfo(propkey, &ci);
        // OK but nothing happened!

        colmgr->GetColumnInfo(propkey, &ci);
        // dwState is still 1 (CM_STATE_VISIBLE)
    }
}

The column has dwState 1 (CM_STATE_VISIBLE).

The SetColumnInfo function to set dwState to 0 (CM_STATE_NONE) returns OK.

The GetColumnInfo call after SetColumnInfo shows that dwState is still 1 and not 0! So SetColumnInfo seems to have done nothing!

As mentioned above: The other way around (from hidden (0) to visible (1)) works! (And setting the width of a column works as well!)

Questions: Is this code correct (in my view yes)? Is this a known issue that this doesn't work in Windows Explorer? Is there another approach to hide the columns? Any hint is welcome!

In my view another approach would be to do it via Context Menu that appears when right clicking on the column headers. But I have found zero information about how I can access this Context Menu via code. Any advice how to get this Context Menu is appreciated as well!


Solution

  • To hide columns, you must redefine the whole set of columns using the IColumnManager::SetColumns method.

    Here is some sample code (I've used error checks and ATL's smart pointers to avoid possible memory leaks). In this code, I remove the "Title" column if it's the last one, so just make sure it's the last one in the first opened view before you test it:

    #include <Windows.h>
    #include <atlbase.h>
    #include <exdisp.h>
    #include <shobjidl_core.h>
    #include <ShlGuid.h>
    
    int main()
    {
      CoInitialize(NULL);
      {
        CComPtr<IShellWindows> windows;
        ATLASSERT(SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)));
    
        CComPtr<IDispatch> first;
        ATLASSERT(SUCCEEDED(windows->Item(CComVariant(0), &first)));
    
        CComPtr<IColumnManager> mgr;
        ATLASSERT(SUCCEEDED(IUnknown_QueryService(first, SID_SFolderView, IID_PPV_ARGS(&mgr))));
    
        // get the visible columns
        auto flags = CM_ENUM_VISIBLE;
        UINT count = 0;
        ATLASSERT(SUCCEEDED(mgr->GetColumnCount(flags, &count)));
    
        CComHeapPtr<PROPERTYKEY> pks;
        pks.Allocate(count);
        ATLASSERT(SUCCEEDED(mgr->GetColumns(flags, pks, count)));
    
        // remove last if it's "Title"
        if (count > 1)
        {
          CM_COLUMNINFO ci{ sizeof(CM_COLUMNINFO) };
          ci.dwMask = CM_MASK_NAME;
          ATLASSERT(SUCCEEDED(mgr->GetColumnInfo(pks[count - 1], &ci)));
    
          if (!lstrcmp(ci.wszName, L"Title"))
          {
            ATLASSERT(SUCCEEDED(mgr->SetColumns(pks, count - 1)));
          }
        }
      }
      CoUninitialize();
      return 0;
    }