Jump to content
FPiette

How to register a shell property sheet for a single file type?

Recommended Posts

I wrote a simple shell property sheet handler using Delphi 11. It works perfectly when I register it for all file types. But it does nothing (My property page not shown) when I Register it for a specific file type.

 

Registration for all file types:
 

    // Error handling removed for clarity
    Reg         := TRegistry.Create;
    Reg.RootKey := HKEY_CLASSES_ROOT;
    if Reg.OpenKey('\*\ShellEx\PropertySheetHandlers\MyDelphiPropSheetHandler', True) then begin
       Reg.WriteString('', ClassID);
    Reg.CloseKey;

ClassID is registered as part of the COM server. This is done automatically by Delphi RTL.

 

Registering for a single file type:

I simply replace "*" in the above code by the file extension which is ".arw" in my case:

 

    // Error handling removed for clarity
    Reg         := TRegistry.Create;
    Reg.RootKey := HKEY_CLASSES_ROOT;
    if Reg.OpenKey('\.arw\ShellEx\PropertySheetHandlers\MyDelphiPropSheetHandler', True) then begin
       Reg.WriteString('', ClassID);
    Reg.CloseKey;

 

This simply does nothing. No error, just nothing. The key is correctly written in the registry.

 

What am I doing wrong?

 

Share this post


Link to post
1 hour ago, FPiette said:

Registering for a single file type:

I simply replace "*" in the above code by the file extension which is ".arw" in my case:

Per the documentation, Registering Shell Extension Handlers, you need to create your ShellEx subkey underneath the extension's ProgID subkey, not under the extension subkey, eg:

HKEY_CLASSES_ROOT
  .arw
    (Default) = <MyArwProgID>
  MyArwProgID
    ShellEx
      PropertySheetHandlers
        MyDelphiPropSheetHandler
          (Default) = <MyClassID>

For example:

