Custom Component TImage events causing error "Does not exist" - delphi

I have a custom component witht the following events
private
{ Private declarations }
...
fOnImageClick: TNotifyEvent;
fOnImageMouseUp: TMouseEvent;
fOnImageMouseDown: TMouseEvent;
fOnImageMouseMove: TMouseMoveEvent;
fOnImageMouseEnter: TNotifyEvent;
fOnImageMouseLeave: TNotifyEvent;
fOnImageSelect: TNotifyEvent;
fOnImageDblClick: TNotifyEvent;
protected
...
public
{ Public declarations }
...
published
...
property OnImageClick: TNotifyEvent read fOnImageClick write fOnImageClick;
property OnImageSelect: TNotifyEvent read fOnImageSelect write fOnImageSelect;
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write fOnImageDblClick;
property OnImageMouseDown: TMouseEvent read fOnImageMouseDown write fOnImageMouseDown;
property OnImageMouseUp: TMouseEvent read fOnImageMouseUp write fOnImageMouseUp;
property OnImageMouseMove: TMouseMoveEvent read fOnImageMouseMove write fOnImageMouseMove;
property OnImageMouseLeave: TNotifyEvent read fOnImageMouseLeave write fOnImageMouseLeave;
property OnImageMouseEnter: TNotifyEvent read fOnImageMouseEnter write fOnImageMouseEnter;
end;
I assign them to a TImage whose parent is TPanel whos parent is TScrollBox
img:= TImage.Create(ThumbPnl);
img.Parent:= ThumbPnl;
img.Tag:= I;
img.Align:= alClient;
img.Stretch:= true;
img.OnClick:= fOnImageClick;
img.OnDblClick:= fOnImageDblClick;
img.OnMouseEnter:= fOnImageMouseEnter;
img.OnMouseLeave:= fOnImageMouseLeave;
img.OnMouseDown:= fOnImageMouseDown;
img.OnMouseUp:= fOnImageMouseUp;
img.OnMouseMove:= fOnImageMouseMove;
The component compiles and bulds just fine. The application with this component also compiles and runs jus fine. If I assign an OnClick event, it works. All other events, if I assign them and try to run the app, i get an error saying the event doesn't exist
Anyone know why that is?

You are making a fairly common mistake for new component creators. You are breaking this rule:
Component users write event handlers, component writers "fire" events
In your case what you are doing is assigning events when you as the component developer shouldn't be doing that. It's up to the component user to assign code to occur in the OnClick, OnDblClick, etc. events.
It's enough for you to declare the events as Published and as TNotifyEvent types.
property OnSomeEvent: TNotifyEvent read FOnSomeEvent write FOnSomeEvent;
That's all you need to do to create the events. Your job is to "fire" them; that is, make them happen.
That is done in your component at the appropriate moment. Usually, what you'll do is create a method DoXXXXX where XXXXX is the thing that is happening. So you'd create
procedure TMyImage.DoSomeEvent;
begin
if Assigned(FOnSomeEvent) then FOnSomeEvent(Self);
end;
Then, within your code, when the event should be fired, you simply call DoSomeEvent.
Now, if you want to enhance the functionality of the default events, then you need to override the method that fires the event.
I hate to do it, but my classic component TSmiley illustrates this simply and clearly:
http://tsmiley.svn.sourceforge.net/viewvc/tsmiley/
Take a look at that example and you should see how to create events.

What is an event
The event in Delphi is a method pointer. A method pointer is basically two pointers, one points to the method you assign to the event, and the other points to the live instance (object) you assign.
What I assume you're expecting
I think you expect the event in your inner object follows the events assigned to your outer object, and that will not happen automatically.
When you assign it, you're doing pointer assignments. So to take an example, your line
img.OnDblClick:= fOnImageDblClick;
Performs a pointer assignment with the value fOnImageDblClick have at the time. If it is nil, img.OnDblClick will be nil from now. If it points to Form1.MyComponentImageClick at the time, img.OnDblClick will point to the same method on the same object from now, but, if you later change where fOnImageDblClick points, the imgOnDblClick will not follow... it will remains pointing to the same old address. If you want it to change, you have to make that change by code also.
If you want that to happen, you can do that in a event setter in the outer class.
First, declare your events like this:
published
...
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write SetOnImageDblClick;
...
procedure TMyClass.SetOnImageDblClick(Value: TNotifyEvent);
begin
FOnImageDblClick := Value;
//pass the new value to the inner object.
if Assigned(Img) then
Img.OnDblClick := Value;
end;
If your Img inner object exists all the time, you don't need the FOnImageDblClick variable, you can write also a getter for the property and take the value directly from the inner object, like this:
function TMyClass.GetOnImageDblClick: TNotifyEvent;
begin
Result := Img.OnDblClick;
end;

