Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

.NET Shell Extensions - Shell Preview Handlers

4.93/5 (23 votes)
20 May 2014MIT8 min read 148.2K   6.2K  
Quickly create Shell Preview Handlers for Windows or Outlook using .NET!

Introduction

Shell PreviewHandlers are DLLs that can be registered in the system to allow you to create visually rich previews for items that are displayed directly in Windows Explorer. They can also be used in Outlook. In this article, I will show you how to create a Preview Handler extension using .NET and a library called SharpShell. We'll create a Preview Handler that shows the user all of the images contained in an icon file when one is selected, just as below:

Image 1

Above: A folder full of icons on my machine. When an icon is selected, and the preview pane is open, the Icon Preview Handler extension shows each of the different icon images in the file.

The Series

This article is part of the series '.NET Shell Extensions', which includes:

  1. .NET Shell Extensions - Shell Context Menus
  2. .NET Shell Extensions - Shell Icon Handlers
  3. .NET Shell Extensions - Shell Info Tip Handlers
  4. .NET Shell Extensions - Shell Drop Handlers
  5. .NET Shell Extensions - Shell Preview Handlers
  6. .NET Shell Extensions - Shell Icon Overlay Handlers
  7. .NET Shell Extensions - Shell Thumbnail Handlers
  8. .NET Shell Extensions - Shell Property Sheets

How Preview Handlers Work

Preview handlers are pretty funky. Ever since adding support for them to my SharpShell library, I've kept the preview pane open and been surprised by how useful they can be. Essentially, what the system does when you select a file in Explorer and have the preview handler open is:

  1. Check the registry to see if there's a preview handler for the file type.
  2. If there is, create an instance of the prevhost process and load the preview handler DLL into it.
  3. Instantiate the object that implements IPreviewHandler.
  4. Tell that object what the parent window is, what its size is, etc.
  5. Tell the object to render a preview, supplied file details (either the path, stream of the file or IShellItem).

This is a pretty easy process to hook into, Preview Handlers are well documented. However, writing all of the COM plumbing requires a lot of work with pinvoke, the smallest mistake will cause exceptions to be thrown in explorer and generally be a hassle. So with SharpShell, the plumbing is done for you, leaving you with the task of actually creating the UI and logic you want.

Let's get started creating the Icon Preview Handler.

Step 1: Creating the Project

First, create a new C# Class Library project.

Tip: You can use Visual Basic rather than C# - in this article, the source code is C# but the method for creating a Visual Basic Shell Extension is just the same.

In this example, we'll call the project 'IconPreviewHandler'. Rename the 'Class1.cs' file to 'IconPreviewHandler.cs'.

Now add the following references:

  1. System.Windows.Forms
  2. System.Drawing

These references contain various useful bits and pieces that other parts of the SharpShell library will need, such as icons and context menus.

Tip: If you use Nuget to install SharpShell (see 'Step 2'), you don't need to add these references - they'll be added automatically.

Step 2: Referencing SharpShell

We now need to add a reference to the core SharpShell library. You can do that in a few different ways:

Add Reference

Download the 'SharpShell Core Library' zip file at the top of the article and add a reference to the SharpShell.dll file.

Tip: The download on this article is correct at the time of writing - if you need the latest version, use Nuget (as described below) or get the library from sharpshell.codeplex.com.

Use Nuget

If you have Nuget installed, just do a quick search for SharpShell and install it directly - or get the package details at https://www.nuget.org/packages/SharpShell.

Image 2

Step 3: Deriving from SharpPreviewHandler

The now that we've set up the project, we can derive the IconPreviewHandler class from SharpPreviewHandler. SharpPreviewHandler is the base class for Preview Handler Shell Extensions.

C#
public class IconPreviewHandler : SharpPreviewHandler
{
} 

For a preview handler, there is one abstract function in the base class that we must implement in the derived class.

DoPreview

C#
protected abstract PreviewHandlerControl DoPreview();

DoPreview is called when the class must create the user interface for the preview. It returns a PreviewHandlerControl which is just a UserControl with a few extra virtual functions. Typically, the following will happen:

  1. The derived class will create an instance of some user control.
  2. The user control will be populated with data based on what's in the SelectedFilePath property.
  3. The user control will be returned.

That's actually all you need to do!

So for this project, add a new User Control and call it IconHandlerPreviewControl. Change its base class to 'PreviewHandlerControl' and add the 'DoPreview' function to your IconPreviewHandler class:

C#
/// <summary>
/// DoPreview must create the preview handler user interface and initialize it with data
/// provided by the shell.
/// </summary>
/// <returns>
/// The preview handler user interface
/// </returns>
protected override PreviewHandlerControl DoPreview()
{
    //  Create the handler control
    var handler = new IconPreviewHandlerControl();
 
    //  Do we have a file path? If so, we can do a preview
    if(!string.IsNullOrEmpty(SelectedFilePath))
        handler.DoPreview(SelectedFilePath);
 
    //  Return the handler control
    return handler;
}

Here, we do just as I've described. We actually call the 'DoPreview' function of the IconHandlerPreviewControl, which has not yet been written. If you need the details, you can get the source code from the top of the article, but actually the code is straightforward enough to drop in here:

