Introduction
In this article, I will show you how to use SharpShell to quickly create Shell Property Sheet extensions. These are extensions that add extra pages to the property sheets shown for shell items such as files, network shares, folders and so on. Here's a screenshot of what we'll create in the tutorial.
Above: A screenshot of a Shell Property Sheet Extension. This sample adds a 'File Times' page that lets the file times for a file be modified.
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
What is a Shell Property Sheet Extension
Technically, a Shell Property Sheet extensions is a COM server that exports an object that implements the interface IShellPropSheetExt. This extension lets us do two things - add property pages to a shell property sheet, or replace them.
Now, typically working with old-fashioned APIs like this can be quite difficult when you're in the managed world. You need to do a lot of interop, you need to import functions from the Win32 APIs to create property pages, create message pumps and so on. We're going to use the SharpShell
library to do all of the plumbing for us, leaving us with the much easier task of simply creating property pages and adding them to our extension class.
Now that we know what a Property Sheet Extension is, we can get started implementing one.
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 'FileTimesPropertySheet
'.
Now add the following references:
System.Windows.Forms
System.Drawing
Rename 'Class1.cs' to 'FileTimesPropertySheet.cs'.
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:
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. This is the best way - you'll always get the latest version of the library and it'll add any dependencies automatically.
Add Reference
Download the 'SharpShell Core Library' zip file at the top of the article and add a reference to the downloaded 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 CodePlex
Rather than getting the library from this page, which may not be the latest version, you can always get the very latest version of the library from CodePlex - on the SharpShell home page which is sharpshell.codeplex.com. Nuget will always have the latest stable version - CodePlex may have betas available, and the Code Project articles will have the version that was available at the time of writing.
Step 3: Derive from SharpPropertySheet
We must have a class that derives from SharpPropertySheet - this will be the main extension class. Let's take the file we named 'FileTimesPropertySheet
' and derive from SharpPropertySheet
now:
public class FileTimesPropertySheet : SharpPropertySheet
Now we're going to have to create implementations of two abstract functions:
CanShowSheet
This function returns a bool
. If the result is true
, then the property sheet pages we create are going to be shown, otherwise we won't show them. Here's our implementation:
protected override bool CanShowSheet()
{
return SelectedItemPaths.Count() == 1;
}
Because we're derived from SharpPropertySheet
, we have a property called 'SelectedItemPaths
' - this is an enumerable set of string
s that store the paths selected by the user when the sheet was invoked. In our example, we're only going to show the pages if we have just one file selected.
CreatePages
This function returns a set of SharpPropertyPage
objects - these are the actual property pages we're going to add to the shell property sheet.
Here's how our implementations will look:
protected override IEnumerable<SharpPropertyPage> CreatePages()
{
var page = new FileTimesPropertyPage();
return new[] {page};
}
This isn't going to work until we create the 'FileTimesPropertyPage
' class, we'll do that soon!
Step 4: Create the Associations
We need to define what shell objects this extension is going to be used for. This is done by decorating the extensions with the COMServerAssociation
attribute. Here's how we can apply this extension will all files.
[COMServerAssociation(AssociationType.AllFiles)]
public class FileTimesPropertySheet : SharpPropertySheet
There are a number of different types of associations we can make - we can associate with files, folders, files with specific extensions and so on. There's a more complete guide here on the page COM Server Associations.
Step 5: Create the Property Sheet Page
You can follow this step as many times as you need - one property sheet extension can add any number of pages.
First, add a UserControl
to your class library, call it 'FileTimesPropertyPage
'. Now open up the code-behind for the control and change the parent class from 'UserControl
' to 'SharpPropertyPage
'.
Here's the first thing we can do - set the title of the property page:
public partial class FileTimesPropertyPage : SharpPropertyPage
{
public FileTimesPropertyPage()
{
InitializeComponent();
PageTitle = "File Times";
}
We set the title of the page by setting the PageTitle
property. We can also optionally set the icon for the page by setting the PageIcon
property.
Now there are a bunch of virtual functions in the SharpPropertyPage
that we can override if we choose to. Here, I'll go through the key ones.
OnPropertyPageInitialised
This function is called when the page has been created and is about to be displayed to the user. If a property sheet is shown, but the user doesn't click on this particular page, then this function won't be called. Any initialization should be done here. In this function, you are provided with the parent SharpPropertySheet
object, which contains the selected file paths.
private string filePath;
protected override void OnPropertyPageInitialised(SharpPropertySheet parent)
{
filePath = parent.SelectedItemPaths.First();
LoadFileTimes();
}
In our example, we store the selected file path and then update the UI from the file times (through the LoadFileTimes
function).
What other functions should we implement? Well, almost always, we'll want to handle 'OK' and 'Apply':
OnPropertySheetOK and OnPropertySheetApply
protected override void OnPropertySheetApply()
{
SaveFileTimes();
}
protected override void OnPropertySheetOK()
{
SaveFileTimes();
}
Nothing too advanced here - in this class, we're just going to save any changes the user has made.
Property Sheet Pages Quick Reference
Here's a quick reference for property sheet pages.
Function | Usage |
OnPropertyPageInitialised | Called when the page must be initialized. |
OnPropertyPageSetActive | Called when the page is about to be presented to the user. This is always called after OnPropertyPageInitialised . |
OnPropertyPageKillActive | Called when the page is about to be deselected. |
OnPropertySheetApply | Called when the Apply button is pressed. |
OnPropertySheetOK | Called when the OK button is pressed. |
OnPropertySheetCancel | Called when the Cancel button is pressed. |
OnPropertySheetClose | Called when the cross at the top right of the dialog is pressed. |
SetPageDataChanged | Call this function to enable the 'Apply' button. |
Step 6: Expose to COM
We're going to need to do a few things now to allow our class to be created by the shell.
First, we must add the COMVisible
attribute to our class:
[ComVisible(true)]
[COMServerAssociation(AssociationType.AllFiles)]
public class FileTimesPropertySheet : SharpPropertySheet
Now we must sign the assembly. 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:
Debugging the Shell Extension
The Shell Extension can be debugged in the following way:
- Run the 'Server Manager' project from the source code.
- Open the server you have built, select it, choose 'Install' and then 'Register' for your processor architecture.
- Now press the 'Open Shell Dialog' button.
- This will show Shell Open Dialog. If you right click on a file and choose the properties, then the extension will be shown and any breakpoints that you have enabled will work successfully, as the shell window is in the address space that we've got a debugger attached too.
- Once you're done debugging, you can close the shell open dialog and unregister/uninstall it.
Installing and Registering the Shell Extension
See the article Shell Context Menus for a guide on how to register SharpShell extensions.
History
- 8th April, 2013: Initial version