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;
Related
Code like this logs all table inserts (from the entire application):
procedure TForm1.ACRDatabase1AfterInsertRecord(Sender: TACRDataSet;
const TableName: WideString; const FieldValues: TACRArrayOfTACRVariant);
begin
if (AnsiUpperCase(TableName) = AnsiUpperCase(LogTable.TableName)) then
Exit;
if (Sender is TACRTable) then
LogTable.Insert();
LogTable.FieldByName('EventTime').AsDateTime := Now;
LogTable.FieldByName('TableName').AsString := TableName;
LogTable.FieldByName('EventType').AsString := 'Insert ';
LogTable.FieldByName('Whatever').AsString := FieldValues[4].AsString;
LogTable.Post();
end;
But fieldValues are different for each table so you might crash
the application (almost sure) using fieldvalues i.e their index number.
How do you overcome this ? Is it possible to log each table separately ?
As I mentioned in a comment, I don't have Accuracer, but thought it might be helpful
to post a generic method of doing client-side logging, which can capture the value
of one or more fields and be used for as many datasets as you need. You may be
able to use part of it in your ACRDatabase1AfterInsertRecord handler, as its Sender
parameter appears to identify the dataset into which the new row has been inserted.
As you can see, there is a LogFields procedure which can be included in the AfterInsert
handler of any dataset you like and this calls a separate GetFieldsToLog procedure which
adds the names of the field(s) to log for a given dataset to a temporary StringList. It's
only the GetFieldsToLog procedure which needs to be adapted to the needs of a given set of datasets.
procedure TForm1.GetFieldsToLog(ADataSet : TDataSet; FieldList : TStrings);
begin
FieldList.Clear;
if ADataSet = AdoQuery1 then begin
FieldList.Add(ADataSet.Fields[0].FieldName);
end
else
// obviously, deal with other specific tables here
end;
procedure TForm1.LogFields(ADataSet : TDataSet);
var
TL : TStringList;
i : Integer;
ValueToLog : String;
begin
TL := TStringList.Create;
try
GetFieldsToLog(ADataSet, TL);
for i := 0 to TL.Count - 1 do begin
ValueToLog := ADataSet.FieldByName(TL[i]).AsString;
// do your logging here however you want
end;
finally
TL.Free;
end;
end;
procedure TForm1.ADOQuery1AfterInsert(DataSet: TDataSet);
begin
LogFields(DataSet);
end;
Btw, one of the points of having a separate GetFieldsToLog procedure is that it helps to extend
client-side logging to changes in existing dataset records. If you generate this list
at start-up and save it somewhere, you can use it in the BeforePost event of a dataset to
pick up the current and previous values of the field (using its Value and OldValue properties),
save those in an another StringList and log them in the AfterPost event. Of course,
if you'e using a common store for these value from more than one dataset, you need to make
sure that the AfterPost of one dataset fire before the BeforePost of any other, or do the logging
entirely within the BeforePost (having to store the old and current field values between
Before- and AfterPost is messy, and it would be better to do everything in the AfterPost,
but unfortunately the OldValue is out-of-date by the time AfterPost occurs.
Be aware that getting the OldValue requires the specific dataset type to correctly implement
it. Not all types of dataset I've come across do, though, so it needs checking.
Btw #2, supposing you have a procedure like this
procedure TForm1.DoSomething(AnObject : TObject);
then you can use "if AnObject is ..." to do something like this
var
AnAdoQuery : TAdoQuery;
begin
if AnObject is TAdoQuery then begin
// First, use a cast to assign Sender to the local AnAdoQuery variable
AnAdoQuery := TAdoQuery(AnObject);
// Then, we can do whatever we like with it, e.g.
Caption := AnAdoQuery.Name;
end;
end;
Otoh, if for some reason (and I can't immediately think why we would want to but never mind)
we just want to check that what we've been passed as the AnObject parameter is a particular
object, we can omit the cast and just do
if AnObject = AdoQuery1 then
ShowMessage('Received AdoQuery1');
This equality check works, regardless of the actual class of what we've been passed
as the AnObject parameter because all other classes are descendants of AnObject's
declared class, namely TObject.
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;
I have some menu items where the action is this:
procedure TMISMainFrm.ActiSalesInvoiceExecute(Sender: TObject);
begin
if CheckMdiList('Sales Invoice') then
BEGIN
SalesInvFrm := tSalesInvFrm.Create(Self,0,UserIdNo,1,1);
SalesInvFrm.Show;
END;
end;
The above action can be called from several locations, but the 2nd parameter (the 0), may change. How do I pass the required parameter such that I do not have to recode the routine?
One simple approach, commonly used, is to set the Tag property of the action. It should be different for each action, obviously. Then you change your execute handler like so:
procedure TMISMainFrm.ActiSalesInvoiceExecute(Sender: TObject);
begin
if CheckMdiList('Sales Invoice') then
BEGIN
SalesInvFrm := tSalesInvFrm.Create(Self,(Sender as TAction).Tag,UserIdNo,1,1);
SalesInvFrm.Show;
END;
end;
I have a design and run-time component that contains a large number of event handlers. I'll call it TNewComp for now. I create an instance of TNewComp on a TForm and fill in the event stubs with specific code via the property editor at design time, and realize I would like to be able to create new instances of TNewcomp that use the current set of event handler code.
To do this now, I call TNewComp's constructor and then "manually" assign each of the new instance's event handlers the corresponding event stub code resident on the form that contains the TNewComp instance created at design time. So if I have an instance of TNewComp assigned to a variable named FNewComp on a form called TNewForm, for each event handler I would do:
FNewComp.onSomething = TNewform.onSomething
(... repeat for each event handler belonging to TNewComp ...)
This works fine, but it is cumbersome and worse, if I add a new event handler to TNewComp, I have to remember to update my "newTComp()" function to make the event handler assignment. Rinse and repeat this process for every unique component type that I create new instances of dynamically.
Is there way to automate this process, perhaps using property inspection or some other Delphi 6 introspection technique?
-- roschler
I used the following code. Be careful with the Dest owner when creating, the safest way is to pass Nil and free the component by yourself later.
implementation uses typinfo;
procedure CopyComponent(Source, Dest: TComponent);
var
Stream: TMemoryStream;
TypeData : PTypeData;
PropList: PPropList;
i, APropCount: integer;
begin
Stream:=TMemoryStream.Create;
try
Stream.WriteComponent(Source);
Stream.Position:=0;
Stream.ReadComponent(Dest);
finally
Stream.Free;
end;
TypeData := GetTypeData(Source.ClassInfo);
if (TypeData <> nil) then
begin
GetMem(PropList, SizeOf(PPropInfo)*TypeData^.PropCount);
try
APropCount:=GetPropList(Source.ClassInfo, [tkMethod], PropList);
for i:=0 to APropCount-1 do
SetMethodProp(Dest, PropList[i], GetMethodProp(Source, PropList[i]))
finally
FreeMem(PropList);
end;
end;
end;
One option would be to save "the properly set up component" into stream and then load that strem into new, dynamically created component as if it is done by Delphi IDE/runtime.
Another option is to use RTTI, the TypInfo unit. There you have function GetPropList witch will enable you to query for available events (TypeKind tkMethod) and then you can use GetMethodProp and SetMethodProp to copy eventhandlers from one component to other.
I tweaked Maksee's solution to the following:
function CopyComponent(Source: TComponent; Owner: TComponent = nil): TComponent;
var
Stream: TMemoryStream;
TypeData : PTypeData;
PropList: PPropList;
i, APropCount: integer;
begin
if not Assigned(Source) then
raise Exception.Create('(CopyComponent) The Source component is not assigned.');
Result := TComponent.Create(Owner);
Stream := TMemoryStream.Create;
try
Stream.WriteComponent(Source);
Stream.Position := 0;
Stream.ReadComponent(Result);
finally
Stream.Free;
end; // try()
// Get the type data for the Source component.
TypeData := GetTypeData(Source.ClassInfo);
if (TypeData <> nil) then
begin
// Get the property information for the source component.
GetMem(PropList, SizeOf(PPropInfo) * TypeData^.PropCount);
try
// Get the properties count.
APropCount := GetPropList(Source.ClassInfo, [tkMethod], PropList);
// Assign the source property methods to the destination.
for i := 0 to APropCount - 1 do
SetMethodProp(Result, PropList[i], GetMethodProp(Source, PropList[i]))
finally
// Free the property information object.
FreeMem(PropList);
end; // try()
end; // if (TypeData <> nil) then
end;
So that a new component is returned by the function rather than passing in an existing component reference (the Dest parameter in Maksee's version). If anyone can see a flaw or problem that will result from this variant please comment.
I have a custom component of type TSpeedButton that has two extra properties defined:
CommentHeading: string;
CommentText: string;
I set CommentHeading at design time.
When the speed button is pressed a memo is shown with a button beneath it for saving its contents. The procedure that handles this:
procedure CustomSpeedButton1Click(Sender: TObject);
begin
Receiver := CustomSpeedButton1.Name; // possibly used to save the memo text back to this speedbuttons property after comments are submitted
ViewComments(CustomSpeedButton1.CommentTitle,CustomSpeedButton1.CommentText);
end;
And the ViewComments procedure itself:
procedure ViewComments(comment_caption:string; comment_text:string);
begin
label15.Hide; // label showing editing in progress, hidden until user begins typing
Button1.Enabled := false; // the button for saving the memo text, hidden until user begins typing
CommentsBox.Visible := true; // pop up the comment box at the bottom of the form
CommentsBox.Caption := 'Comments: ' + comment_caption;
CommentsMemo.Text := comment_text; // if there are existing comments assign them to memo
end;
The contents of the memo need to be assigned to the CommentText property of the custom SpeedButton.
What I was initially thinking was that I could pass the component name to a variable when the custom SpeedButton gets pressed and then retrieve that name when the save button on the memo is pressed and use it to assign the memo text to the speedbuttons CommentText property. But then I realized that to do this I'd have to use some kind of case..of statement that checked for each possible speedbutton name and then assign the memo value to its properties and this just seems ridiculously tedious.
Is there an easier way to assign the memo text to the speedbutton that opened the memo to begin with?
Ultimately, you're asking how to tell the ViewComments function which button's properties it's working with.
Do you understand what the Sender parameter is doing in the OnClick event? It's telling the event handler which object's event is being handled. It's serving precisely the role that you're looking to bring to the ViewComments function.
That's what Mason was getting at in his answer. Rather than pass all the property values, pass the object itself:
procedure ViewComments(CommentButton: TCustomSpeedButton);
Then call it from all your buttons' event handlers:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton1);
end;
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton2);
end;
No strings, no case statements, no lookups.
That should answer your question, but you can do it even better. Remember what I said before about the Sender parameter? When someone clicks the first button, the Sender parameter of that OnClick handler will be the button, so we can rewrite the first event handler like this:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
And you can rewrite the second event handler like this:
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Hmm. They're the same. Having two identical functions is wasteful, so get rid of one and rename the other so it doesn't sound button-specific:
procedure TForm1.CommentButtonClick(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Then set the OnClick properties of both buttons to refer to that one event handler. You can't do that just by double-clicking the property in the Object Inspector. You'll need to either type the name yourself, choose it from the drop-down list, or assign the event property at run time:
CustomSpeedButton1.OnClick := CommentButtonClick;
CustomSpeedButton2.OnClick := CommentButtonClick;
I'd also like to encourage you to use more meaningful names for your controls. That Label15 is particularly egregious. How can you remember that the fifteenth label is the one that indicates that editing is in progress? Call it EditInProgressLabel, for instance.
Since you're already passing extra variables around, why not just pass the SpeedButton itself? Then you won't need to look up the reference.
A small change to your code should do the trick:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
var
btn: TCustomSpeedButton;
begin
btn := Sender as TCustomSpeedButton;
Receiver := btn.Name;
ViewComments(btn.CommentTitle, btn.CommentText);
end;
and after editing the comment:
procedure TForm1.StoreComments(comment: string);
var
btn: TCustomSpeedButton;
begin
btn := FindComponent(Receiver) as TCustomSpeedButton;
btn.CommentText := comment;
end;
You can also memorize the button itself instead of just it's name.