Click here to Skip to main content
Click here to Skip to main content

.NET Shell Extensions - Shell Thumbnail Handlers

, 17 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Create Shell Thumbnail Handler Extensions using .NET!

Introduction 

Shell Thumbnail Handlers (or as they're sometimes known, Shell Thumbnail Providers) are COM servers that you can write to customise the appearance of the thumbnail icons in the Windows Shell. We're familiar with many of these - for example, we expect to see a thumbnail of a jpeg file, but it turns out that making your own is quite straightforward, especially with a little help from the library SharpShell.  

In article, I'll show you how to write a Shell Thumbnail Handler for a Text File. This is a great starting point if you want to create your own shell thumbnail providers. 

Above: A screenshot of an example Thumbnail Handler for text files. This thumbnail handler renders the first few lines of text. 

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    

Getting Started 

Typically, writing Shell Extension handlers involves creating a class that implements a well defined set of COM interfaces. Now, as these COM interfaces are often not included in the .NET Framework, we have to implement them ourselves. However, the SharpShell library has all of these interfaces included - it also provides base classes for extensions, making it very straightforward to implement them. Let's get started with our text file thumbnail handler now.

Our Goal  

We're going to build the extension step-by-step shortly, but first let's just see the end result, because I think it highlights very well how easy SharpShell makes it to implement these handlers. Below is the entire code for the extension - as you can see it is very simple and straightforward! Don't worry about the details too much now, because we'll build the extension from the ground up next.

/// <summary>
/// The TxtThumbnailHandler is a ThumbnailHandler for text files.
/// </summary>
[ComVisible(true)]
[COMServerAssociation(AssociationType.FileExtension, ".txt")]
public class TxtThumbnailHandler : SharpThumbnailHandler
{
    /// <summary>
    /// Initializes a new instance of the <see cref="TxtThumbnailHandler"/> class.
    /// </summary>
    public TxtThumbnailHandler()
    {
        //  Create our lazy objects.
        lazyThumbnailFont = new Lazy<Font>(() => new Font("Courier New", 12f));
        lazyThumbnailTextBrush = new Lazy<Brush>(() => new SolidBrush(Color.Black));
    }
 
    /// <summary>
    /// Gets the thumbnail image.
    /// </summary>
    /// <param name="width">The width of the image that should be returned.</param>
    /// <returns>
    /// The image for the thumbnail.
    /// </returns>
    protected override Bitmap GetThumbnailImage(uint width)
    {
        //  Attempt to open the stream with a reader.
        try
        {
            using(var reader = new StreamReader(SelectedItemStream))
            {
                //  Read up to ten lines of text.
                var previewLines = new List<string>();
                for (int i = 0; i < 10; i++)
                {
                    var line = reader.ReadLine();
                    if (line == null)
                        break;
                    previewLines.Add(line);
                }
 
                //  Now return a preview of the lines.
                return CreateThumbnailForText(previewLines, width);
            }
        }
        catch (Exception exception)
        {
            //  Log the exception and return null for failure.
            LogError("An exception occured opening the text file.", exception);
            return null;
        }
    }
 
    /// <summary>
    /// Creates the thumbnail for text, using the provided preview lines.
    /// </summary>
    /// <param name="previewLines">The preview lines.</param>
    /// <param name="width">The width.</param>
    /// <returns>
    /// A thumbnail for the text.
    /// </returns>
    private Bitmap CreateThumbnailForText(IEnumerable<string> previewLines, uint width)
    {
        //  Create the bitmap dimensions.
        var thumbnailSize = new Size((int) width, (int) width);
 
        //  Create the bitmap.
        var bitmap = new Bitmap(thumbnailSize.Width, thumbnailSize.Height, PixelFormat.Format32bppArgb);
 
        //  Create a graphics object to render to the bitmap.
        using (var graphics = Graphics.FromImage(bitmap))
        {
            //  Set the rendering up for anti-aliasing.
            graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
 
            //  Draw the page background.
            graphics.DrawImage(Properties.Resources.Page, 0, 0, thumbnailSize.Width, thumbnailSize.Height);
            
            //  Create offsets for the text.
            var xOffset = width * 0.2f;
            var yOffset = width * 0.3f ;
            var yLimit = width - yOffset;
 
            graphics.Clip = new Region(new RectangleF(xOffset, yOffset, thumbnailSize.Width - (xOffset * 2), thumbnailSize.Height - width*.1f));
 
            //  Render each line of text.
            foreach (var line in previewLines)
            {
                graphics.DrawString(line, lazyThumbnailFont.Value, lazyThumbnailTextBrush.Value, xOffset, yOffset);
                yOffset += 14f;
                if (yOffset + 14f > yLimit)
                    break;
            }
        }
 
        //  Return the bitmap.
        return bitmap;
    }
 
    /// <summary>
    /// The lazy thumbnail font.
    /// </summary>
    private readonly Lazy<Font> lazyThumbnailFont;
 
    /// <summary>
    /// The lazy thumbnail text brush.
    /// </summary>
    private readonly Lazy<Brush> lazyThumbnailTextBrush;
}

This is not very much work to get a working shell extension! Now let's go into the details.

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 'TxtThumbnailHandler'. Rename the 'Class1.cs' file to 'TxtThumbnailHandler.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.    

IMPORTANT: SharpShell is being updated regularly, I get feature requests often and implement them quickly, so I strongly recommend using the Nuget package to reference SharpShell - it's easier to automatically keep it up-to-date.

Step 3: Deriving from SharpThumbnailHandler 

The now that we've set up the project, we can derive the TxtThumbnailHandler class from SharpThumbnailHandler, which is the base class for Thumbnail Handler Shell Extensions.  

public class TxtThumbnailHandler : SharpThumbnailHandler
{
} 


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

GetThumbnailImage

protected abstract Bitmap GetThumbnailImage(uint width);

This function will be called to get the bitmap for your thumbnail. It's really important to understand the following points:

  1. Thumbnail handlers are initialised via streams - not file paths. This means you don't know the path of the file your previewing, you only have its stream.
  2. The file stream is stored in the SelectedItemStream property. You can use this to read the file and build the thumbnail.
  3. Try and respect the width variable, its the width the system wants. However, worst comes to the worst, it will scale your image if needed.
  4. Typically, you set the height of the bitmap to the same as the width, but it can be less, the system will respect this. However, the thumbnail cannot be higher than it is wide. 
Now lets see how we'd implement this function in our example.

protected override Bitmap GetThumbnailImage(uint width)
{
    //  Create a stream reader for the selected item stream.
    try
    {
        using(var reader = new StreamReader(SelectedItemStream))
        {
            //  Read up to ten lines of text.
            var previewLines = new List<string>();
            for (int i = 0; i < 10; i++)
            {
                var line = reader.ReadLine();
                if (line == null)
                    break;
                previewLines.Add(line);
            }
 
            //  Now return a preview of the lines.
            return CreateThumbnailForText(previewLines, width);
        }
    }
    catch (Exception exception)
    {
        //  Log the exception and return null for failure.
        LogError("An exception occured opening the text file.", exception);
        return null;
    }
}

Well the first thing we do, is make sure that we use an exception handler around the bulk of the logic.

Tip: SharpShell wraps all calls to abstract functions in exception handlers and logs exceptions to the windows event log if the server is compiled in debug mode. But it's good practice to deal with exceptions ourselves if we expect them. Don't forget, you can call 'Log' or 'LogError' to write to the SharpShell Windows Application event log.

Next, we create a stream reader for the SelectedItemStream (which is the stream of the shell item we're previewing). From this, we read a few lines of text, and pass onto the function CreateThumbnailForText

So, we also need to create the CreateThumbnailForText function: 

private Bitmap CreateThumbnailForText(IEnumerable<string> previewLines, uint width)
{
    //  Create the bitmap dimensions.
    var thumbnailSize = new Size((int) width, (int) width);
 
    //  Create the bitmap.
    var bitmap = new Bitmap(thumbnailSize.Width, thumbnailSize.Height, PixelFormat.Format32bppArgb);
 
    //  Create a graphics object to render to the bitmap.
    using (var graphics = Graphics.FromImage(bitmap))
    {
        //  Set the rendering up for anti-aliasing.
        graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
 
        //  Draw the page background.
        graphics.DrawImage(Properties.Resources.Page, 0, 0, thumbnailSize.Width, thumbnailSize.Height);
        
        //  Create offsets for the text.
        var xOffset = width * 0.2f;
        var yOffset = width * 0.3f ;
        var yLimit = width - yOffset;
 
        graphics.Clip = new Region(new RectangleF(xOffset, yOffset, thumbnailSize.Width - (xOffset * 2), thumbnailSize.Height - width*.1f));
 
        //  Render each line of text.
        foreach (var line in previewLines)
        {
            graphics.DrawString(line, lazyThumbnailFont.Value, lazyThumbnailTextBrush.Value, xOffset, yOffset);
            yOffset += 14f;
            if (yOffset + 14f > yLimit)
                break;
        }
    }
 
    //  Return the bitmap.
    return bitmap;
} 

Again we're dealing with some fairly straightforward logic here. We create a bitmap of the requested size, making sure that we use 32 bit ARGB mode (i.e. we make sure we have an alpha-channel). Then we create a graphics object to draw onto it. 

Following that, we render a *.png resource file onto the bitmap (the resource file is in the sample project, it's an image of a page). Now we just render some of the text on top of the page image (making sure we keep within the bounds of the image we just drew).

That's it - we can return the bitmap and we're done! Well, actually, there are two member variables used in this function, here's their declaration:

    public TxtThumbnailHandler()
    {
        //  Create our lazy objects.
        lazyThumbnailFont = new Lazy<Font>(() => new Font("Courier New", 12f));
        lazyThumbnailTextBrush = new Lazy<Brush>(() => new SolidBrush(Color.Black));
    }
 
    /// <summary>
    /// The lazy thumbnail font.
    /// </summary>
    private readonly Lazy<Font> lazyThumbnailFont;
 
    /// <summary>
    /// The lazy thumbnail text brush.
    /// </summary>
    private readonly Lazy<Brush> lazyThumbnailTextBrush;  

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 TxtThumbnailHandler : SharpThumbnailHandler

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:

[ComVisible(true)]
[COMServerAssociation(AssociationType.FileExtension, ".txt")]
public class TxtThumbnailHandler : SharpThumbnailHandler

So what have we done here? We've told SharpShell that when registering the server, we want it to be associated with *.txt files.   

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

Tip: Many SharpShell servers support the 'AssociationType.ClassOfExtension' association. With this, you provide something like *.txt and the server is registered for all text files (which might be txt, text, txtfile, dat etc). Please be aware that Thumbnail handlers do not support the ClassOfExtension association type! 

We're done! Building the project creates the TxtThumbnailHandler assembly, which can be registered as a COM server to add the extension to the system, allowing you to quickly have a preview of the contents of text files. 

Debugging the Shell Extension  

If you have seen any of my other articles on .NET Shell Extensions, you may recognise 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 TxtThumbnailHandler.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 you can choose 'text server in test shell'. All files associated with the server are highlighted in bold, select the file and the thumbnail tab on the right - your thumbnail handler will be used to generate a thumbnail preview.

 

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.  

Troubleshooting

Troubleshooting shell extensions is very hard, so I've got a page I'm keeping up to date with some tips, it's on the CodePlex site at:

http://sharpshell.codeplex.com/wikipage?title=Troubleshooting%20SharpShell%20Servers 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dave Kerr
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.
Follow on   Twitter

Comments and Discussions

 
QuestionSomebody willing to share the end result (thumbnail for text files) PinmemberJan Bethmann16-Nov-14 11:20 
AnswerRe: Somebody willing to share the end result (thumbnail for text files) PinmvpDave Kerr16-Nov-14 18:02 
GeneralRe: Somebody willing to share the end result (thumbnail for text files) PinmemberJan Bethmann17-Nov-14 8:04 
GeneralRe: Somebody willing to share the end result (thumbnail for text files) PinmvpDave Kerr19-Nov-14 3:11 
GeneralRe: Somebody willing to share the end result (thumbnail for text files) [modified] PinmemberJan Bethmann19-Nov-14 5:44 
GeneralRe: Somebody willing to share the end result (thumbnail for text files) PinmemberJan Bethmann10-Dec-14 8:25 
QuestionGet filename PinmemberMember 7585235-May-14 3:12 
AnswerRe: Get filename PinmvpDave Kerr5-May-14 22:15 
GeneralRe: Get filename PinmemberMember 7585235-May-14 22:44 
GeneralRe: Get filename PinmvpDave Kerr7-May-14 1:01 
GeneralRe: Get filename PinmemberJuBett7-May-14 5:40 
QuestionCan't modify the text file. Pinmembercarbokart25-Apr-14 2:41 
AnswerRe: Can't modify the text file. PinmvpDave Kerr25-Apr-14 5:53 
AnswerRe: Can't modify the text file. [modified] Pinmembercarbokart25-Apr-14 9:25 
GeneralRe: Can't modify the text file. PinmvpDave Kerr26-Apr-14 23:00 
GeneralRe: Can't modify the text file. Pinmembercarbokart29-Apr-14 7:32 
GeneralMy vote of 4 PinprofessionalVBTheory9-Mar-14 16:33 
GeneralRe: My vote of 4 PinmvpDave Kerr10-Mar-14 9:17 
GeneralRe: My vote of 4 PinprofessionalVBTheory16-Mar-14 3:23 
QuestionUnregister, uninstall and replace? [modified] Pinprofessional i0014-Feb-14 22:11 
AnswerRe: Unregister, uninstall and replace? PinmvpDave Kerr15-Feb-14 21:37 
GeneralRe: Unregister, uninstall and replace? Pinprofessional i0016-Feb-14 17:25 
GeneralRe: Unregister, uninstall and replace? PinmvpDave Kerr1-Mar-14 7:47 
QuestionGet Filename?? [modified] Pinprofessional i0014-Feb-14 16:21 
AnswerRe: Get Filename?? PinmvpDave Kerr15-Feb-14 3:11 
GeneralRe: Get Filename?? Pinprofessional i0015-Feb-14 4:35 
AnswerRe: Get Filename?? PinmvpDave Kerr4-Mar-14 12:24 
QuestionThumbnail is displayed with '?' sign PinmemberAtuladhar24-Nov-13 18:13 
AnswerRe: Thumbnail is displayed with '?' sign PinmvpDave Kerr25-Nov-13 9:36 
GeneralRe: Thumbnail is displayed with '?' sign PinmemberAtuladhar25-Nov-13 18:45 
GeneralRe: Thumbnail is displayed with '?' sign PinmvpDave Kerr27-Nov-13 8:28 
GeneralRe: Thumbnail is displayed with '?' sign PinmemberAtuladhar29-Nov-13 0:06 
GeneralRe: Thumbnail is displayed with '?' sign PinmvpDave Kerr2-Dec-13 11:12 
GeneralRe: Thumbnail is displayed with '?' sign Pinprofessional i0014-Feb-14 17:41 
GeneralMy vote of 5 Pinmemberscosta_FST12-Sep-13 6:22 
GeneralRe: My vote of 5 PinmvpDave Kerr12-Sep-13 21:09 
GeneralRe: My vote of 5 Pinmemberscosta_FST16-Sep-13 1:53 
GeneralRe: My vote of 5 PinmvpDave Kerr16-Sep-13 1:58 
GeneralRe: My vote of 5 Pinmemberscosta_FST16-Sep-13 2:04 
GeneralRe: My vote of 5 PinmvpDave Kerr16-Sep-13 2:14 
GeneralRe: My vote of 5 Pinmemberscosta_FST16-Sep-13 6:09 
GeneralRe: My vote of 5 PinmvpDave Kerr16-Sep-13 6:20 
GeneralRe: My vote of 5 Pinmemberscosta_FST16-Sep-13 6:33 
GeneralRe: My vote of 5 PinmvpDave Kerr16-Sep-13 9:13 
QuestionNice work, but... PinmemberW. Kleinschmit18-Mar-13 2:39 
AnswerRe: Nice work, but... PinmvpDave Kerr18-Mar-13 3:53 
GeneralRe: Nice work, but... PinmemberW. Kleinschmit18-Mar-13 4:03 
GeneralRe: Nice work, but... PinmvpDave Kerr18-Mar-13 4:21 
QuestionMy vote of.... PinmemberBrisingr Aerowing17-Mar-13 7:28 
AnswerRe: My vote of.... PinmvpDave Kerr17-Mar-13 8:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 17 Mar 2013
Article Copyright 2013 by Dave Kerr
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid