mybox.Checked := true;
Setting TRadioButton to checked (by code) causes OnClick event handler to be called.
How can I recognize if user is making the state change by GUI interaction
You can nil the OnClick event handler while changing a radiobutton state programmatically:
procedure TForm1.Button6Click(Sender: TObject);
var
Save: TNotifyEvent;
begin
Save:= RadioButton2.OnClick;
RadioButton2.OnClick:= nil;
RadioButton2.Checked:= not RadioButton2.Checked;
RadioButton2.OnClick:= Save;
end;
mybox.Tag := 666;
mybox.Checked :=true;
mybox.Tag := 0;
procedure myboxOnclick(Sender : TObject);
begin
if Tag = 0 then
//Do your thing
end;
If you have an action connected to the radiobutton, you can set the checked property of the action instead. This will also prevent the OnClick event to be fired.
TRadioButton (like TCheckBox) provides a protected property ClicksDisabled that can help you.
I use class helpers to add the needed functionality:
RadioButton1.SetCheckedWithoutClick(False);
with the following class helper for a VCL TRadioButton:
TRadioButtonHelper = class helper for TRadioButton
procedure SetCheckedWithoutClick(AChecked: Boolean);
end;
procedure TRadioButtonHelper.SetCheckedWithoutClick(AChecked: Boolean);
begin
ClicksDisabled := True;
try
Checked := AChecked;
finally
ClicksDisabled := False;
end;
end;
Related
I'm doing a toggle switch, that when I click it changes its On/off state, I created it by overriding the Paint method in TCanvas, everything fine but the borders looked pixelated.
So I modified the component to load an image for each state depending on the checked of the image, in short I'm putting a TImage component inside a Checkbox inherited from TwwCheckbox
But when the image is pressed/clicked/checked, the Checked changes its state to true but it doesn't show the state that was saved in the database the next time I reopen the form, I mean, if I activate the toggle it is saved with an S and when opening the form where the Toggle is, it should be On and not off
{here I create the toggle in runtime and assign it a datasource
to connect it to the database}
procedure TForm1.FormCreate(Sender: TObject);
begin
ToggleButton:= TinToggleButton2.Create(Self);
ToggleButton.Name := 'ToggleButton';
ToggleButton.Parent:= Self;
ToggleButton.Left:= 200;
ToggleButton.Top:=100;
ToggleButton.Visible:= true;
ToggleButton.ValueChecked:= 'S';
ToggleButton.ValueUnchecked:= 'N';
ToggleButton.DisplayValueChecked:= 'S';
ToggleButton.DisplayValueUnchecked:= 'N';
ToggleButton.DataField:= 'TAutoSave';
ToggleButton.DataSource:= dsToggleButton;
ToggleButton.DataSource.DataSet:= qryToggleButton;
end;
//here the query
procedure TForm1.Button1Click(Sender: TObject);
begin
qryToggleButton.Active:= true;
end;
//i send the data to database
procedure TForm1.Button2Click(Sender: TObject);
begin
qryToggleButton.Post;
end;
{here I create the component in another unit where the click event is
to switch between image}
constructor TinToggleButton2.Create(AOwner: TComponent);
begin
inherited;
Width:= 34;
Height:= 15;
Image:= TImage.Create(Self);
Image.Visible:= true;
Image.Picture.LoadFromFile('toggle_Toggle1.ico');
Image.Parent:= Self;
Image.OnClick:=Self.Image1Click;
Self.Checked:= false;
Checked:= false;
end;
procedure TinToggleButton2.Image1Click(Sender: TObject);
begin
if DataSource <> nil then
begin
DataSource.Edit;
if Self.Checked then
begin
Self.Checked:= false;
DataSource.DataSet.FieldByName(DataField).Value:= ValueUnchecked;
Image.Picture.LoadFromFile('toggle_Toggle1.ico');
end
else
begin
Self.Checked:= true;
DataSource.DataSet.FieldByName(DataField).Value:= ValueChecked;
Image.Picture.LoadFromFile('toggle_Toggle2.ico');
end;
end;
end;
I create the toggle in runtime but I put the other components such as the TwwDataSource and the TADOQuery in the form and I assign the DataField and the DataSource to the checkbox
I have to open many forms in an application and I'm using a TtoolButton and TActionlist as a menu bar. I coded a procedure to create/show each form. I'm having difficult to trigger Form OnActivate event inside this procedure.
Each form is opened inside a Tpanel which is located in the main form FormHome.
I appreciatte your help !
See my code in Delphi 10.2
procedure TFormHome.PR_OpenForm(Pform : TFormClass);
var
vform : TForm;
begin
vform := Pform.Create(Application);
vform.Parent := PanelCorpo;
vform.Align := alclient;
vform.BorderIcons := [biSystemMenu];
vform.BorderStyle := bsNone;
vform.Show;
vform.SetFocus;
vform.OnActivate(??); // That is the issue, how to call this event ?
end;
Thanks in advance !
**Adding complimentary information to explain why I need one single method to create/open my forms **
This is the code I use to open each particular forms. I have one method to each form with exactly the same code. The only difference is the Form instance itself :
procedure TFormHome.OpenDiretorioExecute(Sender: TObject);
begin
if Not Assigned(FormDiretorio) then
begin
FormDiretorio := TFormDiretorio.Create(Self);
FormDiretorio.Parent := PanelCorpo;
FormDiretorio.Align := alclient;
FormDiretorio.BorderIcons := [biSystemMenu];
FormDiretorio.BorderStyle := bsNone;
FormDiretorio.Show;
FormDiretorio.SetFocus;
FormDiretorio.OnActivate(Sender); // In this way , OnActivate works fine
end;
end;
What I need/want :
I need only one method to open all forms. This TFormHome.PR_OpenForm(Pform : TFormClass) coded above is almost there, except by the OnActivate method that is not working !
Could you help me to fix that ?
Thanks!
Sample Code - Project with Old code and new code
===> Main Form "FormHome"
... // This is the main Form FormHOme which calls FormA, FormB and FormC
// There is a TToolbar with 3 Toolbutton that uses a TActionlist
// FormA and FormB are called by the old style method Action1Execute
// and Action2Execute
// FormC is called by the new method PR_CreateOpenForm , which
// presents the error
var
FormHome: TFormHome;
implementation
uses
UnitFormA,
unitFormB,
UnitFormC;
{$R *.dfm}
procedure TFormHome.Action1Execute(Sender: TObject);
// Action1 : OnExecute event, called from ToolButton1
begin
if Not Assigned(FormA) then
begin
FormA := TFormA.Create(Self);
end;
FormA.Parent := Panelhome;
FormA.Align := alclient;
FormA.BorderIcons := [biSystemMenu];
FormA.BorderStyle := bsNone;
FormA.Show;
FormA.SetFocus;
FormA.OnActivate(Sender); // There is a code in OnActivate event in FormA
end;
procedure TFormHome.Action2Execute(Sender: TObject);
// Action2 : OnExecute event , called from ToolButton2
begin
if Not Assigned(FormB) then
begin
FormB := TFormB.Create(Self);
end;
FormB.Parent := Panelhome;
FormB.Align := alclient;
FormB.BorderIcons := [biSystemMenu];
FormB.BorderStyle := bsNone;
FormB.Show;
FormB.SetFocus;
FormB.OnActivate(Sender); // There is a code in OnActivate event in FormB
end ;
procedure TFormHome.Action3Execute(Sender: TObject);
// Action3 OnExecute event, called from ToolButton3
// This is the desired code to implment in all Action OnExecute event
begin
PR_CreateOpenForm(TFormC); // Fails in the OnActivate event
end;
procedure TFormHome.PR_CreateOpenForm(PClassform : TFormClass);
// This routine should be used to create/open all forms
//
var
vform : TForm;
begin
if Not Assigned(Tform(PClassform)) then
begin
vform := Pclassform.Create(Application);
end;
vform.Parent := PanelHome;
vform.Align := alclient;
vform.BorderIcons := [biSystemMenu];
vform.BorderStyle := bsNone;
vform.Show;
vform.SetFocus;
vform.onActivate(self); // Does not work !! Tried with : vform.Onactivate(nil) - vform.Onactivate(Tform)
end;
end.
FORMA - OnActivate event
procedure TFormA.FormActivate(Sender: TObject);
begin
Edit1.Text := 'content from OnActivate';
end;
FORMB - OnActivate event
procedure TFormB.FormActivate(Sender: TObject);
begin
Edit1.Text := 'content from OnActivate';
end;
FORMC - OnActivate event
procedure TFormC.FormActivate(Sender: TObject);
begin
Edit1.Text := 'content from OnActivate';
end;
Error when calls PR_CreateOpenForm(TFormC)
DEBUG - running step by step reach this event handler error :
procedure TWinControl.MainWndProc(var Message: TMessage);
begin
try
try
WindowProc(Message);
finally
FreeDeviceContexts;
FreeMemoryContexts;
end;
except
Application.HandleException(Self);
end;
end;
Please let me know if I have to provide any other information/code in order to have your suggestions and valuable tips !
Thank you guys !
Update
You asked in a comment
The point is : given a parameter PClassForm of TformClass class, how to check if there is any instance of the such parameter created in the Application ? " ,
You can do this using a function like the FormInstance one below. The Screens object of a VCL application has a Forms property, and you can iterate that, looking to see if one of the forms is a specified class, which is returned as the function's result (which it Nil otherwise). Once you have found the instance, you could of course use a cast to call some specific method of it.
function FormInstance(AClass : TClass) : TForm;
var
i : Integer;
begin
Result := Nil;
for i := 0 to Screen.FormCount - 1 do begin
if Screen.Forms[i].ClassType = AClass then begin
Result := Screen.Forms[i];
Break;
end;
end;
end;
procedure TMyForm.Button1Click(Sender: TObject);
var
F : TForm;
begin
F := FormInstance(TForm2);
if F <> Nil then
Caption := 'Found'
else
Caption := 'Not found';
end;
original answerThe way you've written your q seems to tangle up your actual technical q
vform.OnActivate(??); // That is the issue, how to call this event ?
with a lot of issues which aren't directly related. Rather that try to
invoke the OnActivate handler (If there is one), it may be better
to override the form's Activate procedure to do whatever special handling you want
and then leave it to the code in TForm to decide when to invoke the OnActivate. This is less likely to wrong-foot other form behaviour (like in TScreen).
The code below shows how to do this.
type
TForm1 = class(TForm)
procedure FormActivate(Sender: TObject);
protected
procedure Activate; override;
public
end;
[...]
procedure TForm1.Activate;
begin
inherited;
Caption := Caption + ' called from TForm1.Activate';
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
Caption := 'Activated';
end;
Of course, maybe you could just put the code you want to execute in OnActivate in the OnShow handler instead.
Try change the working OpenDiretorioExecute method to shown code below and tell me if you still get the error. Use vform.OnActivate(Self) inside PR_OpenForm. Please also show the OnActivate event handler for TFormDiretorio.
procedure TFormHome.OpenDiretorioExecute(Sender: TObject);
begin
FormDiretorio:=PR_OpenForm(TFormDiretorio) as TFormDiretorio;
(* if Not Assigned(FormDiretorio) then
begin
FormDiretorio := TFormDiretorio.Create(Self);
FormDiretorio.Parent := PanelCorpo;
FormDiretorio.Align := alclient;
FormDiretorio.BorderIcons := [biSystemMenu];
FormDiretorio.BorderStyle := bsNone;
FormDiretorio.Show;
FormDiretorio.SetFocus;
FormDiretorio.OnActivate(Sender); // In this way , OnActivate works fine
end; *)
end;
Change PR_OpenForm to return vForm. I assume you need the variable FormDiretorio.
The error is in this piece of code:
if Not Assigned(TForm(PClassform)) then
begin
vform := PClassform.Create(Application);
end;
If you look in the source for implementation of Assigned() you will see that it only checks wheter the passed in argument is nil or not. Thus, your code doesn't check for the existence of a form of type PClassForm, as you might think. It only checks whether the parameter PClassForm is nil or not.
In your case Assigned() returns true, the form is not created and subsequently vform contains whatever happens to be on the stack. That it only crashes at the line where you call OnActivate() is just a coincidence. You may have destroyed significant data (and probably have) by accessing the uninitialized vform variable.
To prevent errors like this to become fatal, you should initialize local pointer variables to nil if they might stay uninitialized. You probably also got a compiler warning for this but neglected it.
Already earlier I wanted to ask you where you plan to hold references to the forms that you create, so that you can access them, but I didn't, because it was not your question.
You need to decide on that and then use those references, both to check for existense and to access the forms.
In my Delphi XE2 Project, I am having Form1, Label1 and CheckBox1.
My requirement is to set the CheckBox1.Font.Color := clGreen;.
Thought I have written
procedure TForm1.FormCreate(Sender: TObject);
begin
CheckBox1.Font.Color := clGreen;
end;
yet the Font Color is default Black. So I have defined it in other way as follows:
I have removed the Caption from the CheckBox1 and changed the Width to 17.
Then I have placed Label1 next to CheckBox1 like CleckBox1 Caption.
After that I have written:
procedure TForm1.Label1Click(Sender: TObject);
begin
CheckBox1.Click;
end;
to Toggle the state of CheckBox1.
But I am getting [DCC Error] Unit1.pas(37): E2362 Cannot access protected symbol TCustomCheckBox.Click.
And another question is that whether the OnMouseDown Event of CheckBox1 can be triggered as the following image:
The Click() method merely triggers the contro's OnClick event, nothing else. It does not actually cause the control to perform click-related logic, like updating its internal state.
You can toggle the CheckBox's state like this:
CheckBox1.Checked := not CheckBox1.Checked;
Alternatively, use an accessor class to reach protected members:
type
TCheckBoxAccess = class(TCheckBox)
end;
TCheckBoxAccess(CheckBox1).Toggle;
You can use it like :
procedure TForm1.Label1Click(Sender: TObject);
begin
//either
CheckBox1.Checked := not CheckBox1.Checked; // this trigger onClick event!!
// or
// if you absolutely need it..
CheckBox1Click(Sender); // NOTE this will not check or uncheck CheckBox1
end;
But note you use here a TLabel Object (Sender). If you do not use Sender you can do it Without further attention.
But it's better to put the code for enable and disable other control out of the event. only one line for example doenable() .
procedure TForm1.doEnable(enable: Boolean);
begin
Edit1.Enabled := enable;
Edit2.Enabled := enable;
Edit3.Enabled := NOT enable;
if enable then Label1.Font.Color := clGreen else Label1.Font.Color := clWindowText;
...
end;
procedure TForm1.Label1Click(Sender: TObject);
begin
// NOTE This trigger also CheckBox1 Click event.
CheckBox1.Checked := not CheckBox1.Checked;
// NOT needed.
//if CheckBox1.Checked then doEnable(true) else doEnable(false);
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
if CheckBox1.Checked then doEnable(true) else doEnable(false);
end;
How do you prevent a new event handling to start when an event handling is already running?
I press a button1 and event handler start e.g. slow printing job.
There are several controls in form buttons, edits, combos and I want that a new event allowed only after running handler is finnished.
I have used fRunning variable to lock handler in shared event handler. Is there more clever way to handle this?
procedure TFormFoo.Button_Click(Sender: TObject);
begin
if not fRunning then
try
fRunning := true;
if (Sender = Button1) then // Call something slow ...
if (Sender = Button2) then // Call something ...
if (Sender = Button3) then // Call something ...
finally
fRunning := false;
end;
end;
Another option (that does not require a flag field) would be to temporarily assign NIL to the event:
procedure TForm1.Button1Click(Sender: TObject);
var
OldHandler: TNotifyEvent;
begin
OldHandler := (Sender as TButton).OnClick;
(Sender as TButton).OnClick := nil;
try
...
finally
(Sender as TButton).OnClick := OldHandler;
end;
end;
For convenience sake this could be wrapped into an interface:
interface
function TempUnassignOnClick(_Btn: TButton): IInterface;
implementation
type
TTempUnassignOnClick = class(TInterfacedObject, IInterface)
private
FOldEvent: TNotifyEvent;
FBtn: TButton;
public
constructor Create(_Btn: TButton);
destructor Destroy; override;
end;
constructor TTempUnassignOnClick.Create(_Btn: TButton);
begin
Assert(Assigned(_Btn), 'Btn must be assigned');
inherited Create;
FBtn := _Btn;
FOldEvent := FBtn.OnClick;
FBtn.OnClick := NIL;
end;
destructor TTempUnassignOnClick.Destroy;
begin
FBtn.OnClick := FOldEvent;
inherited;
end;
function TempUnassignOnClick(_Btn: TButton): IInterface;
begin
Result := TTempUnassignOnClick(_Btn);
end;
to be used like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
TempUnassignOnClick(Sender as TButton);
...
end;
Your solution is OK. You can also link button clicks to actions and enable/disable actions in TAction.OnUpdate event handler, but you still need fRunning flag to do it. The "if no fRunning" line may be not nessesary here, but I don't removed it because it is more safe:
// Button1.Action = acButton1, Button2.Action = acButton2, etc
procedure TForm1.acButtonExecute(Sender: TObject);
begin
if not fRunning then
try
fRunning:= True;
if (Sender = acButton1) then // Call something slow ...
if (Sender = acButton2) then // Call something ...
if (Sender = acButton3) then // Call something ...
finally
fRunning:= False;
end;
end;
procedure TForm1.acButtonUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled:= not fRunning;
end;
You don't have to do this at all, since all of this is happening in the main (VCL) thread:
No other button (VCL) event can be entered until the previous (VCL) event handler has returned...
The simultaneous execution of another event handler could only happen unexpectedly if some other thread was preemptively entering a second button event (before the first one has completed), but that can't happen, since there is only one VCL thread.
Now if the lengthy thing you are doing is done in another thread because you don't want it to block the GUI, then you can simply set the Button.Enabled property to false until your processing is done.
And if you decide to just stick in the button event until everything has completed, use application.processmessages frequently enough in your processing loop to prevent the gui from freezing. In which case, yes, you must disable the original button to prevent reentry.
As Gerry already mentioned in one of the comments, you can disable entire form:
procedure TFormFoo.Button_Click(Sender: TObject);
begin
try
Enabled := False;
//...
finally
Enabled := True;
end;
end;
If your app is a single-threaded one, then while your event-handler code is running, your app cannot run other codes, so all calls to that event-handler will be serialized, and you don't need to be worried.
If your event-handler is running any asynchronous job, then you can use the technique you presented in your question.
I'm wondering so when I change state of CheckBox
CheckBox->Checked=false;
It calls CheckBoxOnClick Event , how to avoid it ?
In newer Delphi versions you can use class helpers to add this functionality:
CheckBox.SetCheckedWithoutClick(False);
by using the following class helper for a VCL TCheckBox:
TCheckBoxHelper = class helper for TCheckBox
procedure SetCheckedWithoutClick(AChecked: Boolean);
end;
procedure TCheckBoxHelper.SetCheckedWithoutClick(AChecked: Boolean);
begin
ClicksDisabled := True;
try
Checked := AChecked;
finally
ClicksDisabled := False;
end;
end;
Just for completeness: A FMX TCheckBox will behave similar (triggering OnChange). You can workaround this by using the following class helper:
TCheckBoxHelper = class helper for TCheckBox
procedure SetCheckedWithoutClick(AChecked: Boolean);
end;
procedure TCheckBoxHelper.SetCheckedWithoutClick(AChecked: Boolean);
var
BckEvent: TNotifyEvent;
begin
BckEvent := OnChange;
OnChange := nil;
try
IsChecked := AChecked;
finally
OnChange := BckEvent;
end;
end;
Disclaimer: Thanks, dummzeuch for the original idea. Be aware of the usual hints regarding class helpers.
You could surround the onClick event code with something like
if myFlag then
begin
...event code...
end;
If you don't want it to be executed, set myFlag to false and after the checkbox state's change set it back to true.
Another option is to change the protected ClicksDisable property using an interposer class like this:
type
THackCheckBox = class(TCustomCheckBox)
end;
procedure TCheckBox_SetCheckedNoOnClick(_Chk: TCustomCheckBox; _Checked: boolean);
var
Chk: THackCheckBox;
begin
Chk := THackCheckBox(_Chk);
Chk.ClicksDisabled := true;
try
Chk.Checked := _Checked;
finally
Chk.ClicksDisabled := false;
end;
end;
I hope there's a button solution but you could store the current event in a TNotifyEvent var, then set Checkbox.OnChecked to nil and afterwards restore it.
try this way:
Checkbox.OnClick := nil;
try
Checkbox.Checked := yourFlag;
finally
Checkbox.OnClick := CheckboxClick;
end;
Use the focused property to establish if the control has been clicked or the checked has been updated outside the control.
If tcheckbox.focused then
run the content of the method
else
skip the content
Some other and much easier solution is not avoiding the the OnClick event but modifying the event handler not to respond unless the DataSet.State is in either dsEdit or dsInsert as initiated by a user triggered TDBCheckBox click e.g.:
procedure TForm1.chkSelectClick(Sender: TObject);
begin
if chkSelect.Checked = True then
if DataSource1.DataSet.State in [dsEdit,dsInsert] then
begin
{ your event handler }
end;
end;
CheckBox.State := cbUnchecked; works in Delphi, this doesn't fire onClickEvent AFAIK
Simple solution is to put your onclick code in onmouseup event;