I have an ADOStoredProc on my form. It's not visual but in code.Normally it's pretty easy to handle an event if a component is visual.It's just a matter of double clicking the desired event. But how do I do it with code.I've declared a procedure:
procedure SP_SearchAfterScroll(DataSet:TDataSet)
Now how do I assign SP_Search(this is the ADOStoredProc) AfterScroll event handler property to the procedure I wrote above. I'm sure you're going to answer it. So thanks in advance.
When SP_Search is the TAdoStoredProc and has an OnAfterScroll property, all you need to do is:
SP_Search.OnAfterScroll := SP_SearchAfterScroll;
I am assuming that you used the correct signature for SP_SearchAfterScroll. That is to say that the OnAfterScroll property has a type looks like:
TScrollEvent = procedure(DataSet: TDataSet) of object;
If the OnAfterScroll property has a type that differs from this, you will need to make sure that your SP_SearchAfterScroll procedure matches the parameters in that type.
Edit
In the comments Mikayil asked
SP_Search.AfterScroll :=
SP_SearchAfterScroll(SPSearch)' the
compiler complains saying incompatible
types TNotifyEvent and procedure. But
when I write SP_Search.AfterScroll :=
SP_SearchAfterScroll it works. What's
the difference?
I hadn't gotten round to answering that and in the mean time Mikey explained it very well, so for (easier) future reference I am including his explanation up here:
SP_Search.AfterScroll := that code
assigns a function to handle the event
when it fires - you are not making a
call to SP_SearchAfterScroll at
'assign time' just assigning a value
to a variable, so you don't pass
parameter. Parameter is needed when
call is made - when event fires then
caller will assign parameter with the
right value. When you pass the
parameter,compiler assumes you are
calling the function, not assigning
it, so you get incompatible types
error. When you simply assign the
function without the parameter,
compiler understands you're assigning,
not calling the function.
Declare as:
TDataSetNotifyEvent
then it works
Related
I have come across a below function in a delphi code. I am quite new to delphi.There are quite a few places in Delphi where this function is called. However I can't seem to find the definition of this function. Could someone please explain what this means.
property OnProcessEvent: TOnProcessEventProc read FOnProcessEvent write FOnProcessEvent;
That declaration is not a function, it is a property, or more specifically an event. In that same class, you will see a data member named FOnProcessEvent of type TOnProcessEventProc. If you look at the declaration of TOnProcessEventProc, you will see that it is an alias for a method pointer of a specific signature, eg:
type
TOnProcessEventProc = procedure(Sender: TObject; ... other parameters here ...) of object;
That means any non-static class method that matches that signature can be assigned to the OnProcessEvent event. And if the event is declared as published, such a method can even be assigned at design-time instead of in code at run-time.
In the code for the class that declares the event property, all it has to do is call FOnProcessEvent() as if it were a procedure, eg:
if Assigned(FOnProcessEvent) then
FOnProcessEvent(Self, ... parameter values here ...);
Whatever method is actually assigned to FOnProcessEvent, if any, will be called.
When I'm building a custom component, I may implement published persistent properties. For example...
type
TMyComponent = class(TComponent)
private
FMyPersistent: TMyPersistent;
...
public
...
published
property MyPersistent: TMyPersistent read FMyPersistent write SetMyPersistent;
...
end;
Note that the procedure SetMyPersistent is not here yet, that's where the next step comes in. I right-click this object and select "Complete Class at Cursor" (or Shift + Control + C) to invoke the code completion. When it automatically creates this property setter, it automatically puts the assignment code in...
procedure TMyComponent.SetMyPersistent(const Value: TMyPersistent);
begin
FMyPersistent := Value;
end;
Now it's nice that it went ahead and completed this assignment for me. However, in normal cases, I've always grown accustomed to using...
procedure TMyComponent.SetMyPersistent(const Value: TMyPersistent);
begin
FMyPersistent.Assign(Value);
end;
In cases where the property is a type such as String or Integer, then a direct assignment is the proper way to do it. But when implementing a published property of a TPersistent, isn't it the correct method using TPersistent.Assign?
What's the essential difference between using these two assignment mechanisms? Because if using TPersistent.Assign is the appropriate thing to do, then the code completion has a slight flaw - that is, assuming that FMyPersistent := Value is considered "wrong".
Call Assign. That's why you have a property setter in the first place. If you were to directly overwrite the field, you wouldn't need a setter. Overwriting it would leak the original object you created in the constructor. You'd also notice access violations in the IDE when you modified the property in the Object Inspector.
Code completion puts the same code in every setter it creates. For properties that have additional work to do before ultimately storing the value in a field, so the field-storage statement is correct. The IDE doesn't know what you really want.
The question you should be asking yourself is - who owns the objects involved? If your component creates and owns FMyPersistent then use FMyPersistent.Assign(Value) to copy values from Value into FPersistent. If FMyPersistent merely points at an external object that someone else owns then use FMyPersistent := Value instead.
What's the difference between parameters declared with var and those declared with out? How does the compiler treat them differently (e.g., by generating different code, or by changing which diagnostics it issues)? Or do the different modifiers merely allow the programmer to document intended use of the parameters? What effect do the types of the parameters have on the matter?
A var parameter will be passed by reference, and that's it.
An out parameter is also passed by reference, but it's assumed that the input value is irrelevant. For managed types, (strings, Interfaces, etc,) the compiler will enforce this, by clearing the variable before the routine begins, equivalent to writing param := nil. For unmanaged types, the compiler implements out identically to var.
Note that the clearing of a managed parameter is performed at the call-site and so the code generated for the function does not vary with out or var parameters.
There is not much difference, for the compiler that is. See Mason's answer for that.
Semantically, there is a big difference:
var tells the programmer that the routine could work with its current value,
out tells the programmer that the routine will ignore/discard its current value.
Slightly late but just for the record, I came across a case where var or out made a big difference.
I was working on a SOAP web service which exported the following method:
function GetUser( out User :TUser ) :TResult;
which was getting imported into C# as the equivalent of
function GetUser( out Result :TResult) :TUser;
when I changed the out to a var it it imported correctly.
I'm guessing that the Delphi SOAP invoker treats the function result as an out parameter and that having two out parameters confuses the Delphi SOAP routines. I'm not sure if there is a workaround to allow you to use out parameters.
I read earlier that out parameter is set to default by called function, but today I realized that it is not completely true. Value of out parameter is discarded by called routine, but if that routine does not change its value, caller can still get it initial value, which was assigned before passing to called thread.
For example:
procedure JustNothing(out x : integer);
begin
// do nothing
end;
procedure TestOutVar;
var i : Integer;
begin
i := 100;
JustNothing(i); // after this call, i will still be 100
end;
I'm effectively trying to deserialize a form.
One of the objects on the serialized form has a method which takes a series of events as parameters.
Now since I don't have the class type of the object when I'm deserializing, I have a method on the object doing the deserialization called AddMethod which is declared like this:
procedure TMyDeserializer.AddMethod(ControlName, EventName: String;
MethodAddr: Pointer);
var
TargetControl : TControl;
Method : TMethod;
begin
if Not Assigned(TempForm) then
Exit;
if TempForm.Name = ControlName then
TargetControl := TempForm
else
TargetControl := TempForm.FindChildControl(ControlName);
if Assigned(TargetControl) then
begin
Method.Code := MethodAddr;
Method.Data := TargetControl;
SetMethodProp(TargetControl, EventName, Method);
end;
end;
So that I can poke subroutines into the various controls as I deserialize them, The problem is I need to add events as a list of parameters (not to a control). e.g.
SetUpEvents(EventHandler1:TNotifyEvent;EventHandler2:TNotifyEvent);
Where EventHandler1 and EventHandler2 are defined somewhere in code as
Procedure EventHandler1(Sender:TNotifyEvent);
begin
// Do something
end;
These are not methods but stand alone subroutines.
When I'm assigning these to objects the subroutine doesn't need to be part of an object as the AddMethod procedure handles it with a call like
MyDeserializerInstance.AddMethod('Button1','OnClick',#EventHandler1);
This works for standard event handlers, such as Button1.OnClick but not if I want to do
Procedure SetUpButton1Click(Method: TNotifyEvent)
begin
TButton(MyDeserializerInstance.TempForm.FindChildControl('Button1')).OnClick = Method;
end;
The problem is I can't pass the subroutine as a method to the example Set Up Procedure.
The form being created isn't declared in an interface and is entirely defined by the file it is read from as well as a few stand alone routines in code.
So I suppose the question is how do turn a subroutine into a method at run time (after creating the object it is supposed to be part of), and if I can't do that how do I pass the subroutines in code as parameters in another method?
So far I've tried casting a TMethod as the correct event type and filling in the .Data as the TempForm. It called the correct method but it scrambled the parameters.
Delphi version is 2007
Non-static class methods have a hidden Self input parameter that is filled in when the method is called. That is what the TMethod.Data field corresponds to. In order to use a standalone procedure as a handler for an event that expects a class method, the procedure must have an extra parameter defined to represent the Self parameter so the value of TMethod.Data has somewhere to go, ie:
procedure Button1ClickHandler(Self: Pointer; Sender: TObject);
begin
// Do something
end;
MyDeserializerInstance.AddMethod('Button1', 'OnClick', #Button1ClickHandler);
Your AddMethod() implementation is assigning the TargetControl as the TMethod.Data value, so the Self and Sender parameters above will end up pointing at the same object at runtime, but that is OK.
Without the explicit Self parameter defined, that explains why your parameters are getting "scrambled" when the procedure called at runtime. The hidden Self value is being assigned to the Sender parameter, and the real Sender value is being ignored.
I'm sure someone will correct me if I'm wrong but I don't believe there is a way to create a type definition at runtime in native Delphi. Delphi's RTTI just doesn't handle this yet.
The two scenarios that come to mind for object serialization are persistence and IPC. (There may be more that I haven't thought of).
Delphi's DFM serialization would be an example of persistence. If you look at a dfm you'll notice it isn't defining methods at all. It's simply assigning event handlers to properties expecting an event handler. Both the handlers and the properties are defined at design time using normal type definitions.
If your intent is IPC(whether on the same machine or a remote one) there are already existing frameworks for accomplishing this. (RemObjects comes to mind)
You don't "make a method" at run time. That would amount to compiling new code. The methods that you assign to various event properties need to already exist.
Furthermore, you can't "add events." The object you're deserializing already has events. You defined them when you wrote the class declaration in your Delphi code. You can't add new event properties to a class after it's been compiled.
It appears that what you're really saying is that you have a standalone procedure that you happen to have named Method1, and you want to pass it as a TNotifyEvent parameter when you call SetUpMethods.
That's the wrong way to go. That Method1 procedure isn't a method, despite its name, so you mustn't use it where a method is required. Change that declaration so it belongs to a class, and then it will be a method.
If you don't want to have to instantiate the class that the method belongs to, that's fine — you can declare it as a class method instead:
class procedure TSomeClass.Method1(Sender: TNotifyEvent);
I encourage you to change the declaration of AddMethod so that the last parameter is of type TMethod. Then you're sure to have both the code and data portions of the method pointer. Right now, you're assigning the data portion based on the object whose event property you're assigning, but as I mentioned in my comment, it's rare for that relationship to exist, especially now that the method belongs to an entirely unrelated class (TSomeClass in my example). The value of the TMethod.Data field becomes the Self value when the method gets called. It's your responsibility to ensure that the value you store in that field is of a compatible type for the class the code belongs to.
I need a way to write a generic procedure to act upon an object type or any of its descendants.
My first attempt was to declare
procedure TotalDestroy(var obj:TMyObject);
but when using it with a descendant object
type TMyNewerObject = class(TMyObject);
var someNewerObject: TMyNewerObject;
TotalDestroy(someNewerObject);
I get the infamous error "types of formal and actual parameters must be identical"
So, while strugling to find a solution, I looked at the source code of Delphi system FreeAndNil procedure. And I found this awesome declaration, along with this astonishing comment
{ FreeAndNil frees the given TObject instance and
sets the variable reference to nil.
Be careful to only pass TObjects to this routine. }
procedure FreeAndNil(var Obj);
It avoids the type checking error, but it uses no safety net.
My question is ... is there any safe way to check the type of an untyped var parameter?
or in other words, can you improve this Delphi source code so that the warning would not be needed?
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
Let's examine what you want to do.
You want to call a method that takes X, passing in an object of type Y, where Y is a descendant of X. The snag, the parameter is a "var" parameter.
Let's analyze what you could do if that was possible.
type
TBase = class
end;
TDescendant = class(TBase)
end;
procedure Fiddle(var x: TBase);
begin
x := TDescendant.Create;
end;
type
TOtherDescendant = class(TBase)
end;
var a: TOtherDescendant;
a := TOtherDescendant.Create;
Fiddle(a);
Uh-oh, now a no longer contains an instance of TOtherDescendant, it contains an instance of TDescendant. That probably comes as a surprise to the code that follows the call.
You must not only consider what you intend to do with the syntax you propose, but effectively what you could do with the syntax.
You should read Eric Lipperts excellent blog post about similar issues in .NET, found here: Why do ref and out parameters not allow type variation?.
I've written about this before, using an example very similar to Lasse's:
Delphi Q&A: Why must the types of actual and formal var parameters be identical?
Unless you're writing an assignment statement to change the value of the input parameter itself, and not just one of its properties, you shouldn't pass a parameter by reference in the first place.
If you are writing an assignment statement to change the parameter's value, then the compiler message really is true, and you should heed it.
One reason for needing to by-pass the error is when you're writing a function like TApplication.CreateForm. Its job is to change the input parameter's value, and the type of the new value varies and cannot be determined at compile time. If you're writing such a function, then your only option with Delphi is to use an untyped var parameter, and then there is extra burden on both the caller and the receiver to make sure everything goes right. The caller needs to make sure it passes a variable that is capable of holding values of whatever type the function will put in it, and the function needs to make sure it stores a value of a type compatible with what the caller requested.
In the case of CreateForm, the caller passes in a class-reference literal and a variable of that class type. The function instantiates the class and stores the reference in the variable.
I don't think very highly of either CreateForm or FreeAndNil, largely because of the way their untyped parameters sacrifice type safety in return for comparatively little extra convenience. You haven't shown the implementation of your TotalDestroy function, but I suspect its var parameter will ultimately provide the same low utility as in those other two functions. See my articles on both:
When should I use FreeAndNil?
Why shouldn't I call Application.CreateForm?
In addition to what Lasse wrote, which is quite correct, most of the time you don't want to pass an object to a var parameter anyway.
An object is a reference type. What you see as the object is actually a reference to it. You would only want to pass an object reference to a var parameter if you wanted to change your object out for a new object. If you just want to be able to modify the members of the object, then you can do that by simply passing it to a normal parameter. Make method call take a TMyObject parameter instead of a var TMyObject parameter and it should work.
Of course, if you really are replacing the object, then feel free to disregard all this, and see Lasse's answer.
can you improve this Delphi source code so that the warning would not be needed?
Yes, you can get a type safe way to avoid the compiler error.
In the newest Delphi 10.4 Sidney, the FreeAndNil procedure has been changed into this:
procedure FreeAndNil(const [ref] Obj: TObject);
var
Temp: TObject;
begin
Temp := Obj;
TObject(Pointer(#Obj)^) := nil;
Temp.Free;
end;
It is type safe for objects and will catch errors when passing an interface reference for example.
The way to pass a parameter by const [ref] means that the parameter is passed by reference. Without the [ref] attribute, parameters with size equal and smaller than a pointer would otherwise be passed by value.
Here, even though the object is passed as a constant, the reference will be modified.
In that sense, it is not a perfect declaration, but will do its job better than the former implementation.
From New features in Delphi 10.4:
This means that incorrect usage of FreeAndNil will now cause a compiler error. In the past, incorrect usage would not be caught, leading to difficult bugs. Note that although the parameter is declared as const, the by-reference variable is indeed modified.
A new, but ‘not as bad’, class of incorrect calling is possible with this declaration of FreeAndNil: the method can be called passing in properties or a method result, as well as cast expressions, a type’s implicit conversion to TObject, etc. The nil-ed value will then be the temporary variable in the expression.