Anders Melander 1782 Posted March 11, 2023 21 minutes ago, FPiette said: Bingo! it works. That's great! However, as far as I can tell, there's no documentation to explain this behavior. A bit unfortunate, to say the least. Or maybe I'm just not understanding how association arrays work. I have already updated my code to support registration under HKCU\Software\Classes instead of HKCR. I will update it to also add the SystemFileAssociation keys. 2 Share this post Link to post
FPiette 382 Posted March 12, 2023 21 hours ago, Anders Melander said: Or maybe I'm just not understanding how association arrays work. They work even easier than I thought: When registering a property sheet, it is enough to register it in the SystemFileAssociations. No need to register the class for the file extension nor the file class. To register a property sheet for .foobar files, under de key HKLM\SOFTWARE\Classes\SystemFileAssociations\.foobar\ShellEx\PropertySheetHandlers (May also have to be create) create an entry with the name registered for the COM object implementing the shell extension and set default value to the CLSID. This is an important feature for me to avoid interfering with an existing file type already having a program defined. Tested the behavior with both Win10 and Win11. Share this post Link to post
FPiette 382 Posted March 30, 2023 On 3/12/2023 at 2:28 PM, FPiette said: They work even easier than I thought: When registering a property sheet, it is enough to register it in the SystemFileAssociations. No need to register the class for the file extension nor the file class. To register a property sheet for .foobar files, under de key HKLM\SOFTWARE\Classes\SystemFileAssociations\.foobar\ShellEx\PropertySheetHandlers (May also have to be create) create an entry with the name registered for the COM object implementing the shell extension and set default value to the CLSID. This is an important feature for me to avoid interfering with an existing file type already having a program defined. Tested the behavior with both Win10 and Win11. @Anders Melander I had the opportunity to test on Win7. The behavior is the same as on Win 10 and 11: it is enough to register it in the SystemFileAssociations. No need to register the class for the file extension nor the file class. Share this post Link to post
Anders Melander 1782 Posted March 30, 2023 5 hours ago, FPiette said: I had the opportunity to test on Win7. The behavior is the same as on Win 10 and 11: it is enough to register it in the SystemFileAssociations. No need to register the class for the file extension nor the file class. Yes, that's my conclusion too. I'm on Win7, 10 and 11 here. This is the relevant code I use for registration: TPropertySheetHandlerFactory class const // CLSID for this shell extension. // Modify this for your own shell extensions (press [Ctrl]+[Shift]+G in // the IDE editor to gererate a new CLSID). CLSID_PropertySheetHandler: TGUID = '{1067C264-8B1F-4B22-919F-DB5191C359CB}'; sFileClass = 'pasfile'; sFileExtension = '.pas'; sClassName = 'DelphiPropSheetShellExt'; resourcestring // Description of our shell extension. sDescription = 'Drag and Drop Component Suite property sheet demo'; ... //////////////////////////////////////////////////////////////////////////////// type TPropertySheetHandlerFactory = class(TShellExtFactory) protected function HandlerRegSubKey: string; override; end; function TPropertySheetHandlerFactory.HandlerRegSubKey: string; begin Result := 'PropertySheetHandlers'; end; //////////////////////////////////////////////////////////////////////////////// initialization TPropertySheetHandlerFactory.Create(ComServer, TDataModulePropertySheetHandler, CLSID_PropertySheetHandler, sClassName, sDescription, sFileClass, sFileExtension, ciMultiInstance); end. TShellExtFactory class //////////////////////////////////////////////////////////////////////////////// // // TShellExtFactory // //////////////////////////////////////////////////////////////////////////////// // Class factory for component based COM classes. // Specialized for Shell Extensions. //////////////////////////////////////////////////////////////////////////////// type TShellExtFactory = class(TVCLComObjectFactory) private FFileExtension: string; FFileClass: string; protected function GetProgID: string; override; function HandlerRegSubKey: string; virtual; abstract; function UseSystemFileAssociations: boolean; virtual; function OwnsFileExtension: boolean; virtual; public constructor Create(ComServer: TComServerObject; ComponentClass: TComponentClass; const ClassID: TGUID; const ClassName, Description, AFileClass, AFileExtension: string; Instancing: TClassInstancing); procedure UpdateRegistry(ARegister: Boolean); override; property FileClass: string read FFileClass write FFileClass; property FileExtension: string read FFileExtension write FFileExtension; end; ..... //////////////////////////////////////////////////////////////////////////////// // // TShellExtFactory // //////////////////////////////////////////////////////////////////////////////// constructor TShellExtFactory.Create(ComServer: TComServerObject; ComponentClass: TComponentClass; const ClassID: TGUID; const ClassName, Description, AFileClass, AFileExtension: string; Instancing: TClassInstancing); begin inherited Create(ComServer, ComponentClass, ClassID, ClassName, Description, Instancing); FFileClass := AFileClass; FFileExtension := AFileExtension; end; function TShellExtFactory.GetProgID: string; begin Result := ''; end; function TShellExtFactory.OwnsFileExtension: boolean; begin // Return True if it's safe to delete the file association upon unregistration. // Be careful that we don't delete file associations used by other applications Result := False; end; procedure TShellExtFactory.UpdateRegistry(ARegister: Boolean); var RegPrefix: string; RootKey: HKEY; ClassIDStr: string; Registry: TRegistry; begin ComServer.GetRegRootAndPrefix(RootKey, RegPrefix); ClassIDStr := GUIDToString(ClassID); if ARegister then begin inherited UpdateRegistry(ARegister); if UseSystemFileAssociations and (FileExtension <> '') then begin CreateRegKey(RegPrefix+'SystemFileAssociations\'+FileExtension+'\shellex\'+HandlerRegSubKey+'\'+ClassName, '', ClassIDStr, RootKey); end else if (FileClass <> '') then begin if (FileExtension <> '') and (GetRegStringValue(RegPrefix+FileExtension, '', RootKey) = '') then CreateRegKey(RegPrefix+FileExtension, '', FileClass, RootKey); CreateRegKey(RegPrefix+FileClass+'\shellex\'+HandlerRegSubKey+'\'+ClassName, '', ClassIDStr, RootKey); end; if (Win32Platform = VER_PLATFORM_WIN32_NT) then begin Registry := TRegistry.Create; try if (ComServer.PerUserRegistration) then Registry.RootKey := HKEY_CURRENT_USER else Registry.RootKey := HKEY_LOCAL_MACHINE; if Registry.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', False) then Registry.WriteString(ClassIDStr, Description); finally Registry.Free; end; end; end else begin if (Win32Platform = VER_PLATFORM_WIN32_NT) then begin Registry := TRegistry.Create; try if (ComServer.PerUserRegistration) then Registry.RootKey := HKEY_CURRENT_USER else Registry.RootKey := HKEY_LOCAL_MACHINE; if Registry.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', False) then Registry.DeleteKey(ClassIDStr); finally Registry.Free; end; end; if UseSystemFileAssociations and (FileExtension <> '') then begin DeleteDefaultRegValue(RegPrefix+'SystemFileAssociations\'+FileExtension+'\shellex\'+HandlerRegSubKey+'\'+ClassName, RootKey); DeleteEmptyRegKey(RegPrefix+'SystemFileAssociations\'+FileExtension+'\shellex\'+HandlerRegSubKey+'\'+ClassName, True, RootKey); end else if (FileClass <> '') then begin if (FileExtension <> '') and (OwnsFileExtension) and (GetRegStringValue(RegPrefix+FileExtension, '', RootKey) = FileClass) then DeleteDefaultRegValue(RegPrefix + FileExtension, RootKey); DeleteDefaultRegValue(RegPrefix+FileClass+'\shellex\'+HandlerRegSubKey+'\'+ClassName, RootKey); DeleteEmptyRegKey(RegPrefix+FileClass+'\shellex\'+HandlerRegSubKey+'\'+ClassName, True, RootKey); end; inherited UpdateRegistry(ARegister); end; SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil); end; function TShellExtFactory.UseSystemFileAssociations: boolean; begin Result := True; end; TVCLComObjectFactory class //////////////////////////////////////////////////////////////////////////////// // // TVCLComObjectFactory // //////////////////////////////////////////////////////////////////////////////// // Class factory for component based COM classes. // Does not require a type library. // Based on TComponentFactory and TComObjectFactory. //////////////////////////////////////////////////////////////////////////////// type TVCLComObjectFactory = class(TComObjectFactory, IClassFactory) private protected function CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; out Obj): HResult; stdcall; public constructor Create(ComServer: TComServerObject; ComponentClass: TComponentClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing); function CreateComObject(const Controller: IUnknown): TComObject; override; procedure UpdateRegistry(Register: Boolean); override; end; ... //////////////////////////////////////////////////////////////////////////////// // // TVCLComObjectFactory // //////////////////////////////////////////////////////////////////////////////// constructor TVCLComObjectFactory.Create(ComServer: TComServerObject; ComponentClass: TComponentClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing); begin inherited Create(ComServer, TComClass(ComponentClass), ClassID, ClassName, Description, Instancing, tmApartment); end; function TVCLComObjectFactory.CreateComObject(const Controller: IUnknown): TComObject; begin Result := TVCLComObject.CreateFromFactory(Self, Controller); end; function TVCLComObjectFactory.CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; out Obj): HResult; begin if not IsLibrary then begin LockServer(True); try with TApartmentThread.Create(Self, UnkOuter, IID) do begin if WaitForSingleObject(Semaphore, INFINITE) = WAIT_OBJECT_0 then begin Result := CreateResult; if Result <> S_OK then Exit; Result := CoGetInterfaceAndReleaseStream(IStream(ObjStream), IID, Obj); end else Result := E_FAIL end; finally LockServer(False); end; end else Result := inherited CreateInstance(UnkOuter, IID, Obj); end; type TComponentProtectedAccess = class(TComponent); TComponentProtectedAccessClass = class of TComponentProtectedAccess; procedure TVCLComObjectFactory.UpdateRegistry(Register: Boolean); begin if Register then inherited UpdateRegistry(Register); TComponentProtectedAccessClass(ComClass).UpdateRegistry(Register, GUIDToString(ClassID), ProgID); if not Register then inherited UpdateRegistry(Register); end; Share this post Link to post
Remy Lebeau 1393 Posted March 30, 2023 10 hours ago, FPiette said: @Anders Melander I had the opportunity to test on Win7. The behavior is the same as on Win 10 and 11: it is enough to register it in the SystemFileAssociations. No need to register the class for the file extension nor the file class. Just FYI, I have just now looked back at an old project of mine, which was first written back in the XP days before SystemFileAssociations was introduced. It still registers a custom PropertySheetHandler for a specific file extension under HKCR\<FileExtProgID>\ShellEx\PropertySheetHandlers and HKLM\SOFTWARE\Windows\CurrentVersion\Shell Extensions\Approved, and it still works fine on Win7 and Win10 (I didn't test on Win11). If SystemFileAssociations also works, good for it. Share this post Link to post
Anders Melander 1782 Posted March 30, 2023 2 hours ago, Remy Lebeau said: It still registers a custom PropertySheetHandler for a specific file extension under HKCR\<FileExtProgID>\ShellEx\PropertySheetHandlers and HKLM\SOFTWARE\Windows\CurrentVersion\Shell Extensions\Approved, and it still works fine on Win7 and Win10 (I didn't test on Win11). Yes, that was the initial observation but if you reread the thread you will see that the original problem was that for some file types, it didn't work. Probably those that already have stuff registered under SystemFileAssociations. FWIW I think this is a bug in Windows. Regardless, unless one has to support XP, SystemFileAssociations should be used since it's the only way to safely extend file types that you don't "own". Share this post Link to post
FPiette 382 Posted March 31, 2023 13 hours ago, Anders Melander said: Regardless, unless one has to support XP, SystemFileAssociations should be used since it's the only way to safely extend file types that you don't "own". That's what I do in my property sheet and so far (win11) I don't see any adverse effect. Share this post Link to post
FPiette 382 Posted March 31, 2023 15 hours ago, Remy Lebeau said: Just FYI, I have just now looked back at an old project of mine, which was first written back in the XP days before SystemFileAssociations was introduced. It still registers a custom PropertySheetHandler for a specific file extension under HKCR\<FileExtProgID>\ShellEx\PropertySheetHandlers and HKLM\SOFTWARE\Windows\CurrentVersion\Shell Extensions\Approved, and it still works fine on Win7 and Win10 (I didn't test on Win11). If SystemFileAssociations also works, good for it. My experience (Win11) shows that it DOESN'T WORK for some file extension, specifically the one that was my target (.arw). Share this post Link to post