So when you double click on a button and it autocompletes the buttonclick procedure, I'm curious about how the compiler knows which button the function is linked to. For example it would make TForm1.Button1Click(Sender: TObject);
So how does the compiler know which button that is linked too? Does it just parse the procedure name to see what button it is?
You can name the method to any name, and Delphi doesn't parse or use the method name to identify the component or event associated.
If you do it at design time, the event association with a event handler is stored in the DFM file, where you find something like:
object Button1: TButton
Left = 104
Top = 64
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
The OnClick = Button1Click makes your program associate the method (also known as the event handler) to the event (a special kind of property) of the object at runtime, when the form is created.
You can also associate an event to any compliant method at runtime, for example with this code:
type
TForm1 = class(TForm)
Button1: TButton;
private
procedure MyClick(Sender: TObject);
procedure MyOtherClick(Sender: TObject);
....
procedure TForm1.AssociateClickHandler;
begin
Button1.OnClick := MyClick;
end;
procedure TForm1.MyClick(Sender: TObject);
begin
Button1.OnClick := MyOtherClick; //from now, the other method with handle the event.
end;
Use any name you want
At design time, you can write the name you want for the event handler directly in the ObjectInspector, then press Enter and Delphi will create the method with that name for you. If you don't provide a name, Delphi auto-generates the name for the method using the component name, plus the event name without the "On". If the method already exists, the IDE just associate the event with that method.
Write the desired method name:
Press enter:
You can associate the same method to different events of the same object, or to the same event of different objects.
For example, you can associate the MyOwnMethodName shown above to the OnClick of any number of buttons. Usually, the Sender parameter contains a reference to the object that is firing the event.
The compiler is not involved in any of this. The IDE is handling everything instead.
When you double-click on a control at design-time, the Form Designer knows which control is being clicked on, as it is a live object in memory.
The Form Designer uses the control's RTTI and registered TComponentEditor implementation (the VCL provides a default implementation if a user-defined implementation is not registered) to determine which event for that class type is the default event (in the case of TButton, that is the OnClick event), then uses the RTTI to check if that event already has a handler assigned to it.
If a handler is not assigned yet, the Form Designer uses the RTTI to read the control's Name property and the event's declared name, concatenates them together (dropping the On portion of the event name), and looks for a procedure of that name in the source code of the control's Owner. If that procedure is not found, it is created at that time.
Once the Form Designer finds the procedure, it uses the RTTI to validate that the procedure matches the signature of the event, then assigns the procedure as the new event handler if needed before then finally jumping to the procedure implementation in the Code Editor.
If you click on the control's event in the Object Inspector and rename the handler, the corresponding procedure in the source code is renamed to match the new name, and any other events, even in other components, that were linked to that same procedure are updated via their RTTI to match the new name as well.
When compiling the project, the IDE first utilizes RTTI and registered component streaming routines to create a .DFM file that contains all of the various component property/event values. Then it invokes the compiler, which compiles the source code and links in the .DFM file as a binary resource into the final executable.
At runtime, the RTL parses the DFM resource, using RTTI and registered custom component streaming routines, to locate the various components and hook up their property/event values as needed.
Related
I have added a published event to my main form, expeecting it would show up in the editor, but it doesn't.
Am I doing something wrong, or is this just not the case?
THIFISongEndEvent = procedure(Sender: TObject; EstimatedEndTime: TDateTime) of object;
TMain = class(TForm)
[...]
published
property OnSongEnd: THIFISongEndEvent read FOnSongEnd write SetOnSongEnd;
EDIT: Added Code
Published properties of TComponent descendants are visible in the Object Inspector only when these components are registered in a design-time package. Otherwise, you would not even be able to drop the component onto the Form.
As Forms are usually designed in the Form Designer, they are probably not registered for design-time, and thus their published properties added on top of the base TForm properties cannot be known by the Object Inspector.
When I call WebBrowser1.Navigate('www.google.com'); from a Button OnClick event, eg:
procedure TForm4.Button1Click(Sender:TObject);
begin
WebBrowser1.Navigate('www.google.com');
end;
The web page shows up in WebBrowswer1.
But, if I make my own procedure, eg:
procedure MyProcedure;
var
WebBrowser1:TWebBrowser;
begin
WebBrowser1.Navigate('www.google.com');
end;
And then try to call this procedure from a Button OnClick event, I get an Access Violation error.
Just wondering, why does it work when Delphi makes the procedure for me, but it doesn't work when I write the procedure myself? How can I correct this, or what code do I have to write in the procedure to make it work?
In the first excerpt you have added a TWebBrowser control to the form in the IDE designer. As such the VCL framework instantiates the control for you. Make it be a child control of the form, and applies all the steps needed to get the control up and running correctly.
In the second excerpt, there is no form, no control added in the designer. You declare a local variable WebBrowser1, which you do not initialise. No browser control is created, and any attempt to use the uninitialized variable WebBrowser1 leads to undefined behaviour. A runtime error is pretty much inevitable.
If you want to correct this you would need to instantiate an instance of the TWebBrowser control, set its parent appropriately, and take all the other steps that the VCL does for you.
I'm trying to name columns (in this grey row) in a string grid. I know that I should use something like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.Cells[0,0] := 'Text 1';
StringGrid1.Cells[1,0] := 'Text 2';
end;
The problem is that there is error:
'TForm1' does not contain a member named 'FormCreate'at line 81".
I'm a beginner. What is wrong with my program?
You need to declare the method in the type.
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
...
end;
And that line of code to the declaration of the type that you will find near the top of your unit. Then your program will compile. You also need to make sure that the event handler attaches the handler to the form's OnCreate event. Use the Object Inspector to check that.
But the easiest way to make this all happen is to get the IDE to write it all. So, you would:
Delete the code that you have shown in the question.
Click on the form in the designer.
Select the Events view in the Object Inspector.
Find the OnCreate event in the Object Inspector.
Double click in the handler column of the OnCreate event in the Object Inspector.
Now the code editor opens showing an empty event handler body for you to add code to, and all the other parts are joined up. Specifically the method is declared in the type, and the handler is connected to the event.
Now, that's how you do it normally, but it does pay to know the three things that need to be in place for an event to fire:
The event handler is declared in the type of the class.
The event handler is defined in the implementation of the class.
The event handler is attached to the event in the Object Inspector. In fact, although you set it in the Object Inspector, the information actually lives in the .dfm file.
If you don't know all this already, then asking questions on Stack Overflow is really not the most effective way to get up to speed. A good book would certainly help. Even if it's for an older version of Delphi, the primary concepts have not changed for years. But if you don't have a book, then you should at least follow the tutorial.
Is there a way to create forms dynamically by only their names;
The concept goes like this. I have a main form, and by some user selection, a number of predefined forms must be created and docked on tabitems on a pagecontols on the main form.
I do know the names of the forms and i do know when to create each one of those, but i would like to know if a better way of creating these forms by a single procedure call, and not having all of these information in my code.
Its Delphi XE3 firemonkey, on win 7.
Thanks in advance for any help
Apparently on Firemonkey Delphi doesn't automatically register form classes to be available by name, so you'll first need to add something like this to the end of the unit that holds your form class:
unit Form10;
[ ... ]
// Right before the final "end."
initialization
RegisterFmxClasses([TForm10]);
end.
This will automatically register TForm10 so it'll be available by name. Next you can use this kind of code to create a form at Runtime by it's class name:
procedure TForm10.Button1Click(Sender: TObject);
var ObjClass: TFmxObjectClass;
NewForm: TCustomForm;
begin
ObjClass := TFmxObjectClass(GetClass(ClassName));
if ObjClass <> nil then
begin
NewForm := ObjClass.Create(Self) as TCustomForm;
if Assigned(NewForm) then
NewForm.Show;
end
end;
You can only create objects when you have a class reference for it. To get a class reference for something given its string name, call FindClass. Call the constructor on the result. You may have to type-cast the result to a different metaclass before the compiler will allow you to access the constructor you want. In the VCL, you might use TFormClass, but plain old TComponentClass will work, too, since all FireMonkey objects are descendants of TComponent; the important part is that you have access to the right constructor, and that's where the one you need is introduced.
It only works for classes that have been registered. Your form classes should be registered by Delphi automatically, but if they're not, you can call RegisterClasses manually, or RegisterFmxClasses if you need to put your classes in groups.
Delphi.About.com has a VCL demonstration.
I need to loop through Components and assign an event handler (for example Dynamically assigning OnClick event for all TButton to
ShowMessage('You clicked on ' + (Sender as TButton).Name);
The problem is that in some cases I already assigned the TButton OnClick event.
Is there a way to solve the problem?
Let's imagine I have Button1 for which the harcoded onclick event handler is:
ShowMessage('This is Button1');
After my "parsing" I would like that the full event handler for Button1 becomes:
ShowMessage('This is Button1'); // design time event handler code
ShowMessage('You clicked on ' + (Sender as TButton).Name); // runtime added
Note: I am looking for a soliution that allows me to use TButton as it is without inheriting from it.
you could look for an assignment of OnClick before overwriting it, persist this and use it in your new handler - basically chaining the events.
Something like this:
var original : TNotifyEvent;
original := Component.OnClick;
Component.OnClick := NewMethod;
and then in your NewMethod:
if assigned(original) then original(Sender);
you'll likely not want a single original variable but instead hold a collection keyed on the component.
If you are writing the replacement handlers yourself you could do the following:
Before assignment of the new handler, store a reference to the object and a reference to the original handler in a list or other structure.
In your replacement handler, included code to check the list to see if the current object has an entry. If it does, call the stored method before doing your own work.
This is fairly lightweight (a list where each entry is two pointers). With a little extra work (to support ordering) you could probably even chain more than one replaced handler.
You can't do that directly as Delphi does not support chained events (I'm assuming VCL Delphi).
So anything that you want to do that involves directly assigning the OnClick event will not work. What you might do is create an interceptor component, that is, something with this signature:
type
TButton = class(stdctrls.TButton)
You'll have to override the OnClick property so that the write event stores the event handler into an internal list, available to class.
property OnClick: TNotifyEvent read GetOnClick write SetOnClick;
then on the SetOnClick handler you'll be able to just store your event handler in the internal list.
Now, you will have to override the Click procedure so that it invokes every delegate (every event handler) in turn.
procedure Click; override;
And that will do it.
Note that after all you'll be kind of subclassing the TButton class, even if you're using the same name... Anyway that's the best solution I can think of. The cool thing about this is that you can still use assignments without having to know about chained events, you can just do:
MyButton.OnClick := MyHandler
and it will get automatically chained with the rest of event handlers.