How to get the IPreviewHandler for a file extension?

How do i get the shell IPreviewHandler for a particular file extension?

Background

Windows allows developers to create a preview handler for their custom file types:

Preview handlers are called when an item is selected to show a lightweight, rich, read-only preview of the file's contents in the view's reading pane. This is done without launching the file's associated application.

A preview handler is a hosted application. Hosts include the Windows Explorer in Windows Vista or Microsoft Outlook 2007.

I want to leverage the existing IPreviewHandler infrasturcture to get a thumbnail for a file.

In A Stream

The problem is that my files are not housed in the shell namespace (ie they are not sitting on the hard drive). They are sitting in memory, accessable through an IStream . This means i cannot use the legacy IExtractImage interface; as it does not support loading a file from a Stream .

Fortunately, this is why the modern IPreviewHandler supports (recommends, and prefers) loading data from a Stream , and recommends against loading previews from a file:

This method is preferred to Initialize due to its ability to use streams that are not accessible through a Win32 path, such as the contents of a compressed file with a .zip file name extension.

So how do i get it?

There is no documentation on the correct way to get ahold of the IPreviewHandler associated with a particular extension. But if i take the directions of how to register an IPreviewHandler , and read the contract from the other side:

HKEY_CLASSES_ROOT
  .xyz
     (Default) = xyzfile

HKEY_CLASSES_ROOT
   xyzfile
      shellex
         {8895b1c6-b41f-4c1c-a562-0d564250836f} //IPreviewHandler subkey
             (Default) = [clsid of the IPreviewHandler]

I should be able to follow the same route, given that i know the extension. Lets follow that with a real world example, a .jpg file:

在这里输入图像描述

在这里输入图像描述

Notice that the file has a preview. Notice i included the second screenshot only to reinforce the idea that the preview doesn't come from a file sitting on the hard drive.

Lets get spellunking!

First is the fact that it's a .jpg file:

HKEY_CLASSES_ROOT
   .jpg
      (Default) = ACDC_JPG

HKEY_CLASSES_ROOT
   ACDC_JPG
      ShellEx
         {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}
         ContextMenuHandlers

Wait, there is no {8895b1c6-b41f-4c1c-a562-0d564250836f} subkey for a previewhandler. That must mean that we cannot get a thumbnail for .jpg files.

reducto an absurdum

The Real Question

The careful reader will realize that the actual question i'm asking is:

How do i get the preview of an image contained only in a stream?

And while that is a useful question, and the real issue i'm having, having an answer on how to use IPreviewHandler is also a useful question.

So feel free to answer either; or both!

Bonus Reading

  • MSDN: Preview Handlers and Shell Preview Host
  • MSDN: How to Register a Preview Handler
  • MSDN: IInitializeWithStream::Initialize method
  • IPreviewHandler throws uncatchable exception
  • Outlook IPreviewHandler for Delphi

  • @hvd had the right answer.

    File types have a ShellEx key, with {guid} subkeys. Each {guid} key represents a particular InterfaceID .

    There are a number of standard shell interfaces that can be associated with a file type:

  • {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1} IExtractImage
  • {953BB1EE-93B4-11d1-98A3-00C04FB687DA} IExtractImage2
  • {e357fccd-a995-4576-b01f-234630154e96} IThumbnailProvider
  • {8895b1c6-b41f-4c1c-a562-0d564250836f} IPreviewHandler
  • If i want to find, for example, the clsid of the IPreviewHandler associated with a .jpg file, i would look in:

    HKEY_CLASSES_ROOT/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
       (default) = [clsid]
    

    But that's not the only place i could look. I can also look in:

    HKEY_CLASSES_ROOT/.jpg
       (default) = jpgfile
    HKEY_CLASSES_ROOT/jpgfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
       (default) = [clsid]
    

    But that's not the only place i could look. I can also look in:

    HKEY_CLASSES_ROOT/SystemFileAssociations/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
       (default) = [clsid] 
    

    But that's not the only place i could look. I can also look in:

    HKEY_CLASSES_ROOT/SystemFileAssociations/jpegfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
       (default) = [clsid]
    

    But that's not the only place i could look. If i think the file is an image, i can also look in:

    HKEY_CLASSES_ROOT/SystemFileAssociations/image/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
       (default) = [clsid]
    

    How did i find these locations? Did i only follow documented and supported locations? No, i spied on Explorer using Process Monitor as it went hunting for an IThumbnailProvider .

    So now i want to use a standard shell interface for a file-type myself. This means that i have to crawl the locations. But why crawl these locations in an undocumented, unsupported way. Why incur the wrath from [the guy][1] from high atop [the thing][2]? Use [ AssocQueryString ][3]:

    Guid GetShellClsidForFileType(String fileExtension, Guid interfaceID)
    {
        //E.g.:
        //   String fileExtension = ".jpg"
        //   Guid   interfaceID   = "{8895b1c6-b41f-4c1c-a562-0d564250836f}"; //IExtractImage
    
        //The interface we're after - in string form
        String szInterfaceID := GuidToString(interfaceID);
    
        //Buffer to receive the clsid string
        DWORD bufferSize := 1024; //more than enough to hold a 38-character clsid
        String buffer;
        SetLength(buffer, bufferSize);
    
        HRESULT hr := AssocQueryString(
              ASSOCF_INIT_DEFAULTTOSTAR, 
              ASSOCSTR_SHELLEXTENSION, //for finding shell extensions
              fileExtension, //e.g. ".txt"
              szInterfaceID, //e.g. "{8895b1c6-b41f-4c1c-a562-0d564250836f}"
              buffer,        //will receive the clsid string
              @bufferSize);
       if (hr <> S_OK) 
          return Guid.Empty;
    
       Guid clsid;
       HRESULT hr = CLSIDFromString(buffer, out clsid);
       if (hr <> NOERROR) 
          return Guid.Empty;
    
       return clsid;
    }
    

    And so to get the clsid of IPreviewHandler for .xps files:

    Guid clsid = GetShellClsidForFileType(".xps", IPreviewHandler);
    
    链接地址: http://www.djcxy.com/p/20452.html

    上一篇: F#:高级使用活动模式

    下一篇: 如何获得文件扩展名的IPreviewHandler?