Jump to content
bazzer747

Where did I come from

Recommended Posts

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

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;

 

  • Like 1

Share this post


Link to post
Guest

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

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

@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
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

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

@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

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

 

  • Like 2

Share this post


Link to post
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.

  • Like 1

Share this post


Link to post
Guest
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 by Guest

Share this post


Link to post
Guest

I forgot to mention this helper is using the most significant byte in Tag for this signaling 

Share this post


Link to post
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×