In the ancient days of Windows XP and Delphi 7 I used to provide a preview of the proprietary file content inside the file open dialog of one of my applications. This was accomplished by deriving from TOpenDialog, just like the TOpenPictureDialog does that comes with Delphi itself.
The drawback was that since Windows Vista this was no way to go when you wanted to use the new Vista style dialogs. This was an even more nuisance as the new dialogs already come with a preview ability – so why not use it directly?
Googling a bit reveiled that others have to fight with the same problem, but no one seemed to have bitten the bullet to make a nice Delphi wrapper around the usual hurdles encountered when programming directly against the operating system. Looks like it was my turn to volunteer and here are the results. A simple implementation of a preview handler the Delphi way can be written in 45 lines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
unit MyPreviewHandler; interface uses PreviewHandler, Classes, Controls, StdCtrls; const CLASS_MyPreviewHandler: TGUID = '{64644512-C345-469F-B5FB-EB351E20129D}'; type TMyPreviewHandler = class(TFilePreviewHandler) private FTextLabel: TLabel; public constructor Create(AParent: TWinControl); override; procedure DoPreview(const FilePath: string); override; property TextLabel: TLabel read FTextLabel; end; implementation uses SysUtils; constructor TMyPreviewHandler.Create(AParent: TWinControl); begin inherited; FTextLabel := TLabel.Create(AParent); FTextLabel.Parent := AParent; FTextLabel.AutoSize := false; FTextLabel.Align := alClient; FTextLabel.Alignment := taCenter; FTextLabel.Layout := tlCenter; FTextLabel.WordWrap := true; end; procedure TMyPreviewHandler.DoPreview(const FilePath: string); begin TextLabel.Caption := GetPackageDescription(PWideChar(FilePath)); end; initialization TMyPreviewHandler.Register(CLASS_MyPreviewHandler, 'bplfile', 'BPL Binary Preview Handler', '.bpl'); end. |
This is all that must be done to display the description of a BPL file in the preview pane, whereas most of the work is actually setting up a TLabel do display it nicely.
Overriding DoPreview is obiligatory as the inherited declaration is abstract. If you refuse to override it, you actually don’t want to write your own preview handler, so stop reading further and mind your own business.
The overridden constructor Create is the place to establish the needed controls for the preview and connect it to the passed parent. You can easily place a derived TFrame instance onto the parent control, which allows the use of the visual form designer for your preview. Be aware that the constructor has to be declared as override.
Overriding the Unload method is optional in case you have to clear the preview and/or free some resources. It just didn’t make sense in this example, unless you want to clear the label caption. (and it would have increased the line count to 51)
Don’t forget to register your preview handler in the initialization part of the unit using a fresh GUID. (Pre-emptive comment: No, class constructors are not the way to go here)
Of course the compiled DLL has to be registered with regsvr32. Make sure to run it as Administrator.
OK, I admit that the overall benefit of this preview handler is somewhat limited, but it does serve well as an example. I also admit that I suppressed the project file, which in turn is quite simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
library MyPreviewHandlerLib; uses ComServ, PreviewHandler in 'PreviewHandler.pas' {PreviewHandler: CoClass}, MyPreviewHandler in 'MyPreviewHandler.pas'; exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer, DllInstall; {$R *.RES} begin end. |
I tried to get rid of the {PreviewHandler: CoClass} entry in the uses clause, but the IDE insisted to put it in again. So, be it…
The real work (as usual) is done under the hood in unit PreviewHandler.pas, which I will explain shortly in the following.
The interface section declares a base class TPreviewHandler and two derived classes TStreamPreviewHandler and TFilePreviewHandler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
type TPreviewHandler = class abstract public constructor Create(AParent: TWinControl); virtual; class function GetComClass: TComClass; virtual; abstract; class procedure Register(const AClassID: TGUID; const AName, ADescription, AFileExtension: string); procedure Unload; virtual; end; TStreamPreviewHandler = class abstract(TPreviewHandler) public procedure DoPreview(Stream: TStream); virtual; abstract; class function GetComClass: TComClass; override; final; end; TFilePreviewHandler = class abstract(TPreviewHandler) public procedure DoPreview(const FilePath: String); virtual; abstract; class function GetComClass: TComClass; override; final; end; |
Microsoft recommends to implement the stream based preview handler as it will also work for previews, say, inside a zip file, but there are cases when a file based preview handler is just a bit more handy. You would have difficulties to implement the sample preview handler from above on a stream with the same simplicity. There is indeed a third possibility to implement a preview handler based on an IShellItem, but let’s say I left the implementation as an exercise.
You may have noticed that TPreviewHandler is derived from TObject instead of TComObject, which actually was my first approach. I decided to separate the COM stuff from the preview implementation to get a lean inheritance and small base classes not cluttered with methods you better shouldn’t know about. The actual COM class is retrieved from the virtual class function GetComClass, which is overriden in both of the derived preview handler classes. The declaration of the derived function as final ensures that no one can override this function in a descendant class. This is necessary because the underlying factory assumes that the COM class descends from a specific private class (I’m open for suggestions on that). If you are going to implement your own COM classes you should better implement your own complete preview handler framework at all. For anyone else, just ignore the GetComClass function.
The implementing COM classes resemble the file/stream structure of the preview handler classes, just because they have to implement either IInitializeWithFile or IInitializeWithStream. The base class TComPreviewHandler is responsible for most of the internal stuff and the implementation is mostly straight forward. The mapping to the Delphi type TWinControl properties is made with a class helper just to bundle all that stuff (and to make it harder to port it to older Delphi versions).
The TComPreviewHandlerFactory class is responsible for updating the various registry entries needed to make Windows aware of the preview handler. It also takes care of doing the right things to use a 32-bit handler in a 64-bit environment. The code to check for this situation is taken from www.delphidabbler.com and I expanded it a little bit so that it hopefully will also work with the upcoming 64-bit Delphi compiler.
As a gimmick you get a TStream implementation that maps to an IStream interface with the TIStreamAdapter class. The other way round is already available in Delphi, but it didn’t fit in here very well.
The sources for Delphi XE can be downloaded here: Preview Handler
Hallo Uwe,
das war jetzt etwas zu technisch – auf welche Weise läßt sich die DLL nutzen bzw.
einbinden? Auch ist mir ein wenig der Benefit entgangen…
Hallo Thomas,
man kann die DLL ganz einfach einbinden: Unter Windows 7 “Start – Alle Programme – Zubehör – Eingabeaufforderung – rechte Maustaste – Als Administrator ausführen”, dann in das Verzeichnis der DLL wechseln und “regsvr32.dll” aufrufen. Damit wird die DLL registriert. Dann den Explorer öffnen und eine BPL auswählen. In der Toolbar des Explorers gibt es einen Button zum Ein/Ausblenden des Vorschaufensters. Bei installierter DLL wird die Package-Bezeichnung als Vorschau angezeigt. Das ist jetzt nicht unbedingt die Killer-App, aber es sollte nur ein Beispiel sein. Man kann so aber leicht auch andere Dateiformate in der Vorschau anzeigen. Das gilt insbesondere für eigene Formate, für die es keine eingebaute Vorschau gibt.
Excellent post!
It took me less than an hour to create a preview handler for presenting the data from our custom .DAT files in explorer.
A minor note though. The Memo control I’m using doesn’t seem to be able to hold on to the focus. All clicks causes the explorer to flash and regain focus.
Haven’t looked into it closer, perhaps I will if it gets too annoying.
You may concentrate your searches on TComPreviewHandler.SetFocus trying to skip the current behaviour. If that solves the problem we might find a better implementation.
While direct debugging is somewhat limited, I had a good experience with CodeSite.
Great Code Uwe, I have experience written previews handlers in C#, Delphi prism and Delphi but my way to generate the preview handler (I use a lot of COM calls and interfaces) differs from you way (the delphi way), your code is much more simple than my code, you are handling very tricky stuff in a simpler way. You are a very smart guy 🙂 congrats for you work.
See You.
I’ll probably use this for one of my projects I’ve been wanting to create a Preview Handler for. Your work is much appreciated as it looks like it will now be an easy task for me. Thank you.
Do you know of a similarly simple way to display a preview of a file/stream within a Delphi application using Preview Handlers? For example, if I want to display a PDF using its’ Preview Handler or my own .ABC extension using my custom Preview Handler?
Interesting question – I might have a look into this…
Wonderful!. Excellent!.
Thanks Uwe.
Where can I found the file “PropSys”?. I use Delphi 2009 Pro.
Thanks in advance
Sorry, the code is meant for Delphi XE – I didn’t test it for earlier versions. Propsys.pas was introduced in Delphi 2010, though. It contains some interface declarations that can also be found somewhere in MSDN.
Thank you very much Uwe.
I recently upgrade to XE but I haven’t installed it yet. Your work is a motivation to do it soon.
Thanks again
i wrote memo1.Lines.SaveToFile under windows xp and under windows 7 its not working,
is it known problem ?
any solutions
thanks very much
yuda
What are you trying to do? What “memo1”?`When do you call SaveToFile?
Currently, I have no idea what you are doing.