5
votes

I'm using Delphi 5 and I'm creating a number of panels at runtime, then creating buttons on the panels, obviously again at runtime. I need to do it this way because I may need to dynamically create more panel/button combinations in the future.

I can do all of this, but I'm at a loss as to how to reference the panels I've created, because I can't find a way to access the panels' component name. Hunting around the Internet I've found that I can use FindComponent to find the panel components by name, but I still don't know how to use that name, because I can't use a string variable to refer to it - e.g. StringVar := Panel.Name. I get a type mismatch, TComponentName versus String.

I created the buttons for each panel as I created the panels. Simplified, it looks like this:

   With TypeQuery do begin // Create Panels
   First;
   While (not eof) do begin        // create the actual panel
      panelno := FieldByName('Product_type_id').AsInteger;
      pnl := Tpanel.Create(Self);
      pnl.name := FieldByName('PanelName').AsString;
      pnl.color := clInactiveCaption;
      pnl.parent := MainForm;
      pnl.width := 365;
      pnl.Height := 551;
      pnl.left := 434
      pnl.top := 122;
      pnl.caption := '';
      With ButtonQuery do begin
         Close;
         Parameters.parambyname('PanelID').Value := PanelNo;
         Open;
         First;
         While (not eof) and (FieldByName('Product_type_id').AsInteger = PanelNo) do begin    //put the buttons on it.
            btnName := FieldByName('ButtonName').AsString;
            BtnText := FieldByName('ButtonText').AsString;
            BtnGroup := FieldByName('Product_Group_ID').AsString;
            GrpColour := FieldByName('ButtonColour').AsString;
            btn := TColorButton.Create(Self);
            btn.Parent := pnl;
            btn.Name := BtnName;
            Btn.backcolor := HexToTColor(GrpColour);
            btn.Font.Name := 'Arial Narrow';
            btn.Font.Style := [fsBold];
            btn.Font.Size := 10;
            . . .
        end;
        . . .
     end; 
  end;

I've read on several forums (including this one) that there is no way to reference the panels by name directly. I've tried using a component array, but I strike the same problem - I need to refer to the component by its assigned component name.

Okay, I'm not a gun programmer - I've used Delphi for years to create simple programs, but this one is a lot more complex. I've never worked with runtime component creation before.

Can I use FindComponent to make the panels visible or invisible? If so, given what I have shown you above, can you give me the approach I should take in baby-steps?

Thanks in advance ...

4
You can add the components you need references to a TList|Container... and then, use your list|container to access them...Whiler

4 Answers

12
votes

I'm not sure what you mean by: "I can't use a string variable to refer to it - e.g. StringVar := Panel.Name."

Try this:

procedure TForm1.FormCreate(Sender: TObject);
var
  p: TPanel;
begin
  p := TPanel.Create(Self); // create a TPanel at run-time
  p.Name := 'MyPanel'; // set a unique name
  p.Parent := Self;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  p: TPanel;
  StringVar: string;
begin
  p := FindComponent('MyPanel') as TPanel;
  if Assigned(p) then // p has reference to MyPanel
  begin
    // use that reference
    p.Caption := 'Foo';
    StringVar := p.Name;
    ShowMessage(StringVar);
  end;
end;

Or did I miss anything?

6
votes

You're conflating the component name with the variable name. The Delphi IDE strives to keep those two the same, for IDE-created components, but they're not necessarily equal. You don't have variable names because you're creating the components dynamically, and you don't know how many variables you'd need. However, you still have control over the component names: Simply assign the Name property of the component, and then you can use it the same way you use any other component names, by calling FindComponent. Just make sure the names are unique for each panel instance.

Remember also that the way to deal with variables, when you don't know at compile time how many you'll need, is to use arrays or lists. You can use plain old arrays, or you can use a more sophisticated data structure like TComponentList or TDictionary.

Finally, to make it easier to refer to the controls on the panels you're creating, you can dispense with the panels and use frames instead. You can visually design a TFrame in the IDE and give names to the buttons, and at run time, you can instantiate the frame class, and it will automatically create all the buttons for you, just like when you instantiate a form or data module. You only need to give a name to the new frame object, but that object will already have named fields referring to the buttons.

2
votes

There should be no need to create a secondary list of items you created to display on a tForm instance.

AFAIK, whenever you create a new panel that uses a Form or an other Container in place of self

  pnl := Tpanel.Create(Self);

you don't need to take care of destroying then new pnl, because it is handled by the containing "self" component.

This means that there should be any construct that will hold the child components of the parent component.

I expect that you will find, either a ComponentCount, or Components list or a FindComponent method in the parent object. Assuming that the "Self" referenced object is a Tform.

 for i := 0 to tForm(self).ComponentCount -1 do 
   if tForm(self).Components[i] is tPanel then 
      tPanel(tForm(self).Components[i]).Caption := intToStr(i) ;

will change all Captions of tPanels in your application. In order to distinguish between Panels created by code and designer you can use the TAG property of each created tPanel and use it in the above example.

0
votes

You can add the components you need references to a TList|Container... and then, use your list|container to access them

var
  slPanels: TStringList;
...


 With TypeQuery do begin // Create Panels
   First;
   While (not eof) do begin        // create the actual panel
      panelno := FieldByName('Product_type_id').AsInteger;
      pnl := Tpanel.Create(Self);
      pnl.name := FieldByName('PanelName').AsString;
      slPanels.AddObject(FieldByName('PanelName').AsString, pnl);

when you need it:

TPanel(slPanels.Objects[slPanels.IndexOf(FieldByName('PanelName').AsString)]) ...

I dislike the code above (there are better container... but this should do the job :o))