Reg := TRegistry.Create;
try
  Reg.RootKey := HKEY_CLASSES_ROOT;
  if Reg.OpenKeyReadOnly('\.arw') then
  begin
    ProgID := Reg.ReadString('');
    Reg.CloseKey;
    Reg.Access := KEY_CREATE_SUB_KEY or KEY_SET_VALUE;
    if (ProgID <> '') and Reg.OpenKey('\' + ProgID + '\ShellEx\PropertySheetHandlers\MyDelphiPropSheetHandler', True) then
    begin
      Reg.WriteString('', ClassID);
      Reg.CloseKey;
    end;
  end;
finally
  Reg.Free;
end;

Note that you really should not be writing directly to HKEY_CLASSES_ROOT, you should be writing to either HKEY_LOCAL_MACHINE\Software\Classes\ or HKEY_CURRENT_USER\Software\Classes\ instead, per the documentation, HKEY_CLASSES_ROOT Key:

Quote

To change the settings for the interactive user, store the changes under HKEY_CURRENT_USER\Software\Classes rather than HKEY_CLASSES_ROOT.

 

To change the default settings, store the changes under HKEY_LOCAL_MACHINE\Software\Classes.

 

If you write keys to a key under HKEY_CLASSES_ROOT, the system stores the information under HKEY_LOCAL_MACHINE\Software\Classes. If you write values to a key under HKEY_CLASSES_ROOT, and the key already exists under HKEY_CURRENT_USER\Software\Classes, the system will store the information there instead of under HKEY_LOCAL_MACHINE\Software\Classes.

For instance:

Reg := TRegistry.Create;
try
  if ForTheCurrentUserOnly then
    Reg.RootKey := HKEY_CURRENT_USER
  else
    Reg.RootKey := HKEY_LOCAL_MACHINE;
  if Reg.OpenKeyReadOnly('\Software\Classes\.arw') then
  begin
    ProgID := Reg.ReadString('');
    Reg.CloseKey;
    Reg.Access := KEY_CREATE_SUB_KEY or KEY_SET_VALUE;
    if (ProgID <> '') and Reg.OpenKey('\Software\Classes\' + ProgID + '\ShellEx\PropertySheetHandlers\MyDelphiPropSheetHandler', True) then
    begin
      Reg.WriteString('', ClassID);
      Reg.CloseKey;
    end;
  end;
finally
  Reg.Free;
end;

 

Edited by Remy Lebeau

Share this post


Link to post

I dont know if help you, but you try see the result when associating any new EXTention file

 

Doesn't help. My question is not related to the creation of a new file type (extension).

Share this post


Link to post
12 hours ago, Remy Lebeau said:

Per the documentation, Registering Shell Extension Handlers, you need to create your ShellEx subkey underneath the extension's ProgID subkey, not under the extension subkey

Thanks. I already saw that documentation. It is probably outdated because there is no ProgID for the file extension.

Edited by FPiette

Share this post


Link to post
39 minutes ago, programmerdelphi2k said:

well, here in my test it at least works...

Thank you for your message but you are still out of scope of my question!

My question about a property sheet handler associated with a file extension. To say it otherwise, I don't want to create a new file extension nor associate a program to open a file having that file extension (This is what you show).

I guess that you don't know what a "Property Sheet Handler" is. A property sheet is what you see when you right-click on a file and select "properties" in the popup menu. Then you see a window with several tabs. A property sheet is a shell extension that build a show a new tab.

I wrote such a property sheet handler using Delphi. I can easily register it for ALL file types (AKA file extension) but I don't want it for all files, I want it only for a small number of file extensions.

Look at this: https://learn.microsoft.com/en-us/windows/win32/shell/propsheet-handlers

 

Share this post


Link to post

@FPiette  sorry ... really I think that was about association... 😁 ok, im get out here sorry again...  im not has intimity with "prop-sheets" at all

Edited by programmerdelphi2k

Share this post


Link to post
9 hours ago, FPiette said:

Thanks. I already saw that documentation. It is probably outdated because there is no ProgID for the file extension.

It is not outdated.  I've used that info in the past to create shell handlers, and my machines have many 3rd party property sheet handlers installed that are registered the way I described.

 

If your extension does not have a ProgID assigned to it (why?), you need to create and assign your own ProgID, even if just to create the Shellex subkey.  All extension subkeys should have a ProgID subkey associated with them, because it is the ProgID subkey that contains the actual commands that tell Windows what to do with files that have those extensions.  Multiple file extensions that share a common set of commands can be assigned to a single ProgID.

Edited by Remy Lebeau

Share this post


Link to post
5 minutes ago, Remy Lebeau said:

All extension subkeys should have a ProgID subkey associated with them, because it is the ProgID subkey that contains the commands that tell the OS what to do with the file.

There is no ProgID for the file type. And yet, Windows open it using Microsoft Windows Photo application (.ARW file is Sony RAW file for photo).

image.thumb.png.cc5f0685643a0a7dae8f7e360ba639f8.png

image.thumb.png.f0dfefb3cdf552d171e3a5dc0cc9629f.png

 

If you want to try with a real file on your system, you can download a photo in .ARW format: http://wiki.overbyte.be/arch/FPI09894.ARW

(Can't attach it here because it is a 50MB).

It is possible that you need to install Microsoft RAW extension (https://apps.microsoft.com/store/detail/extension-dimage-raw/9NCTDW2W1BH8) It is needed for Win10 but already included by default with Win11. If you have Adobe Photoshop or Lightroom installed, thing may be different, not sure.

 

Share this post


Link to post

sorry (im trying undertand more about)...

{ GUID } -> is it appointing to real exe/dll/ocx app = ProgID

HKEYS_CLASSES_ROOT == * (all files)

image.thumb.png.213992a9cc621bb51f0cb4f6261e46fb.png

 

MSWin10 has this {C7657C4A-9F68-40fa-A4DF-96BC08EB3551} by default "Photo Thumbnail Provider"

Edited by programmerdelphi2k

Share this post


Link to post
4 hours ago, FPiette said:

There is no ProgID for the file type. And yet, Windows open it using Microsoft Windows Photo application (.ARW file is Sony RAW file for photo).

That is because the extension key has a Thumbnail Provider registered, which is registered via a "ShellEx" subkey on the extension key itself.  However, since you want to register a Property Sheet handler instead, the extension key must have a ProgID registered with it, as I showed earlier.  Why are you being so hesitant to define one?  If you want your Property Sheet Handler to work correctly, you don't have a choice, this is simply how Property Sheet Handlers operate.  A "ShellEx" subkey on the extension key itself will not work for a Property Sheet Handler.

Edited by Remy Lebeau

Share this post


Link to post
10 hours ago, Remy Lebeau said:

the extension key must have a ProgID registered with it, as I showed earlier.  Why are you being so hesitant to define one?

Because I don't know what to make it point to. You told that the ProgId is used when the file must be opened. I don't want to change the application opening it. My code is just to display some metadata from the file, not the file data.

Share this post


Link to post
16 hours ago, FPiette said:

Because I don't know what to make it point to. You told that the ProgId is used when the file must be opened. I don't want to change the application opening it. My code is just to display some metadata from the file, not the file data.

Creating a new ProgID just to define a PropertySheetHandler will not change which app opens the file type.  Those are separate operations.  Besides, in your example, there is no app registered for the file type anyway, since there is no ProgID registered for the file type (the file is being opened through a separately registered Thumbnail Handler instead).  Simply don't create any Shell commands inside of your new ProgID, only register your ShellEx handler.

Edited by Remy Lebeau

Share this post


Link to post
On 3/5/2023 at 7:27 PM, Remy Lebeau said:

Per the documentation, Registering Shell Extension Handlers, you need to create your ShellEx subkey underneath the extension's ProgID subkey, not under the extension subkey

I have coded that. And it doesn't work at all.

Here is the code:

procedure TMyDelphiPropSheetHandlerFactory.UpdateRegistry(ARegister: Boolean);
const
    Key = '*\shellex\PropertySheetHandlers\';
var
    Reg         : TRegistry;
    ClassID     : String;
{$IFDEF REG_FILENAME}
    ProgNameVer : String;
    RegExtArray : TStringDynArray;
    Ext         : String;
    ExtCount    : Integer;
    ProgID      : String;
    ProgTmp     : String;
{$ENDIF}
begin
    ClassID     := GUIDToString(Class_MyDelphiPropSheetHandler);

    Reg         := TRegistry.Create;
    try
{$IFDEF REG_FILENAME}
        if ARegister then
            inherited UpdateRegistry(ARegister);

        Reg.RootKey := HKEY_LOCAL_MACHINE;
        ProgNameVer := ProgName + '.' + ProgVer;
        RegExtArray := SplitString(RegFileExts, ';');
        if ARegister then begin
            // Register
            if Length(RegExtArray) > 0 then begin
                ExtCount := 0;
                for Ext in RegExtArray do begin
                    if not Reg.OpenKey('\Software\Classes\' + Ext, FALSE) then
                        MyMessageBox('File type "%s" doesn''t exists', [Ext])
                    else begin
                        Inc(ExtCount);
                        ProgID := Reg.ReadString('');
                        if ProgID = '' then begin
                            ProgID := ProgNameVer;
                            Reg.WriteString('', ProgID);
                            MyMessageBox('Created ProgID[%s]="%s"', [Ext, ProgID]);
                            Reg.CloseKey;
                            if not Reg.OpenKey('\Software\Classes\' + ProgID, FALSE) then begin
                                Reg.OpenKey('\Software\Classes\' + ProgID, TRUE);
                                MyMessageBox('Created key "\Software\Classes\%s"', [ProgID]);
                            end;
                        end
                        else
                            MyMessageBox('ProgID[%s]="%s"', [Ext, ProgID]);

                        Reg.CloseKey;
                        if ExtCount = 1 then begin
                            if Reg.OpenKey('\Software\Classes\' + ProgID +
                                           '\ShellEx\PropertySheetHandlers\' +
                                           ProgName, TRUE) then begin
                                Reg.WriteString('', ClassID);
                                Reg.CloseKey;
                                MyMessageBox('PropertySheetHandlers[%s]="%s"', [Ext, ProgName]);
                            end;
                        end;
                        MyMessageBox('Registered for Ext="%s"', [Ext]);
                        SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
                    end;
                end;
            end;
        end
        else begin
            // Unregister
            ProgID := ProgName + '.' + ProgVer;
            if Length(RegExtArray) > 0 then begin
                for Ext in RegExtArray do begin
                    if Reg.OpenKey('\Software\Classes\' + Ext, FALSE) then begin
                        ProgTmp := Reg.ReadString('');
                        if SameText(ProgID, ProgTmp) then begin
                            Reg.DeleteValue('');
                            MyMessageBox('Deleted ProgID="%s"  for Ext="%s"', [ProgID, Ext]);
                        end;
                    end;
                end;
            end;
            if Reg.OpenKey('\Software\Classes\' + ProgNameVer, False) then
               Reg.DeleteKey('\Software\Classes\' + ProgNameVer);
            inherited UpdateRegistry(ARegister);
        end;
{$ELSE}
        inherited UpdateRegistry(ARegister);
        Reg.RootKey := HKEY_CLASSES_ROOT;
        if ARegister then begin
            if Reg.OpenKey('\*\ShellEx\PropertySheetHandlers\' + ProgName, True) then
                Reg.WriteString('', ClassID)
            else
                MyMessageBox('UpdateRegistry : Can''t open registry key!');
        end
        else begin
            if Reg.OpenKey('\*\ShellEx\PropertySheetHandlers\' + ProgName, False) then begin
                if not Reg.DeleteKey('\*\ShellEx\PropertySheetHandlers\' + ProgName) then
                    MyMessageBox('Registry key *NOT* deleted!');
            end;
        end;
{$ENDIF}
    finally
        Reg.CloseKey;
        Reg.Free;
    end;
end;

As you can see, the symbol REG_FILENAME which select registration for all files or single file name extension.

MyMessageBox is a message box that I use for debugging.

The registration for all file name extension work nicely, so I'm sure the property sheet code is correct. Only the registration for a single extension doesn't work (no error, just do nothing).

 

The complete source code is there: MyDelphiPropSheetHandlerSourceCode.zip

There is also an image in .arw file format to test: http://wiki.overbyte.be/arch/FPI09894.ARW

BTW: The property sheet does nothing except displaying the selected files in a TMemo.

 

 

Share this post


Link to post

Rather than writing code, just edit the registry manually to debug your problem. Once you know what the solution is, then write code. If you want somebody here to troubleshoot then showing registry structure is much easier to work through than Delphi code to write registry values.

Share this post


Link to post
1 hour ago, David Heffernan said:

Rather than writing code, just edit the registry manually to debug your problem.

showing registry structure is much easier to work through than Delphi code to write registry values.

The registry structure is what @Remy Lebeaushowed in his constructive answer and is conform to Microsoft documentation and yet it doesn't work. Using registry I checked that my code create the keys as specified by Microsoft (see below). Entering something manually? OK but what? That's the question. I miss something and I don't know what.

 

If I don't publish code, someone (you for example) will say "Show what you tried...". That's why I copied relevant code here and made complete project available.

 

image.thumb.png.1083150c8b78f6066e1908de899a29df.png

Lines marked "<= Not me" was existing before.

I write in HKLM\SOFTWARE\Classes and Windows automatically replicate the keys in HKCR.

 

On my system, there is no property sheet that I can analyze and reproduce the structure. 

 

Share this post


Link to post
Quote

The registration for all file name extension work nicely, so I'm sure the property sheet code is correct. Only the registration for a single extension doesn't work (no error, just do nothing).

How would you know fore sure? You are not reporting every error that may occur.  For example, if an extension does not have a ProgID yet, and you try to create a new ProgID but Reg.OpenKey() fails, you don't report that.  Also, I see you creating your PropertySheetHandlers subkey only for the 1st extension in your array, because your code assumes that all extensions are using the same ProgID, which is not a guarantee, so you should be checking/creating the PropertySheetHandlers subkey for every ProgID you encounter.

 

Try something more like this:

procedure TMyDelphiPropSheetHandlerFactory.UpdateRegistry(ARegister: Boolean);
var
  Reg         : TRegistry;
  ClassID     : String;
  Updated     : Boolean;
{$IFDEF REG_FILENAME}
  ProgNameVer : String;
  RegExtArray : TStringDynArray;
  Ext         : String;
  ProgID      : String;
{$ENDIF}
begin
  ClassID := GUIDToString(Class_MyDelphiPropSheetHandler);

  Updated := False;
  try
    Reg := TRegistry.Create;
    try
{$IFDEF REG_FILENAME}
      Reg.RootKey := HKEY_LOCAL_MACHINE;

      ProgNameVer := ProgName + '.' + ProgVer;
      RegExtArray := SplitString(RegFileExts, ';');

      if ARegister then begin
        inherited UpdateRegistry(ARegister);

        for Ext in RegExtArray do begin
          if not Reg.OpenKeyReadOnly('\Software\Classes\' + Ext) then begin
            if Reg.LastError = ERROR_FILE_NOT_FOUND then
              MyMessageBox('Ext="%s" doesn''t exist', [Ext])
            else
              MyMessageBox('Can't open registry key for Ext="%s"', [Ext]);
            Continue;
          end;
          ProgID := Reg.ReadString('');
          Reg.CloseKey;
          
          if ProgID = '' then begin
            ProgID := ProgNameVer;
            Reg.Access := KEY_SET_VALUE;
            if not Reg.OpenKey('\Software\Classes\' + Ext, True) then begin
              MyMessageBox('Can't open registry key to update Ext="%s"', [Ext]);
              Continue;
            end;
            Reg.WriteString('', ProgID);
            Reg.CloseKey;
            Updated := True;
            MyMessageBox('Created ProgID[%s]="%s"', [Ext, ProgID]);
          end else
            MyMessageBox('ProgID[%s]="%s"', [Ext, ProgID]);
            
          Reg.Access := KEY_SET_VALUE;
          if not Reg.OpenKey('\Software\Classes\' + ProgID + '\ShellEx\PropertySheetHandlers\' + ProgName, True) then begin
            MyMessageBox('Can''t create PropertySheetHandler="%s" for ProgID="%s"', [ProgName, ProgID]);
            Continue;
          end;
          Reg.WriteString('', ClassID);
          Reg.CloseKey;
          Updated := True;
          MyMessageBox('PropertySheetHandlers[%s]="%s"', [ProgID, ProgName]);

          MyMessageBox('Registered for Ext="%s"', [Ext]);
        end;
      end
      else begin
        for Ext in RegExtArray do begin
          if not Reg.OpenKeyReadOnly('\Software\Classes\' + Ext) then begin
            if Reg.LastError <> ERROR_FILE_NOT_FOUND then
              MyMessageBox('Can't open registry key for Ext="%s"', [Ext]);
            Continue;
          end;
          ProgID := Reg.ReadString('');
          Reg.CloseKey;
        
          if SameText(ProgID, ProgNameVer) then begin
            Reg.Access := KEY_SET_VALUE;
            if not Reg.OpenKey('\Software\Classes\' + Ext, False) then begin
              MyMessageBox('Can't open registry key to update Ext="%s"', [Ext]);
            end
            else begin
              if Reg.DeleteValue('') then begin
                Reg.CloseKey;
                Updated := True;
                MyMessageBox('Deleted ProgID="%s" value for Ext="%s"', [ProgID, Ext])
              end
              else begin
                Reg.CloseKey;
                MyMessageBox('Can''t delete ProgID="%s" value for Ext="%s"', [ProgID, Ext]);
              end;
            end;
          end;

          if ProgID <> '' then begin
            Reg.Access := KEY_SET_VALUE or _DELETE;
            if not Reg.OpenKey('\Software\Classes\' + ProgID + '\ShellEx\PropertySheetHandlers\', False) then begin
              if Reg.LastError <> ERROR_FILE_NOT_FOUND then
                MyMessageBox('Can''t open PropertySheetHandlers key for ProgID="%s"', [ProgID]);
            end
            else if Reg.DeleteKey(ProgName) or (Reg.LastError = ERROR_FILE_NOT_FOUND) then begin
              Reg.CloseKey;
              Updated := True;
              MyMessageBox('Deleted PropertySheetHandler="%s" for ProgID="%s"', [ProgName, ProgID]);
            end
            else begin
              Reg.CloseKey;
              MyMessageBox('Can''t delete PropertySheetHandler="%s" for ProgID="%s"', [ProgName, ProgID]);
            end;
          end;
        end;

        if Reg.DeleteKey('\Software\Classes\' + ProgNameVer) or (Reg.LastError = ERROR_FILE_NOT_FOUND) then begin
          Updated := True;
          MyMessageBox('Deleted registry key for ProgID="%s"', [ProgNameVer])
        end else
          MyMessageBox('Can''t delete registry key for ProgID="%s"', [ProgNameVer]);

        inherited UpdateRegistry(ARegister);
      end;
{$ELSE}
      Reg.RootKey := HKEY_CLASSES_ROOT;
      if ARegister then begin
        inherited UpdateRegistry(ARegister);
        Reg.Access := KEY_SET_VALUE;
        if Reg.OpenKey('\*\shellex\PropertySheetHandlers\' + ProgName, True) then begin
          Reg.WriteString('', ClassID);
          Reg.CloseKey;
          Updated := True;
          MyMessageBox('Created registry key for PropertySheetHandler="%s"', [ProgName]);
        end else
          MyMessageBox('Can''t create/open registry key for PropertySheetHandler="%s"', [ProgName]);
      end
      else begin
        if Reg.DeleteKey('\*\shellex\PropertySheetHandlers\' + ProgName) or (Reg.LastError = ERROR_FILE_NOT_FOUND) then begin
          Updated := True;
          MyMessageBox('Deleted registry key for PropertySheetHandler="%s"', [ProgName]);
        end else
          MyMessageBox('Can''t delete registry key for PropertySheetHandler="%s"', [ProgName]);
        inherited UpdateRegistry(ARegister);
      end;
{$ENDIF}
    finally
      Reg.Free;
    end;
  finally
    if Updated then
      SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  end;
end;

UPDATE: something else I just realized - you did not say whether you are running your code from a 32bit or 64bit executable.  If 32bit on a 64bit system, you need to take WOW64 into account, by including the KEY_WOW64_32KEY or KEY_WOW64_64KEY flag in the TRegistry.Access property as needed.

Edited by Remy Lebeau

Share this post


Link to post
36 minutes ago, FPiette said:

If I don't publish code, someone (you for example) will say "Show what you tried...". That's why I copied relevant code here and made complete project available.

 

image.thumb.png.1083150c8b78f6066e1908de899a29df.png

Your PropertySheelHandler should not have its own ProgID assigned to it.  You can assign your PropertySheetHandler to other ProgIDs as needed, but it should not have its own ProgID.  So, get rid of the "HKLM\SOFTWARE\Classes\CLSID\{AF6B...}\ProgID" subkey.

Edited by Remy Lebeau

Share this post


Link to post
1 hour ago, Remy Lebeau said:

because your code assumes that all extensions are using the same ProgID

First thing first, I try with a single extension: .arw.

Then I'll update the code when required for multiple extensions, that's not the issue.

 

Share this post


Link to post
35 minutes ago, Anders Melander said:

Works for me:

Wonderful. From the screen dump I cannot determine that the property sheet handler is registered for all extensions (That work here also) or for a single one (Don't work here). If REG_FILENAME symbol is defined, my code will register for a single file extension (.arw in my original code), that is what you need to try.

 

36 minutes ago, Anders Melander said:

I'll clean up the code and post it later..

I will really be grateful for that code.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×