adding/editing new extended properties to the details tab in existing files
Asked Answered
K

2

6

I tried looking this on SO but all the questions that attempt to answer do not provide a full answer.

I actually want to add a property to the details tab of an existing the file. File is of the sldprt extension. The property/value has to be visible in Windows Explorer.

Not sure how this be done with Windows API Code Pack Shell or DSOFile? Any other solution is fine too.

I'm using VS, C# on Windows 10.

If someone could provide a detailed solution, I would be very thankful.

The perfect solution would:

  • Add the property to the file through dsofile, ADS, or whatever means possible.
  • Edit to Windows Registery or whatever part of Windows that is responsible for showing the new property in the details tab of the file properties and in Windows Explorer
  • Show how the property's value can be edited.

This answer of this question does not add attributes to the details tab nor Windows Explorer detailed view.

I think it's possible since SOLIDWORKS (3d package) adds a property called sw last saved with to all SOLIDWORKS files. There are tons of other properties in the details window.

Please if you don't have the right solution, do not answer. Thank you very much.

I'm not sure this question is a duplicate of Add new metadata properties to a file.

Previews:

enter image description here enter image description here

Edit:

Registery for the sldprt extension (for reference purpose):

enter image description here

Kreiner answered 4/6, 2018 at 17:45 Comment(9)
have you looked at the details to this question - #7991359 it is not exactly the same but approach may be similar.Companionway
It's not really helpful...Kreiner
Possible duplicate of Add new metadata properties to a fileHolton
Do you want that for all files, of any extension, on the system of for a given extension (solidworks does that only for a given set of extensions)?Rebel
for solidworks extensions (sldprt, sldasm, slddrw). You can show it how it is done for one extension. I'm sure the process will be similar. Thnks @SimonMourierKreiner
I have added the registery key for the sldprt extension. Hope this helpsKreiner
You're supposed to 1) create custom properties, 2) register them, 3) write a property handler for that extension. The property handler is supposed to know where to get the property value from the file. msdn.microsoft.com/en-us/library/windows/desktop/ff728870.aspx property handlers must not be written in C#.Rebel
If you can show me an example in C++ (from start to finish - this is the reason why I have put a quarter of rep points as a bounty), that'll be great. Right now, I want to know how someone can do this properly.Kreiner
Is the property you want to show an existing property of sld files? Or do you have something you're the only one to know? But where does this info would come from? In the regedit screenshot, you can see solidworks indeed uses some custom properties (for example: "SolidWorks.Document.LastSavedWith") it has registered probably at install time. The "FullDetails" reg value lists the property you want to see. If you change that key, you will change what's shown in the property sheet.Rebel
S
8

The details tab in the properties window is populated with metadata property handlers. The metadata property system is something Microsoft introduced with Windows Vista and it was made open and extensible, enabling independent developers (like Solidworks) to implement and support their own file properties. Very roughly, the flow of execution is something like this:

User clicks file properties
Look up property handler for the file format
If found property handler:
    Query property handler for properties
    Populate file details with queried properties
Else:
    Populate file details with generic file info

Property handlers are COM objects. COM (Component Object Model) is Microsoft's attempt at a language-independent object oriented framework whose origins go all the way back to the nineties, but for the purposes of this explanation suffice it to say that a COM object is a C++ class that implements the IUnknown interface. A property handler has to implement the IPropertyStore interface on top of that:

struct IPropertyStore : public IUnknown
{
public:
    virtual HRESULT GetCount( 
        DWORD *cProps) = 0;

    virtual HRESULT GetAt( 
        DWORD iProp,
        PROPERTYKEY *pkey) = 0;

    virtual HRESULT GetValue( 
        REFPROPERTYKEY key,
        PROPVARIANT *pv) = 0;

    virtual HRESULT SetValue( 
        REFPROPERTYKEY key,
        REFPROPVARIANT propvar) = 0;

    virtual HRESULT Commit( void) = 0;
};

A convenience implementation of this interface is CLSID_InMemoryPropertyStore provided to developers to ease their own implementation of IPropertyStore. The interesting methods here are GetValue and SetValue. Properties are assigned a unique GUID, which the PROPERTYKEY structure passed into these functions contains to identify the property. The implementations details for GetValue and SetValue are left to the developer, so it is up to the developer how and where to store the value for each property -- these values could be stored in another file, in an alternate file stream, or in the registry to name a few options -- but for transportability reasons it is recommended to store the values in the file itself. This way, if the file is zipped up and emailed, for example, the properties go with it.

The property handler COM object is compiled into a DLL and registered with the system with regsvr32. This allows Windows to know where to go look for properties for that specific file format. Once registered, the property handler can be obtained in a number of ways, one of which is the convenience function SHGetPropertyStoreFromParsingName:

