c++c++buildervcl-stylesc++builder-10.2-tokyo

How to unregister style using TStyleManager::UnRegisterStyle()


I want to unregister a specific style using this code:

void __fastcall TfrmMain::btnUnregStyleClick(TObject *Sender)
{
    TCustomStyleServices *MyStyle;

    // Get wanted style
    MyStyle = TStyleManager::Style["Emerald"];   // this will return a TCustomStyleServices obj

    if (MyStyle != NULL)
    {
        // Remove it
        TStyleManager::UnRegisterStyle(MyStyle);   // This will set default Windows style (no style)
    }
}

It works. The style seems to be unregistered, and the GUI switches automatically to the default Windows style.

But when program shuts down, I get this error:

Project Project.exe raised exception class $C0000005 with message 'access violation at 0x5005fd50: read of address 0xffffffd0'.

Here is the Call stack:

:5005fd50 rtl250.@System@TObject@InheritsFrom$qqrp17System@TMetaClass + 0x8
:50d12a8d vcl250.@Vcl@Styles@TStyleEngine@Notification$qqr54Vcl@Themes@TCustomStyleEngine@TStyleEngineNotificationpv + 0x1d
:00e5a612 vclwinx250.@Vcl@Winxctrls@TSearchBox@$bcdtr$qqrv + 0x1e
:0041fa0f __cleanup + 0x1F
:0041fb92 ; __wstartup

[Update: this crash is fixed if I remove TeeChart from my form. But UnRegisterStyle() still won't work]


If after UnRegisterStyle() I call:

TStyleManager::LoadFromFile(usStylePath);
TStyleManager::SetStyle("Emerald");

it will tell me that the "emerald style is already registered".

So, obviously UnRegisterStyle() fails.

Getting the "styles" list via TStyleManager::StyleNames() shows that the list remains unchanged after UnRegisterStyle().

Embarcadero has no help on this function. Should I call something else, additionally to UnRegisterStyle()?


Solution

  • Answer has been updated! Again.

    Well, I am not very experienced user of VCL's styles but I will try to explain what to do to avoid your problem. Before you will read further I should tell you: there is no way to unregister any style.

    Style or not Style

    First, you must know that VCL's styles uses internal FRegisteredStyles dictionary to store all registered styles. Style became registered after TStyleManager.LoadFromFile(YourStyleName) has been called. Unfortunately, style never gets removed from dictionary.

    Do not use UnregisterStyle procedure. It doesn't unregister style at all. Simply removes specified style from the list of available styles. Thus, after you call UnregisterStyle(YourStyle) you just wipe out YourStyle from internal list, not from dictionary mentioned earlier and cannot set this style.

    After successful call to UnregisterStyle() you may call LoadFromFile() method and wonder why applicaition said:

    Style 'Emerald' already registered.

    This happens because LoadFromFile() method loads specified style and checks its existing in internal dictionary of registered styles. This checking always returns true as UnregisterStyle() procedure doesn't delete specified style from dictionary.

    The same thing related also with property StyleNames. This property returns name of style uses internal FRegisteredStyles dictionary to obtain name of style with specified index.

    If you want to know my opinion, such behaviour of style is weird. Method UnregisterStyle() should also delete specified style from dictionary. Maybe I am wrong but this is a real bug. On the other hand, there is no bug - you are trying to use undocumented method. The truth is somewhere near.

    Styled Result

    As conclusion, I would recommend you to use direct access to styles to decide can you use the selected style or not. See the code below (assuming you have styling of application turned on):

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      if Assigned(TStyleManager.Style['Emerald']) and TStyleManager.Style['Emerald'].Available then
        // Do your code
      else
        ShowMessage('Specified style is not available!');
    end;  
    

    If you have sources of VCL.Themes you can easily check the plausibility of what I wrote here.

    High Technology

    I have created class-helper for TStyleManager that allows to check if style-file registered and unregister it if needed. In order to fill TStyleInfo record with real values from style-file, we need save specified style to TMemoryStream and then pass stream to IsValidStyle function. Also we could use physical path to style (f.e. C:\Embarcadero\Delphi\Styles\Emerald.vsf) instead of variant with stream, but latter looks more elegant.

    Unfortunately, according to Remy's comment (I don't know how to make a link to it), C++ Builder (which is used by OP) doesn't support class-helper feature. The only solution is to create simple unit which contains all code needed for class-helper. To work with it is enough to add such unit to uses clause and call appropriate public procedures\functions.

    This unit have the following structure:

    unit StyleManager_CH;
    
    interface
    
    uses
      VCL.Themes;
    
    
      function IsStyleRegistered_CH(var AStyle: TCustomStyleServices): Boolean;
      procedure UnregisterStyleEx_CH(var AStyle: TCustomStyleServices);
    
    
    implementation
    
    uses
      System.SysUtils, System.Classes, System.Generics.Collections;
    
    
    type
      TStyleManagerHelper = class helper for TStyleManager
      public
        class function IsStyleRegistered(var AStyle: TCustomStyleServices): Boolean;
        class procedure UnregisterStyleEx(var AStyle: TCustomStyleServices);
      end;
    
    
    class function TStyleManagerHelper.IsStyleRegistered(var
      AStyle: TCustomStyleServices): Boolean;
    begin
      Result := Assigned(AStyle) and
                TStyleManager.FRegisteredStyles.ContainsKey(AStyle.Name);
    end;
    
    class procedure TStyleManagerHelper.UnregisterStyleEx(var
      AStyle: TCustomStyleServices);
    var
      MS: TMemoryStream;
      StyleInfo: TStyleInfo;
      SourceInfo: VCL.Themes.TStyleManager.TSourceInfo;
    begin
      if Assigned(AStyle) then
        begin
          MS := TMemoryStream.Create;
          try
            AStyle.SaveToStream(MS);
            MS.Position := 0;
            if AStyle.IsValidStyle(MS, StyleInfo) then
              begin
                if TStyleManager.FRegisteredStyles.ContainsKey(StyleInfo.Name) then
                  begin
                    SourceInfo := TStyleManager.FRegisteredStyles.Items[StyleInfo.Name];
                    if Assigned(SourceInfo.Data) then
                      FreeAndNil(SourceInfo.Data);
    
                    TStyleManager.FStyles.Remove(AStyle);
                    TStyleManager.FRegisteredStyles.Remove(StyleInfo.Name);
                    FreeAndNil(AStyle);
                  end;
              end;
          finally
            MS.Free;
          end;
        end;
    end;
    
    function IsStyleRegistered_CH(var AStyle: TCustomStyleServices): Boolean;
    begin
      Result := TStyleManager.IsStyleRegistered(AStyle);
    end;
    
    procedure UnregisterStyleEx_CH(var AStyle: TCustomStyleServices);
    begin
      TStyleManager.UnregisterStyleEx(AStyle);
    end;
    
    
    end.
    

    Addendum _CH stands for class-helper abbreviation. There were also fixed some memory leaks.

    I am not sure if there is another way to reload style-file "on-the-fly".