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.
Related
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 want to introduce some new methods into a custom control I am deriving from TCustomListBox.
What I want is a method that can be used when a item is added/inserted into the listbox, and a method for when an item is removed from the listbox.
What would be a good place to start with this? I know controls like TListView for example have an OnInsert event but I cannot see anything for listbox?
I wanted to introduce into my control for example:
OnInsert
OnRemove
Would I need to use some kind of API or Messages to detect when items are added/removed and then take it from there? Is there an easier way to do this or does it require some difficulty?
I tried looking at some of the VCL source but most of it is confusing for me.
Thanks in advance.
The API you need already exists.
If you inspect the source of a TCustomListBox you will see that the mechanism by which items are added, inserted or removed from a list is implemented using window messages. For example in TListBoxString.Add() as well as house-keeping code you will see that the string is eventually added by sending a message to the control:
Result := SendTextMessage(ListBox.Handle, LB_ADDSTRING, 0, S);
Delphi provides various mechanisms for providing handlers on control and window classes that respond to specific messages. Perhaps the most straightforward and appropriate, for adding a simple notification mechanism such as your require, is to implement a specific message handler method.
You provide a message handler procedure and declare which message it responds to. In your case, for example, you could add your own handling of the LB_ADDSTRING message:
TChattyList = class(TCustomListbox)
procedure LBAddString(var aMessage: TMessage); message LB_ADDSTRING;
end;
The parameters of the message (wParam and lParam) are packaged up inside the TMessage record passed as the by reference parameter to your handler). You will need to consult the Windows API documentation for the message in question to determine the use of these parameters.
You can do pretty much whatever you want in your message handler although you should always pay close attention to what a window is expected to do in response to documented messages, including any return values (set in the Result field of the TMessage parameter, which is why it is passed by reference, as var.
In this trivial example, the new handler calls inherited to ensure that the inherited implementation is allowed to respond by actually adding the new item string and then crudely pops up a message box to let us know that an item was added:
procedure TChattyList.LBAddString(var aMessage: TMessage);
begin
inherited;
ShowMessage('item added');
end;
In essence your event mechanism will do exactly the same, but instead of presenting a message box you would trigger your new event after allowing the inherited implementation to do it's work (and checking the result code set, to ensure that it was successful, according to the expected return values for the message in question):
procedure TChattyList.LBAddString(var aMessage: TMessage);
begin
inherited;
if (aMessage.Result = LB_ERR) or (aMessage.Result = LB_ERRSPACE) then
EXIT;
if Assigned(fOnInsert) then
fOnInsert(self);
end;
If the inherited handler failed to add an item then according to the documentation it should set the result to LB_ERR or LB_ERRSPACE, so we test for these values and exit if they are found. Otherwise we call the appropriate event handler, if one is assigned.
This assumes that for your purposes a simple TNotifyEvent is sufficient and that you do not discriminate between an item being inserted vs an item being added. You could of course have separate events or provide some indication in parameters to a specialised event type.
Which messages you choose to handle and expose as which sorts of events is then a question of exactly what your requirements are, but based on what you have stated in your question, at a minimum I think you will need message handlers for LB_ADDSTRING, LB_INSERTSTRING and LB_DELETESTRING.
You may need to handle additional messages and should consult the Windows API documentation for listbox controls for further information.
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.
Is there any way to allow one form to use the event procedures from another form?
E.g. I have a form called PongForm and another called ObstPongForm. There is a ticker on PongForm and another one on ObstPongForm. Is it possible to get ObstPongForm to use the code from PongForm's 'tick' event in it's own 'tick' event? Maybe by letting ObstPongForm inherit from PongForm?
You can simply assign it by code (as long as you have access to both instances):
ObstPongForm.Ticker.OnTick := PongForm.TickerTick;
Yes, forms are just classes like any other, and Delphi supports visual inheritance, so you can call inherited methods normally.
If ObstPongForm is a specialized version of PongForm then inheritance makes sense, but be careful as ObstPongForm will inherit all visual controls from the PongForm, including whatever you may add in the future.
Also since I assume you already have both forms, making one inherit from another is doable but requires some manual DFM editing, mainly changing the
Object ObstPongForm: TObstPongForm
to
Inherited ObstPongForm: TObstPongForm
If the code you want to reuse may be needed in several unrelated forms, then moving the code to a common unit used by these forms may be the best solution
It would be better style to have both of the forms call another class that implements the logic used by both. If you're writing all your program logic in your OnTimer event handler, you're heading down a bad road that many delphi programmers take years to realize was a bad idea
So one form needs to call your method, it does it like this:
procedure TForm1.DoSomething;
begin
DataModule1.LogicMethod;
end;
Elsewhere there is a timer...
procedure TForm2.Timer1Timer(Sender:TObject);
begin
DataModule1.LogicMethod;
end;
And then the method itself:
procedure TDataModule1.LogicMethod;
begin
// Everything that you used to have in Timer1Timer goes here, except the setting of
// UI properties in Form1 which is kept in Form1:
Inc(FCounter);// stupid example.
//
if Assigned(FOnResults) then
FOnResults(Self, FCounter, FDataObject1);
// Form2 is connected to FOnResults event, and stores the
// result in the UI somewhere.
end;
Event handlers are just normal procedures. If your ObstPongForm tick handler has additional code that it needs to run in addition to the PongForm's code, then you can call the PongForm's tick handler manually when needed, eg:
uses
..., PongForm;
procedure ObstPongForm.TickHandler(Sender: TObject);
begin
...
PongForm.TickHandler(Self);
...
end;
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.