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.
The details pane from explorer. Notice how the properties are ordered the same as my output.
The other important property lists are:
- System.PropList.PreviewTitle - The 2 properties used as a title in the details pane
- System.PropList.FullDetails - The list of properties found in the details dialog
- System.PropList.InfoTip - The list of properties found in the infotip of the item
- See propkey.h for less common property lists
Ok, this was much much longer than I intended. I'll talk about how to register property lists another day.
-Ben Karas
Comments
- Anonymous
January 09, 2007
Someone asked if I had a copy of the code I've been using in my blog so far. Well, I didn't as of 5pm