9

I have a control derived from TMemo. It worked nice until I used for the first time with Delphi XE7 VCL Styles. Under Delphi XE7, styles are not applied to the scroll bars of the control. If dark theme/style is used, it looks horrible, while the scroll bars are silver.

bug

Trying to create a minimum project for which we can reproduce the bug I have discovered something really interesting: Adding/deleting random lines of code (or DFM controls), will make the bug appear/disappear.

Question: What REALLY causes this weird behavior and how to fix it?

Source code here:

http://s000.tinyupload.com/index.php?file_id=24129853712119260018

Ken White
  • 123,280
  • 14
  • 225
  • 444
Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • As I understand `WMSetText` is a message handler. Code `Message.Text := PChar(s);` pass the message with pointer to var in the stack which not available outside this procedure... – Abelisto Feb 11 '15 at 20:42
  • 1
    @Altar, try registering the `TMemoStyleHook` style hook for your custom Memo control like so `TStyleManager.Engine.RegisterStyleHook(TMyMemo, TMemoStyleHook);` – RRUZ Feb 11 '15 at 23:17
  • Your problem is your message handler is 'public'. Make it 'protected'. The other message handlers too. – Sertac Akyuz Feb 12 '15 at 00:31
  • Well, that's not your problem really, but it fixes the case here. – Sertac Akyuz Feb 12 '15 at 00:51
  • BTW, your post is informative, but it doesn't ask any question. Hence any random interpretation can qualify as an answer. Like remove the second panel and your are good to go!! – Sertac Akyuz Feb 12 '15 at 01:03
  • I just deleted my answer, because I don't check your sample code from the posted link before (my bad). Even if my answer is working for your case this not explain the weird behavior of your app. To find an explanation is needed more time, I will try tomorrow. – RRUZ Feb 12 '15 at 02:38
  • @SertacAkyuz-"Like remove the second panel" that is not the solution also. If I remove the panel the 'bug' disappear indeed but if I put something else back, the bug re-appears. The same if I delete any other controls. – Gabriel Feb 12 '15 at 10:37
  • @SertacAkyuz - "Make it protected" - That's another weird behavior of this bug. Moving code around (not necessary only the WMSetText) will make the bug appear/disappear. I made the bug disappear/hide by changing other lines of code also - code that was never executed, so that could not cause the bug. This makes me think the bug is related to the way the code is 'laid' in the compiled file/memory. – Gabriel Feb 12 '15 at 10:38
  • Your repro isn't perfect. You can remove all the code from `TMyMemo` and still see the fault. I'm making a better repro now. – David Heffernan Feb 12 '15 at 11:07
  • Well, I have a better repro but it's not very illuminating. This is just massively weird. – David Heffernan Feb 12 '15 at 11:20
  • @David - Additionally repros do not seem to be deterministic. I couldn't reproduce with the case in the deleted answer unless I dropped a combo. But then I could remove the entire subclass. – Sertac Akyuz Feb 12 '15 at 11:25
  • @SertacAkyuz It's really really weird isn't it. – David Heffernan Feb 12 '15 at 11:31
  • "You can remove all the code from TMyMemo and still see the fault" - In my computer, no. The code I uploaded is the MINIMUM code that still shows the bug. Deleting anything else, any single line, even USES units (from PAS or DFM) will make the bug go away (or rather hide). So, let's say it one more time: weird. – Gabriel Feb 12 '15 at 11:43
  • I have seen a similar problem in other projects on mine: the edges (the few pixel lines that make the control look 3D) of some controls are also not correctly painted. So, the bug appears not only for this project. I though is something wrong with my code and I simply didn't used Styles until I had more time to dig into it. – Gabriel Feb 12 '15 at 11:52
  • I think VCL Styles make apps look lame, as well as being the most bug ridden part of the VCL. I like to let Win32 paint my controls! – David Heffernan Feb 12 '15 at 12:49
  • 1
    Another idea (but we need David's repro project) - since the project contains only Embarcadero code (no custom libraries), why don't we call this a Delphi bug? (and report it). – Gabriel Feb 12 '15 at 18:13
  • 2
    Reported as https://quality.embarcadero.com/browse/RSP-10066 – Dalija Prasnikar Feb 12 '15 at 20:20
  • @DalijaPrasnikar - +1 for posting the bug to Embarcadero. But I cannot reproduce it just with: TYPE TMyMemo = class(TMemo) end. I hope they will be able to see the bug. – Gabriel Feb 13 '15 at 10:58
  • Now I cannot reproduce it either in blank project :( I will create test project that exhibits issue and will upload it to QP. – Dalija Prasnikar Feb 13 '15 at 11:15
  • Test project uploaded – Dalija Prasnikar Feb 13 '15 at 13:40
  • Great. Note to all: There is a 'vote for this issue' option at the top of the page. – Gabriel Feb 13 '15 at 17:21

1 Answers1

7

Registering StyleHook for custom class solves issue:

  TMyMemo = class(TMemo)
  strict private
    class constructor Create;
    class destructor Destroy;
  end;

class constructor TMyMemo.Create;
begin
  TCustomStyleEngine.RegisterStyleHook(TMyMemo, TMemoStyleHook);
end;

class destructor TMyMemo.Destroy;
begin
  TCustomStyleEngine.UnRegisterStyleHook(TMyMemo, TMemoStyleHook);
end;

There is bug in TStyleEngine.HandleMessage function, specifically in part that tries to find appropriate StyleHook class to handle messages

if RegisteredStyleHooks.ContainsKey(Control.ClassType) then
  // The easy way: The class is registered
  LStyleHook := CreateStyleHook(RegisteredStyleHooks[Control.ClassType])
else
begin
  // The hard way: An ancestor is registered
  for LItem in RegisteredStyleHooks do
    if Control.InheritsFrom(LItem.Key) then
    begin
      LStyleHook := CreateStyleHook(Litem.Value);
      Break;
    end;

If StyleHook is registered for exact class then there is no problem, and appropriate StyleHook class will be returned. However, "the hard way" part is flawed. It will try to find class ancestor that has registered StyleHook. But it will return first ancestor it comes across. If it finds TEditStyleHook first (that is registered for TCustomEdit class), it will use that one instead of TMemoStyleHook. Since TEditStyleHook does not know how to handle scrollbars issue appears.

Randomness in buggy behavior is due the way RegisteredStyleHooks are stored. They are stored in dictionary where key is TClass. And order is determined by TClass hash that is basically pointer to class info and can change as you change code.

Issue is reported as RSP-10066 and there is attached project that reproduces it.

With help of following code it is easy to see how order of registered classes changes as you add/remove code and/or other controls.

type
  TStyleHelper = class(TCustomStyleEngine)
  public
    class function GetClasses: TArray<TClass>;
  end;

class function TStyleHelper.GetClasses: TArray<TClass>;
begin
  Result := Self.RegisteredStyleHooks.Keys.ToArray;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LItem: TClass;
  Classes: TArray<TClass>;
begin
  Classes := TStyleHelper.GetClasses;
  for LItem in Classes do
    MyMemo1.Lines.Add(LItem.ClassName);
end;
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • @DalijaPrasnikar No, this isn't it. You can see the bug when `TMyMemo` is declared like this: `TMyMemo = class(TMemo) end;` – David Heffernan Feb 12 '15 at 11:19
  • There is something weird going on. This answer doesn't really scrape the surface. – David Heffernan Feb 12 '15 at 11:32
  • You should delete the answer. It really serves no purpose. About a gazillion changes can make the problem go away. I removed ExtDlgs and it went. Who knows what that means. Any answer to this question starts by explaining why the fault occurs. – David Heffernan Feb 12 '15 at 11:44
  • I have added explanation where bug appears and why – Dalija Prasnikar Feb 12 '15 at 20:09
  • 1
    I don't believe this explains it at all. – David Heffernan Feb 12 '15 at 20:16
  • This looks like it accounts for component randomness. However, I wonder how does it fit with the appearance of the bug due to introduction of a message handler. Or even modification of that message handler's visibility scope. – Sertac Akyuz Feb 12 '15 at 21:33
  • @SertacAkyuz I don't have answer to that part. Digging that could be more time consuming than I can afford. – Dalija Prasnikar Feb 12 '15 at 21:35
  • 1
    Fair enough. +1 in any case for locating a bug which may or may not be the sole cause of the issue. – Sertac Akyuz Feb 12 '15 at 21:45
  • 2
    @SertacAkyuz I found out why adding/removing code changes behavior. Registered StyleHooks are stored in dictionary where key is TClass. And order is determined by TClass hash that is basically pointer to class info and can change as you change code. – Dalija Prasnikar Feb 13 '15 at 13:42
  • @DavidHeffernan My latest comment to Sertac explains randomness in buggy behavior. – Dalija Prasnikar Feb 13 '15 at 13:46
  • 3
    That should be in the answer. The problem then is `for LItem in RegisteredStyleHooks` iterates in an ill-defined order. Well done. – David Heffernan Feb 13 '15 at 13:52
  • According to http://qc.embarcadero.com/wc/qcmain.aspx?d=104035 and http://qc.embarcadero.com/wc/qcmain.aspx?d=111029 this issue has been fixed twice before. I wonder what is going on. – LU RD Feb 13 '15 at 16:23
  • @LURD - My guess is they never got to the bottom of the randomness factor. Rodrigo comments to one of the reports that XE2 update 3 is fine. I duplicated, with a little effort, with XE2 update 4. Dalija did a good job here, maybe this leads to a final fix. – Sertac Akyuz Feb 13 '15 at 18:27
  • 1
    @SertacAkyuz, but the analysis by Olivier Sannier is right on spot: "Well, when the code inside TStyleEngine.HandleMessage tries to find the hook for an instance of TMyForm, it will find the one for TCustomForm before the one for TBaseForm, with the consequences that one can imagine. Using a dictionary was a good idea for the "easy way", but clearly, there should be a list ordered by inheritance for the "hard way"." – LU RD Feb 13 '15 at 20:25
  • I don't understand how do I apply this bug fix: if my custom class name is TMyMemo, I create a helper class with named MyMemo2 and register this class? RegisterComponents('Mine', [TMyMemo2]) OR I put the RegisterStyleHook/UnRegisterStyleHook call directly into the Create/Destroy of TMyMemo? If I do so, I get: "TMemoStyleHook is already registered for TMyMemo" – Gabriel Oct 12 '15 at 14:41
  • @DalijaPrasnikar-Seems to work if I call only the UnRegisterStyleHook in TMyMemo.Destroy (and skip the RegisterStyleHook part in Constructor) – Gabriel Oct 12 '15 at 14:53
  • I don't know what you mean by helper class. Anyway, it is important that you put hook register/unregister code in class constructors and destructors, and that you use exact class name. For instance if you have `TMyMemo2` then call `TCustomStyleEngine.RegisterStyleHook(TMyMemo2, TMemoStyleHook);` – Dalija Prasnikar Oct 12 '15 at 16:22
  • If that means anything to you, issue has been resolved in XE8. You can also fix bug patching Vcl.Styles TStyleEngine method as shown in comments in QP. – Dalija Prasnikar Oct 12 '15 at 16:25