The Road to Delphi

Delphi – Free Pascal – Oxygene

Hosting Preview Handlers in Delphi VCL Applications

47 Comments

In this post i will show you, how you can host an existing Preview Handler in your Delphi VCL App. Preview handlers are a lightweight and read-only preview of a file contents that are bound to a the preview pane window of the explorer or a another window, all this is done without launching the file’s associated application.

The Preview Handlers was introduced in Windows Vista and are used mainly by the Windows Explorer and other applications like MS Outlook. Hosting an existing preview handler in your application will able to display a preview of most major office document formats, media files, CAD files and so on.

To host a preview handler, first we need find the CLSID of the preview associated to a file extension, this info is located in the windows registry, the default value of the {8895b1c6-b41f-4c1c-a562-0d564250836f} subkey is the class identifier (CLSID) of the handler. An example of the extfile ProgID subkey is shown here, associating a handler of CLSID {11111111-2222-3333-4444-555555555555}.

HKEY_CLASSES_ROOT
   extfile
      shellex
         {8895b1c6-b41f-4c1c-a562-0d564250836f}
            (Default) = [REG_SZ] {11111111-2222-3333-4444-555555555555}

So you can wrote a method like this to get the CLSID of the preview handler associated to a file.

function GetPreviewHandlerCLSID(const AFileName: string): string;
var
  LRegistry: TRegistry;
  LKey: String;
begin
  LRegistry := TRegistry.Create();
  try
    LRegistry.RootKey := HKEY_CLASSES_ROOT;
    LKey := ExtractFileExt(AFileName) + '\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}';
    if LRegistry.KeyExists(LKey) then
    begin
      LRegistry.OpenKeyReadOnly(LKey);
      Result:=LRegistry.ReadString('');
      LRegistry.CloseKey;
    end
    else
      Result := '';
  finally
    LRegistry.Free;
  end;
end;

Now with the proper CLSID we can create an instance the IPreviewHandler interface

var
    FPreviewHandler : IPreviewHandler;
begin
  ...
  ...
  FPreviewHandler := CreateComObject(LPreviewGUID) As IPreviewHandler;

The next step is determine how the preview handler was implemented using a IInitializeWithStream.Initialize, IInitializeWithFile, or IInitializeWithItem interface and then call the proper Initialize method.

  if FPreviewHandler.QueryInterface(IInitializeWithFile, LInitializeWithFile) = S_OK then
    LInitializeWithFile.Initialize(StringToOleStr(FFileName), STGM_READ)
  else
  if FPreviewHandler.QueryInterface(IInitializeWithStream, LInitializeWithStream) = S_OK then
  begin
    LFileStream := TFileStream.Create(FFileName, fmOpenRead);
    LIStream := TStreamAdapter.Create(LFileStream, soOwned) as IStream;
    LInitializeWithStream.Initialize(LIStream, STGM_READ);
  end
  else
  if FPreviewHandler.QueryInterface(IInitializeWithItem, LInitializeWithItem) = S_OK then
  begin
    SHCreateItemFromParsingName(PChar(FileName), nil, StringToGUID(GUID_ISHELLITEM), LShellItem);
    LInitializeWithItem.Initialize(LShellItem, 0);
  end;

Finally we need to call the SetWindow (passing the proper host window handle and TRect) and the DoPreview methods of the IPreviewHandler interface.

I encapsulate all the above code in a component called THostPreviewHandler and this is the source code.

{**************************************************************************************************}
{                                                                                                  }
{ Unit uHostPreview                                                                                }
{ component for host preview handlers                                                              }
{                                                                                                  }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the    }
{ License at http://www.mozilla.org/MPL/                                                           }
{                                                                                                  }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF   }
{ ANY KIND, either express or implied. See the License for the specific language governing rights  }
{ and limitations under the License.                                                               }
{                                                                                                  }
{ The Original Code is uHostPreview.pas.                                                           }
{                                                                                                  }
{ The Initial Developer of the Original Code is Rodrigo Ruz V.   Copyright (C) 2013.               }
{ All Rights Reserved.                                                                             }
{                                                                                                  }
{**************************************************************************************************}

unit uHostPreview;

interface

