Preview Handlers are a concept introduced in Windows Vista (and supported in later operating systems). Most prominently used by Windows Explorer (and notably in Outlook 2007), they allow third-party developers to provide lightweight, interactive previews of data that can be hosted within other applications. You might use them to preview documents in a custom file explorer, or from somewhere that Windows Explorer cannot go, such as files stored as BLOBs in a database. Any exercise that involves reviewing or managing documents benefits extremely from a mechanism that will preview the content without opening another program, and this is it!

From an implementation perspective, preview handlers are basically borderless windows that are bound to a window or control in your application. Their operation and content can be controlled, but their UI is isolated. All preview handlers implement the COM interface IPreviewHandler and are associated with file formats within the Windows Registry. At the time of writing, there is no official managed API for hosting preview handlers in Windows Forms applications, so I have written my own.

Windows Forms app with an embedded Microsoft Word previewer

In my implementation, PreviewHandlerHost is a custom control to which the preview handler is bound. With each filename/stream that is opened, the appropriate handler is loaded and displayed. To display a preview handler in a Windows Forms app, given the filename of the content you want to preview, the following must be performed:

  • Locate the registry key in HKEY_CLASSES_ROOT that corresponds to the file extension. Within the ShellEx key, there should be a key with the GUID {8895b1c6-b41f-4c1c-a562-0d564250836f}. Within this key, there should be a GUID that identifies the COM type that implements IPreviewHandler. If you cannot locate the key here, try the file type’s class key – this corresponds to the default value of the first key mentioned above.
private Guid GetPreviewHandlerGUID(string filename) {
    RegistryKey ext = Registry.ClassesRoot.OpenSubKey(Path.GetExtension(filename));
    if (ext != null) {
        RegistryKey test = ext.OpenSubKey("shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}");
        if (test != null) return new Guid(Convert.ToString(test.GetValue(null)));

        string className = Convert.ToString(ext.GetValue(null));
        if (className != null) {
            test = Registry.ClassesRoot.OpenSubKey(className + "\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}");
            if (test != null) return new Guid(Convert.ToString(test.GetValue(null)));
        }
    }

    return Guid.Empty;
}
  • Using reflection (since there is no assembly or type library we can use), create an instance of the preview handler (using Type.FromCLSID() and Activator.CreateInstance()).
Type comType = Type.GetTypeFromCLSID(mCurrentPreviewHandlerGUID);
mCurrentPreviewHandler = Activator.CreateInstance(comType);
  • Depending on the type, initialise the preview handler with a filename (IInitializeWithFile) or a stream containing the content to preview (IInitializeWithStream).
if (mCurrentPreviewHandler is IInitializeWithFile) {
    ((IInitializeWithFile)mCurrentPreviewHandler).Initialize(filename, 0);
}
else if (mCurrentPreviewHandler is IInitializeWithStream) {
    mCurrentPreviewHandlerStream = File.Open(filename, FileMode.Open);
    StreamWrapper stream = new StreamWrapper(mCurrentPreviewHandlerStream);
    ((IInitializeWithStream)mCurrentPreviewHandler).Initialize(stream, 0);
}
  • Bind the preview handler to the control you want to host it in (you can limit it to a region within the control’s bounds) and activate it.
Rectangle r = ClientRectangle;
((IPreviewHandler)mCurrentPreviewHandler).SetWindow(Handle, ref r);
((IPreviewHandler)mCurrentPreviewHandler).DoPreview();

There are some hurdles involved in getting the COM types into .NET:

  • The relevant COM interfaces have to be manually declared and mapped using the [ComImport] attribute (so it is necessary to know their GUIDs and structure).
  • IInitializeWithStream cannot be used directly with System.IO.Stream, so it was necessary to write a wrapper class that implements System.Runtime.InteropServices.IStream.
  • The usual COM clean-up routine is required (Marshal.FinalReleaseComObject()) in order to release resources – this is integrated into the control’s Dispose() method.

The finished control can simply be dropped onto a Form, and will display the preview after a call to the Open() method. If you resize the control while a preview handler is active, it will be resized accordingly. If no file has been loaded or an error is encountered, it will be displayed on the empty control. You can also explicitly unload a file by calling UnloadPreviewHandler(). Preview handlers may have transparent backgrounds (for example, the Microsoft Office handlers use rounded borders), so the control permits this.

Final Words

