4

I'm attempting to host a file preview handler within a dialog. I've set up an event sink for selection changes in Explorer. When the selection changes, I feed the selected shell item to the dialog, which in turn feeds it to a function that prepares the preview frame.

In general, it successfully loads the correct handler and displays the contents of the file, but for certain file types (namely, Excel and Word files), it runs into various problems like focus-loss or flashing. Here's a demo of the Excel preview handler messing up focus (and by mess up, I mean it wrongfully steals the focus from Explorer, which I'd like to maintain focus):

enter image description here

Word files may load successfully once, but they'll subsequently fail, especially if Word is opened.

As for the code:

For starters, here's my function for obtaining the preview handler from the file extension. This seems to work fine:

HRESULT PreviewHandlerFromExt(LPCWSTR pszExt, IPreviewHandler** ppph)
{
    WCHAR szCLSID[CLSID_LEN] = { 0 };
    DWORD cchOut = CLSID_LEN;
    HRESULT hr = AssocQueryString(  ASSOCF_INIT_DEFAULTTOSTAR,
                                    ASSOCSTR_SHELLEXTENSION,
                                    pszExt,
                                    L"{8895b1c6-b41f-4c1c-a562-0d564250836f}",
                                    szCLSID,
                                    &cchOut );
    if (FAILED(hr))
    {
        return hr;
    }

    CLSID clsid;
    hr = CLSIDFromString(szCLSID, &clsid);
    if (FAILED(hr))
    {
        return hr;
    }

    CComPtr<IUnknown> punk;
    hr = punk.CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER);
    if (FAILED(hr))
    {
        return hr;
    }

    CComPtr<IPreviewHandler> pPrevHandler;
    hr = punk->QueryInterface(&pPrevHandler);
    if (FAILED(hr) || !pPrevHandler)
    {
        return hr;
    }

    return pPrevHandler.CopyTo(ppph);
}

And now here's the function in my dialog that prepares the preview, given a shell item (m_pPreviewHandler is the active preview handler, IDC_PREVIEWFRAME is a placeholder in the dialog for the preview pane, and m_mapExtsToPreviewHandlers is just a map for storing preview handlers as the user comes across them):

void CMyDialog::ShowPreview(IShellItem* pShItem)
{
    HRESULT hr;

    if (m_pPreviewHandler)
    {
        m_pPreviewHandler->Unload();
        m_pPreviewHandler.Release();
    }

    CComHeapPtr<WCHAR> pszPath;
    hr = pShItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
    if (FAILED(hr))
    {
        return;
    }

    LPWSTR pszExt = CharLower(PathFindExtension(pszPath));

    auto it = m_mapExtsToPreviewHandlers.find(pszExt);
    if (it == m_mapExtsToPreviewHandlers.end())
    {
        hr = PreviewHandlerFromExt(pszExt, &m_pPreviewHandler);
        if (FAILED(hr) || !m_pPreviewHandler)
        {
            return;
        }

        m_mapExtsToPreviewHandlers[pszExt] = m_pPreviewHandler;
    }

    else
    {
        m_pPreviewHandler = m_mapExtsToPreviewHandlers[pszExt];
    }

    CComPtr<IInitializeWithFile> pInitWithFile;
    hr = m_pPreviewHandler->QueryInterface(&pInitWithFile);
    if (SUCCEEDED(hr))
    {
        hr = pInitWithFile->Initialize(pszPath, STGM_READ);
        if (FAILED(hr))
        {
            return;
        }
    }

    else
    {
        CComPtr<IInitializeWithStream> pInitWithStream;
        hr = m_pPreviewHandler->QueryInterface(&pInitWithStream);
        if (SUCCEEDED(hr))
        {
            CComPtr<IStream> pStream;
            hr = SHCreateStreamOnFile(pszPath, STGM_READ, &pStream);
            if (FAILED(hr) || !pStream)
            {
                return;
            }

            hr = pInitWithStream->Initialize(pStream, STGM_READ);
            if (FAILED(hr))
            {
                return;
            }
        }
    }

    CWindow wndPreviewFrame( GetDlgItem(IDC_PREVIEWFRAME) );
    CRect rectPreviewFrame;
    wndPreviewFrame.GetClientRect(&rectPreviewFrame);

    hr = m_pPreviewHandler->SetWindow(wndPreviewFrame, &rectPreviewFrame);
    if (FAILED(hr))
    {
        return;
    }

    hr = m_pPreviewHandler->DoPreview();
    if (FAILED(hr))
    {
        return;
    }

    hr = m_pPreviewHandler->SetRect(&rectPreviewFrame);
    if (FAILED(hr))
    {
        return;
    }
}

Does anyone know what I'm doing wrong or what might fix these focus problems?

I've also tried placing LockSetForegroundWindow at various places, but no lock.

Also, this is what the dialog resource looks like:

enter image description here

  • It's not you, it's the Excel preview handler not following the standard (which says preview handlers must not steal focus). – Jonathan Potter Jul 19 '20 at 23:24
  • Do you have any thoughts on workarounds? Explorer's preview pane is able to host the Excel preview handler without any focus changes, so perhaps there's something else it's doing to maintain focus? –  Jul 20 '20 at 00:16
  • Preview window is a child window ([`WS_CHILDWINDOW`](https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles)) in File Explorer. Here is an example: "[Recipe Preview Handler Sample](https://learn.microsoft.com/en-us/windows/win32/shell/samples-recipepreviewhandler)" you can check to see if it helps. – Rita Han Jul 20 '20 at 02:56
  • @RitaHan-MSFT I've added a picture of my dialog resource. –  Jul 20 '20 at 14:27
  • 1
    Do you have a complete reproducing project? Devil sometimes hides in details – Simon Mourier Jul 20 '20 at 15:04
  • I think one problem (among many others) is that this code does not launch the Preview Handler Surrogate Host. Do you know how to instantiate a preview handler through that surrogate? –  Jul 20 '20 at 21:08
  • 1
    @loop123123 Have you tried showing preview in a child window instead of a dialog? Or you can show a mini, complete and reproducible sample so we can do a further investigation. – Rita Han Jul 22 '20 at 08:03
  • Yes, I'm trying to put together a mini sample right now –  Jul 22 '20 at 13:35
  • Have you tried to play with your target window's style and/or extended style? For example: WS_EX_NOACTIVATE. Or disable it: EnableWindow(hwnd, FALSE) – Simon Mourier Jul 27 '20 at 07:57
  • @loop123123 It seems the dialog window doesn't catch the focus when the File Explorer window lose it. If this is true, can you check who actually catch the focus? And have you tried change the file sequence? I mean, for example, put the Excel file first and put the TXT file at the last. – Rita Han Jul 27 '20 at 08:54

0 Answers0