Find controls caption and change it state - delphi

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.

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!

Continuing the 'for' where you left off

The code below goes through the entire contents of Memo1, and exit when Label2 = Edit1. So far so good, I would like to know how I do so when I click Button1 again after the exit, it continues from the line below Memo1 and not from the beginning again.
procedure TForm1.Button1Click(Sender: TObject);
var i: Integer;
begin
for i := 0 to Memo1.Lines.Count - 1 do
begin
Label2.Caption := Memo1.Lines.Strings[i];
Sleep(2500);
if Trim(Label2.Caption) = (Edit1.Text) then
begin
messageBeep(0);
exit;
end;
end;
Declare a variable in your form class named FCurrentLine of type Integer
Change your loop to read for i := FCurrentLine to ...
When you exit the loop set FCurrentLine := i + 1

Doing a continuous action while a TButton is held down

How would I be able to do a continuous action while a button is held down? For example, I have made a custom 'Numpad' for my application, which has a Delete button. As of right now, I have to click it separately, but I want it to keep deleting while it is held down.
procedure TFrame1.deleteClick(Sender: TObject);
var
MiString: string;
begin
global_gotten_mode := precheck.global_edit_mode;
precheck.Form2.input_field.SetFocus;
MiString := Copy(precheck.Form2.input_field.Text, 0, (length(precheck.Form2.input_field.Text) - 1));
precheck.Form2.input_field.Text := MiString;
Form2.input_field.SelStart := high(integer);
end;
Add a timer and activate it on the OnMouseDown event.
As long as the button is held down, the timer will kick in at a rate of your choice.
When the button is released, the OnMouseUp event disables the timer.
Something in this way:
procedure TFrame1.BtnMouseDown(Sender : TObject);
begin
global_gotten_mode := precheck.global_edit_mode;
precheck.Form2.input_field.SetFocus;
fMyBtnTimer.Interval := 500; // Initial repetition rate
fMyBtnTimer.Enabled := true;
end;
procedure TFrame1.BtnMouseUp(Sender : TObject);
begin
fMyBtnTimer.Enabled := false;
end;
procedure TFrame1.MyBtnTimerEvent(Sender : TObject);
var
MiString: string;
begin
fMyBtnTimer.Interval := 200; // Increase repetition rate
MiString := Copy( precheck.Form2.input_field.Text,
0,
length(precheck.Form2.input_field.Text) - 1);
precheck.Form2.input_field.Text := MiString;
Form2.input_field.SelStart := high(integer);
end;

Close Delphi dialog after [x] seconds

