dormky 2 Posted August 4, 2023 (edited) I have checkboxes that get initialized in FormCreate. This means that the OnClick callback gets called (SetChecked does that). How can I prevent this ? Either by detecting whether the callbakc is triggered by an actual click or by setting the value without triggering the callback. I'm using checkboxes in this instance but this is of course a general question for other controls too. Thanks ! Edited August 4, 2023 by dormky Share this post Link to post
Uwe Raabe 2064 Posted August 4, 2023 It affects all TButtonControl descendants of which TCheckBox is just one. The easiest way to avoid that is to use actions. Another way would be to make use of the (protected) ClicksDisabled property, which skips the call to the event handler. That is basically the same as what the actions do. 2 Share this post Link to post
Patric van de Pol 0 Posted August 4, 2023 (edited) Several ways to do that. 1. A small procedure in your toolbox: procedure CheckBoxPreset(ACheckBox: TCheckBox; AChecked: Boolean); var LBckEvent: TNotifyEvent; begin LBckEvent := ACheckBox.OnClick; ACheckBox.OnClick := nil; ACheckBox.Checked := AChecked; ACheckBox.OnClick := LBckEvent; end; 2. Use a class helper type TCustomCheckBoxHelper = class helper for TCustomCheckBox procedure ForceChecked(); procedure ForceUnchecked(); procedure Preset(AValue: Boolean); end; Implementation procedure TCustomCheckBoxHelper.ForceChecked(); begin with Self do FState := cbChecked; end; procedure TCustomCheckBoxHelper.ForceUnchecked(); begin with Self do FState := cbUnchecked; end; procedure TCustomCheckBoxHelper.Preset(AValue: Boolean); begin if AValue then ForceChecked() else ForceUnchecked(); end; Then access the helper methods like a regular method of checkbox: cbMyCheckbox.ForceChecked(); cbMyCheckbox.ForceUnchecked(); cbMyCheckbox.Preset(True); Edited August 4, 2023 by Patric van de Pol Share this post Link to post
Uwe Raabe 2064 Posted August 4, 2023 Simply setting FState doesn't reflect the change in the control while its handle is allocated. That may work for some initialization during FormCreate before the handle allocated, but is no general solution. Share this post Link to post
Remy Lebeau 1436 Posted August 4, 2023 10 hours ago, dormky said: I have checkboxes that get initialized in FormCreate. This means that the OnClick callback gets called (SetChecked does that). How can I prevent this ? You can either: not assign the OnClick handler at design-time, but at runtime instead after you are done initializing the control. set a variable somewhere that the event handler can look at, and then clear that variable when finished. Share this post Link to post
David Schwartz 430 Posted August 4, 2023 I have a var in the form class named 'ignore_this' and set it to true before doing something that will fire an event automatically that I want ignored, then set it false afterwards, and I put "if ignore_this then Exit;" at the top of the event handler. Sometimes there's so much stuff like that in the OnFormCreate that I just have a 'creating_form' variable that's set True at the top and False at the bottom of OnFormCreate so event handlers can get bypassed. Share this post Link to post
Vincent Parrett 765 Posted August 5, 2023 I have a loading flag variable that I set in the form create and then check in the event handlers - clear the flag when done initialising the controls. Crude, but simple. Share this post Link to post
Uwe Raabe 2064 Posted August 5, 2023 One can also use the csUpdating flag in ComponentState, which is controlled by the Updating and Updated methods. It works for all TComponent descendants. But now we are in a much wider realm than the original OnClick problem describes. Share this post Link to post
dormky 2 Posted August 9, 2023 Sorry for the delay. Here's how it ended up, using @Uwe Raabe's proposition of setting ClicksDisabled to True : uses Vcl.StdCtrls; type THelperCheckBox = class helper for TCheckBox procedure SetValue(const value: Boolean); end; implementation procedure THelperCheckBox.SetValue(const value: Boolean); begin ClicksDisabled := True; try Checked := value; finally ClicksDisabled := False; end; end; For now I've set it for checkboxes only, but will probably copy/extend it to other components or just TButtonControl. Maybe with runtime checks of the actual class of the instance the method is called on. Thanks everyone. Share this post Link to post
Alexander Halser 26 Posted August 25, 2023 (edited) On 8/4/2023 at 9:48 AM, dormky said: I have checkboxes that get initialized in FormCreate. This means that the OnClick callback gets called That doesn't apply to checkboxes alone. Radio buttons, radio groups, checked menu items, actions, OnResize events, you name it. Some forms have plenty of details to initialize. We have been using update counters for that, since more than 20 years. Like, every TForm that does some initialization which may auto-trigger events, has an IsUpdating property. The setter increases/decreases the value, the getter returns a boolean. If IsUpdating returns true (> 0), the checkboxes do not execute their OnChange event, manually arranged panels do not update, etc. constructor TForm1.Create(AOwner: TObject); begin inherited; FIsUpdating := 0; IsUpdating := true; //increases FIsUpdating try //do your init here finally IsUpdating := false; end; end; procedure TForm1.SetIsUpdating(value: boolean); begin if value then inc(FIsUpdating) else dec(FIsUpdating); end; function TForm1.GetIsUpdating: boolean; begin result := FIsUpdating > 0; end; A counter is more flexible and error-tolerant than using the csUpdating flag in ComponentState. It can be used to update corresponding controls in an OnChange event as well, without triggering their OnChange. The counter enables nested IsUpdating without worries. The only thing to remember is to consequently use try-finally. Edited August 25, 2023 by Alexander Halser Share this post Link to post
David Schwartz 430 Posted August 26, 2023 8 hours ago, Alexander Halser said: A counter is more flexible and error-tolerant than using the csUpdating flag in ComponentState. csUpdating is for use within components, and it's not just used for initialization. Using it to manage initialization in forms is ill-advised. There are three "levels" of initialization in forms: FormCreate, FormShow, and FormActivate. They occur at different times, and afford a lot of control over timing issues. If you want to see some really unpredictable timing issues with events, take a look at TTreeView and TListView controls. If you put some trace logic in the event handlers, you'll notice that several are called twice for every change. That can cause really strange things to happen if the handler does anything other than update a spot in the control. 1 Share this post Link to post