uses
  ShlObj,
  Classes,
  Messages,
  Controls;

type
  THostPreviewHandler = class(TCustomControl)
  private
    FFileStream     : TFileStream;
    FPreviewGUIDStr : string;
    FFileName: string;
    FLoaded :Boolean;
    FPreviewHandler : IPreviewHandler;
    procedure SetFileName(const Value: string);
    procedure LoadPreviewHandler;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  protected
    procedure Paint; override;
  public
    property FileName: string read FFileName write SetFileName;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;


implementation

uses
 SysUtils,
 Windows,
 Graphics,
 ComObj,
 ActiveX,
 Registry,
 PropSys;

constructor THostPreviewHandler.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPreviewHandler:=nil;
  FPreviewGUIDStr:='';
  FFileStream:=nil;
end;

procedure THostPreviewHandler.Paint;
const
  Msg = 'No preview available.';
var
  lpRect: TRect;
begin
 if (FPreviewGUIDStr<>'') and (FPreviewHandler<>nil) and not FLoaded then
 begin
  FLoaded:=True;
  FPreviewHandler.DoPreview;
  FPreviewHandler.SetFocus;
 end
 else
 if FPreviewGUIDStr='' then
 begin
   lpRect:=Rect(0, 0, Self.Width, Self.Height);
   Canvas.Brush.Style :=bsClear;
   Canvas.Font.Color  :=clWindowText;
   DrawText(Canvas.Handle, PChar(Msg) ,Length(Msg), lpRect, DT_VCENTER or DT_CENTER or DT_SINGLELINE);
 end;
end;

destructor THostPreviewHandler.Destroy;
begin
  if (FPreviewHandler<>nil) then
    FPreviewHandler.Unload;

  if FFileStream<>nil then
    FFileStream.Free;

  inherited;
end;

function GetPreviewHandlerCLSID(const AFileName: string): string;
var
  LRegistry: TRegistry;
  LKey: String;
begin
  LRegistry := TRegistry.Create();
  try
    LRegistry.RootKey := HKEY_CLASSES_ROOT;
    LKey := ExtractFileExt(AFileName) + '\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}';
    if LRegistry.KeyExists(LKey) then
    begin
      LRegistry.OpenKeyReadOnly(LKey);
      Result:=LRegistry.ReadString('');
      LRegistry.CloseKey;
    end
    else
      Result := '';
  finally
    LRegistry.Free;
  end;
end;

procedure THostPreviewHandler.LoadPreviewHandler;
const
  GUID_ISHELLITEM = '{43826d1e-e718-42ee-bc55-a1e261c37bfe}';
var
  prc                   : TRect;
  LPreviewGUID          : TGUID;
  LInitializeWithFile   : IInitializeWithFile;
  LInitializeWithStream : IInitializeWithStream;
  LInitializeWithItem   : IInitializeWithItem;
  LIStream              : IStream;
  LShellItem            : IShellItem;
begin

  FLoaded:=False;
  FPreviewGUIDStr:=GetPreviewHandlerCLSID(FFileName);
  if FPreviewGUIDStr='' then exit;

  if FFileStream<>nil then
    FFileStream.Free;

  LPreviewGUID:= StringToGUID(FPreviewGUIDStr);

  FPreviewHandler := CreateComObject(LPreviewGUID) As IPreviewHandler;
  if (FPreviewHandler = nil) then
    exit;

  if FPreviewHandler.QueryInterface(IInitializeWithFile, LInitializeWithFile) = S_OK then
    LInitializeWithFile.Initialize(StringToOleStr(FFileName), STGM_READ)
  else
  if FPreviewHandler.QueryInterface(IInitializeWithStream, LInitializeWithStream) = S_OK then
  begin
      FFileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyNone);
      LIStream := TStreamAdapter.Create(FFileStream, soOwned) as IStream;
      LInitializeWithStream.Initialize(LIStream, STGM_READ);
  end
  else
  if FPreviewHandler.QueryInterface(IInitializeWithItem, LInitializeWithItem) = S_OK then
  begin
    SHCreateItemFromParsingName(PChar(FileName), nil, StringToGUID(GUID_ISHELLITEM), LShellItem);
    LInitializeWithItem.Initialize(LShellItem, 0);
  end
  else
  begin
    FPreviewHandler.Unload;
    FPreviewHandler:=nil;
    exit;
  end;

  prc := ClientRect;
  FPreviewHandler.SetWindow(Self.Handle, prc);