Is it possible to get Delphi to close a ShowMessage or MessageDlg Dialog after a certain length of time?
I want to show a message to the user when the application is shut down, but do not want to stop the application from shutting down for more than 10 seconds or so.
Can I get the default dialog to close after a defined time, or will I need to write my own form?
Your application is actually still working while a modal dialog or system message box or similar is active (or while a menu is open), it's just that a secondary message loop is running which processes all messages - all messages sent or posted to it, and it will synthesize (and process) WM_TIMER and WM_PAINT messages when necessary as well.
So there's no need to create a thread or jump through any other hoops, you simply need to schedule the code that closes the message box to be run after those 10 seconds have elapsed. A simple way to do that is to call SetTimer() without a target HWND, but a callback function:
procedure CloseMessageBox(AWnd: HWND; AMsg: UINT; AIDEvent: UINT_PTR;
ATicks: DWORD); stdcall;
var
Wnd: HWND;
begin
KillTimer(AWnd, AIDEvent);
// active window of the calling thread should be the message box
Wnd := GetActiveWindow;
if IsWindow(Wnd) then
PostMessage(Wnd, WM_CLOSE, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TimerId: UINT_PTR;
begin
TimerId := SetTimer(0, 0, 10 * 1000, #CloseMessageBox);
Application.MessageBox('Will auto-close after 10 seconds...', nil);
// prevent timer callback if user already closed the message box
KillTimer(0, TimerId);
end;
Error handling ommitted, but this should get you started.
You can try to do it with a standard Message dialog. Create the dialog with CreateMessageDialog procedure from Dialogs and after add the controls that you need.
In a form with a TButton define onClick with this:
procedure TForm1.Button1Click(Sender: TObject);
var
tim:TTimer;
begin
// create the message
AMsgDialog := CreateMessageDialog('This is a test message.',mtWarning, [mbYes, mbNo]) ;
lbl := TLabel.Create(AMsgDialog) ;
tim := TTimer.Create(AMsgDialog);
counter := 0;
// Define and adding components
with AMsgDialog do
try
Caption := 'Dialog Title' ;
Height := 169;
// Label
lbl.Parent := AMsgDialog;
lbl.Caption := 'Counting...';
lbl.Top := 121;
lbl.Left := 8;
// Timer
tim.Interval := 400;
tim.OnTimer := myOnTimer;
tim.Enabled := true;
// result of Dialog
if (ShowModal = ID_YES) then begin
Button1.Caption := 'Press YES';
end
else begin
Button1.Caption := 'Press NO';
end;
finally
Free;
end;
end;
An the OnTimer property like this:
procedure TForm1.MyOnTimer(Sender: TObject);
begin
inc(counter);
lbl.Caption := 'Counting: ' + IntToStr(counter);
if (counter >= 5) then begin
AMsgDialog.Close;
end;
end;
Define the variables and procedure:
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
AMsgDialog: TForm;
lbl:TLabel;
counter:integer;
procedure MyOnTimer(Sender: TObject);
end;
And test it.
The form close automatically when the timer final the CountDown. Similar this you can add other type of components.
Regards.
Try this:
function MessageBoxTimeOut(hWnd: HWND; lpText: PChar; lpCaption: PChar;
uType: UINT; wLanguageId: WORD; dwMilliseconds: DWORD): integer;
stdcall; external user32 name 'MessageBoxTimeoutA';
I've been using this for quite some time; it works a treat.
OK. You have 2 choices:
1 - You can create your own MessageDialog form. Then, you can use it and add a TTimer that will close the form when you want.
2 - You can keep using showmessage and create a thread that will use FindWindow (to find the messadialog window) and then close it.
I recommend you to use you own Form with a timer on it. Its cleaner and easier.
This works fine with windows 98 and newers...
I don't use the " MessageBoxTimeOut" because old windows 98, ME, doesn't have it...
this new function works like a "CHARM"..
//add this procedure
procedure DialogBoxAutoClose(const ACaption, APrompt: string; DuracaoEmSegundos: Integer);
var
Form: TForm;
Prompt: TLabel;
DialogUnits: TPoint;
ButtonTop, ButtonWidth, ButtonHeight: Integer;
nX, Lines: Integer;
function GetAveCharSize(Canvas: TCanvas): TPoint;
var
I: Integer;
Buffer: array[0..51] of Char;
begin
for I := 0 to 25 do Buffer[I] := Chr(I + Ord('A'));
for I := 0 to 25 do Buffer[I + 26] := Chr(I + Ord('a'));
GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
Result.X := Result.X div 52;
end;
begin
Form := TForm.Create(Application);
Lines := 0;
For nX := 1 to Length(APrompt) do
if APrompt[nX]=#13 then Inc(Lines);
with Form do
try
Font.Name:='Arial'; //mcg
Font.Size:=10; //mcg
Font.Style:=[fsBold];
Canvas.Font := Font;
DialogUnits := GetAveCharSize(Canvas);
//BorderStyle := bsDialog;
BorderStyle := bsToolWindow;
FormStyle := fsStayOnTop;
BorderIcons := [];
Caption := ACaption;
ClientWidth := MulDiv(Screen.Width div 4, DialogUnits.X, 4);
ClientHeight := MulDiv(23 + (Lines*10), DialogUnits.Y, 8);
Position := poScreenCenter;
Prompt := TLabel.Create(Form);
with Prompt do
begin
Parent := Form;
AutoSize := True;
Left := MulDiv(8, DialogUnits.X, 4);
Top := MulDiv(8, DialogUnits.Y, 8);
Caption := APrompt;
end;
Form.Width:=Prompt.Width+Prompt.Left+50; //mcg fix
Show;
Application.ProcessMessages;
finally
Sleep(DuracaoEmSegundos*1000);
Form.Free;
end;
end;
////////////////////////////How Call It//////////////////
DialogBoxAutoClose('Alert'', "This message will be closed in 10 seconds',10);
/////////////////////////////////////////////////////////
MessageBox calls this function internally and pass 0xFFFFFFFF as timeout parameter, so the probability of it being removed is minimal (thanks to Maurizio for that)
I thought about using a separate thread, but it's probably going to get you into a lot of unnecessary code etc. Windows dialogs were simply not made for this thing.
You should do your own form. On the good side, you can have custom code/UI with a countdown like timed dialog boxes do.
No. ShowMessage and MessageDlg are both modal windows, which means that your application is basically suspended while they're displayed.
You can design your own replacement dialog that has a timer on it. In the FormShow event, enable the timer, and in the FormClose event disable it. In the OnTimer event, disable the timer and then close the form itself.
You can hook up the Screen.OnActiveFormChange event and use Screen.ActiveCustomForm if it is a interested form that you want to hook up the timer to close it
{code}
procedure abz.ActiveFormChange(Sender: TObject);
var
Timer: TTimer;
begin
if (Screen.ActiveCutomForm <> nil) and //valid form
(Screen.ActiveCutomForm.Tag = 0) and //not attached a timer yet
(Screen.ActiveCutomForm.ClassName = 'TMessageForm') //any interested form type check
then
begin
Timer := TTimer.Create(Screen.ActiveCutomForm); // let the form owned so it will be freed
Timer.Enabled := False;
Timer.Tag := Integer(Screen.ActiveCutomForm); // keep track to be used in timer event
.... setup any timer interval + event
Screen.ActiveCutomForm.Tag := Integer(Timer);
Timer.Enabled := True;
end;
end;
{code}
enjoy
Best way is to use a stayontop form and manage a counter to disappear using the alfpha blend property of the form, at the end of the count just close the form, but
the control will be passed to the active control needed before showing the form, this way, user will have a message which disappears automatically and wont prevent the usage of the next feature, very cool trick for me.
You can do this with WTSSendMessage.
You can find this in the JWA libraries, or call it yourself.

Create TToolbutton runtime

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.

Resources