how to determine which event calls procedure in delphi - delphi

I have a procedure named XYZ(sender:TObject) in delphi. There is one button on my form.
Button.onclick:= xyz;
Button.OnExit:= xyz;
Both the events calls the same procedure. I want to determine in procedure XYZ, which event calls this(onclick or onexit) and according to that proceed with coding.
How to determine which event gets fired? thanks

You can't get hold of that information by fair means. The solution is to use two separate top-level event handlers which in turn can call another method passing a parameter identifying which event is being handled.
type
TButtonEventType = (beOnClick, beOnExit);
procedure TMyForm.ButtonClick(Sender: TObject);
begin
HandleButtenEvent(beOnClick);
end;
procedure TMyForm.ButtonExit(Sender: TObject);
begin
HandleButtenEvent(beOnExit);
end;
procedure TMyForm.HandleButtonEvent(EventType: TButtonEventType);
begin
//use EventType to decide how to handle this
end;

Related

How to access a property of the component that the Sender parameter is referencing (the component that fired the event)

I am new to delphi and pascal and was wondering if there was a way to get/access a property of the component that the Sender is referencing within the procedure.
More specifically I would like to make a procedure that changes the caption property of a label, that label being the component that Sender is referencing.
I imagine that procedure looking something like:
procedure TForm1.LabelEdit(Sender: TObject);
begin
Sender.caption := 'Sample Text';
end;
Naturally this wouldn't work but can something like or something similar to this be done?
Although the example in your question doesn't really make sense (it incorrectly suggests that a TLabel has an OnEdit event), it is very much possible to use the Sender parameter to obtain information about the sender object.
Create a new VCL application and drop a number of TLabel controls on the form. Give them different captions (like Dog, Cat, Rabbit, Horse etc.).
Now select them all in the form designer and then use the Object Inspector to create a common OnClick handler for them. You can name it LabelClick (write LabelClick in the edit field next to OnClick and press Enter).
This will create the following empty method:
procedure TForm1.LabelClick(Sender: TObject);
begin
end;
It has a Sender parameter of type TObject. Now, depending on how this method is called, Sender can be any TObject (a button, a form, a bitmap, ...), or nil (no object at all).
But in our case, we expect this method mainly to be called in response to the labels being clicked on, and in these cases, the Sender will be the corresponding TLabel object.
Let's try to display the caption of the clicked label in a message box!
We try
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage(Sender.Caption); // won't compile!
end;
But this doesn't even compile! The problem is that TObject has no public Caption member. But TLabel does, so we can write
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage(TLabel(Sender).Caption);
end;
Here we are telling the compiler that we know Sender will always be a TLabel, and we ask it to assume that it is.
But this will crash or do other bad things if somehow this method is called with a non-TLabel Sender. So it is safer to do
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage((Sender as TLabel).Caption);
end;
This does the same, except that the compiler will now create code that checks at runtime that Sender really is a TLabel object. If not, the code will raise an exception. That's much better than the kind of memory corruption/AV issues you may get with the unsafe cast above.
Arguably even better is
procedure TForm1.LabelClick(Sender: TObject);
begin
if Sender is TLabel then
ShowMessage(TLabel(Sender).Caption);
end;
This will also test the type of Sender at runtime. If it is a label, we display its caption. Otherwise, we choose to do nothing. Notice that there is no point in using a safe (and slightly, slightly, slower) as cast here.
You cast Sender to the type that the event connects.
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(Sender) then
(Sender as TButton).Caption := 'Clicked';
end;
If you're sharing the event among different types of controls, you can test first to see what type it is:
procedure TForm1.ControlClick(Sender: TObject);
begin
if (Sender is TEdit) then
TEdit(Sender).Text := 'Clicked'
else if (Sender is TButton) then
TButton(Sender).Caption := 'Clicked';
end;
end;

How to pass DataSet: TDataSet as procedure parameter

