Item Properties and Folder Columns

Windows Property System

Starting with Windows Vista, each item in the Shell (items and folders) is associated with a set of properties from the Windows Property System. These properties not only are used to describe the item but also are used in Shell folder views. This is especially true and visible in the case of the Details View, in which case a column is associated with a property. This also is true for other views and the standard property sheet, which is available using the "Properties" menu item. Properties are used by the Shell all the time.

A property:

  • 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 (e.g., "System.ItemNameDisplay"); and
  • has a schema description, which is specified in a .propdesc XML file format and expressed programmatically through the Windows-defined IPropertyDescription interface.

A property has a property definition and a property value.

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

The latest version of Windows 10 defines around 1,600 properties of all types. They are all defined as PropertyKey instances in the CBFS Shell .NET assembly, callback.ShellBoost.Core.WindowsPropertySystem Namespace. Note that 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) has a PROPVARIANT type, which is a structure that can hold a variety of data.

To simplify things, CBFS Shell also defines a PropVariant .NET class that can be used to store property values. Most of the time, however, a developer that uses CBFS Shell only will have to deal with values of standard .NET types (e.g., strings, integers, booleans, arrays), because CBFS Shell components automatically handle PropVariant conversions.

Folder Columns

Because each column is now associated with a property, each column also is associated with a PropertyKey and its property definition.

The CBFS Shell 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 CBFS Shell defines.

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

When an end user opens a CBFS Shell Shell folder, this is what will be displayed the first time:


As you can see, only two columns are displayed, although six are available, because of 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, they 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). To add a column, you will add the associated PropertyKey. In general, columns are added in the ShellFolder-derived class constructor, which in the case of a Shell folder that would display photos, looks like this:

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 callback.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 (e.g., standard .NET values, strings, integers, booleans) for all of these properties accordingly; for example, like this sample, in which we are using a Photo class, defined elsewhere, that has all of the 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);
}

The following image shows what such a folder would like:


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 be better to override the TryGetPropertyValue() method, similar to the following code:

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);
} 

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

Property Lists

Property lists are semicolon 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 be shown in the infotip/tooltip for a Shell item.

These properties are defined in the System.PropList Namespace. Flags are technically defined on the MSDN site in the PROPDESC_VIEW_FLAGS enumeration. Each value in the enumeration has a corresponding special character. The correspondence is available on MSDN: IPropertySystem::GetPropertyDescriptionListFromString method.

Instead of building these strings manually, you may use the CBFS Shell utility class named PropertyDescriptionList in the callback.ShellBoost.Core.WindowsPropertySystem Namespace. The class also supports flags, which will make the task a lot easier. The following code shows an example:

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

This code will build the "prop:System.ItemNameDisplay;System.ItemTypeText;" string. The utility class also handles various flags that the Shell supports.

Custom Properties

The Windows Property System supports custom properties. As a developer that uses CBFS Shell, you may create your own properties and use them to create custom columns. The process is documented on MSDN: Creating Custom Properties. Custom property definitions must be registered using a custom .propdesc XML file in the system before the Shell can use it. CBFS Shell provides a helper method in the PropertySystem class in the callback.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 this process with two custom columns defined in a custom .propdesc file.

Note: Because registration (and unregistration) requires write access to some part of the HKEY_LOCAL_MACHINE registry hive, the program that will run that code will need sufficient permissions. This is an inconvenient aspect of 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 detail view.

The Local Folder sample demonstrates a feature with a custom column named "Icon". The following example shows how the end user could enable that column by right-clicking on the folder's view header:


The following image shows the result:


As you can see, the "Icon" column is rendered with an image instead of a text. CBFS Shell has support for this kind of property, but the procedure is a bit more complex than for other properties. First, you must define two custom properties: one for each icon-rendered column. This is accomplished in a standard way, using a .propdesc file and registering it in Windows:

  • One UInt32 property with an "enumerated" display type. It will define all of the possible values and associated icons for this icon property.
  • One property of the "blob" type (the binary representation of the icon) 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, like this:

// this code uses the PropertySystem utility class to get the PropertyDescription
// from its canonical name. It also checks that 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, as follows:

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 may use an enum value, CBFS Shell 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 the 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
}

The following code shows 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="Callback Technologies" 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>

The example schema 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 may be associated with an image that will represent the value. In the sample, we have reused a standard Windows DLL (imageres.dll), but you may use your own. The image must be located in a binary file as a Win32 resource. The syntax is the standard Win32 resource syntax, as follows: <path>,-<resource index>

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

Important Windows Properties

Given that Windows defines around 1,600 properties, we list some of the most interesting and important ones. Most of the properties are handled internally by CBFS Shell:

Property (System and Internal)

Item Default Value

Folder Default Value

System.ItemNameDisplay DisplayName

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

Key/ID value Key/ID value

System.Size Size

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

File Size (physical) or 0 (virtual) 0

System.ItemType ItemType

The canonical type of the item.

<empty> <empty>

System.DateModified DateModified

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

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

System.DateCreated DateCreated

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

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

System.DateAccessed DateAccessed

Indicates the last time the item was accessed.

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 date of an item changes. Note: This *must* be an UInt64 (C# ulong) value. If unset, Shell will handle this internally.

<empty> <empty>

System.SFGAOFlags Attributes

SFGAO values.

See the Shell Folder Attributes constants. See the Shell Folder Attributes constants.

System.PerceivedType Perceived

The perceived file type based on its canonical type.

PERCEIVED_TYPE_DOCUMENT PERCEIVED_TYPE_FOLDER

System.Kind KindList

Maps extensions to various .Search folders.

KIND_DOCUMENT KIND_FOLDER

System.FileName FileName

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

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 to more easily organize the UI. Note:This defines what is 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 to this item in the context of browsing. This is explained in detail on MSDN: 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 be shown 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). The Windows Property System, however, requires elevated rights to register properties. Therefore, it usually cannot be done on the fly during program execution.

It is 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, and you do not necessarily have to use the one that has been registered previously. When such a column is added, CBFS Shell will ask for details about that column with the ShellFolder's OnGetDynamicColumnDetailsEvent method. If you define dynamic columns, you must override this method. The following code shows how to do 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 string (SHCOLSTATE_TYPE_STR) type 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: This is not a dynamic property definition, but rather a dynamic column. Support of such columns 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 also can define how the column will be shown or what its type will be (only SHCOLSTATE_TYPE_STR, SHCOLSTATE_TYPE_INT, and SHCOLSTATE_TYPE_DATE are supported). Dynamic columns do not carry any definitions, schema, search information, type information, or display information like registered properties do.

Copyright (c) 2022 Callback Technologies, Inc. - All rights reserved.
CBFS Shell 2022 .NET Edition - Version 22.0 [Build 8367]