Related

How to tell if an object is created from Streaming or not

I have two separate requirements to detect if an object is being created from a stream while in the Create / AfterConstruction code.
In the first case I have an existing object which is being refeactored so it is implemented as a component to allow consuming users to drop the component on a form or data module. One of the properties of this component is a Uuid which needs to be assigned uniquely to each object instance, and needs to remain unique for that object instance across different runs of the program. Internally the Uuid is held in our own class but we present a UuidString property to the user in the IDE. I need to know whether to allocate a Uuid and register the component on first creation, or wait until the Loaded routine (which is never called if it's not read from a stream).
In the second case I have a set of components that provide an 'OnReady' event to the application. Once the object is completely initialised (which could be asynchronous) the event is called. If the object is being streamed then I can override Loaded method to undertake additional configuiration, but if it's not being streamed Loaded will never be called and I should start the additional work in AfterConstruction.
Looking at the documentation I though I could use:
if( not (csLoading in Self.ComponentState) ) then
...
Or, to catch newly created objects in the designer specifically:
if( (csDesigning in Self.ComponentState) And
not (csLoading in Self.ComponentState) ) then
...
However having looked into the Code (I'm not really a Delphi programmer by background) I see that csLoading is only set after the Create / AfterConstruction has executed.
During Create / AfterConstruction execution is there anyway I can tell if Loaded is going to be called?
I have realised that all components created by streaming will have Owner<>nil but it's expected that components created at runtime would normally have Owner<>nil as well.
My only thought at the moment is to see if the owner is loading with something like:
if( (Self.Owner<>nil) And (not (csLoading in Self.Owner.ComponentState)) ) then
...
Is this the correct approach? Or is there a better 'Delphi Way' to do it?
Assuming your property is named UUIDString backed by a field FUUIDString. Then this approach should work:
type
TMyComponent = class(TComponent)
private
FUUIDString: string;
function GetUUIDString: string;
protected
procedure Loaded; override;
public
property UUIDString: string read GetUUIDString write FUUIDString;
end;
function TMyComponent.GetUUIDString: string;
begin
if FUUIDString = '' then
FUUIDString := CreateNewUUIDString;
Result := FUUIDString;
end;
procedure TMyComponent.Loaded;
begin
inherited;
RegisterUUIDString(UUIDString);
end;
If UUIDString is loaded during the stream reading it will contain the stored value. Otherwise the register call inside Loaded will generate a new one.
You were pretty close to a solution. You just needed to override the correct procedure.
I’m using "CreateWnd" for the following reasons:
I need to initialise images that require the parent (Panel) to have a Handle ready, so this procedure is the right place to do that.
It is called when ComponentState is established (with csDesigning and/or csLoading).
Any Published Property is available (has been streamed in from the DFM) at this point.
It is only called once.
The component I’m working on is a “Picture- Button”, made form a TCustomPanel with a TSpeedButton on it and up to 4 images also on the panel.
So I wanted to put initial (default) images on the panel, which requires determining when the component is first created (dropped on the form).
See some of the code below >>
………
protected
{ Protected declarations }
procedure CreateWnd; override;
procedure DoInitialConfig;
………
procedure TJEPicButton.CreateWnd;
begin
inherited;
if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
begin // Initial State Only
DoInitialConfig;
end;
// More code here....
end;
procedure TJEPicButton.DoInitialConfig;
begin
// Load initial (default) image(s) on panel
// The user can (using Object Inspector) replace this image(s) with his own…
end;

Delphi using pointers to methods properly

If I have a Pointer focused to a FormActivate(Sender: TObject); -
MyPtr := #FormActivate; - how do I run FormActivate with a sender of my choice.
I do not understand how to add in the sender.
You should not use a Pointer in this case. I don't think that you can even do it as you need the form instance as well as the method. The FormActivate is a plugged into the OnActivate event handler, defined as TNotifyEvent:
TNotifyEvent = procedure(Sender: TObject) of object;
NB: The "of object" which the compiler interprets to require an instance (the mechnanism hidden from us as it should be).
The correct was to do what you want (not sure that you should be doing it, but hey..) is to replace your Pointer with a MyForm :TMyForm (what ever yours is) and then call:
MyForm.FormActivate(mySender);
Alternatively, you can replace Pointer with a MyEvent :TNotifyEvent, thus:
MyEvent := MyForm.OnActivate;
then call
MyEvent(mySender);
Replacing mySender with whatever you wish in both examples.
I hope that this clears things up for you.

Assigning a customized event to an Objects OnMouseEnter event

I am looking for a way to reroute the event handler for OnMouseEnter of TPanel. I have a class that contains an array of TPanels that I am using to represent a seating chart. I would like to be able to assign a custom event to the OnMouseEnter event of each of the TPanels which are created dynamically at runtime. I will provide some pseudo code to try and help clarify:
//Class Declaration
TMyClass = Class(TObject)
PanelArray : Array of TPanel;
end
//Forms declarations
MyClass : TMyClass //Created on the on create event of form.
//Loop assigning the new event
For I:=0 To High(MyClass.PanelArray) do
Begin
//Instance of panel
MyClass.PanelArray[I].OnMouseEnter := NewOnMouseEnterEvent(Parm1,Parm2,Parm3,....ParmN);
end;
When New OnMouseEnter event is called, in place of TPanel's OnMouseEnter I imagine it would function exactly the way it would if you had placed a panel on a form at design time and created an OnMouseEnter event from the object Inspector, but with the ability to pass additional parameters:
TForm1.Panel1OnMouseEnter(Sender: Tobject; Parm1,Parm2,Parm3,....ParmN:String);
begin
ShowMessage(Parm1);
end;
Can anyone suggest a possible solution to this? I hope I was clear enough, I am a first time poster. Thanks in advance for any help you can provide.
The OnMouseEnter property has type TNotifyEvent, which is a method pointer that expects exactly one parameter. You cannot assign pointers to other methods with other signatures.
The parameter you get is Sender, which is technically all you need. That tells you which panel is being acted on. Using that, you can look up whatever other values you need that should be associated with that panel. For example, you could define a record that holds all your other parameters, and then store them in a dictionary keyed off the panels:
type
TMouseEnterParameters = record
Param1, Param2, ..., ParamN: string;
end;
TMouseEnterDictionary = TDictionary<TPanel, TMouseEnterParameters>;
Populate the dictionary when you assign the event handlers:
for i := 0 to High(MyClass.PanelArray) do begin
MyClass.PanelArray[i].OnMouseEnter := Panel1OnMouseEnter;
ParamDict[MyClass.PanelArray[i]] := MakeParameters(Param1, Param2, ..., ParamN);
end;
Then you can find the associated values in the OnMouseEnter hander:
Params := ParamDict[Sender as TPanel];
If you Delphi version is too old to support a generic TDictionary class, you can use any number of other data structures, too. TDictionary just makes it easy.

How can I detect when a TCollection has been deleted at design time?

I have a visual component that needs to detect when a collection item has been deleted so it can re-paint.
TCollection has the following protected procedure:
procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); virtual;
Unfortunately it is only called when an item is added and just before it is deleted. I need to know when an item has been deleted.
Internally, TCollection uses a TList that also provides a Notify procedure. The TList version does include a Deleted notification. Unfortunately, the TList is a private member.
How can I detect when a TCollection item has been deleted?
TCollection.Notify() is the correct way to go. Simply don't repaint immediately, Invalidate() the component instead. By the time the next repaint is actually triggered, the removed item will be gone.
Override the collection item's SetCollection method. Implement it something like this:
procedure TFooCollectionItem.SetCollection(const Value: TCollection);
var
OldCollection: TFooCollection;
begin
OldCollection := Collection as TFooCollection;
inherited;
if (OldCollection <> Value) and Assigned(OldCollection) then
OldCollection.NotifyItemReallyRemoved(Self);
end;
You can provide and implement the hypothetical NotifyItemReallyRemoved method yourself.

Delphi - Accessing a Frame object from a Form

I need to run an action that is attached to a button (say SQLBtn) that is placed on a Frame1 within my app, from Form1.
I have included the frame in Form1 uses, but can't seem to address in any way.
I've tried Frame1.SQLbtn TFrame1.SQLbtn TFrameSQLBtn etc but can't get to it.
I would like to get to something similar to 'SQLbtn.click' to run the event behind it.
Does any one have any ideas how to address it?
I am not sure I understand your question correctly. Sounds like you have a frame with a button (and either an TAction or click event handler on the button) and this frame is sitting on a form. Now you want to programmatically simulate a click on that button.
Obviously you need to add the frame unit to your form's uses clause. You also need an instance of the frame on the form which should lead to a form field of the frame type, e.g.
TForm1...
...
Frame1: TFrame1;
end;
Then you can execute that code via Frame1.SQLbtn.Click from within any of the form's methods. A better way would probably be to provide a public method on the frame which you can use from the form. Then you don't need to access the button directly (the button is an implementation detail of the frame, frame private so to speak).
Edit after clarification
I understand you have the following scenario:
TFrameForm1...
...
Frame1: TFrame1;
end;
TForm1...
...
procedure something;
end;
procedure TForm1.something;
begin
// how to call a method on Frame1 which is on FrameForm1
end;
Your best choice is to move the code from frame button OnClick event handler into a separate unit. This can be a datamodule, or a just another unit with a standalone procedure. Then you can call that code from both Form1 and the Frame1 button event handler. This is what Vegar has commented.
If that is not possible, e.g. because the processing requires access to other controls on Frame1, move the code into a new procedure on Frame1 (my original suggestion):
TFrame1...
...
public
procedure framestuff;
end;
procedure TFrame1.framestuff;
begin
...
end;
procedure TFrame1.SQLbtnClick(Sender...);
begin
framestuff;
end;
Now you need to call that method from Form1. You'll need a reference to FrameForm1 for that. Which you need to initialize manually (!) when you create TFrameForm1. In this example, the reference is a field FFrameForm:
TForm1...
...
FFrameForm: TFrameForm1;
procedure something;
end;
procedure TForm1.something;
begin
FrameForm.framestuff;
end;
Or, by default Delphi adds global variables for all forms to the form units (auto form creation, check project options / forms). Then you do this:
procedure TForm1.something;
begin
FrameForm1.framestuff; // if FrameForm1 is the name Delphi used for the global variable
end;
Of course there are many other variations...
procedure TDiferentForm.DoSomething();
begin
Form1.YourFrame.ButtonClick(nil);
end;
One thing that might help you understand: when you create an instance of a form (or frame), delphi goes through the DFM and creates instances of all the objects described there.
IF you have a variable in the form's definition that matches the name of the object in the DFM, the loader will make the variable point to the object; if you don't have a variable, the object is created but you would have to iterate through .Components or .Controls to get to it.
If the form has an instance variable of the frame (and that variable is public), then any other form's code can access it (e.g. MainForm.Frame1...) and do what it wants to.
To encapsulate the frame, the form (which is, after all just a class) can have public properties that have accessors and mutators to proxy the information to and from the embedded frame. Encapsulation is good (IMHO the most important aspect of OOP) because it makes the link between the caller and the frame loose: you can change either side a lot without breaking things.
Cheers
Another solution is to use interfaces to avoid the circular reference problem and simplify the code a bit. Lets say that you have a procedure named foo that you want to invoke from anyplace in the system. The implementation of this procedure is in tFooForm which is not the main form, but a form that the main form knows about.
First create a new unit and call it Foo_Intf.pas
Its contents will be the following:
unit Foo_Intf;
interface
type
IFoo = interface
['{4AC12AB9-557B-4E61-AB2D-8B10E591E33A}']
// CTRL-SHIFT-G to create a new guid
procedure Foo;
end;
implementation
end.
then add the method to the tFooForm class, and also include the interface. Don't forget to use the foo_intf.pas unit in your interface uses clause. Implement the foo class to do what ever you want that procedure to perform.
tFooForm = class(tForm,IFoo)
:
procedure Foo;
:
end;
Also add the IFoo interface to the main form, exactly like the previous step but change the implementation to be the following:
procedure tMainForm.Foo;
begin
if not Assigned(FooForm) then
FooForm := tFooForm.Create(Application); // for simplicity sake
FooForm.Foo;
end;
Now, anyplace you want to call the foo function, just include ONLY the Foo_Intf unit in the uses clause and use the following snippit:
var
FooIntf : IFoo;
begin
if Not Supports(Application.MainForm, IFoo, FooIntf) then
Raise Exception.create('Application.mainform does not implement IFoo');
FooIntf.Foo;
end;

Resources