Preview handlers are a powerful addition to file explorers, document management systems and the like, and they can be harnessed for use in .NET as well. Unfortunately, however, they are not the only mechanism that Windows Explorer and other previewers use to display content. Under Vista and Windows 7, for example, there are no preview handlers registered for images, plain text documents or HTML files. That means that PreviewHandlerHost is far from a holistic solution. Rather, it should be combined with existing constructs to preview a fuller range of file formats. However, in testing I was able to demonstrate support for most major office document formats and media files.

I hope you find it useful :)

Download

There is a newer version of this code available here: Preview Handlers Revisited

PreviewHandlers.cs

Note: You must add a reference to the System.Design assembly in order to compile the source code.
Also, there is a known issue where the Windows Media Player 11 preview handlers leave behind a black rectangle after unloading.

Share/Bookmark

34 thoughts on “Hosting Preview Handlers in Windows Forms Applications

  1. vote

    Hi Bradley,

    Thanks for this. Have you figured out a problem with TXT Previewer, i still can’t solve it.

    Also I would some small code at the end of GetPreviewHandlerGUID method, kind of a fallback (work for all the files that have text value of PercievedType):

    else
    {
    string PercievedType = Convert.ToString(extension.GetValue(“PerceivedType”));
    if (PercievedType != null)
    {
    string PercievedTypePreviewHandlerCLSID = string.Format(@”SystemFileAssociations\{0}\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}”, PercievedType);
    registryKey = Registry.ClassesRoot.OpenSubKey(PercievedTypePreviewHandlerCLSID);
    if (registryKey != null)
    return new Guid(Convert.ToString(registryKey.GetValue(null)));
    }
    }

    Best Regards,
    Marko

    Reply
    • vote

      Unfortunately, I have no new information about the TXT Preview Handler; it just doesn’t seem to work in some environments. Thanks for the code snippet; it seems there are several different locations in the registry that preview handler GUIDs may be declared.

      Reply
  2. vote

    Thank you for your reply.

    It works when app is build in x64 mode, but not x86, that’s all I could get.

    Thanks again for your sample, it helped a lot.

    Best regards,
    Marko

    Reply
    • vote

      Unfortunately that’s not possible; the preview handler API only exposes the functionality to load content into the handler, not to interact with it in any way.

      Reply
  3. vote

    Hi,

    By default, when I drop your previewHost component on a WinForm, I can open word files with it but can’t open pdf files for example.

    Can you tell me how can I register additional handlers for another file types which I need in my App, to be opened with this component?

    Thanks,
    Vedran

    Reply
  4. vote

    Very nice post.

    Just one quick remark: preview handlers can also be registered per user, ie: the guid discovery code should check HKCU as well (don’t know if it’s documented anywhere though…)

    Reply
    • vote

      Aha, I wondered if there was something in the Windows SDK to deal with this – thanks for your contribution, should save a lot of time and effort!

      Reply
    • vote

      The complete source code is already provided. As stated in my post, the formats that can be previewed will depend on the handlers installed on your machine. If you can only preview Word files, then I presume you only have the appropriate preview handler for those.

      Reply
  5. +1
    vote

    Found some issues (Windows 7 x64, Office 2010, VS 2010, .Net 4.0):
    – OnResize need SetRect(ref r) to be called instead of SetWindow(…);
    – Dispose(…) raises “COM object that has been separated from its underlying RCW cannot be used.”. Next helps:
    if (Marshal.AreComObjectsAvailableForCleanup()) { UnloadPreviewHandler(); }

    Reply
  6. vote

    Great post! It works very well when I’m trying to open a file directly on my disk using the filename but what I’m actually trying to do is to open a Stream object that comes from a Livelink database. Is it actually possible? When I download content from the Livelink database the Livelink webservice returns me a System.IO.Stream Object and when I try to use the Open(stream, GUID) method I’m not sure on how to create the GUID object parameter. If I create the GUID with the GetPreviewHandlerGUID with the extension of the file that I can get from the Livelink database and try to initialize it says “No such interface supported” since it believes it has been instantiate from a file type and not a stream (or it is what I understand from it). Of course I could get the Stream object, save it to a temporary file and preview it but I don’t think it’s a good practice. Any help would be greatly appreciated.

    Reply
  7. vote

    Great job!
    Any idea / hint how todo that in Delphi (win32, not .NET)
    Should be even more straight forward (directly using COM)

    Reply
  8. vote

    Hi Bradley, This is excellent code – I have however found something very peculiar when previewing Word documents, an occurence of Winword starts up and if I then switch to a different type of file (e.g. pdf) Winword shuts down when calling the Marshal.FinalReleaseComObject(mCurrentPreviewHandler) inside the public bool Open(string filename) – this is great.

    However if the last document being previewed is a Word document then even though the dispose method calls UnloadPreviewHandler and then Marshal.FinalReleaseComObject – Winword is left running – so you can end up with a whole load of Winword processes running.

    So anyone have any ideas – I am on Windows 7 / Office 2010 / VS 2010 / tried both .net 4 and .net 3.5

    Reply
    • vote

      Hi Andrew, I have to admit that this one has me stumped. The call to Unload() should be enough to remove any runtime dependency on Winword.exe, and even if the code in the Dispose() method was not sufficiently releasing the object, the shutting down of the runtime should cause the process to exit anyway. COM can be a mysterious beast at times, and I think this is just one of those instances. I have not made an attempt to replicate this, but if I find a solution, I will be sure to post about it.

      Reply
      • vote

        I seem to have solved this problem by disposing the previewhandler in the “closing” event of the form – i.e. not leaving it to the general component dispose – this seems to shut down word correctly:

        public Preview_Form()
        {
        InitializeComponent();
        this.FormClosing += new FormClosingEventHandler(Preview_FormClosing);
        }

        void Preview_FormClosing(object sender, FormClosingEventArgs e)
        {
        //We seem to need to dispose of the previewhandler before the parent form is disposed
        // otherwise Word does not get unloaded!!
        PreviewHandlerHost1.Dispose();
        }

        No idea why this should matter – I guess it implies things are getting disposed of out of sequence.

        Anyhow put it here in case someone else has this issue.

        Reply
        • vote

          Hi Andrew (and a shout out to Brad as well),

          Not sure if you are still reading this blog entry, but I ran across exactly the same issue that you describe above some years ago myself. At the time, I also found out that doing the unload and dispose at the parent level improved things, but even back then my testing showed that the problem would still happen every now and again. I was never happy with this outcome and in fact, the problem has been bugging me since that time.

          This week I started reworking some code for other reasons and I decided to place the iPreviewHandler stuff into its own thread whilst I was at it. That is when the fun began and I finally realise what is going on.

          Basically what is happening is that my application exits before Unload() has finished, which it can do because of the way iPreviewHandler works. I found this out purely by accident as I placed a block on my main thread whilst testing and I saw that Unload() never returned. No error message, it just never returned at all.

          My first thoughts were that perhaps I was destroying the file stream too soon but if that is the problem its not the one that counts as I blocked the main thread immediately after calling Unload() but before the stream has been destroyed. Sure enough Unload() waits, so it does appear to require the Message Pump.

          I then tried various combinations of delays and even a message pump separate to the main one – get the delay right and all is fine. If the delay is too short, Unload() simply stops and just sits there.
          That is why WINWORD.EXE just hangs around and although my application appears to have exited, it is still in the list of processes as the worker thread is still waiting. Manually end the Winword process, Unload() returns and my thread closes.

          I have only just discovered this and I am somewhat stunned by what I think my tests are telling me. I guess I made the assumption that the Previewer would have its own message loop if it needed one. Perhaps it doesn’t need one but does need to communicate with the host Window/Control for some reason. I thought we were pushing information but that cannot be the case. So it does appear that some sort of Synchronisation is needed to ensure things shutdown smoothly.

          Since altering my application to close down the Preview and then wait before calling PostQuitMessage(), the problem appears to have been completely resolved. I have really hammered it and so far the issue has not resurfaced, fingers-crossed.

          If anyone wants to try this out for themselves, note that the larger the file that needs to be unloaded the greater chance that your application will exit before unload has occurred. I did most of my testing with a large doc file (10 meg) and a 60Meg PDF file both of which I previewed across my network. This allowed me to easily replicate the problem at will.

          Reply
  9. vote

    when i try to preview sometimes i get following error:
    Cannot create a top-level child window. (Exception from HRESULT: 0x8007057E)
    can u tell me why is it so???

    Thanks

    Reply
  10. vote

    HI Smith,

    First of all thank you very much for a great article on preview handlers.
    I was searching for a long time for this type of previewer to use with my C# application. Finally got from you site.

    By the way I am getting an error when I resize the form where this handler is placed.
    This is only happen when the handler is loaded with MS Office File.

    ************** Exception Text **************
    System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
    at IPreviewHandler.SetWindow(IntPtr hwnd, Rectangle& rect)
    at PreviewHandlerHost.OnResize(EventArgs e) in C:\Users\vadelk\Documents\Visual Studio 2010\Projects\WindowsFormsApplication1\WindowsFormsApplication1\PreviewHandler.cs:line 126
    at System.Windows.Forms.Control.OnSizeChanged(EventArgs e)
    at System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height, Int32 clientWidth, Int32 clientHeight)
    at System.Windows.Forms.Control.UpdateBounds()
    at System.Windows.Forms.Control.WmWindowPosChanged(Message& m)
    at System.Windows.Forms.Control.WndProc(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

    Do You Have any idea on above error?

    Reply
  11. Pingback: Previewing Microsoft Office Documents and PDF in WPF using a Preview Handler Host « Hmadrigal's Blog

  12. vote

    Environment (Window 8, Adobe PDF Reader 11.1)
    exceute : ((IPreviewHandler)mCurrentPreviewHandler).DoPreview();
    in case of pdf file, it gives exception like : error hresult e_fail has been returned from a call to a com component. It happens onlu in windows 8. It working fines in Windows 7.

    Reply
    • vote

      The Adobe PDF Preview Handler can be used in this environment – I can confirm that it works under the environment I use (Reader 11.0.3, Windows 8 64-bit). Be sure to check the other comments on this post for additional considerations, in particular mismatches that may occur in relation to architecture (64-bit processes cannot invoke 32-bit preview handlers, and vice versa). Windows 8 also introduces some changes in relation to UAC and permissions; you may experience problems invoking preview handlers if your application is not started with appropriate permissions.

      Reply
    • vote

      I also had this problem and discovered that yet again we have a bit of a dogs breakfast going on with Windows 8.

      The problem specifically with the previewing of the PDF file is that Windows 8 comes with its own PDF reader which is a metro application and DoPreview() fails.

      What I then did was install Adobe Reader 9.5 for testing, but I still got the error. Then I went into the Default Programs section in Control Panel and made sure that .PDF was associated with Adobe Reader. This solved the problem.

      Note that Windows 8 causes grief for the Outlook 2007 preview facility as well. Yet another example of things not being thought through correctly on WIn8.

      Reply
  13. vote

    hii smith,

    I have created a preview handler to dispaly an image and i am capable to display the image on preview pane of window explorer(using compilation in VSC++ and then registering it using regsvr32 fName.dll). it works succesfully in Wexplorer but now i tried to do so in in preview pane of outlook 2010 by attching that file on a mail.

    by random search on mail i got some idea that i need to create add-in for my^preview handler and then only it will work on outlook … do you have any idea how to do that ..even i registered my preview handler on the path ( hr = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_CURRENT_USER,L”Software\\Microsoft\\Office\\Outlook\\Addins\\AMEPreviewHandler”, 0, KEY_WRITE, &hKey));)

    but still when i check outlook i dont have any add-in for it …

    Could you please tellme how to do that ???

    thanks,
    shekhar

    Reply
    • vote

      I’m afraid I cannot help you; all of my experience has been in consuming preview handlers, not creating new ones.

      Reply
  14. vote

    We’re using your sample code in a project of ours, and it works great. We use it to add a preview of word files inside a VSTO solution. Sadly, we then get issues when users click inside the preview (which is a live Word document then), as they cannot get out of the preview anymore (Focus is caught up). Only way to get out of it is to switch to another project, and return to Word.
    Have you got any ideas on this issue?

    Reply
    • vote

      I’m afraid I can’t shed much light on this. If this is a VSTO solution, then I suspect the problem is due to your code running within another of the Office applications, which may process certain window messages (e.g. focus) and not filter them through to child windows. There may well be a solution to this, but I expect it would be complicated and involve a lot of direct calls to the Win32 API.

      Reply
  15. vote

    hi bradley,

    excellent code. just one thing i noticed was that when you load power point file with auto advance slide settings using timing, the slides doesn’t move and stays on the first slide. is there a work around to solve this problem?

    thanks
    raxit

    Reply
    • vote

      I suspect that the PowerPoint preview handler simply doesn’t support this. You have to remember that these are just previews of the slides, and that the preview handler has been optimised to load quickly and use few resources.

      Reply

Leave a reply

required

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>