HRESULT GetPropertyStore(PCWSTR pszFilename, GETPROPERTYSTOREFLAGS gpsFlags, IPropertyStore** ppps)
{
    WCHAR szExpanded[MAX_PATH];
    HRESULT hr = ExpandEnvironmentStrings(pszFilename, szExpanded, ARRAYSIZE(szExpanded)) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (SUCCEEDED(hr))
    {
        WCHAR szAbsPath[MAX_PATH];
        hr = _wfullpath(szAbsPath, szExpanded, ARRAYSIZE(szAbsPath)) ? S_OK : E_FAIL;
        if (SUCCEEDED(hr))
        {
            hr = SHGetPropertyStoreFromParsingName(szAbsPath, NULL, gpsFlags, IID_PPV_ARGS(ppps));
        }
    }
    return hr;
}

Once obtained, GetValue and SetValue can be invoked on the IPropertyStore object to either obtain, change or set new value for a property. If using SetValue though, make sure to invoke Commit as well.


Microsoft provides a utility, called PropertyEdit, to get and set metadata properties on a file as part of their Windows Classic Samples. It's a shame they don't mention it anywhere in their help pages. Since you already have Solidworks installed, the property handler for the file formats you're interested in should already be registered on the system and it should be a matter of compiling PropertyEdit and using it to get and set the metadata properties the handler supports. It's a simple command line utility.

If you need, or want to support custom metadata for your own file format, there is a full-blown sample property handler as well: RecipePropertyHandler.

For reference, to set a property by its canonical name:

HRESULT GetPropertyStore(PCWSTR pszFilename, GETPROPERTYSTOREFLAGS gpsFlags, IPropertyStore** ppps)
{
    WCHAR szExpanded[MAX_PATH];
    HRESULT hr = ExpandEnvironmentStrings(pszFilename, szExpanded, ARRAYSIZE(szExpanded)) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (SUCCEEDED(hr))
    {
        WCHAR szAbsPath[MAX_PATH];
        hr = _wfullpath(szAbsPath, szExpanded, ARRAYSIZE(szAbsPath)) ? S_OK : E_FAIL;
        if (SUCCEEDED(hr))
        {
            hr = SHGetPropertyStoreFromParsingName(szAbsPath, NULL, gpsFlags, IID_PPV_ARGS(ppps));
        }
    }
    return hr;
}

HRESULT SetPropertyValue(PCWSTR pszFilename, PCWSTR pszCanonicalName, PCWSTR pszValue)
{
    // Convert the Canonical name of the property to PROPERTYKEY
    PROPERTYKEY key;
    HRESULT hr = PSGetPropertyKeyFromName(pszCanonicalName, &key);
    if (SUCCEEDED(hr))
    {
        IPropertyStore* pps = NULL;

        // Call the helper to get the property store for the
        // initialized item
        hr = GetPropertyStore(pszFilename, GPS_READWRITE, &pps);
        if (SUCCEEDED(hr))
        {
            PROPVARIANT propvarValue = {0};
            hr = InitPropVariantFromString(pszValue, &propvarValue);
            if (SUCCEEDED(hr))
            {
                hr = PSCoerceToCanonicalValue(key, &propvarValue);
                if (SUCCEEDED(hr))
                {
                    // Set the value to the property store of the item.
                    hr = pps->SetValue(key, propvarValue);
                    if (SUCCEEDED(hr))
                    {
                        // Commit does the actual writing back to the file stream.
                        hr = pps->Commit();
                        if (SUCCEEDED(hr))
                        {
                            wprintf(L"Property %s value %s written successfully \n", pszCanonicalName, pszValue);
                        }
                        else
                        {
                            wprintf(L"Error %x: Commit to the propertystore failed.\n", hr);
                        }
                    }
                    else
                    {
                        wprintf(L"Error %x: Set value to the propertystore failed.\n", hr);
                    }
                }
                PropVariantClear(&propvarValue);
            }
            pps->Release();
        }
        else
        {
            wprintf(L"Error %x: getting the propertystore for the item.\n", hr);
        }
    }
     else
    {
        wprintf(L"Invalid property specified: %s\n", pszCanonicalName);
    }
    return hr;
}
Swound answered 7/6, 2018 at 21:39 Comment(3)
Do you mind if you could providing details? I'm not really familiar with C++. This is the reason I'm giving away my rep points :)Kreiner
@JLILIAmen Added some more detail. BTW if you're not that familiar with C++, COM is not exactly the best way to start learning, but good luck!Swound
I'm going tick this answer for the effort put in it and will definitely use this information. Do you mind if we exchange e-mails?Kreiner
M
2

There are two full-scale sample projects that demonstrate how to do that in Windows 7 SDK samples pack: RecipePropertyHandler and PlaylistPropertyHandler.

The main idea is implementing the IPropertyStore interface which is a bit like collection/iterator:

Commit    Saves a property change.
GetAt     Gets a property key from an item's array of properties.
GetCount  Gets the number of properties attached to the file.
GetValue  Gets data for a specific property.
SetValue  Sets a new property value, or replaces or removes an existing value.

Your code needs to initialize the handler by implementing the IInitializeWithStream interface. Then it needs to respond to the GetCount, GetAt and GetValue calls to provide the property name and value.

The sample projects that I've liked to do all this and come with instructions on how to register the property handlers.

P.S. mnistic's answer mainly talks about a way to add properties (and values) to individual files that exist in some filesystem. This answer provides general solution to add property to a particular file type.

Moonscape answered 8/6, 2018 at 1:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.