end;

procedure THostPreviewHandler.SetFileName(const Value: string);
begin
  FFileName := Value;
  HandleNeeded;
  LoadPreviewHandler;
end;

procedure THostPreviewHandler.WMSize(var Message: TWMSize);
var
  prc  : TRect;
begin
  inherited;
  if FPreviewHandler<>nil then
  begin
    prc := ClientRect;
    FPreviewHandler.SetRect(prc);
  end;
end;

end.

And you can use it in this way

  FPreview := THostPreviewHandler.Create(Self);
  FPreview.Top := 0;
  FPreview.Left := 0;
  FPreview.Width  := Panel1.ClientWidth;
  FPreview.Height := Panel1.ClientHeight;
  FPreview.Parent := Panel1;
  FPreview.Align  := alClient;
  FPreview.FileName:=FileName;

This is a sample image of a preview handler hosted in a VCL Application.

previewhost


Check the source code on Github.

Author: Rodrigo

Just another Delphi guy.

47 thoughts on “Hosting Preview Handlers in Delphi VCL Applications

  1. With Windows 7 and your compiled demo, no previews are shown at all while windows explorer shows previews for a lot of files.
    When running the demo compiled in Delphi 2010 on Windows 7 FPreviewGUIDStr=” and FPreviewHandler=nil in the debugger.

    Perhaps the GUID is incorrect or the location of the registry key is different?

    Bill

    • Try running the GetPreviewHandlerCLSID method using a filename with a preview handler registered.

      • ‘C:\Users\Bill\Documents\Word\Delphi\ClientDataSet.doc’ appears in Explorer Preview panel.
        LoadPreview(‘C:\Users\Bill\Documents\Word\Delphi\ClientDataSet.doc’); Still does not display in the demo.
        .doc files are registered in ShellEx
        HKEY_CLASSES_ROOT\.doc
        HKEY_CLASSES_ROOT\.doc\ShellEx
        HKEY_CLASSES_ROOT\.doc\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f}

        I also note that all image files including *.bmp, *.png and *.tif do not have a ShellEx key in the registry, but they to also are shown in the Explorer Preview?

        • The content of your registry looks fine, maybe is a permission issue, debug the method and check if the the value of the key is read. About the images this is normal, the preview of the images don’t uses preview handlers.

  2. The value of the key is not read.

    How does explorer preview images if it does not use preview handlers?

    I have a component descended from TFileOpenDialog that displays information about image files:
    // Set the FileOpenDialog labels
    c.SetControlLabel(1, PWideChar(IntToStr(iFrames)));
    c.SetControlLabel(2, PWideChar(iColors));
    c.SetControlLabel(3, PWideChar(iDimensions));
    c.SetControlLabel(4, PWideChar(iDPI));
    c.SetControlLabel(5, PWideChar(iFileType));
    c.SetControlLabel(6, PWideChar(iFileSize));
    c.SetControlLabel(7, PWideChar(iMemorySize));

    the image information is displayed nicely in the dialog, but I can not figure out how to show the image preview in the dialog. There is no information about this that I can find, so I was hoping you would know to do this.

    • The standard image formats like gif, jpeg, bmp, png and so on don’t uses preview handlers, these formats are managed by Windows. For another custom formats you must implement a Preview handler, additionally you can register a thumbnail using a Thumbnail Handlers and the IThumbnailProvider interface.

    • I just updated the GetPreviewHandlerCLSID method now uses OpenKeyReadOnly to open the key, try and let me know the result.

      • Very nice !

        On my Win8 computer it works better with OpenKeyReadOnly :-)

        There are some file extensions that need another level of indirection (for exemple .wmv files: the ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f} is not associated with .wmv, but with WMP11.AssocFile.WMV). So I added preview for this kind of files with the following modification to GetPreviewHandlerCLSID:

        function GetPreviewHandlerCLSID(const AFileName: string): string;
        var
        LRegistry: TRegistry;
        LKey: String;
        begin
        LRegistry := TRegistry.Create();
        try
        LRegistry.RootKey := HKEY_CLASSES_ROOT;
        LKey := ExtractFileExt(AFileName) + ‘\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}’;
        if LRegistry.KeyExists(LKey) then
        begin
        LRegistry.OpenKeyReadOnly(LKey);
        Result:=LRegistry.ReadString(”);
        LRegistry.CloseKey;
        end
        else begin
        LKey := ExtractFileExt(AFileName);
        LRegistry.OpenKeyReadOnly(LKey);
        LKey := LRegistry.ReadString(”) + ‘\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}’;
        LRegistry.CloseKey;
        if LRegistry.KeyExists(LKey) then
        begin
        LRegistry.OpenKeyReadOnly(LKey);
        Result:=LRegistry.ReadString(”);
        LRegistry.CloseKey;
        end
        else
        Result := ”;
        end
        finally
        LRegistry.Free;
        end;
        end;

        Thierry

      • Hi Rodrigo. Your compiled demo works now as expected. I can see previews of rtf, doc… but not imagesjpg, bmp, png. Can you add image preview with Thumbnail Handlers and the IThumbnailProvider to your demo?. If so that would be great and I am sure a lot of developers would appreciate it because there is not much information available about this. If you can get it working I will also try to use your code in TFileDialog to display images in the preview for unsupported image types by using ImageEn for FileIO. Thanks, Bill

        ________________________________

  3. Your compiled exe is working fine in win7 64 bit. But with XE3 and XE4 it will not show the preview with the old source and the new on.

  4. Sorry I copied the wrong version: With XE3 and XE4 the new version with OpenKeyReadOnly is working fine for me.

  5. This is a nice complement for how to create preview handlers in Delphi:

    http://www.uweraabe.de/Blog/2011/06/01/windows-7-previews-the-delphi-way/

    Thank you.

  6. When accesing any registry information from HKEY_CLASSES_ROOT you need to specificaly specify that you are only reading it (OpenKeyReadOnly) othervise your acces to it would be denied as this part of the registry is considered as protected and needs elevated permisions othervise (running your application as administrator).

  7. Thanks for the component. Maybe it doesn’t show as many preview as the windows explorer, because all Preview Handler are not registered in the .xyz extension. if you look at http://msdn.microsoft.com/en-us/library/windows/desktop/cc144144(v=vs.85).aspx they mention that the proper behaviour for a handler is to be registered in the ProgID key

  8. Yes. TXT file for example. But my own explanation will not give a proper solution for that case. On my machine, ‘.txt’ have no shellEx entry in the registry while I have a HKCR\SystemFileAssociations\text\ShellEx with a PreviewHandler inside. Windows explorer previews those txt. But I wonder how to land on that last regkey…

  9. About those filetypes not previewed. I found a topic on msdn http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/23f4b1f0-83bc-43e5-8471-4b4ce8d3feaa.
    The key is to also look for a previewhandler under the perceivedtype. Here’s what the link above mentions:

    “TIF and JPG files are registered as having a PerceivedType of “image” and so use the preview handler registered for images (see http://msdn.microsoft.com/en-us/site/bb776871 ).

    Your application shouldn’t need to go through the registry to figure this out on its own though. It can let the shell do the heavy lifting:

    Get the IQueryAssociations interface for the file and then call IQueryAssociations::GetString to find the CLSID of the appropriate preview handler.
    pQueryAssociation->GetString(ASSOCF_NOUSERSETTINGS, ASSOCSTR_SHELLEXTENSION, szIID_IPreviewHandler, wszCLSID, &cch); “

  10. Great post/project!

    I think the problem that quite a lot files don’t get previewed (like: pdf, msg, etc) is in the actual use of the IPreviewHandler.
    Only the
    LInitializeWithFile.Initialize(StringToOleStr(FFileName), STGM_READ)
    seems to work, the others below don’t seem to work.

    But it’s not clear to me why…

  11. Pingback: Hosting Preview Handlers in Delphi VCL Applicat...

  12. Pingback: Anonymous

  13. Hello,
    Your code works fine with XE, is there a way to make it work with BDS 2006 ?

    I copied the PropSys and StructuredQueryCondition unit and added the IPreviewInterface and some constants, but it gives after 1 minute an Unknown OLE Error.

    Can’t find why…

    • Are you using Eurekalog or Madexcept to get extended info about the error?

      • It is on this line : FPreviewHandler := CreateComObject(LPreviewGUID) as IPreviewHandler; in LoadPreviewHandler
        An EOleSysError “Unspecified Error” is thrown.

        If I continue with F9 : EOleSysError : “Unspecified Error, ClassID: {DC6EFB56-9CFA-464D-8880-44885D7DC193}”

      • If I start the program ouside of Delphi and choose a pdf file, after 5 seconds Windows says it has stopped working…

      • PS: The XE version stop answering after selecting 3 pdfs ! I have Adobe Reader 11.0.06 on Win7 Pro 64b.

        • I have found the problem of the XE version, the IStream made a memory leak.
          I added after “LInitializeWithStream.Initialize(LIStream, STGM_READ);” “LIStream := nil;”

  14. Hi,
    can you reupload the sample application (XE2)?
    I cant download it, because “Error (509)This account’s public links are generating too much traffic and have been temporarily disabled!”

  15. Hi Rodrigo,
    thank you for the article, I used the solution in my application and it worked well until now, it’s not working for pdf documents in Windows10, you can try yourself, your demo also does not show the preview of pdf.
    Do you have any idea where is the problem?

  16. Hi, I’m trying to use your very useful component on my windows 10 but it seems not working. I couldn’t get any preview (no pdf, no txt , no docx ecc). Is there an update verssion of the sources? Thank you!

    • I’m compiling it with XE7

    • Hi, Check updated version of the code on GitHub.

      • Hi, now I switched to D10 on Windows 10 and I downloaded github sources. With the sample I could see only Word files and txt files no PDF, no excel, no images. And also, when I try to preview two or more files I got strange access violations or invalid pointer operations closing the program expecially when I try to preview a file that doen’t work. On this machine I’ve upgraded Win 8.1 to Win 10. Is it possibile that in the upgrade preview handlers get broken? Is there a way to fix them?

        Thank you!

        • Hi, I just tested using D10 Seattle on Windows 10.

          “…could see only Word files and txt files no PDF, no excel, no images. ”

          The excel and word files can be previewed without problems.
          The Images are not supported for this previewer , you must use a thumbnail preview.
          There some issues with the PDF preview handler of Adobe , try using the preview handler of foxit

          “I got strange access violations or invalid pointer operations closing the program expecially when I try to preview a file that doesn’t work”
          I’m not getting any AV, but maybe is related to the TShellListView, try using this version of the code which not uses this component https://github.com/RRUZ/blog/tree/master/Winapi/COM/Preview%20Handlers/PreviewHandler%20Host_Without_TShellListView

          Check these sample images


          • In effect I’m using acrobat reader and my customers too. Not all Excel files are previewable but it works on many of them. Using the sample without TShellListView seems working without strange errors.

            Thanks for the answer.

  17. Problem with Outlook-Preview and VCL-Styles
    Using the Sample-Project with VCLStyle-enabled (eg. LUNA) the preview of an email causes an StackOverflow:

    STATUS_STACK_BUFFER_OVERRUN encountered
    Prozess PreviewHost.exe (13820)

    System Win10x64 Delphi Seattle Upd1
    The line FPreviewHandler.DoPreview fails and the process ends.

  18. Hi,
    This is a very interesting development. Also, as far as i searched the web, this post is the closest one to my issue.
    Actually, i’m lloking to do the contrary : not calling a PreviewHandler in a delphi App, but writing a PreviewHandler in delphi language to integrate with windows explorer. If you know a few things about this i would really appreciate your help !

  19. Hola Rodrigo!
    Estoy haciendo una aplicación en Delphi y estoy utilizando este magnífico componente para mostrar las vistas previas. Quisiera saber si debo actualizar el Copyright ya que muestro en mi Acerca de… que utilizo este componente.
    Muchas gracias por tan genial trabajo!

    Sacher

Leave a comment