bazzer747 25 Posted April 24, 2020 Hi On a form I have a dbgrid and a radiogroup. When a User clicks on the dbgrid I read a table which updates relevant radio buttons in the radiogroup ( a Yes, Maybe, No selection). If a User clicks one of the radio buttons it fires off table updates (setting the Yes, Maybe, No fields). However, the click on the dbgrid also sets the radio buttons by reading the information on the row clicked, which in turn is firing the radiogroup onclick event, which I don't want to happen. Is there a way to 'know' what component was used when running a procedure. This way if the dbgrid was where it came from I could bypass the updates. I've used Sender several times, but this seems to only say what form I came from. Share this post Link to post
Der schöne Günther 316 Posted April 24, 2020 The easiest way is probably just temporarily deactivating the OnClick event that bugs you when you set it yourself: var notifyEvent: TNotifyEvent; begin notifyEvent := radioGroup.OnClick; try radioGroup.OnClick := nil; radioGroup.Something := Your_Choice; finally radioGroup.OnClick := notifyEvent; end; end; 1 Share this post Link to post
Attila Kovacs 631 Posted April 24, 2020 Same goes for checkbox too... Did you check TDBRadioGroup? Can't you use that instead? Share this post Link to post
Guest Posted April 24, 2020 As Gunter says, is a quite common practice. Another way is to use sentries, a form field that will tell to skip updates. From the one place: ... Inc(FInUpdatingTable); try ... finally Dec(FInUpdateingTable); ... In the other place if FInUpdatingTable > 0 then exit; You can use a boolean, but using an integer is common because it can handle multiple levels. Do not forget the try ... finally even if it looks like nothing could happen. HTH Share this post Link to post
bazzer747 25 Posted April 24, 2020 Dany, Works a treat - once I got my head around the logic of what goes where :-). Many thanks for the tip. Gunther, will try your method as well. Attila, this seems like another way, which would avoid the complication I got myself into, so I'll also try this. To all, many thanks. Share this post Link to post
Guest Posted April 24, 2020 @Dany Marmur That is neat ! Went ahead and switched to that model in my project, i might add using the TComponent.Tag every cleaner ! Only one downside ( inconvenient ), Inc and Dec can't be used with Tag , a helper did the job. Share this post Link to post
aehimself 399 Posted April 24, 2020 4 hours ago, Dany Marmur said: As Gunter says, is a quite common practice. I am actively using this solution (usually with an atomic value) but it always felt hacky. Strange to see that it seems to be THE way. At least I did not write smelly code. At least in this part... 🙂 Share this post Link to post
Fr0sT.Brutal 900 Posted April 25, 2020 Flags or removing event handlers are OK. It's the only option to get things done. VCL is very dumb in some places. F.ex., some controls do not distinguish between user-made and app-made change calling handlers in both cases and some see the difference so you have to run control.OnChange() manually Share this post Link to post
Guest Posted April 26, 2020 @Kas Ob. why would you need the component property (Tag) in this case? IMHO keeping the sentry as form-member or as-component-member-accessed-by-form do not usually provide any benefits. You are welcome to expand on your idea. /D Share this post Link to post
Lars Fosdal 1793 Posted April 26, 2020 I usually have a construct which I check in the event UI handlers. If I am writing changes to the UI, the event handler contains a busy check. type TGuard = record private FBusy: Integer; public procedure Enter; // Increments FBusy procedure Leave; // Decrements FBusy function Busy: Boolean; //True if FBusy > 0 function Idle: Boolean; // not Busy end; // In the form/frame type TMyForm = class(TForm) protected Update: TGuard; //... procedure TMyForm.UpdateUI; begin Update.Enter; try CheckBox1.Checked := TrueOrFalse; finally Update.Leave; end; end; procedure TMyForm.OnCheckBox1Clicked(sender: TObject); begin if Update.Busy then Exit; // do the event handling 2 Share this post Link to post
dummzeuch 1517 Posted April 26, 2020 15 minutes ago, Lars Fosdal said: type TGuard = record private FBusy: Integer; public procedure Enter; // Increments FBusy procedure Leave; // Decrements FBusy function Busy: Boolean; //True if FBusy > 0 function Idle: Boolean; // not Busy end; end; When I see code like this I always wonder why the functions aren't called isBusy and isIdle rather than Busy and Idle. Prefixing them with "is" makes it clear that they will return a Boolean value. 1 Share this post Link to post
Guest Posted April 26, 2020 (edited) 4 hours ago, Dany Marmur said: You are welcome to expand on your idea. My idea is simple, to use your brilliant method, but with every control out of the box without introducing new field. Simple like this helper : type // Note : Tag should not have negative value or have a value bigger than 2^24 on 32Bit and 2^56 on 64Bit. TComponentHelper = class helper for TComponent const EVENT_LOCK_BIT_INDEX = (SizeOf(NativeInt) - 1) * 8; EVENT_LOCK_CONST = Int64(1) shl EVENT_LOCK_BIT_INDEX; public procedure EventsEnable; procedure EventsDisable; function EventsEnabled: Boolean; function TagInsideEvent: Integer; overload; procedure TagInsideEvent(Value: Integer); overload; end; { TComponentHelper } procedure TComponentHelper.EventsDisable; begin if NativeUInt(Tag) >= EVENT_LOCK_CONST * 127 then raise Exception.Create('Error : events locks count reached the limit = 127'); Tag := Tag + EVENT_LOCK_CONST; end; procedure TComponentHelper.EventsEnable; begin if Tag < EVENT_LOCK_CONST then raise Exception.Create('Error : events are already Enabled'); Tag := Tag - EVENT_LOCK_CONST; end; function TComponentHelper.EventsEnabled: Boolean; begin Result := Tag shr EVENT_LOCK_BIT_INDEX = 0; end; procedure TComponentHelper.TagInsideEvent(Value: Integer); begin Tag := (Tag and (not (EVENT_LOCK_CONST - 1))) or Value; end; function TComponentHelper.TagInsideEvent: Integer; begin Result := Tag and (EVENT_LOCK_CONST - 1); end; And its usage: procedure TForm1.btnClearClick(Sender: TObject); begin ComboBox1.Tag := 15; Edit1.Tag := 2; ComboBox1.EventsDisable; Edit1.EventsDisable; try if ComboBox1.EventsEnabled or Edit1.EventsEnabled then raise Exception.Create('Error Message'); Edit1.Clear; ComboBox1.ItemIndex := 0; Assert(ComboBox1.TagInsideEvent = 15); Assert(Edit1.TagInsideEvent = 2); ComboBox1.TagInsideEvent(8); Edit1.TagInsideEvent(777); finally ComboBox1.EventsEnable; Edit1.EventsEnable; end; Assert(ComboBox1.Tag = 8); Assert(Edit1.Tag = 777); end; procedure TForm1.ComboBox1Change(Sender: TObject); begin //if ComboBox1.EventsEnabled then //if TComponent(Sender).EventsEnabled then if (Sender as TComponent).EventsEnabled then begin ShowMessage((Sender as TComponent).Name + ' Changed'); end; end; procedure TForm1.Edit1Change(Sender: TObject); begin if (Sender as TComponent).EventsEnabled then begin ShowMessage((Sender as TComponent).Name + ' Changed'); end; end; I like that. EDIT : fixed typo and EventsDisable raise exception in case of Tag value was negative. Edited April 26, 2020 by Guest Share this post Link to post
Guest Posted April 26, 2020 I forgot to mention this helper is using the most significant byte in Tag for this signaling Share this post Link to post
Lars Fosdal 1793 Posted April 27, 2020 10 hours ago, dummzeuch said: When I see code like this I always wonder why the functions aren't called isBusy and isIdle rather than Busy and Idle. Prefixing them with "is" makes it clear that they will return a Boolean value. Good point. Share this post Link to post