I am setting up a new procedure which will show a message after executing a query. I am using the "AfterOpen" Event where i have to pass the "DataSet: TDataSet" parameter.
procedure Tf_SeznamDluzniku.ShowInfoMessage(DataSet: TDataSet; info : string);
begin
l_InfoMessage.Caption := info;
img_success.Visible := True;
end;
query.AfterOpen := ShowInfoMessage(,'InfoMessage here')
Can somebody please explain me what is the DataSet variable and what i have to pass to the procedure as the first parameter ?
If it is attached to the event, it is the dataset that triggered the AfterOpen event. The dataset itself will call the procedure, and pass itself in that parameter.
But you added the Info parameter, which makes the procedure invalid as an event handler. Where do you want that info to come from? From the dataset?
Since it's an event handler, it's bad practise to call it yourself. You could do it, and just pass nil (or a specific dataset), since it's not used anyway. But you can get into weird situations, because it looks like the method is only going to be called after open, but then it turns out it's called on other occasions as well.
So it's better to make a separate procedure to do what you want, and call that from the AfterOpen event handler. You can pass in info from the dataset, but you can also call that procedure from somewhere else, for instance to provide some initial caption until the dataset is opened:
// The procedure doesn't need the dataset, only the info to set.
procedure Tf_SeznamDluzniku.ShowInfoMessage(Info : string);
begin
l_InfoMessage.Caption := info;
end;
// The actual event handler for YourDataset.OnAfterOpen (you have to attach them)
// This can set the info, and/or maybe set the success indicator right away..
procedure Tf_SeznamDluzniku.YourDataSetAfterOpen(DataSet: TDataSet);
begin
ShowInfoMessage(DataSet.FieldByName('info').AsString);
img_success.Visible := True;
end;
// For demonstration, another event handler for showing the form, to put in some initial caption.
procedure Tf_SeznamDluzniku.FormShow(const Sender: TObject);
begin
ShowInfoMessage('Loading...');
end;

Displaying hints

