Create TToolbutton runtime - delphi

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.

Related

How to make the same button run different code everytime it is clicked?

I am currently doing a school project, I am making a Credit Card machine. I need the 'Enter Button' to
run different code when it is clicked. The first click must get the card number from an edit ps... (I clear the edit once the card number has been retrieved), and the second click must get the pin from the same edit.
How would I do this?
procedure TfrmMainMenu.btbtnEnterClick(Sender: TObject);
var
sCvv,sPin:string;
begin
iCount2:=0;
sCardNumber:=lbledtCardInfo.Text;
if (Length(sCardNumber)<>16) AND (iCount2=0) then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
end
else
begin
Inc(iCount2);
lbledtCardInfo.clear;
lbledtCardInfo.EditLabel.Caption:='Enter Pin' ;
btbtnEnter.Enabled:=false;
end; //if
if iCount2=2 then
begin
btbtnEnter.Enabled:=true;
sPin:=lbledtCardInfo.Text;
ShowMessage(sPin);//returns a blank
end;
You could try to do everything in a single event handler. There are several different ways to handle that. However, a different solution would be to use separate event handlers for each task, and then each task can assign a new handler for the next click to perform, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnEnter.OnClick := GetCCNumber;
end;
procedure TfrmMainMenu.GetCCNumber(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnEnter.OnClick := GetCCPin;
end;
procedure TfrmMainMenu.GetCCPin(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnEnter.OnClick := GetCCNumber;
end;
A variation of this would be to create multiple buttons that overlap each other in the UI, and then you can toggle their Visible property back and forth as needed, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCNumEnterClick(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnCCNumEnter.Visible := False;
btbtnCCPinEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCPinEnterClick(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
Notice that you test iCount2 = 0 immediately after setting iCount2 := 0. Thus, that test will always be True. Furthermore, the later test iCount2 = 2 will always be False because the value starts at 0 and you only have one Inc in between.
Instead try the following.
Add two string fields FCardNumber and FPin to your form class:
private
FCardNumber: string;
FPin: string;
Also create an enumerated type TEntryStage = (esCardNumber, esPin) and add a field of this type. This will make your code look like this:
private
type
TEntryStage = (esCardNumber, esPin);
var
FCardNumber: string;
FPin: string;
FEntryStage: TEntryStage;
In Delphi, class fields (class member variables) are always initialized, so FEntryStage will be esCardNumber (=TEntryStage(0)) when the form is newly created.
Add a TLabeledEdit (I see you use those) and a TButton; name them eInput and btnNext, respectively. Let the labeled edit's caption be Card number: and the caption of the button be Next.
Now add the following OnClick handler to the button:
procedure TForm1.btnNextClick(Sender: TObject);
begin
case FEntryStage of
esCardNumber:
begin
// Save card number
FCardNumber := eInput.Text;
// Prepare for the next stage
eInput.Clear;
eInput.EditLabel.Caption := 'Pin:';
FEntryStage := esPin;
end;
esPin:
begin
// Save pin
FPin := eInput.Text;
// Just do something with the data
ShowMessageFmt('Card number: %s'#13#10'Pin: %s', [FCardNumber, FPin]);
end;
end;
end;
You might notice that you cannot trigger the Next button using Enter, which is very annoying. To fix this, do
procedure TForm1.eInputEnter(Sender: TObject);
begin
btnNext.Default := True;
end;
procedure TForm1.eInputExit(Sender: TObject);
begin
btnNext.Default := False;
end;
Much better!

Adding image to TTabItem in Firemonkey

I have a TTabControl with a single tab that displays an image on the tab when a field in the tab has contents. I've assigned an ImageList to the TTabControl. When I add additional TTabItems to the TTabControl, I am unable to assign an ImageIndex later in the code. Here is how I'm adding the new tab:
procedure TfrmAddEditItem.LoadCustomFields;
var
NewTab: TTabItem;
begin
...
NewTab := TTabItem.Create(tabPictures);
NewTab.Name := 'tab2';
NewTab.Text := FieldByName('FieldName').AsString;
NewTab.Visible := True;
NewTab.Enabled := True;
NewTab.Tag := tabPictures.TabCount + 1;
NewTab.Parent := tabPictures;
...
end;
But in another function in the form, when I try to set the image on the tab I get an Access violation exception:
procedure TfrmAddEditItem.LoadDataFields;
var
FormFieldName: string;
DBFieldName: string;
begin
...
FormFieldName := 'tab' + FieldByName('FieldID').AsString;
DBFieldName := 'Field' + FieldByName('FieldID').AsString;
Tab := TTabItem(Self.FindComponent(FormFieldName));
if FieldByName(DBFieldName).IsNull then
Tab.ImageIndex := -1
else
Tab.ImageIndex := 0;
...
end;
How can I set the ImageIndex value programatically on a TTabItem I added within the code? (Note that there are other events where I will also want to adjust the image based on the value in a database field.)

Repeating a procedure a user specified amount of times

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;

Find controls caption and change it state

I have form with enabled / disabled controls to indicate form is in busy or idle state.
I need to enable only ONE control (a button, but could be else), when it was disabled to abort some process. I change the button caption to 'ABORT'.
I click button A, i change the caption of button A to 'ABORT'. All other control will be disabled, but i want a button with caption 'ABORT' is still enabled.
procedure F1.FormBusy (sender);
var
a: Integer;
begin
for a := 0 to TabSheet1.ControlCount - 1 do
begin
TabSheet1.Controls[a].Enabled := False;
(* if TabSheet1.Controls[a] caption := 'ABORT' then
TabSheet1.Controls[a].Enabled := True
< how to do this ? *)
end;
end;
Usage example :
procedure F1.LB1Click(sender: TObject);
begin
FormBusy(sender);
try
// do something
finally
FormIdle(sender);
end;
end;
Rather than trying to find the button by its Caption property, why not access it directly from the array?
for a := 0 to TabSheet1.ControlCount - 1 do
begin
TabSheet1.Controls[a].Enabled := TabSheet1.Controls[a] = Button1;
end;
Each TControl will be disabled except for Button1 which will be enabled.
You can define another method to assign busy parameter :
procedure F1.MAJIHM(const isBusy : Boolean);
var a: Integer;
begin
for a := 0 to TabSheet1.ControlCount - 1 do
begin
TabSheet1.Controls[a].Enabled := isBusy;
end;
btnABORT.enabled := not isBusy;
end;
procedure F1.FormBusy (sender);
begin
MAJIHM(True);
end;
procedure F1.FormIdle (sender);
begin
MAJIHM(False);
end;
You said:
I click button A, i change the caption of button A to 'ABORT'. All
other control will be disabled, but i want a button with caption
'ABORT' is still enabled.
And from your usage example it is clear that you pass that button to F1.FormBusy() where you can refer to it as the sender parameter:
procedure F1.FormBusy(sender: TObject);
var
a: Integer;
begin
for a := 0 to TabSheet1.ControlCount - 1 do
TabSheet1.Controls[a].Enabled := TabSheet1.Controls[a] = sender;
end;
In the FormIdle() function you simply enable all controls.

Getting shape to show up on a form during runtime [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I have a button and when clicked, i would like for a TMachine (aka TShape) to show up on the form. Currenty i get no errors, but it never shows up on the form.
Code for button click
procedure TfDeptLayout.bAddMachineClick(Sender: TObject);
var
machine: TMachine;
shapeAsset,
shapeShape,
shapeNumber,
shapeName: string;
begin
if not OkToAdd() then
begin
ShowMessage('Please fill out form correctly!');
Exit;
end;
ShapeAsset := Edit2.text;
ShapeShape := Combobox1.Text;
ShapeNumber := Edit3.Text;
ShapeName := Edit1.Text;
if sub = false then
begin
machine := TMachine.Create(self);
machine.Parent := Self;
machine.PlaceShape(0, FDB.GetWW(ShapeShape), FDB.GethW(ShapeShape),
'20', '20', ShapeName, ShapeNumber, ShapeAsset)
//show save button
//lockout add machine button
//let user place machine top / left.
//save all locations
//save top and left for each tmachine to database
//lockout save button
//show add machine button
end;
if sub then
ShowMessage('auto save form');
ShowMessage('congrats you added a machine');
end;
if needed i can show the TMachine unit?..
type
TMachine = class(TShape)
private
FOnMouseEnter: TNotifyEvent;
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
protected
procedure DoMouseEnter; virtual;
published
property OnMouseEnter: TNotifyEvent Read FOnMouseEnter write FOnMouseEnter;
public
mnName: string;
mnAsset: string;
mnNumber: string;
mnIsPanel: string;
mnBasicName: string;
mnLShape: string;
procedure PlaceShape(AM, sizeW, sizeH: Integer; ptop, pleft, name, order,
asset: string);
end;
implementation
uses
deptlayout;
procedure TMachine.CMMouseEnter(var Message: TMessage);
begin
DoMouseEnter;
inherited;
end;
procedure TMachine.DoMouseEnter;
begin
if Assigned(FOnMouseEnter) then
FOnMouseEnter(Self);
end;
procedure TMachine.PlaceShape(AM, sizeW, sizeH: Integer; ptop, pleft, name,
order, asset: string);
var
myLabel: TLabel;
begin
if ptop = '0' then
Top := 136
else
Top := StrToInt(ptop);
Width := sizeW;
Height := sizeH;
if pleft = '0' then
Left := MyDataModule.fDB.LastX + 2 //set left
else
Left := StrToInt(pleft);
MyDataModule.fDB.lastx := Left + sizeW;
if AM = 1 then //if in edit mode..
begin
//create label put inside the shape.
myLabel := TLabel.Create(FDeptLayout);
mylabel.Parent := FDeptLayout;
mylabel.Left := Left;
mylabel.Top := Top + 8;
mylabel.Caption := '#' + mnNumber;
end;
end;
end.
Of course it doesn't work!
The code that adds the machine is inside if not OkToAdd() then, so it will only run if not OkToAdd. BUT! Even if this is the case, you Exit before you run the code! Hence, the code will never run!
Probably you mean it to be like this:
if not OkToAdd then
begin
ShowMessage('Please fill out form correctly!');
Exit;
end; //END!!!!!!
To summarise my comments above:
Change the refer to fDeptLayout to Self, as you have done in your edit:
procedure TfDeptLayout.bAddMachineClick(Sender: TObject);
var
machine : TMachine;
shapeAsset,
shapeShape,
shapeNumber,
shapeName : string;
begin
if not OkToAdd() then
begin
showmessage('Please fill out form correctly!');
Exit;
End;
shapeAsset := edit2.text;
ShapeShape := Combobox1.Text;
ShapeNumber := Edit3.Text;
ShapeName := Edit1.Text;
if sub = false then
begin
machine := TMachine.Create(self);
machine.Parent := Self;
machine.PlaceShape(0,FDB.GetWW(ShapeShape),FDB.GethW(ShapeShape),'20','20',ShapeName,ShapeNumber,ShapeAsset)
//show save button
//lockout add machine button
//let user place machine top / left.
//save all locations
//save top and left for each tmachine to database
//lockout save button
//show add machine button
end;
if sub then
showmessage('auto save form');
showmessage('congrats you added a machine');
end;
To avoid confusion in future, delete the global form variables that the Delphi IDE creates for all but the main form, and any other autocreated forms - they are rarely if ever needed, and "pollute the namespace"
Unknown why this solved it, but after trying to find the parent for Machine by putting
showmessage('Machine Parent: '+Machine.parent.name);
it was giving access errors.
Deleted
Machine.parent := self;
Compile, build. Then reaadded
Machine.parent := self;
and everything worked.

Resources