c++windowsmultithreadingshell-namespace-extension

Receiving parent's window handle on IShellFolder/IShellFolder2 EnumObjects implementations while searching?


I'm scratching my head in receiving the parent window handle in a namespace extension project i'm working on.

The use case is as follows:

  1. User browses to a virtual folder through windows explorer
  2. User performs search (search box above)
  3. I need to retrieve the search box's text before the search starts.

I've managed to do that on a test console app with ISearchBoxInfo interface (https://msdn.microsoft.com/en-us/library/windows/desktop/dd562062%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396)

There are 2 ways i can receive a pointer to this interface:

  1. Using IObjectWithSite::SetSite call - which is not relevant as the search is conducted in a different thread, and i cannot share the COM object between those threads
  2. Identifying the window handle and retrieve the ISearchBox through IWebBrowser2 interface.

Both methods don't work as when i perform the search, the EnumObjects is called via a different thread, and i cannot find a way to identify who is te parent explorer window.

When doing search, the hwnd that comes is always null as follows: EnumObjects while searching

This is the EnumObjects code:

//  Allows a client to determine the contents of a folder by
//  creating an item identifier enumeration object and returning
//  its IEnumIDList interface. The methods supported by that
//  interface can then be used to enumerate the folder's contents.
HRESULT CFolderViewImplFolder::EnumObjects(HWND  hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList)
{
    HRESULT hr;
    _fd = hwnd;

    if (hwnd != NULL) // NULL when performing a search
    {
        const int n = GetWindowTextLength(hwnd);
        wstring text(n + 1, L'#');
        if (n > 0)
        {
            GetWindowText(hwnd, &text[0], text.length());
        }
    }

    if (m_nLevel >= g_nMaxLevel)
    {
        *ppenumIDList = NULL;
        hr = S_FALSE; // S_FALSE is allowed with NULL out param to indicate no contents.
    }
    else
    {
        CFolderViewImplEnumIDList *penum = new (std::nothrow) CFolderViewImplEnumIDList(grfFlags, m_nLevel + 1, this);
        hr = penum ? S_OK : E_OUTOFMEMORY;
        if (SUCCEEDED(hr))
        {
            hr = penum->Initialize();
            if (SUCCEEDED(hr))
            {
                hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
            }
            penum->Release();
        }
    }

    return hr;
}

In addition to my tests, as i have also implementation of IShellFolderViewCB.MessageSFVCB, which runs on the correct thread where i can retrieve the IShellBrowser and thus the handle - when i conduct the search i encounter the following messages:

103, 103, 67, UnmergeMenu, WindowClosing, 106, ViewRelease, ViewRelease

Afterward, no more messages are posted (no matter if i re-search) - the first breakpoint is always at EnumObjects method which i have no context about the parent window.

Any light shedding would be nice.

== EDIT ==

I've came up with some workaround - this is not perfect for all cases but works for most of them - other options will still be nice.

Whenever EnumObjects is called with hwnd = NULL, i'm doing the following: (it's in C# - but it can be easily in C++ also)

static public string PrepareSearch(string currentFolderName, IntPtr hwnd)
        {
            SHDocVw.ShellWindows shellWindows = new ShellWindows();

            SHDocVw.IWebBrowser2 foundBrowser = null;
            bool wasFound = false;
            string foundTxt = null;
            foreach (SHDocVw.IWebBrowser2 eie in shellWindows)
            {
                // as the search is conducted in another thread, while the main window is "free" and in a search mode, it should be first busy.
                string locName = eie.LocationName;
                string exeName = eie.FullName;

                if (!string.IsNullOrEmpty(exeName) && exeName.IndexOf("explorer.exe", StringComparison.OrdinalIgnoreCase) >= 0 &&
                    !string.IsNullOrEmpty(locName) &&
                    eie.Busy && eie.ReadyState == tagREADYSTATE.READYSTATE_LOADING)
                {
                    // in here we're ok, we would also want to get the window title to make sure we're searching correctly.
                    string title = NSEFolder.WindowText((IntPtr)eie.HWND);

                    if (!string.IsNullOrEmpty(title) &&
                        title.IndexOf(currentFolderName) >= 0)
                    {
                        // one or more windows were found, ignore the quick search.
                        if (wasFound)
                        {
                            return null;
                        }

                        wasFound = true;
                        foundTxt = locName;
                    }

                }                                             
            }

            if (wasFound && !string.IsNullOrEmpty(foundTxt))
            {
                return foundTxt;

            }


            return null;
        }

Basically i'm going over all explorer windows, trying to find one that is indeed "explorer.exe" + not empty search string (LocationName) + busy... + title contains name of the current folder name.

it will fail when 2 windows are busy and have the same folder name in the title - but this might be good enough... Not sure here.


Solution

  • So, after a talk with microsoft - it's not possible to do so. the workaround i've added is a valid one (thoguh not perfect).