I have added hints to components on my form. When the components receive the focus, I'd like to set the caption of a label component to display the hint.
I have added a TApplicationEvents object and set the OnShowHint event to
procedure TImportFrm.ApplicationEvents1ShowHint(var HintStr: string;
var CanShow: Boolean; var HintInfo: THintInfo);
begin
HelpLbl.Caption := HintStr;
end;
However it seems that the ShowHint event only fires with mouse movements. Is there a way to fire the hint code when components receive focus, without having to implement the OnEnter event for every single component on the form?
Add a handler for TScreen.OnActiveControlChange in your main form's creation, and handle the hints in that event:
type
TForm2=class(TForm)
...
private
procedure ScreenFocusControlChange(Sender: TObject);
end;
implementation
procedure TForm2.FormCreate(Sender: TObject);
begin
Screen.OnActiveControlChange := ScreenFocusControlChange;
end;
procedure TForm2.ScreenFocusControlChange(Sender: TObject);
begin
Label1.Caption := ActiveControl.Hint;
Label1.Update;
end;
Note that Sender won't do you much good; it's always Screen. You can filter (for instance, to only change the Label.Caption for edit controls) by testing the ActiveControl:
if (ActiveControl is TEdit) then
// Update caption of label with ActiveControl.Hint
Note that if you'll need to reassign the event when you show child forms (to an event on that child form), or you'll always be updating the original form's label with the hints. The easiest way to do the reassignment is to give every form an OnActiveControlChange handler, and assign it in the form's OnActivate event and unassign it in the OnDeactivate event:
procedure TForm1.FormActivate(Sender: TObject);
begin
Screen.OnActiveControlChange := Self.ScreenActiveControlChange;
end;
procedure TForm1.FormDeactivate(Sender: TObject);
begin
Screen.OnActiveControlChange := nil;
end;
This will allow you to update controls other than Label1 on each form, and only use the hint changes on forms you want to do so.
A simple solution is to use OnIdle event:
procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
if Assigned(ActiveControl) then
Label1.Caption:= ActiveControl.Hint;
end;
A more advanced solution is to override protected ActiveChanged method of TForm:
type
TForm1 = class(TForm)
...
protected
procedure ActiveChanged; override;
end;
...
procedure TForm1.ActiveChanged;
begin
inherited;
if Assigned(ActiveControl) then
Label1.Caption:= ActiveControl.Hint;
end;
Receiving focus and OnShowHint are quite different events; OnShowHint can be triggered for non-focused control as well.
Why would you need to implement the OnEnter event for every single component? You can create one generic method / event handler like:
procedure TForm1.AnyControlEnter(Sender: TObject);
begin
lbl1.Caption := TControl(Sender).Hint;
end;
and assign it to every component you placed on the form.
You said:
it seems that the ShowHint event only fires with mouse movements
This is a normal behaviour. The problem you have ( it's a guess) is that hints are not fired directly. Don't try to make a workaround, what you try to do with MouseEnter is exactly what is already happening...the only difference is that you'be forget something...
Keep the event ApplicationEvents1ShowHint() as you've initially done but add this in the form constructor event:
Application.HintPause := 1;
And then hints will be displayed (almost) without delay.

How to free control inside its event handler?

Does anybody know the trick, how to free control inside its event handler ? According delphi help it is not possible...
I want to free dynamicaly created TEdit, when Self.Text=''.
TAmountEdit = class (TEdit)
.
.
public
procedure KeyUp(var Key: Word; Shift :TShiftState);
end;
procedure TAmountEdit.KeyUp(var Key: Word; Shift :TShiftState);
begin
inherited;
if Text='' then Free; // after calling free, an exception arises
end;
How should do to achieve the same effect?
Thanx
The solution is to post a queued message to the control, which it responds to by destroying itself. Ny convention we use CM_RELEASE which is the private message used by TForm in its implementation of the Release method that performs an analogous task.
interface
type
TAmountEdit = class (TEdit)
...
procedure KeyUp(var Key: Word; Shift :TShiftState); override;
procedure HandleRelease(var Msg: TMessage); message CM_RELEASE;
...
end;
implementation
procedure TAmountEdit.KeyUp(var Key: Word; Shift :TShiftState);
begin
inherited;
if Text = '' then
PostMessage(Handle, CM_RELEASE, 0, 0);
end;
procedure TAmountEdit.HandleRelease(var Msg: TMessage);
begin
Free;
end;
The control is destroyed when the application next pumps its message queue.
Before implementing this I would stop and ask "Is this really the best approach?"
Do you really want an edit control class that always destroys itself when key input results in the Text property becoming an empty string?
Is it not more likely to be the case that you have a specific form/dialog where this behaviour is required? In which case, there is no problem... you can free the edit control in the KeyUp event handled by the form without incurring an Access Violation.

Access an event on a DataModule from another Form

In Delphi 2009 I have a Form with a procedure MyProcedure that writes to a label on the Form. The form uses a DataModule with a ClientDataSet. When the AfterScroll event of the ClientDataSet is fired MyProcedure should be executed.
To avoid circular references and more important, as I want the DataModule to be reusable,
the DataModule should not reference to this specific Form.
In short, I hope that I can access the AfterScroll event from my Form. Can I hook up the Afterscroll event on the DataModule from my Form? I thought it should be possible, but I cannot remember how to do it. Thanks in advance.
You put an event property in your DataModule:
private
FOnAfterScroll : TNotifyEvent;
public
property OnAfterScroll : TNotifyEvent read FOnAfterScroll write FOnAfterScroll;
You then call that event in the AfterScroll procedure in the DataModule:
If Assigned(FOnAfterScroll) then FOnAfterScroll(Self);
In Form:
declare event handler
procedure HandleAfterScroll(Sender : TObject);
Then you assign a procedure to DataModule's OnAfterScroll
Datamodule1.OnAfterScroll :=
MyHandleAfterScroll;
Another way would be to send a custom windows message from DataModule and to respond to that message in the Form.
Should be something like:
procedure TForm1.FormCreate(Sender: TObject);
begin
DataModule1.MyCDS.AfterScroll := MyAfterScrollHandler;
end;
If all you want is to declare the event handler in a different unit, like the form, go with Ulrich's suggestion. If you want to be able to put a default event handler in your data module but then be able to extend its behavior, it takes a bit more work. You can do this by adding an event to the data module.
Define a method pointer with the appropriate signature and add one to the data module at public scope, like so:
type
TMyEvent = procedure({arg list here}) of object;
TMyDataModule = class(TDataModule)
//definition goes here
procedure MyTableAfterScroll({arg list here});
private
FExternalEvent: TMyEvent;
public
property ExternalEvent: TMyEvent read FMyEvent write FMyEvent
end;
implementation
procedure TMyDataModule.MyTableAfterScroll({arg list here});
begin
//do whatever
if assigned(FExternalEvent) then
FExternalEvent({whatever arguments});
//do more stuff, if you'd like
end;
To hook it up, in your form's OnCreate, just assign your procedure to MyDataModule.ExternalEvent and you'll be good to go.

Resources