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.
Check the source code on Github.
May 24, 2013 at 4:19 pm
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
May 24, 2013 at 5:19 pm
Try running the GetPreviewHandlerCLSID method using a filename with a preview handler registered.
May 24, 2013 at 6:23 pm
‘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?
May 24, 2013 at 7:02 pm
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.
May 24, 2013 at 7:12 pm
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.
May 24, 2013 at 8:03 pm
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.
October 11, 2016 at 5:13 pm
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?
May 24, 2013 at 8:18 pm
I just updated the GetPreviewHandlerCLSID method now uses OpenKeyReadOnly to open the key, try and let me know the result.
May 25, 2013 at 4:35 am
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
May 25, 2013 at 11:02 am
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
________________________________
December 23, 2013 at 8:11 am
In case you want a ready-to-use control for Delphi based on IPreviewHandler which also supports common image formats and PDFs check out this:
http://www.jam-software.de/shellbrowser_delphi/file-preview.shtml
May 25, 2013 at 6:10 am
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.
May 25, 2013 at 6:19 am
Sorry I copied the wrong version: With XE3 and XE4 the new version with OpenKeyReadOnly is working fine for me.
May 25, 2013 at 12:08 pm
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.
May 25, 2013 at 2:18 pm
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).
May 25, 2013 at 11:52 pm
Yes, because that the code was already modified replacing the OpenKey for the OpenKeyReadOnly method.
May 26, 2013 at 5:08 pm
I saw that code had been already modified. I just wanted to provide better eplanation of why this is necessary.
June 5, 2013 at 11:04 am
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
June 5, 2013 at 11:14 am
The location to read the registered preview handlers is fine, are you having problems to visualize some of them?
June 5, 2013 at 11:50 am
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…
June 6, 2013 at 3:48 am
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); “
June 28, 2013 at 9:36 am
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…
July 8, 2013 at 12:21 pm
I don’t have any problem to preview such formats (pdf, msg).
Pingback: Hosting Preview Handlers in Delphi VCL Applicat...
Pingback: Anonymous
March 7, 2014 at 9:56 am
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…
March 7, 2014 at 12:09 pm
Are you using Eurekalog or Madexcept to get extended info about the error?
March 10, 2014 at 9:33 am
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}”
March 10, 2014 at 9:43 am
If I start the program ouside of Delphi and choose a pdf file, after 5 seconds Windows says it has stopped working…
March 10, 2014 at 10:36 am
PS: The XE version stop answering after selecting 3 pdfs ! I have Adobe Reader 11.0.06 on Win7 Pro 64b.
March 11, 2014 at 3:47 am
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;”
July 14, 2014 at 7:09 am
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!”
July 14, 2014 at 11:39 am
try this link https://docs.google.com/uc?export=download&id=0B7KzPH8HQCZNZ2ozQVdWNktKMWs
August 11, 2015 at 9:34 am
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?
October 4, 2015 at 6:15 pm
Sorry for the late response, Are you using the Adobe PDF preview handler? Also try checking the updated version of the code on GitHub.
October 1, 2015 at 4:51 am
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!
October 1, 2015 at 5:01 am
I’m compiling it with XE7
October 4, 2015 at 4:58 pm
Hi, Check updated version of the code on GitHub.
October 5, 2015 at 3:45 am
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!
October 5, 2015 at 2:47 pm
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



October 6, 2015 at 10:38 am
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.
February 10, 2016 at 12:56 pm
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.
February 11, 2016 at 11:42 am
You must report this, using the issue page of the blog repository on Github.
July 14, 2016 at 6:09 am
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 !
July 14, 2016 at 1:08 pm
Hi, for write a preview handler using Delphi check these resources
http://stackoverflow.com/questions/5195988/is-there-a-preview-handler-vcl-for-windows-7
https://github.com/RRUZ/delphi-preview-handler
November 22, 2022 at 11:45 am
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
November 22, 2022 at 9:37 pm
Hola Sacher, no hay problema si usas Copyright original.