Writing properties #5 - Property lists

So if a property handler doesn't enumerate which properties it supports writing, then how does the explorer pick which properties to show? Well, the shell namespace extension containing the item specifies the list of properties it wants to show in a particular piece of UI. To query this property list, call IPropertyStore::GetValue(PKEY_PropList_XXX) on the read-only property store. The shell namespace extension returns a property list similar to L"prop:System.Title;System.Size;System.Keywords" in a VT_LPWSTR propvariant. There are prefixes for each property name which I won't mention yet. For now, just know that you can let the system parse this string by calling PSGetPropertyDescriptionListFromString.

Let's try this out. I am going to modify parts from past programs to write a quick loop through the property list on my goat picture. First, I modified my wmain function to let me specify the PKEY_PropList_XXX from the command line using its canonical name:

 int wmain(int argc, WCHAR **argv)
...
                    PCWSTR pszList = argv[2];
                    PROPERTYKEY keyList;
                    hr = PSGetPropertyKeyFromName(pszList, &keyList);
                    if (SUCCEEDED(hr))
                    {
                        hr = _PrintPropertyStore(keyList, pps);
                    }

You've seen that kind of stuff before. Next, _PrintPropertyStore is rewritten to obtain the list and loop over it:

 HRESULT _PrintPropertyStore(__in REFPROPERTYKEY keyList, __in IPropertyStore *pps)
{
    // Get the property list ...
    PROPVARIANT propvarList = {0};
    HRESULT hr = pps->GetValue(keyList, &propvarList);
    if (SUCCEEDED(hr))
    {
        // ... in string form
        PCWSTR pszList = PropVariantToStringWithDefault(propvarList, NULL);
        if (pszList)
        {
            // parse the property list string to get an interface
            IPropertyDescriptionList *plist;
            hr = PSGetPropertyDescriptionListFromString(pszList, IID_PPV_ARGS(&plist));
            if (SUCCEEDED(hr))
            {
                // loop through the entries ...
                UINT cProps;
                hr = plist->GetCount(&cProps);
                for (UINT i = 0; SUCCEEDED(hr) && i < cProps; i++)
                {
                    IPropertyDescription *ppropdesc;
                    hr = plist->GetAt(i, IID_PPV_ARGS(&ppropdesc));
                    if (SUCCEEDED(hr))
                    {
                        // ... getting values ...
                        PROPVARIANT propvar = {0};
                        hr = PSGetPropertyValue(pps, ppropdesc, &propvar);
                        if (SUCCEEDED(hr))
                        {
                            PROPERTYKEY key;
                            hr = ppropdesc->GetPropertyKey(&key);
                            if (SUCCEEDED(hr))
                            {
                                // ... checking if it is writable ...
                                if (_IsPropertyValueWritable(pps, key))
                                {
                                    wprintf(L"WRITABLE: ");
                                }
                                // ... and printing the value
                                hr = _PrintPropertyValue(key, propvar);
                            }
                            PropVariantClear(&propvar);
                        }
                        ppropdesc->Release();
                    }
                }
                plist->Release();
            }
        }
        PropVariantClear(&propvarList);
    }
    return hr;
}

This code is pretty straight forward. I get the list in string form, convert it to an interface, and then loop through its entries. Since the list enumerates IPropertyDescription interfaces, I snuck a call in to PSGetPropertyValue which wraps a call to IPropertyStore::GetValue for me. Next, I grab the property key so to pass to _IsPropertyValueWritable() and _PrintPropertyValue(). I thought about modifying these to take an IPropertyDescriptionList, but this seemed simpler. 

Anyway, this program shows a more complete picture about what values are present and writable on my test goat:

 > propshow4.exe scan0010.jpg System.PropList.PreviewDetails
System.PropList.PreviewDetails properties for 'scan0010.jpg'
WRITABLE: System.Photo.DateTaken: VT_FILETIME: 2006/07/30:00:33:34.000 --> 7/29/2006 4:33 PM
WRITABLE: System.Keywords: VT_VECTOR|VT_LPWSTR == Sol Duc Valley; Wildlife
WRITABLE: System.Rating: VT_UI4: 99 --> 5 Stars
System.Image.Dimensions: VT_LPWSTR == 1139 x 769
System.Size: VT_UI8: 653729 --> 638 KB
WRITABLE: System.Title: VT_LPWSTR == Mountain goat
WRITABLE: System.Author: VT_VECTOR|VT_LPWSTR == Ben Karas
WRITABLE: System.Comment: VT_EMPTY ==
System.OfflineAvailability: VT_EMPTY ==
System.OfflineStatus: VT_EMPTY ==
WRITABLE: System.Photo.CameraManufacturer: VT_LPWSTR == HP
WRITABLE: System.Photo.CameraModel: VT_LPWSTR == HP Scanjet 4370
WRITABLE: System.Subject: VT_EMPTY ==
System.Photo.FNumber: VT_EMPTY ==
System.Photo.ExposureTime: VT_EMPTY ==
WRITABLE: System.Photo.ISOSpeed: VT_EMPTY ==
...[snip]...

System.PropList.PreviewDetails (PKEY_PropList_PreviewDetails) property list controls which properties appear in the details pane of explorer so it's a nice start to listing out the useful writable properties on an item. As you can see, there is a mixture of read-only and editable properties. That's pretty common for property lists. They are UI oriented and not the easiest to work with. But if you have the right building blocks you can figure out what to do.

Details Pane
The details pane from explorer. Notice how the properties are ordered the same as my output.

The other important property lists are:

Ok, this was much much longer than I intended. I'll talk about how to register property lists another day.

-Ben Karas