# Hosting Preview Handlers in Delphi VCL Applications

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.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
else
if FPreviewHandler.QueryInterface(IInitializeWithStream, LInitializeWithStream) = S_OK then
begin
LIStream := TStreamAdapter.Create(LFileStream, soOwned) as IStream;
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    }
{                                                                                                  }
{ 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.               }
{                                                                                                  }
{**************************************************************************************************}

unit uHostPreview;

interface

uses
ShlObj,
Classes,
Messages,
Controls;

type
THostPreviewHandler = class(TCustomControl)
private
FFileStream     : TFileStream;
FPreviewGUIDStr : string;
FFileName: string;
FPreviewHandler : IPreviewHandler;
procedure SetFileName(const Value: string);
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
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

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.CloseKey;
end
else
Result := '';
finally
LRegistry.Free;
end;
end;

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

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
else
if FPreviewHandler.QueryInterface(IInitializeWithStream, LInitializeWithStream) = S_OK then
begin
FFileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyNone);
LIStream := TStreamAdapter.Create(FFileStream, soOwned) as IStream;
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:=nil;
exit;
end;

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

procedure THostPreviewHandler.SetFileName(const Value: string);
begin
FFileName := Value;
HandleNeeded;
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.

Check the source code on Github.

### Author: Rodrigo

Just another Delphi guy.

### 45 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.

• Rodrigo, do you know if there is a way to use a preview handler for images (e.g., a jpeg) instead of “being managed by Windows?

• 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.CloseKey;
end
else begin
LKey := ExtractFileExt(AFileName);
LRegistry.CloseKey;
if LRegistry.KeyExists(LKey) then
begin
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).

• Yes, because that the code was already modified replacing the OpenKey for the OpenKeyReadOnly method.

• I saw that code had been already modified. I just wanted to provide better eplanation of why this is necessary.

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

• The location to read the registered preview handlers is fine, are you having problems to visualize some of them?

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
seems to work, the others below don’t seem to work.

But it’s not clear to me why…

• I don’t have any problem to preview such formats (pdf, msg).

11. Pingback: Anonymous

12. 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.

13. 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!”

14. 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?

• Sorry for the late response, Are you using the Adobe PDF preview handler? Also try checking the updated version of the code on GitHub.

15. 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.

16. 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.

• You must report this, using the issue page of the blog repository on Github.

17. 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 !

• Hi, for write a preview handler using Delphi check these resources