ShellBoost

Item properties / folder columns

Windows Property System

Starting with Windows Vista, each item in the Shell (items and folders) is now associated with a set of properties, from the Windows Property System. These properties are used to describe the item but are also used in Shell Folder views. It’s especially true and visible in the case of the Details View, where a column is associated with a property, but it’s also true for other views, and the standard property sheet (available using the “Properties” item menu). Properties are in fact used everywhere anytime by the Shell.

A property definition:

is uniquely addressed by its PROPERTYKEY, which is a Windows-defined structure (composed of a Guid value and an int32 value),

has a canonical human-readable name (for example “System.ItemNameDisplay”),

has a schema description, which is specified in a .propdesc XML file format and expressed programmatically through the Windows-defined IPropertyDescription interface.

A property is a property definition and a property value.

To manipulate these concepts easily in .NET, ShellBoost defines the PropertyKey structure in the ShellBoost.Core.WindowsPropertySystem namespace. Its binary layout is 100% compatible with the Windows PROPERTYKEY structure. ShellBoost also defines the PropertyDefinition class in the same namespace that uses a PropertyKey as a unique identifier. Both classes have useful methods that allow to navigate from one to another, and to explore the Windows property schema.

The latest version of Windows 10 predefines around 1600 properties of all sort and types. There are all defined as PropertyKey instances in the ShellBoost.Core .NET assembly, namespace ShellBoost.Core.WindowsPropertySystem. Note all properties are not officially documented.

Property values

In pure Windows code, the value of a property for a given item (if that item contains that property) is a PROPVARIANT which is a structure that can hold a variety of data.

To simplify things, ShellBoost also defines a PropVariant .NET class that can be used to store Property values. However, most of the time, a developer using ShellBoost will only have to deal with standard .NET values (strings, integers, booleans, arrays, etc.), as ShellBoost components will handle PropVariant conversions back and forth automatically.

Folder columns

Since each column is now associated with a property, each column is therefore associated also with a PropertyKey (and its property definition).

The ShellBoost ShellFolder class has several AddColumn methods to add columns to a shell folder view. In general, the set of available columns for a shell folder is fixed and initialized in the ShellFolder constructor, so the RemoveColumn methods are mostly used to remove the default columns that ShellBoost defines.

By default, ShellBoost defines the following columns / property keys for a Shell Folder, in that exact order (shown here using their canonical names):

System.ItemNameDisplay, with the state flag SHCOLSTATE_ONBYDEFAULT

System.ItemType, with the state flag SHCOLSTATE_ONBYDEFAULT

System.Size

System.DateModified

System.PerceivedType

System.Kind

So, when the end-user opens a ShellBoost Shell Folder, this is what will be displayed the first time:

Folder columns - Picture 7

As you see, only two columns are displayed, although six are available, due to the SHCOLSTATE_ONBYDEFAULT flag that was set only on the first two ones. If the end user right-clicks on the folder view’s header, he will be able to see all available columns and choose which one will be shown in the folder view.

Adding columns to a folder

Columns are by default associated with registered Properties (from the Windows Property System), so to add a column, you will in fact add the associated PropertyKey. In general, columns are added in the ShellFolder derived class constructor, something like this for example, in the case of a shell folder that would display photos:

public PhotosFolder(ShellFolder parent, ShellItemId id)
    : base(parent, id)
{
    CanPaste = true;
    CanLink = true;
 
    // luckily, all these media/photo properties are already defined in Windows
    // SysProps is an alias for ShellBoost.Core.WindowsPropertySystem.System
    AddColumn(SysProps.Photo.CameraManufacturer, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.CameraModel, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.ExposureTime, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.Flash, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.FocalLength, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.ISOSpeed, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.FNumber, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.DateTaken, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Photo.SubjectDistance, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Media.UniqueFileIdentifier, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Image.VerticalSize, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Image.HorizontalSize, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
    AddColumn(SysProps.Image.ImageID, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT);
}

Then, each shell item in this folder would set the value (standard .NET values, strings, integers, booleans, etc.) for all these properties accordingly, for example (here we use a Photo class, defined elsewhere, that has all information for a photo):

public PhotoItem(BaseFolder parent, Photo photo)
    : base(parent, new StringKeyShellItemId(photo.Id))
{
    SetPropertyValue(SysProps.Photo.CameraManufacturer, photo.ExifMake);
    SetPropertyValue(SysProps.Photo.CameraModel, photo.ExifModel);
    SetPropertyValue(SysProps.Photo.ExposureTime, photo.ExifExposure);
    SetPropertyValue(SysProps.Photo.Flash, (photo.ExifFlash ? PHOTO_FLASH_FLASH : PHOTO_FLASH_NONE));
    SetPropertyValue(SysProps.Photo.FocalLength, photo.ExifFocalLength);
    SetPropertyValue(SysProps.Photo.ISOSpeed, photo.ExifIso);
    SetPropertyValue(SysProps.Photo.FNumber, photo.ExifFStop);
    SetPropertyValue(SysProps.Photo.DateTaken, photo.ExifTime);
    SetPropertyValue(SysProps.Photo.SubjectDistance, photo.ExifDistance);
    SetPropertyValue(SysProps.Media.UniqueFileIdentifier, photo.ExifImageUniqueId);
    SetPropertyValue(SysProps.Image.HorizontalSize, photo.Width);
    SetPropertyValue(SysProps.Image.VerticalSize, photo.Height);
    SetPropertyValue(SysProps.Image.ImageID, photo.Id);
}

This is how such a folder could like:

Adding columns to a folder - Picture 13

A shell item (or folder) can set a property value using the SetPropertyValue() method as seen previously, but if the value is dynamic, it may better to override the TryGetPropertyValue() method, like this:

public override bool TryGetPropertyValue(PropertyKey key, out object value)
{
    // we use the current date time as the Display Name
    if (key == Props.System.ItemNameDisplay)
    {
        value = DateTime.Now.ToString();
        return true;
    }
    return base.TryGetPropertyValue(key, out value);
} 

As you see, the way they are rendered, including header names, is consistent with the type of data they contain because we only used Windows well-known properties that have a lot of pre-defined characteristics. The benefit of using properties – built-in or not - doesn’t stop at the data type and the name, but also how they are rendered, how they are grouped (using a right-click on column headers), how they are sorted, how they are searched, etc. More info is available on MSDN site in Understanding the Property Description Schema

Property Lists

The property lists are semi-colon delimited strings that have the following form.

prop:[flags]PropertyCanonicalName;[flags]PropertyCanonicalName;...;[flags]PropertyCanonicalName;

They are used as values for properties that expect property lists, for example, System.PropList.InfoTip, which contains the list of properties to show in the infotip / tooltip for a shell item.

By conventions, these properties are defined in the System.PropList namespace. Flags are technically defined on MSDN site in the PROPDESC_VIEW_FLAGS enumeration. To each value in the enumeration corresponds a special character. The correspondence is available here on MSDN: IPropertySystem::GetPropertyDescriptionListFromString method

Instead of building these strings manually, you can use the ShellBoost utility class named PropertyDescriptionList in the ShellBoost.Core.WindowsPropertySystem namespace. The class also supports flags to make things a lot easier. For example, this code:

using Props = ShellBoost.Core.WindowsPropertySystem;
var list = new PropertyDescriptionList(Props.System.ItemNameDisplay, Props.System.ItemTypeText).ToString();

Will build the “prop:System.ItemNameDisplay;System.ItemTypeText;” string. The utility class also handles various flags that the Windows Shell support.

Custom properties

The Windows Property System supports custom properties. As a developer using ShellBoost, you can create your own properties and use them to create custom columns. The process is documented here on MSDN: Creating Custom Properties. Custom property definitions must be registered using a custom .propdesc XML file, on the machine before the Shell can use it. ShellBoost provides a helper method in the PropertySystem class in the ShellBoost.Core.WindowsPropertySystem namespace:

// register a property schema from a .propdesc file path
PropertySystem.RegisterPropertySchema(location);
...
// unregister a property schema from a .propdesc file path
PropertySystem.UnregisterPropertySchema(location);

The Registry Folder sample demonstrates that with two custom columns defined in a custom .propdesc file.

Note: since registration (and unregistration) requires write access to some part of the HKEY_LOCAL_MACHINE registry, the program that will run that code will need sufficient permissions. This is one inconvenient to defining custom properties. Whenever possible, we recommend using built-in Windows properties.

Item icon properties

A Shell Item can have properties / columns whose value will be displayed as an icon. This is not to be confused with the icon or the thumbnail that represents the item in the various folder icon views. Icon properties are displayed in a folder’s details view.

The Local Folder sample demonstrates that feature with a custom column named “Icon”. This is how the end-user could enable that column (right-click on the folder’s view header):

Item icon properties - Picture 9

And here is the result:

Item icon properties - Picture 10

As you see, the “Icon” column is rendered with an image instead of a text. ShellBoost has support for this kind of properties, but the procedure is a bit more complex that for other properties. First, you must define two custom properties, for each icon-rendered column (this is done the standard way, using a .propdesc file and register it to Windows):

One UInt32 property with an “enumerated” display type. It will define all the possible values and associated icons for this icon property.

One property of “blob” (the binary representation of the icon) type that will be the property set to the Shell Item.

Then, in the code, the “blob” type property must be added as a column to the custom Shell Folder, something like this:

// this code uses the PropertySystem utility class to get the PropertyDescription from its canonical name.
// it also checks the property is registered.
public static readonly PropertyDescription IconUIProperty = PropertySystem.GetPropertyDescription("ShellBoost.Samples.LocalFolder.IconUI", true);
 
...
 
public LocalShellFolder(ShellFolder parent, DirectoryInfo info)
    : base(parent, info)
{
    ...
    // we only add the blob type property as a column to the folder
    AddColumn(IconUIProperty);
    ...
}

In the shell item code, you must add the property value, like this:

public LocalShellItem(ShellFolder parent, FileInfo info)
    : base(parent, info)
{
    ...
 
    // create a memory property store, and add the following values to it
    var ms = new MemoryPropertyStore();
    ms.SetValue(Props.System.PropList.StatusIcons, "prop:" + LocalShellFolder.IconProperty.CanonicalName);
    ms.SetValue(Props.System.PropList.StatusIconsDisplayFlag, (uint)2); // this is mandatory
 
    // define the uint32 property value using your own logic
    // this is specific to your code, you can use an enum value, ShellBoost will convert it to an integer automatically
    if (info.Name.Contains("error"))
    {
        ms.SetValue(LocalShellFolder.IconProperty, IconValue.Error);
    }
    else if (info.Name.Contains("warn"))
    {
        ms.SetValue(LocalShellFolder.IconProperty, IconValue.Warning);
    }
    else
    {
        ms.SetValue(LocalShellFolder.IconProperty, IconValue.Ok);
    }
 
    // use the memory property store as the icon blob property value (the one added in the folder)
    SetPropertyValue(LocalShellFolder.IconUIProperty, ms);
    ...
}
 
// values here must match Icon property's enumerated values in the .propdesc XML file
// defining a corresponding C# enum is not mandatory but makes things easier
public enum IconValue
{
    None = 0,
    Ok = 1,
    Error = 2,
    Warning = 3
}

Here is how the .propdesc schema file of the sample is defined:

<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/windows/2006/propertydescription" schemaVersion="1.0" >
       <propertyDescriptionList publisher="Aelyo" product="ShellBoost.Samples.RegistryFolder">
             <propertyDescription name="ShellBoost.Samples.LocalFolder.IconUI" formatID="{d9f69df5-01ef-4838-acb7-055012a678ca}" propID="3">
                    <searchInfo reIndexPatterns="" processReIndexPatternsImmediately="true" inInvertedIndex="false" isColumn="false">
                    </searchInfo>
                    <typeInfo type="Blob" isInnate="true" isViewable="true">
                    </typeInfo>
                    <labelInfo label="Icon">
                    </labelInfo>
                    <displayInfo defaultColumnWidth="10">
                           <drawControl control="IconList">
                           </drawControl>
                    </displayInfo>
             </propertyDescription>
             <propertyDescription name="ShellBoost.Samples.LocalFolder.Icon" formatID="{d9f69df5-01ef-4838-acb7-055012a678ca}" propID="4">
                    <searchInfo reIndexPatterns="" processReIndexPatternsImmediately="true" inInvertedIndex="false" isColumn="false">
                    </searchInfo>
                    <typeInfo type="UInt32" isInnate="true" groupingRange="Enumerated" isViewable="true">
                    </typeInfo>
                    <displayInfo displayType="Enumerated">
                           <enumeratedList>
                                  <enum name="None" value="0" text="">
                                  </enum>
                                  <enum name="Ok" value="1" text="Ok">
                                        <image res="%systemroot%\system32\imageres.dll,-1405">
                                        </image>
                                  </enum>
                                  <enum name="Error" value="2" text="Error">
                                         <image res="%systemroot%\system32\imageres.dll,-1402">
                                        </image>
                                  </enum>
                                  <enum name="Warning" value="3" text="Warning">
                                        <image res="%systemroot%\system32\imageres.dll,-1403">
                                        </image>
                                  </enum>
                           </enumeratedList>
                    </displayInfo>
                    <labelInfo label="Icon UI">
                    </labelInfo>
             </propertyDescription>
       </propertyDescriptionList>
</schema>

It defines two custom properties:

ShellBoost.Samples.LocalFolder.IconUI: which is a property of Blob type, displayed as an IconList.

ShellBoost.Samples.LocalFolder.Icon: which is a property of UInt32 type. Each value can be associated with an image that will represent the value. In the sample, we’ve reused a standard Windows dll (imageres.dll), but you can use your own. The image must be located in a binary file as a Win32 Resource. The syntax is the standard Win32 resource syntax: <path>,-<resource index>

You can define any number of icon-rendered columns in a folder.

Important Windows properties

Since Windows defines around 1600 properties, as of today, we list here some of the most interesting and important ones, most of them being handled undercovers by ShellBoost:

Canonical Name

MSDN description

ShellBoost Comment

ShellBoost Shell Item equivalent Property

Item Default Value

Folder Default Value

System.ItemNameDisplay

The display name in "most complete" form. It is the unique representation of the item name most appropriate for end users.

 

DisplayName

Key/ID value

Key/ID value

System.Size

The system-provided file system size of the item, in bytes.

 

Size

File size (physical) or 0 (virtual)

0

System.ItemType

The canonical type of the item.

 

ItemType

<empty>

<empty>

System.DateModified

The date and time of the last modification to the item.

 

DateModified

File Date Modified (physical) or Date.MinValue (virtual)

Folder Date Modified (physical) or Date.MinValue (virtual)

System.DateCreated

The date and time the item was created on the file system where it is currently located.

 

DateCreated

File Date Created (physical) or Date.MinValue (virtual)

Folder Date Created (physical) or Date.MinValue (virtuel)

System.DateAccessed

Indicates the last time the item was accessed.

 

DateAccessed

File Date Accessed (physical) or Date.MinValue (virtual)

Folder Date Accessed (physical) or Date.MinValue (virtual)

System.ThumbnailCacheId

A unique value used as a key to cache thumbnails. The value changes when the name, volume, or data modified of an item changes.

*must* be an UInt64 (C# ulong) value. If unset, Shell handles this internally

 

<empty>

<empty>

System.SFGAOFlags

SFGAO values.

 

Attributes

See the “Attributes detailed“ sub-chapter in the ShellBoost Binaries setup chapter.

See the “Attributes detailed“ sub-chapter in the ShellBoost Binaries setup chapter.

System.PerceivedType

The perceived file type based on its canonical type.

 

Perceived

PERCEIVED_TYPE_DOCUMENT

PERCEIVED_TYPE_FOLDER

System.Kind

Maps extensions to various .Search folders.

 

KindList

KIND_DOCUMENT

KIND_FOLDER

 

 

 

 

 

 

System.FileName

The file name, including its extension. System.FileExtension is derived from this property.

 

FileName

File name (physical) or <empty> (virtual)

Folder name (physical) or <empty> (virtual)

System.FileAttributes

The attributes of the item.

 

 

File attributes (physical) or <empty> (virtual)

Folder attributes (physical) or <empty> (virtual)

System.FileExtension

Identifies the file extension of the file-based item, including the leading period.

 

 

File extension (physical) or <empty> (virtual)

Folder extension (physical) or <empty> (virtual)

System.PropList.InfoTip

The list of properties to show in the infotip. Properties with empty values will not be displayed.

 

 

“~System.ItemNameDisplay”

“~System.ItemNameDisplay”

System.InfoTipText

The text (with formatted property values) to show in the infotip.

 

 

The DisplayName property

The DisplayName property

System.PropList.PreviewTitle

The one or two properties to display in the preview pane title section. The optional second property is displayed as a subtitle.

 

 

“System.Title”

“System.Title”

System.PropList.PreviewDetails

The list of properties to display in the preview pane.

 

 

“System.ItemNameDisplay;System.ItemTypeText”

“System.ItemNameDisplay;System.ItemTypeText”

System.PropList.FullDetails

The list of all the properties to show in the details page. Property groups can be included in this list in order to more easily organize the UI.

This defines what’s shown when clicking the “Properties” menu item. Property Groups are in the System.PropGroup.* namespace. Each group will create a properties category.

 

The list of parent folder columns

The list of parent folder columns

System.PropList.FileOperationPrompt

The list of properties to show in the file operation confirmation dialog. Properties with empty values will not be displayed. If this list is not specified, then the InfoTip property list is used instead.

 

 

“System.ItemNameDisplay”

“System.ItemNameDisplay”

System.LayoutPattern.ContentViewModeForBrowse

Identifies the layout pattern that the content view mode should apply for this item in the context of browsing.

This is explained in detail here : How to Register Custom Properties and Layout for Your File Type and How to Register a Unique Content View Set of Properties and Layout Pattern for the File Type or Item

 

“Delta”

“Delta”

System.PropList.ContentViewModeForBrowse

The list of properties to show in the content view mode of an item in the context of browsing.

 

 

“~System.ItemNameDisplay”

“~System.ItemNameDisplay”

Dynamic columns

As seen in the previous chapter, the easiest way to add a column to a folder is to do it with a PropertyKey, and its associated property definition and schema (that must have been registered earlier, probably at install time). However, the Windows Property System requires elevated rights to register properties, so it cannot be done on the fly, during program execution.

Starting with ShellBoost version 1.4.0.0, it’s now possible to add a column using a PropertyKey that is not associated with any property definition.

The procedure to add such a column is the same as usual (through the ShellFolder’s AddColumn method), but you can choose any PropertyKey, not necessarily one that has been registered previously. When such a column is added, ShellBoost will ask for details about that column with the ShellFolder’s OnGetDynamicColumnDetailsEvent method. If you define dynamic columns, you must override this method, for example like this:

public class MyRootFolder : RootShellFolder
{
    public static readonly PropertyKey MyKey = new PropertyKey(new Guid("d9f17090-c49e-4ad1-8a4d-1e98ecb431e9"), PropertyKey.PID_FIRST_USABLE);
    public static readonly PropertyKey MyOtherKey = new PropertyKey(new Guid("af9d7a84-4b93-45c6-b064-c567fea27582"), PropertyKey.PID_FIRST_USABLE);
 
    public MyRootFolder(MyShellFolderServer server, ShellItemIdList idList)
        : base(idList)
    {
        if (server == null)
            throw new ArgumentNullException(nameof(server));
 
        // add a dynamic column using a specific unregistered PropertyKey
        AddColumn(MyKey);
 
        // add another dynamic column of type string (SHCOLSTATE_TYPE_STR) using a specific unregistered PropertyKey
        AddColumn(MyOtherKey, SHCOLSTATE.SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE.SHCOLSTATE_TYPE_STR);
    }
 
    protected override void OnGetDynamicColumnDetailsEvent(object sender, GetDynamicColumnDetailsEventArgs e)
    {
        base.OnGetDynamicColumnDetailsEvent(sender, e);
        if (e.PropertyKey == MyKey) // give some details about that PropertyKey
        {
            e.Name = "My Dynamic Column";
        }
    }
}

Note it’s not a dynamic property definition, it’s a dynamic column. It means their support by the Shell is limited in comparison with registered properties. You can only change the column’s name, width, and alignment (left, center, right). You can also define how the column will be shown or what it’s type will be (only SHCOLSTATE_TYPE_STR, SHCOLSTATE_TYPE_INT, and SHCOLSTATE_TYPE_DATE are supported), but they don’t carry any definition, schema, search information, type information, display information like registered properties do.