5

Any idea why TToolbar with TToolButtons would stop displaying button captions after a while? This happens to all toolbars on auto-created forms across the whole application. Toolbars on dynamically created forms work fine even after this problem.

I have only seen this happen on one Windows 7 notebook. No errors are raised when this happens and I can't reproduce the problem on command. The only solution is restarting the application.

TToolbar.ShowCaptions is always True and never changed. This is also visible on the image below because icons are vertically aligned when ShowCaptions is False.

Toolbar with missing captions

A similar problem happened before on a Windows 8 PC. However this time captions were replaced with other text.

Toolbar with weird captions

EDIT:

I was able to reproduce the problem by calling TImageList.Change between 5-10k times. I only have Delphi 2010, so I can't say if this is a Delphi or Windows issue.

Unit:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImgList, ComCtrls, ToolWin, StdCtrls, Gauges;

type
  TImageListHelper = class helper for TImageList
  public
    procedure DoChange;
  end;

  TForm1 = class(TForm)
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    ToolButton3: TToolButton;
    ToolButton4: TToolButton;
    ToolButton5: TToolButton;
    ToolButton6: TToolButton;
    ToolButton7: TToolButton;
    ToolButton8: TToolButton;
    ToolButton9: TToolButton;
    ToolButton10: TToolButton;
    ImageList1: TImageList;
    ProgressBar1: TProgressBar;
    procedure ToolButton1Click(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ToolButton1Click(Sender: TObject);
begin
  repeat
    ImageList1.DoChange;
    ProgressBar1.StepIt;
    Self.Update;
  until ProgressBar1.Position >= ProgressBar1.Max;
end;

procedure TImageListHelper.DoChange;
begin
  Self.Change;
end;

end.

Form:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 66
  ClientWidth = 711
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  PixelsPerInch = 96
  TextHeight = 13
  object ToolBar1: TToolBar
    Left = 0
    Top = 0
    Width = 711
    Height = 41
    ButtonHeight = 36
    ButtonWidth = 71
    Caption = 'ToolBar1'
    Images = ImageList1
    ShowCaptions = True
    TabOrder = 0
    ExplicitWidth = 885
    object ToolButton1: TToolButton
      Left = 0
      Top = 0
      Caption = 'ToolButton1'
      ImageIndex = 0
      OnClick = ToolButton1Click
    end
    object ToolButton2: TToolButton
      Left = 71
      Top = 0
      Caption = 'ToolButton2'
      ImageIndex = 0
    end
    object ToolButton3: TToolButton
      Left = 142
      Top = 0
      Caption = 'ToolButton3'
      ImageIndex = 0
    end
    object ToolButton4: TToolButton
      Left = 213
      Top = 0
      Caption = 'ToolButton4'
      ImageIndex = 0
    end
    object ToolButton5: TToolButton
      Left = 284
      Top = 0
      Caption = 'ToolButton5'
      ImageIndex = 0
    end
    object ToolButton6: TToolButton
      Left = 355
      Top = 0
      Caption = 'ToolButton6'
      ImageIndex = 0
    end
    object ToolButton7: TToolButton
      Left = 426
      Top = 0
      Caption = 'ToolButton7'
      ImageIndex = 0
    end
    object ToolButton8: TToolButton
      Left = 497
      Top = 0
      Caption = 'ToolButton8'
      ImageIndex = 0
    end
    object ToolButton9: TToolButton
      Left = 568
      Top = 0
      Caption = 'ToolButton9'
      ImageIndex = 0
    end
    object ToolButton10: TToolButton
      Left = 639
      Top = 0
      Caption = 'ToolButton10'
      ImageIndex = 0
    end
  end
  object ProgressBar1: TProgressBar
    Left = 0
    Top = 49
    Width = 711
    Height = 17
    Align = alBottom
    Max = 10000
    Step = 1
    TabOrder = 1
    ExplicitTop = 48
  end
  object ImageList1: TImageList
    Left = 8
    Top = 16
    Bitmap = {
      494C010101000500040010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
      0000000000003600000028000000400000001000000001002000000000000010
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000FF000000FF0000000000000000000000FF000000FF000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000FF000000FF0000000000000000000000FF000000FF000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      00000000000000000000000000000000FF000000FF0000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      00000000000000000000000000000000FF000000FF0000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000FF000000FF0000000000000000000000FF000000FF000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000FF000000FF0000000000000000000000FF000000FF000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      FF000000FF000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      0000000000000000000000000000000000000000000000000000000000000000
      000000000000000000000000000000000000424D3E000000000000003E000000
      2800000040000000100000000100010000000000800000000000000000000000
      000000000000000000000000FFFFFF00FFFF000000000000FFFF000000000000
      FFFF000000000000E7E7000000000000E7E7000000000000F99F000000000000
      F99F000000000000FE7F000000000000FE7F000000000000F99F000000000000
      F99F000000000000E7E7000000000000E7E7000000000000FFFF000000000000
      FFFF000000000000FFFF00000000000000000000000000000000000000000000
      000000000000}
  end
end
mwore
  • 476
  • 5
  • 14
  • 1
    Smells like a resource leak of some sort. Try monitoring GDI object counts. – David Heffernan Jun 19 '17 at 14:14
  • Most likely, the captions are changed at runtime. You could define a breakpoint on a toolbutton's `FCaption` attribute or - if it does exist - in `SetCaption` method. – René Hoffmann Jun 19 '17 at 14:14
  • @RenéHoffmann Captions are never changed. Even if they were, this should not affect the whole application. – mwore Jun 19 '17 at 14:29
  • @DavidHeffernan I'll look into that, thanks for the idea. – mwore Jun 19 '17 at 14:42
  • Do you use actions? If you assign an action, then modify the button caption, then make a change to the action, it will override whatever you put for the button caption to be the action's caption instead. – Jerry Dodge Jun 19 '17 at 15:12
  • @JerryDodge I'm not using actions. This could result in the first image but not the second one. – mwore Jun 19 '17 at 15:21
  • If you observe their `Caption` value in debug, does it show the caption or no? – Jerry Dodge Jun 19 '17 at 15:27
  • @JerryDodge Yes, Caption value is correct. I was able to reproduce the bug and have an idea of where to look. Thanks anyway. – mwore Jun 19 '17 at 16:19
  • are you using threads in your application? – whosrdaddy Jun 19 '17 at 18:16
  • @whosrdaddy I am however the bug happens without additional threads. Check updated question for more info. – mwore Jun 19 '17 at 19:24

2 Answers2

4

Given the reproduction in the question, I think that the problem is that the VCL toolbar code removes all the buttons and then recreates them whenever the image list is modified.

I'm looking at Delphi 6 code because I don't have Delphi 2010 immediately to hand, but the code has not materially changed. The pertinent code is in TToolBar.CreateButtons. Towards the bottom of this method we have:

for I := 0 to InternalButtonCount - 1 do Perform(TB_DELETEBUTTON, 0, 0);
UpdateButtons;

The loop removes all the buttons, and then UpdateButtons adds them back. It seems that the underlying control does not appreciate being treated this way. Instead of removing all the buttons we can instead just remove any excess buttons.

var
  Count: Integer;
....
Count := InternalButtonCount;
while Count>FButtons.Count do
begin
  Perform(TB_DELETEBUTTON, Count-1, 0);
  dec(Count);
end;
UpdateButtons;

In your example code, and in the real application, you aren't changing the number of buttons so this version does not even enter the loop.

With this change, your program runs correctly.

You can apply this change in your application by doing the following:

  1. Take a copy of ComCtrls.pas from the source folder of your installation directory, and save it in your project tree.
  2. Add that copied ComCtrls unit to your project.
  3. Make the modifications described above.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • After some more testing this solution causes other problems. Changing button caption at runtime hides captions for all buttons. – mwore Jun 21 '17 at 13:17
  • Not in my tests it doesn't ....... I wonder if you could give me enough detail to reproduce – David Heffernan Jun 21 '17 at 13:20
  • Use test app from question and patched ComCtrls. Replace ToolButton1Click with `ToolButton1.Caption := 'Button'`. On click all I get are icons with no caption on any of the buttons. – mwore Jun 21 '17 at 13:29
  • Not for me. Then again, I'm testing on Delphi 6, perhaps that's the difference. But I doubt it. – David Heffernan Jun 21 '17 at 13:32
  • [Screenshot](http://imgur.com/a/oHQNY) with missing captions. It's not just the captions that are missing, event buttons and toolbar change height. Actually not that surprising as I've always had weird problems with toolbars in Delphi 2010. – mwore Jun 21 '17 at 13:35
  • The call to `UpdateButtons` should populate the internal state of all the buttons, don't know what's going on, sorry – David Heffernan Jun 21 '17 at 13:43
  • Also tested on a clean install of Delphi 2005 and I get the same problem. Thanks anyway. – mwore Jun 21 '17 at 14:40
4

It appears to be a bug in Delphi 2010, in ComCtrls unit at the end of function TToolBar.UpdateItem(). The function starts at line 21476.

In Delphi XE4 (it might have been fixed earlier, I can't check) the following comment and code (which is missing from Delphi 2010) appears at the end of the function:

  // If more than 2^16 strings are TB_ADDSTRING-ed to the tool bar's string
  // pool, the Windows API assumes iString is a pointer to a null terminated
  // string, not an index in the string pool.  Therefore we have to recreate
  // the toolbar to reset the string pool so the strings display propperly.
  if Button.iString >= 65536 then
    RecreateWnd;

Taking a copy of Delphi 2010 ComCtrls.pas to the projects folder and adding the above code, cured the problem that your test creates.

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • 1
    One of the nice things about the solution demonstrated in my answer is that it avoids flooding the string pool in the first place. With the solution described here (well spotted btw) you can see the process private bytes steadily increase while the `ToolButton1Click` loop executes. With my solution that does not happen. It's not a significant amount, but I do think it makes eminent sense to avoid destroying and recreating these buttons for no good reason, Furthermore, avoiding window recreation if possible has to be a good policy. I personally would apply changes from both answers. – David Heffernan Jun 20 '17 at 08:33
  • The time of the answer should not matter. Accept why you feel to be the best answer. – David Heffernan Jun 21 '17 at 04:43