C#
public void DoPreview(string selectedFilePath)
{
    //  Load the icons
    try
    {
        var multiIcon = new MultiIcon();
        multiIcon.Load(selectedFilePath);
 
        //  Add the icon images
        foreach (var iconImage in multiIcon.SelectMany(singleIcon => singleIcon))
            iconImages.Add(iconImage);
 
        //  Add the icons to the control
        AddIconsToControl();
    }
    catch
    {
        //  Maybe we could show something to the user in the preview
        //  window, but for now we'll just ignore any exceptions.
    }
}

Here, we use the excellent IconLib library to load the icons, then store each image. We then call AddIconsToControl to actually add them to the UI:

C#
private void AddIconsToControl()
{
    //  Go through each icon, keep track of y pos
    int yPos = 12;
    foreach (var iconImage in iconImages)
    {
        //  Create the description
        var descriptionLabel = new Label
        {
            Location = new Point(12, yPos),
            Text = string.Format("{0}x{1} - {2}",
                   iconImage.Size.Width, iconImage.Size.Height, iconImage.PixelFormat),
            AutoSize = true,
            BackColor = Color.White
        };
        yPos += 20;
 
        //  Create the picture box
        var pictureBox = new PictureBox
        {
            Location = new Point(12, yPos),
            Image = iconImage.Icon.ToBitmap(),
            Width = iconImage.Size.Width,
            Height = iconImage.Size.Height
        };
        yPos += iconImage.Size.Height + 20;
        panelImages.Controls.Add(descriptionLabel);
        panelImages.Controls.Add(pictureBox);
 
        //  Keep track of generated labels
        generatedLabels.Add(descriptionLabel);
    }
}

There are some member variables that I'll not describe here, but this is the core of the functionality - go through each icon image and create a label that describes it and a picture box with the image in it.

Step 4: Handling the COM Registration

There are just a few things left to do. First, we must add the ComVisible attribute to our class. This because our class is a COM server and must be visible to other code trying to use it.

C#
[ComVisible(true)]
public class IconPreviewHandler : SharpPreviewHandler 

Next, we must give the assembly a strong name. There are ways around this requirement, but generally this is the best approach to take. To do this, right click on the project and choose 'Properties'. Then go to 'Signing'. Choose 'Sign the Assembly', specify 'New' for the key and choose a key name. You can password project the key if you want to, but it is not required:

Image 3

Finally, we need to associate our extension with some the types of shell items we want to use it for. We can do that with the COMServerAssociation attribute:

C#
[COMServerAssociation(AssociationType.ClassOfExtension, ".ico")]
[DisplayName("Icon Preview Handler")]
public class IconPreviewHandler : SharpPreviewHandler 

So what have we done here? We've told SharpShell that when registering the server, we want it to be associated with icofile classes in the system.

You can associate with files, folders, classes, drives and more - full documentation on using the association attribute is available on the CodePlex site at COM Server Associations.

We've also set the DisplayName attribute. The DisplayName is used in the registry and also if the handler fails (when a message saying 'The Icon Preview Handler Failed To Load' or similar will be shown in the preview pane).

We're done! Building the project creates the IconPreviewHandler assembly, which can be registered as a COM server to add the extension to the system, allowing you to quickly see what images an ico file contains.

Advanced Features

The PreviewHandlerControl class has the following virtual functions:

C#
/// <summary>
/// Sets the color of the background, if possible, to coordinate with the windows
/// color scheme.
/// </summary>
 /// <param name="color">The color.</param>
protected virtual void SetVisualsBackgroundColor(Color color){}
 
/// <summary>
/// Sets the color of the text, if possible, to coordinate with the windows
/// color scheme.
/// </summary>
/// <param name="color">The color.</param>
protected virtual void SetVisualsTextColor(Color color){}
 
/// <summary>
/// Sets the font, if possible, to coordinate with the windows
/// color scheme.
/// </summary>
/// <param name="font">The font.</param>
protected virtual void SetVisualsFont(Font font){}

You can override these to change the colours and fonts of your UI to match those defined in the current system theme.

Debugging the Shell Extension

If you have seen any of my other articles on .NET Shell Extensions, you may recognize the 'Server Manager' tool. This is a tool in the SharpShell source code that can be used to help debug Shell Extensions.

Tip: If you want the latest version of the tools, they're available pre-built from the CodePlex site.

Open the Sever Manager tool and use File > Load Server to load the IconPreviewHandler.dll file. You can also drag the server into the main window. Selecting the server will show you some details on it. Select the server.

Image 4

Now choose 'Test Server in Test Shell'. This opens up the Test Shell window. Any files that are associated with the server are highlighted in bold. You can click on one of these files and choose 'Preview Handler' on the right -this will show the preview handler, and also let you debug into it.

Image 5

Tip: You don't have to install or register the server to test it in the Test Shell.

If you want to debug in a more realistic scenario, you can install and register the server (using the 'Server' menu) and open the test shell, then choose 'Open Shell Dialog'. This will create a File Open dialog, which you can use to invoke the preview handler functionality. Remember, if you want to debug into the preview handler, you must attach to the process 'prevhost.exe', not 'explorer.exe'.

Installing and Registering the Shell Extension

You can check the 'Installing and Registering the Shell Extension' section of the .NET Shell Extensions - Shell Context Menus for details on how to install and register these extensions - the process is the same.

Useful Resources

History

  • 20th May, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License