Search code examples
delphiregistrydelphi-7

How to refresh registry keys without reboot?


I use the following code to change region data in the Registry.

procedure TForm1.Button1Click(Sender: TObject);
var
  reg: TRegistry;
begin
  reg:=TRegistry.Create;
  try
    reg.RootKey:=HKEY_CURRENT_USER;
    reg.OpenKey('\Control Panel\International\',true);
    reg.WriteString('iCountry','1');
    reg.WriteString('iCurrDigits','2');
    reg.WriteString('iCurrency','0');
    reg.WriteString('iDate','1');    
    reg.WriteString('iDigits','2');         
    reg.WriteString('iLZero','0'); 
    reg.WriteString('iMeasure','1');    
    reg.WriteString('iNegCurr','0'); 
    reg.WriteString('iNegNumber','1');    
    reg.WriteString('iTimePrefix','0'); 
    reg.WriteString('iTLZero','1');    
    reg.WriteString('Locale','00000409');        
    reg.WriteString('LocaleName','en-US');    
    reg.WriteString('sCountry','United States');    
    reg.WriteString('sDate','/'); 
    reg.WriteString('sDecimal','.');    
    reg.WriteString('iNegCurr','0');    
    reg.WriteString('sShortDate','dd/MM/yyyy'); reg.CloseKey;
  finally    
    reg.free; 
  end; 
end;

but this requires restarting the machine before the changes take effect. Can it be done without rebooting?


Solution

  • After changing the Registry, broadcast a system-wide WM_SETTINGCHANGE message by calling SendMessageTimeout() with its hWnd set to HWND_BROADCAST:

    Applications should send WM_SETTINGCHANGE to all top-level windows when they make changes to system parameters.

    ...

    wParam
    ... When the system sends this message as a result of a change in locale settings, this parameter is zero.

    When an application sends this message, this parameter must be NULL.

    ...

    lParam
    ... When the system sends this message as a result of a change in locale settings, this parameter points to the string "intl".

    For example:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      reg: TRegistry;
    begin
      reg := TRegistry.Create;
      try
        reg.RootKey := HKEY_CURRENT_USER;
        reg.Access := KEY_SET_VALUE;
        if reg.OpenKey('\Control Panel\International\', true) then
        try
          reg.WriteString('iCountry','1');
          reg.WriteString('iCurrDigits','2');
          reg.WriteString('iCurrency','0');
          reg.WriteString('iDate','1');    
          reg.WriteString('iDigits','2');         
          reg.WriteString('iLZero','0'); 
          reg.WriteString('iMeasure','1');    
          reg.WriteString('iNegCurr','0'); 
          reg.WriteString('iNegNumber','1');    
          reg.WriteString('iTimePrefix','0'); 
          reg.WriteString('iTLZero','1');    
          reg.WriteString('Locale','00000409');        
          reg.WriteString('LocaleName','en-US');    
          reg.WriteString('sCountry','United States');    
          reg.WriteString('sDate','/'); 
          reg.WriteString('sDecimal','.');    
          reg.WriteString('iNegCurr','0');    
          reg.WriteString('sShortDate','dd/MM/yyyy');
        finally
          reg.CloseKey;
          SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, LPARAM(PChar('intl')), SMTO_NORMAL, 100, PDWORD(nil)^);
        end;
      finally    
        reg.free; 
      end; 
    end;
    

    And before you ask, yes it is safe to use nil in the last parameter in this manner:

    Passing nil to a variable parameter

    Prior to XE2, Delphi's Windows unit declares the last parameter of SendMessageTimeout() as:

    var lpdwResult: DWORD
    

    But the Win32 API defines the parameter as:

    _Out_opt_ PDWORD_PTR lpdwResult
    

    Which allows a NULL pointer to be passed in. The above nil trick is the only way for Delphi code to pass a NULL value to a var parameter. The machine code generated by the compiler will be correct - it will simply pass a value of 0 to the parameter, it will not actually try access memory address $00000000.

    In XE2, the Windows unit was changed to declare the last parameter as:

    lpdwResult: PDWORD_PTR
    

    To match the Win32 API definition.

    So, if you ever upgrade your code to XE2 or later, simply replace PDWORD(nil)^ with nil instead. Or, you can account for it now and not worry about it later:

    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, LPARAM(PChar('intl')), SMTO_NORMAL, 100, {$IF RTLVersion >= 23}nil{$ELSE}PDWORD(nil)^{$IFEND});