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:
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:
- .NET Shell Extensions - Shell Context Menus
- .NET Shell Extensions - Shell Icon Handlers
- .NET Shell Extensions - Shell Info Tip Handlers
- .NET Shell Extensions - Shell Drop Handlers
- .NET Shell Extensions - Shell Preview Handlers
- .NET Shell Extensions - Shell Icon Overlay Handlers
- .NET Shell Extensions - Shell Thumbnail Handlers
- .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:
- Check the registry to see if there's a preview handler for the file type.
- If there is, create an instance of the
prevhost
process and load the preview handler DLL into it. - Instantiate the object that implements
IPreviewHandler
. - Tell that object what the parent window is, what its size is, etc.
- 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:
System.Windows.Forms
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.
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.
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
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:
- The derived class will create an instance of some user control.
- The user control will be populated with data based on what's in the
SelectedFilePath
property. - 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:
protected override PreviewHandlerControl DoPreview()
{
var handler = new IconPreviewHandlerControl();
if(!string.IsNullOrEmpty(SelectedFilePath))
handler.DoPreview(SelectedFilePath);
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:
public void DoPreview(string selectedFilePath)
{
try
{
var multiIcon = new MultiIcon();
multiIcon.Load(selectedFilePath);
foreach (var iconImage in multiIcon.SelectMany(singleIcon => singleIcon))
iconImages.Add(iconImage);
AddIconsToControl();
}
catch
{
}
}
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:
private void AddIconsToControl()
{
int yPos = 12;
foreach (var iconImage in iconImages)
{
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;
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);
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.
[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:
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:
[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:
protected virtual void SetVisualsBackgroundColor(Color color){}
protected virtual void SetVisualsTextColor(Color color){}
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.
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.
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