views:

87

answers:

3

When Delphi (2006) goes quantum: I've got "something" that appears to be both a TToolBar and a TPanel, depending on how you observe it. I'd like to understand what's going on.

Here is how to create it and what happens:

  1. in the DFM

    • add a TToolBar named bar;
    • in that TToolBar, put a TPanel.
  2. in the code and at runtime:

    • the panel appears in the list of buttons bar.Buttons[], let's say at index i
    • bar.Buttons[i], from the compiler point of view, is a TToolButton
    • bar.Buttons[i].ClassName = 'TPanel'
    • (bar.Buttons[i] is TToolButton) = true, but that's the compiler optimising the call to 'is' out;
    • indeed IsBarButton(bar.Buttons[i]) is false for function IsBarButton (defined below);
    • bar.Buttons[i].Name is the name I gave the TPanel in the DFM
    • inspecting the value bar.Buttons[i] in the debugging:
      • it has a property 'Caption' the real TToolButton's don't have
      • strangely, it has all properties TToolButton's have, like TToolButton.Indeterminate (=true).

IsToolButton:

function IsToolButton(X : TObject) : boolean;
begin
    Result := X is TToolButton;
end;

So bar.Buttons[i] both is and is not a TToolButton... what's up ?

(Bottom story is I'd like to distinguish my TPanel from the genuine TToolButton's. This I can do in more or less hackish ways. My goal by asking this question here, is to get a fuller understanding of what's really happening here.)

Question: what is happening ? Sub-question: is it legitimate to add a TPanel to a TToolBar ?

A: 

When you put a couple of buttons and a panel on a toolbar, and a Memo somewhere, then run this code in the form's onCreate:

procedure TForm1.FormCreate(Sender: TObject);
  function _IsToolButton(const aObject: TObject): Boolean;
  begin
    Result := aObject is TToolButton;
  end;

  function _IsPanel(const aObject: TObject): Boolean;
  begin
    Result := aObject is TPanel;
  end;
var
  i: Integer;
begin
  for i := 0 to bar.ButtonCount - 1 do begin
    Memo.Lines.Add(Format('bar.Buttons[%d].Name: %s', [i, bar.Buttons[i].Name]));
    Memo.Lines.Add(Format('bar.Buttons[%d].ClassName: %s', [i, bar.Buttons[i].ClassName]));
    Memo.Lines.Add(Format('bar.Buttons[%d] is TToolButton: %s', [i, BoolToStr(_IsToolButton(bar.Buttons[i]), True)]));
    Memo.Lines.Add(Format('bar.Buttons[%d] is TPanel: %s', [i, BoolToStr(_IsPanel(bar.Buttons[i]), True)]));
//    Memo.Lines.Add(Format('bar.Buttons[%d] has Caption property: %s', [i, 'dunno yet']));
    Memo.Lines.Add('');
  end;
end;

you'll see that the panel is not a TooButton and most definitely a TPanel.

The debugger showing properties of a ToolButton for the panel, is simply the debugger casting each and every bar.Buttons[i] to a TToolButton. When you right-click on the "Data" tab of the Debug inspector, you can Type Cast it to a TPanel and you will get the correct information.

Marjan Venema
OK thanks. There were strange things going on, for instance the 'Caption' property was shown for the TPanel but not for the next real TToolButton, where it was replaced with (a duplicate of) the 'OnStartDrag' property. Guess as Vegar wrote, this TPanel is going to bring me some trouble... The fact that the IDE blindly and (seemingly) happily casts it to a TToolButton already gives me some shivers.
As it should :-) like the others (and I neglected to) point out...
Marjan Venema
A: 

'is it legitimate?' - well, you are definitely using the toolbar in a way that the creator of the toolbar did not ment it to be used. Will it blow up in your face? Who knows. I guess you could walk through the sourcecode for the toolbar and check if it is safe or not, but what about possible third party tools or components, expecting to find buttons in a toolbar?

I would see if I could find another way of solving my problem. Clever hacks have a tendency to turn out not so clever after all, and it will surely higten the wtf-rate of your code.

Do you have to use a toolbar? What about a flowpanel with buttons and panels instead? Or a panel with a toolbar and a panel?

Vegar
I inherited this code and this appearance of a TPanel indeed brought me some wtfs. My TPanel acts as a separator between buttons in the toolbar (so they fit the parent client width with buttons on the left and buttons on the right). I guess 2 toolbars separated by a panel may do the trick. Or maybe a flowpanel (never heard of such a thing, will look up). Thanks.
Clever hacks that subvert the type system are not that clever anyway IMO. What's strange is that the IDE lets me add a TPanel to a TToolBar. You'd think they would prevent such things at the source.
FlowPanel came, together with gridPanel, in Delphi 2007. I like the FlowPanel, and the idea behind the GridPanel seems nice. The GridPanel sucks big time, though, and is near impossible to work with...
Vegar
Eventually I got rid of the TPanel, going for 2 TToolBar one alRight and one alClient, playing with EdgeBorder to give the appearance of a single continuous toolbar. Not because of the question I asked here which was no big deal, but because the way I recomputed the size of the Panel on parent resizing interacted badly with the VCL so it was not reliable. The 2-toolbar solution is simpler (doesn't try to replicate VCL's work => less code and mostly implemented through the form editor), more VCL-friendly (per previous remark), and works like a charm.
+2  A: 

The only thing the OS allows to be added to a tool bar is a tool button. To add anything else, you technically need to create a button and then put your other things on top of it. The button that gets added is literally a placeholder. It's there to take up space so the next thing you add gets positioned properly.

You can see this sometimes if the non-tool-button control you add is transparent. Then you can see the tool bar's separator underneath, so it looks like there's a vertical line running through the middle of your control.

When you add a non-tool-button control to the tool bar, the Buttons property indeed lies about the type of the control. You'll notice throughout ComCtrls.pas that TToolBar itself always casts the buttons to TControl and then checks whether they really descend from TToolButton. It's completely legitimate to add non-buttons to a tool bar; that's why the Form Designer allows it in the first place.

I suggest you use the Form Designer to create your tool bar. That way, the IDE will maintain an identifier for you in your form, so you'll always have a direct reference to your panel. You won't have to go hunting for it in the tool bar. Even if you're creating the tool bar manually, it's a good idea to make an extra field to refer to the panel. Even if you move the panel around within the tool bar, it will still be the same object the whole time, so you needn't worry about dangling references.

Rob Kennedy