I have numerous procedures which dynamically create a TButton when the user clicks a button. The following code is an example of this:
procedure TForm1.Button2Click(Sender: TObject);
begin
if not Assigned(FSeatButton) then begin
FSeatButton := TButton.Create(self);
FSeatButton.Parent := self;
FSeatButton.Left := 100;
FSeatButton.Top := 100;
FSeatButton.Width := 62;
FSeatButton.Height := 25;
FSeatButton.Caption := ('Seat');
FSeatButton.OnMouseDown := ButtonMouseDown;
FSeatButton.OnMouseMove := ButtonMouseMove;
FSeatButton.OnMouseUp := ButtonMouseUp;
end;
end;
This creates a Tbutton which the user can then drag around through the bottom 3 procedures. I need this procedure to repeat every time a user clicks button2 but if I use a for/repeat loop id have to specify when to end it, but i don't know how many Buttons the user will need to generate.
Also (This could be an idea for another question), how would i save each button created with its own ID as such E.G Button1, Button2... ButtonN. I'm guessing I'd need some sort of variable that increases every time the user clicks the button and it is somehow included in the name E.G ButtonI
It sounds like you just need to keep track of all the buttons that have been added. Use a container to do so:
In the type declaration add a container:
uses
System.Generics.Collections;
....
FButtons: TList<TButton>;
Instantiate it in the form's constructor, and destroy it in the destructor. Or use the OnCreate and OnDestroy events if you prefer.
Then when you create the button, add it to the list:
procedure TForm1.Button2Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(self);
Button.Parent := Self;
Button.Left := 100;
// etc.
FButtons.Add(Button);
end;
If you don't need to refer to the buttons after creating them then you don't need the list and you can just do this:
procedure TForm1.Button2Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(self);
Button.Parent := Self;
Button.Left := 100;
end;
Declare FSeatButton : array of TButton;
That way you have an unique instance of the added buttons and can name them in consecutive order.
Every time Button2 is clicked add a new button to the array:
procedure TForm1.Button2Click(Sender: TObject);
var
len: Integer;
begin
len := Length(FSeatButton);
SetLength(FSeatButton,len+1);
FSeatButton[len] := TButton.Create(self);
FSeatButton[len].Name := 'SeatButton'+IntToStr(len);
etc...
end;
Related
I have a program with n ComboBoxes and n Labels and I want to update the corresponding Label depending on the selection from the adjacent ComboBox i.e ComboBox2 would update Label2.
I am using the same event handler for every ComboBox and currently checking if Combobox1 or Combobox2 has fired the event handler. Is there a way to use the ItemIndex of the ComboBox passed to the procedure, such as Sender.ItemIndex? This is not currently an option and gives the error 'TObject' does not contain a member named 'ItemIndex'.
procedure TForm2.ComboBoxChange(Sender: TObject);
begin
if Sender = ComboBox1 then
Label1.Caption := ComboBox1.Items.Strings[ComboBox1.ItemIndex]
else
Label2.Caption := ComboBox2.Items.Strings[ComboBox2.ItemIndex];
end;
This code has the desired behavior but is obviously not scale-able.
Every component has a Tag property inherited from TComponent, where the Tag is a pointer-sized integer. As such, you can store each TLabel pointer directly in the corresponding TComboBox.Tag, eg:
procedure TForm2.FormCreate(Sender: TObject);
begin
ComboBox1.Tag := NativeInt(Label1);
ComboBox2.Tag := NativeInt(Label2);
end;
This way, ComboBoxChange() can then directly access the TLabel of the changed TComboBox, eg:
procedure TForm2.ComboBoxChange(Sender: TObject);
var
CB: TComboBox;
begin
CB := TComboBox(Sender);
if CB.Tag <> 0 then
TLabel(CB.Tag).Caption := CB.Items.Strings[CB.ItemIndex];
end;
Option 1
This is the most robust one.
Let your form have private members
private
FControlPairs: TArray<TPair<TComboBox, TLabel>>;
procedure InitControlPairs;
and call InitControlPairs when the form is created (either in its constructor, or in its OnCreate handler):
procedure TForm1.InitControlPairs;
begin
FControlPairs :=
[
TPair<TComboBox, TLabel>.Create(ComboBox1, Label1),
TPair<TComboBox, TLabel>.Create(ComboBox2, Label2),
TPair<TComboBox, TLabel>.Create(ComboBox3, Label3)
]
end;
You need to add the controls to this array manually. That's the downside of this approach. But you only need to do this once, right here. Then everything else can be done automagically.
Now, this is where it gets really nice: Let all your comboboxes share this OnChange handler:
procedure TForm1.ComboBoxChanged(Sender: TObject);
var
i: Integer;
begin
for i := 0 to High(FControlPairs) do
if FControlPairs[i].Key = Sender then
FControlPairs[i].Value.Caption := FControlPairs[i].Key.Text;
end;
Option 2
Forget about any private fields. Now instead make sure that each pair has a unique Tag. So the first combo box and label both have Tag = 1, the second pair has Tag = 2, and so on. Then you can do simply
procedure TForm1.ComboBoxChanged(Sender: TObject);
var
TargetTag: Integer;
CB: TComboBox;
i: Integer;
begin
if Sender is TComboBox then
begin
CB := TComboBox(Sender);
TargetTag := CB.Tag;
for i := 0 to ControlCount - 1 do
if (Controls[i].Tag = TargetTag) and (Controls[i] is TLabel) then
begin
TLabel(Controls[i]).Caption := CB.Text;
Break;
end;
end;
end;
as the shared combo-box event handler. The downside here is that you must be sure that you control the Tag properties of all your controls on the form (at least with the same parent as your labels). Also, they must all have the same parent control.
procedure TForm1.Button2Click(Sender: TObject);
var
Button: TButton;
Example : String;
begin
if {Example = ''} InputQuery('Put a question/request here', Example) then
Repeat
InputQuery('Put a question/request here', Example);
if InputQuery = False then
Abort
else
Until Example <> ''; //or InputBox.
Button := TButton.Create(self);
Button.Parent := self;
//Button properties go here
Button.Caption := (Example);
//Any procedures can go here
end;
This procedure continues after the repeat loop even if the user presses cancel. I've tried using the GoTo function using the CancelCreateButton label if InputQuery = False but I just get errors(so i removed some of the code).
How can i make it so that if the user clicks cancel on the inputquery it cancels the procedure and doesn't create a button?
If the Cancel button of the input query form is pressed, then the call to InputQuery returns False. In that scenario you should call Abort, the silent exception, to skip the rest of the event handler.
if not InputQuery(...) then
Abort;
If you want to perform validation in a loop then that would look like this:
repeat
if not InputQuery(..., Name) then
Abort;
until NameIsValid(Name);
You can also use the Exit function
You have too many calls to InputQuery, three when you only need one; it is better to capture the result of InputQuery to a Boolean variable and use that for execution flow control. Try something like this instead:
procedure TForm1.Button2Click(Sender: TObject);
var
FSeatButton: TButton;
Name : String;
InputOK : Boolean;
Label
CancelCreateButton;
begin
InputOK := InputQuery('Enter Students Name', 'Name', Name);
// You can add check's on the validity of the student's name
// (e.g, is it a duplicate) here and update the value of InputOK
// (to False) if you don't want the button creation to go ahead
if InputOK then begin
FSeatButton := TButton.Create(self);
FSeatButton.Parent := self;
FSeatButton.Left := 100;
FSeatButton.Top := 100;
FSeatButton.Width := 59;
FSeatButton.Height := 25;
FSeatButton.Caption := (Name);
FSeatButton.OnMouseDown := ButtonMouseDown;
FSeatButton.OnMouseMove := ButtonMouseMove;
FSeatButton.OnMouseUp := ButtonMouseUp;
end;
end;
Assuming that does what you intend for a single student, you can reinstate the repeat ... until to get the behaviour you need.
If I create multiple TButton objects with this routine:
procedure CreateButton;
begin
Btn := TButton.Create(nil);
end;
Then, how can I refer to a specific object instance to free it using another method like:
procedure FreeButton;
begin
Btn[0].Free; //???
end;
Of course, this does not compile, but I think the question is clear: How do I declare Btn? And how do I free multiple instances?
It doesn't make much sense to create a TButton anywhere that isn't part of a form (which your code does).
With that being said, in order to refer to it later to free it, you need to store a reference to it somewhere.
Since you're referring to "multiple buttons" and using array code in your delete routine, I think you're probably wanting to track an array of buttons. Here's an example of doing just that:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject); // Add via Object Inspector Events tab
private
{ Private declarations }
// Add these yourself
BtnArray: array of TButton;
procedure CreateButtons(const NumBtns: Integer);
procedure DeleteBtn(BtnToDel: TButton);
procedure BtnClicked(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DeleteBtn(BtnToDel: TButton);
var
i: Integer;
begin
// Check each button in the array to see if it's BtnToDel. If so,
// remove it and set the array entry to nil so it can't be deleted
// again.
for i := Low(BtnArray) to High(BtnArray) do
begin
if BtnArray[i] = BtnToDel then
begin
FreeAndNil(BtnArray[i]);
Break;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Create 10 buttons on the form
CreateButtons(10);
end;
// Called when each button is clicked. Assigned in CreateButtons() below
procedure TForm1.BtnClicked(Sender: TObject);
begin
// Delete the button clicked
if (Sender is TButton) then
DeleteBtn(TButton(Sender));
end;
procedure TForm1.CreateButtons(const NumBtns: Integer);
var
i: Integer;
begin
// Allocate storage for the indicated number of buttons
SetLength(BtnArray, NumBtns);
// For each available array item
for i := Low(BtnArray) to High(BtnArray) do
begin
BtnArray[i] := TButton.Create(nil); // Create a button
BtnArray[i].Parent := Self; // Tell it where to display
BtnArray[i].Top := i * (BtnArray[i].Height + 2); // Set the top edge so they show
BtnArray[i].Name := Format('BtnArray%d', [i]); // Give it a name (not needed)
BtnArray[i].Caption := Format('Btn %d', [i]); // Set a caption for it
BtnArray[i].OnClick := BtnClicked; // Assign the OnClick event
end;
end;
If you put this code in a new blank VCL forms application and run it, you'll see 10 buttons ('Btn 0throughBtn 9`) on a form. Clicking on a button will remove it from the form (and the array).
I am trying to create a few checkboxes, how many is decided by the recordcount of a query. Also i need to set the loction of the check box +38 from the previous location. Anyone give me some help with this? not sure how to create the checkboxes, The rest i should be able to do...anyhow he is what i have so far.
var
i, top,left : integer;
begin
......
left := 81;
top := 119;
while i < qry.RecordCount do
begin
// create check box
// set caption of checkbox to i
// set checkbox loction to left + 38, top
// left = left+38??
end;
After clarifying your needs, I would recommend you to use TObjectList as a container for your check boxes. This list can own the objects, what allows you to release them by a simple removing the item from the list either by Clear or by Delete. It also provides a simple access to each element by typecasting the obtained indexed item object to your known class type. More in the following untested pseudo-code:
uses
Contnrs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
CheckList: TObjectList;
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CheckList := TObjectList.Create;
// setting OwnsObjects to True will ensure you, that the objects
// stored in a list will be freed when you delete them from list
CheckList.OwnsObjects := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// this will also release all check boxes thanks to OwnsObjects
CheckList.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
CheckBox: TCheckBox;
begin
...
CheckList.Clear; // this will free all check boxes
for I := 0 to RecordCount - 1 do // iterate over your recordset
begin
CheckBox := TCheckBox.Create(nil); // be sure to use nil as an owner
CheckBox.Parent := Self; // where will be laying (Self = Form)
CheckBox.Caption := IntToStr(I); // caption by the iterator value
CheckBox.Top := 8; // fixed top position
CheckBox.Left := (I * 38) + 8; // iterator value * 38 shifted by 8
CheckBox.Width := 30; // fixed width
CheckList.Add(CheckBox); // add the check box to the list
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
// this will check the first check box from the list (be careful to indexes)
TCheckBox(CheckList.Items[0]).Checked := True;
// this will delete 3rd check box from the list (using Clear will delete all)
CheckList.Delete(2);
end;
Your pseudo code translates almost literally into Delphi code although it's better to use a for loop here:
for I := 0 to qry.RecordCount-1 do
begin
CheckBox := TCheckBox.Create (Self); // the form owns the checkbox
CheckBox.Parent := Self; // checkbox is displayed on the form
CheckBox.Caption := IntToStr (I);
CheckBox.Top := Top;
CheckBox.Left := 81 + I*38;
end;
BTW, you don't have to free the created checkbox thanks to the ownership mechanism built into the VCL.
I have a problem with creating TToolbuttons at runtime and how they appear in my TToolbar.
Basically I got a toolbar with some buttons in it already. I can create buttons at runtime
and set the parent to the toolbar. But they are always shown as the first buttons in my toolbar.
How can I make them appear at the end of my toolbar? Or any position I want them to be.
Here is a generic procedure that takes a toolbar, and adds a button to it, with a specified caption:
procedure AddButtonToToolbar(var bar: TToolBar; caption: string);
var
newbtn: TToolButton;
lastbtnidx: integer;
begin
newbtn := TToolButton.Create(bar);
newbtn.Caption := caption;
lastbtnidx := bar.ButtonCount - 1;
if lastbtnidx > -1 then
newbtn.Left := bar.Buttons[lastbtnidx].Left + bar.Buttons[lastbtnidx].Width
else
newbtn.Left := 0;
newbtn.Parent := bar;
end;
And here is example usage of that procedure:
procedure Button1Click(Sender: TObject);
begin
ToolBar1.ShowCaptions := True; //by default, this is False
AddButtonToToolbar(ToolBar1,IntToStr(ToolBar1.ButtonCount));
end;
Your question does also ask how to add a button to an arbitrary place on the TToolbar. This code is similar to before, but it also allows you to specify which index you want the new button to appear after.
procedure AddButtonToToolbar(var bar: TToolBar; caption: string;
addafteridx: integer = -1);
var
newbtn: TToolButton;
prevBtnIdx: integer;
begin
newbtn := TToolButton.Create(bar);
newbtn.Caption := caption;
//if they asked us to add it after a specific location, then do so
//otherwise, just add it to the end (after the last existing button)
if addafteridx = -1 then begin
prevBtnIdx := bar.ButtonCount - 1;
end
else begin
if bar.ButtonCount <= addafteridx then begin
//if the index they want to be *after* does not exist,
//just add to the end
prevBtnIdx := bar.ButtonCount - 1;
end
else begin
prevBtnIdx := addafteridx;
end;
end;
if prevBtnIdx > -1 then
newbtn.Left := bar.Buttons[prevBtnIdx].Left + bar.Buttons[prevBtnIdx].Width
else
newbtn.Left := 0;
newbtn.Parent := bar;
end;
And here is example usage for this revised version:
procedure Button1Click(Sender: TObject);
begin
//by default, "ShowCaptions" is false
ToolBar1.ShowCaptions := True;
//in this example, always add our new button immediately after the 0th button
AddButtonToToolbar(ToolBar1,IntToStr(ToolBar1.ButtonCount),0);
end;
Good luck!
You can use the left property of the TToolButton component
check this sample
//adding buttons to the end of the ToolBar.
procedure TForm1.Button1Click(Sender: TObject);
var
Toolbutton : TToolButton;
begin
Toolbutton :=TToolButton.Create(ToolBar1);
Toolbutton.Parent := ToolBar1;
Toolbutton.Caption := IntToStr(ToolBar1.ButtonCount);
Toolbutton.Left := ToolBar1.Buttons[ToolBar1.ButtonCount-1].Left + ToolBar1.ButtonWidth;
end;
If it works like the Scroll Panel then you can set the .left property to be 1 more than a button to place it to the left of that button. Or set the .left property to be 1 less than the button to place it to the right of that button.