Custom-events for TFrame - delphi

Is it possible to have the Event list in the IDE for a frame extended by an event I define in a TFrame descendant:
TYPE
TFrame1 = CLASS(TFrame)
...
...
PRIVATE
FSomething : TNotifyEvent;
PUBLISHED
PROPERTY OnSomething : TNotifyEvent Read FSomething Write FSomething;
END;
and then have "OnSomething" Visible in the IDE when I select the dropped TFrame on a form?
In a similar vein:
Is it possible to not have the sub-component on a TFrame selectable in the IDE, so that I can expose only the Events for my TFrame and not allow the user to access the events for the sub-components? And is it possible to "suppress" the standard events for a TFrame so that only the events I PUBLISH is visible?

Related

How can I create an event handler for a non published but public event in delphi?

In RAD Studio 10.1 Berlin quite a few things has changed from previous version. In FMX there are a few previously published events that has now been changed to only be public.
I have a Multi Platform Project that uses a TStringGrid component and the OnDblClick event. When opening this project in Studio 10.1 I get warned that the Property OnDblClick does not exist.
The question is now how I can use the no longer published event?
(I must say that It's hard to understand why they haven't set mouse events to Published anymore. As far as I know most regular PCs and OSX machines doesn't have touch. A true Multi Target Project should be able to target these systems without hassle as they did in Studio 10 Seattle)
In case the event handlers already exist (which I imply by the error message), you can assign these handlers to their events in FormCreate.
procedure TForm1.FormCreate;
begin
StringGrid1.OnDblClick := StringGrid1DblClick;
end;
One solution is to make your own component where you extend the FMX.TStringGrid to have published event handlers again.
See here how to create a new FMX component: creating a firemonkey component
Here's the code to re-publish the mouse events.
unit MyStringGrid;
interface
uses FMX.Grids;
type
TMyStringGrid = class(TStringGrid)
published
property OnDblClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseEnter;
property OnMouseLeave;
end;
procedure Register;
implementation
uses FMX.Types;
procedure Register;
begin
RegisterComponents('NewPage', [TMyStringGrid]);
end;
initialization
RegisterFmxClasses([TMyStringGrid]);
end.
This has been reported as a bug here.
Looking at the source code in Delphi 10.1 berlin the public OnDblClick event is actually inherited from TControl class.
Similar the OnDblClick event is also inherited from TControl class with the exception that it is made public, like many other events that are inherited from TControl˙ class.
Any way it seems that guys at Embarcadero have been doing some refactoring by cleaning the parent property redeclarations˙(not sure if this is the right term) like:
type
TParentClass = clas(Tobject)
public
property ParentPropery: Integer read GetParentProperty write SetParentProperty;
TExampleClass = class(TParentClass)
public
property ParentPropery;
end;
Redeclaring ParentProperty in the above case is not needed as it will be available in all child classes unless you want to change its visibility from public to published for instance.
If you look at Delphi 10 Seattle source code you see that property OnDblClick is redeclared in several TStringGrid parent classes being published in TCustomScrollBox for the first time.

Using Child's events

I have a custom component (TScrollBox) that when dropped on a form, it will add a label inside the ScrollBox. How can I disable the ScrollBox's events (onClick, OnMouseDown, ect..) and instead enable the events for the child (Tlabel)
unit MyScrollBox;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
TMyScrollComponent = class(TScrollBox)
private
FLabel : TLabel;
procedure SetLabelText(AText : string);
function GetLabelText : string;
protected
constructor Create(AOwner : TComponent); override;
published
property LabelText : string read GetLabelText write SetLabelText;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyScrollComponent]);
end;
constructor TMyScrollComponent.Create(AOwner : TComponent);
begin
inherited;
FLabel := TLabel.Create(self);
FLabel.Parent := self;
FLabel.Caption := 'Hello From Scrollbox!';
end;
procedure TMyScrollComponent.SetLabelText(AText : string);
begin
FLabel.Caption := AText;
end;
function TMyScrollComponent.GetLabelText : string;
begin
result := FLabel.Caption;
end;
end.
The published events in TScrollBox cannot be suppressed in derived classes. So, treating your question literally, there is no way to achieve what you ask.
What you could do is derive from TScrollingWinControl. This is the ancestor to TScrollBox. It doesn't publish the events that you want to associate with the control contained in your scroll box.
Then you can surface events in your custom control that are connected to the control contained in your custom control.
Judging from your recent questions I cannot help in thinking that your approach is wrong. I feel that you should have a custom control that has built in scrolling capability.
The event handlers for TControls are declared as protected and dynamic. Redeclare them using the override directive in your derived class - see TScrollBox Members Protected Methods;
To override MouseDown, add the MouseDown method to the TDBCalendar class and many other pages.
But: If you want to implement your own new events you'd have to do something like this:
...
private
fNewEvent:TNotifyEvent;
procedure setNewEvent(notify:TNotifyEvent);
function getNewEvent:TNotifyEvent;
procedure DoOnNewEvent;
....
published
property OnNewEvent:TNotifyEvent read getNewEvent write setNewEvent;
i.e. - You need to implement a property of a method type, like TNotifyEvent which is built into Delphi. You can also create your own if you need to. If you want to see your event in the IDE like other Delphi components' events, you must declare it as published.
Then: In your new component implementation section do something like this:
procedure TMyclass.DoOnNewEvent;
begin
if assigned (fNewEvent) then
begin
....doStuff...
fNewEvent(self);
end;
end;
You call DoOnNewEvent when the event that you want to control 'happens' in your code, so that the function assigned to fNewEvent will get called at that point in your code. (This is commonly known as a callback - when something "happens" in module A it calls back into module B letting it know that it happened, etc.)
If you want to define new GUI behavior, you have to examine the controls you're interested in and understand how to capture their actual "physical" events - i.e. when did the scrollbar scroll, when was the mouse clicked, and when that happens you call your DoOnNewEvent method. (This generally involves inspecting Windows messages coming into your application, "message cracking",etc - these messages inform your application of what's happening in "the outside world".)
In your consumer class, for example your main form where you're putting your scroll box, once you successfully publish your new event, you will see your event in the IDE on your new component, and you assign it and define the behavior you want for it in your consumer class, just like any other event in the IDE.
Take a look at the VCL source code for a simple component to get a better idea of what it looks like.
But: That's only if you really need your own new published events because overriding the parent's events is not sufficient for your needs.

Component property not considered on runtime in Delphi XE2

I am developing a component but I can't make it consider a property set at design-time.
The following is an excerpt of the component:
TRVEditFrame = class(TFrame)
...
private
{ Private declarations }
FRVEditor:TCustomRichView;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
protected
function GetRVEditor:TCustomRichView;
procedure SetRVEditor(Editor:TCustomRichView);
published
property RVEditor:TCustomRichView read GetRVEditor write SetRVEditor;
end;
...
constructor TRVEditFrame.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetRVEditor(FRVEditor);
...
end;
function TRVEditFrame.GetRVEditor:TCustomRichView;
begin
Result:=FRVEditor;
end;
procedure TRVEditFrame.SetRVEditor(Editor:TCustomRichView);
begin
if Assigned(Editor) then begin
FRVEditor:=Editor;
end;
end;
I can register the component, place it in ther form and set FRVEditor on design-time.
Problem is when I run the application the code inside SetRVEditor() is not executed because Editor=nil.
If I was able to set FRVEditor on design-time, how come that it is=nil on run time ? How can I fix this ?
I add here my further comments because the explanation is too long
#Kenneth, thank you for your reply
TCustomRichView is part of a third part component set that manages
hypertext documents and has 4 more specialized descendents and you
are right, TCustomRichView shouldn't be used in a real application.
TRVEditFrame is the component I am developing.
The idea behind my component is to create one single frame (hence the choice of the component TFrame) with menus, shortcuts, popup menus etc to manage each of the 4 TCustomRichView descendents.
This is exactly the reason why I use TCustomRichView: I can "slot" any of the 4 descendents into my component-frame. This is the same principle of TDatasource that can be connected with TTAble and TQuery (they have the same ancestor).
I suppose the reason why the VCL doesn't link RVEditor to the TCustomRichView descendent I set on design-time is because TFrame has no OnCreate event, like TForm for instance.
So far I managed to solve the issue by calling TRVEditFrame.SetRVEditor manually in the TForm.OnCreate that hosts TRVEditFrame but I was wondering if there are better methods to do so and that is why I have asked advice here.
I know you can create a OnCreate event for TFrames as well, maybe I can place TRVEditFrame.SetRVEditor in there but, again, I was wondering if there was a better method.
Regarding the last part of your comment, I am aware of the register procedure but take into account the component is under development. When I develope components I never install them in the IDE because I prefer to keep the test stuff outside the "official" one.
I use this method and as soon as the component is ready then I register it with the procedure you mention. If I want to implement other features to the same component I can work on the test ones and keep on using the "official" one I have in the IDE at the same time.
My suggestion is to change the design in order to get the frame component referencing the custom editor by using the Notification method, instead of manually setting a property. So, change your frame class to
TRVEditFrame = class(TFrame)
...
private
{ Private declarations }
FRVEditor: TCustomRichView;
protected
procedure Notification(aComponent: TComponent; aOperation: TOperation); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
public
property RVEditor:TCustomRichView read FRVEditor;
end;
The implementation of the Notification method does the magic of connecting/disconnecting the frame to the custom editor
procedure TRVEditFrame.Notification(aComponent: TComponent;
aOperation: TOperation);
begin
inherited;
if aComponent is TCustomRichView then
if aOperation=opRemove then begin
if aComponent=FRVEditor then
FRVEditor := nil;
end else
FRVEditor := TCustomRichView(aComponent);
end;
I don´t know if you need any special handling when an editor is set/reset to the frame, so this code does nothing in special, just assigns the component or nil to the FRVEditor data member at the proper moment.
You should create child components.
constructor TRVEditFrame.Create(AOwner: TComponent);
begin
inherited; // Create(AOwner);
FRVEditor := TRichView.Create(self);
end;
DFM streaming engine may load properties of already created class, but it cannot create the class for you for two reasons:
1) DFM can not know of any special tuning done on created component, like what should be constructor parameters (if any), what constructor to use (of many), which niotifications and event handlers to attach and so on.
2) DFM can not know which type the property should be. For example i published TStrings oroperty - object of which class should be created to feel in ? TStrings? TStringList? TRichEditStringList? THashedStringList ? they all were inherited from TStrings and thus any of them is fine choice for DFM - but not for the ocmponent you write.
Thus DFM streaming subsystem is responsible for saving and loading properties of objects, but creating those object is purely your responsibility.
I believe you also may cut the corners by learning how IDE Designer creates your forms and making RVEdit a variable rather than property:
TRVEditFrame = class(TFrame)
...
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
var RVEditor:TCustomRichView; // just like users' forms are created by IDE
end;
Then hopefully TFrame constructor would create the content for this variable for you. But this design is fragile because any outer code would be able by any stupid mistake to make something like MyEditFrame.RVEditor := ... and cause a memory leak and unexpected failures of all the attached connections between the editor and the frame.

TSaveTextFileDialog and Vcl Styles

I'm using the TSaveTextFileDialog component in Delphi XE3, but when a Vcl Style is enabled the encoding combobox is draw using the current vcl style.
How i can fix this, I mean disable the vcl style for the combobox?
The parent class (TOpenTextFileDialog) of the TSaveTextFileDialog component adds a set of Vcl components to implement the Encodings and EncodingIndex properties, you can disable the Vcl styles on these Vcl controls using the StyleElements property. unfortunately these components are private so you need a little hack in order to gain access and disable the Vcl Styles.
Here you have two options.
Using a class helper.
You can introduce a helper function to get the Panel component which contains the Vcl controls of the dialog.
type
TOpenTextFileDialogHelper=class helper for TOpenTextFileDialog
function GetPanel : TPanel;
end;
function TOpenTextFileDialogHelper.GetPanel: TPanel;
begin
Result:=Self.FPanel;
end;
then you can write a method to disable the Vcl Styles, like so :
procedure DisableVclStyles(const Control : TControl);
var
i : Integer;
begin
if Control=nil then
Exit;
Control.StyleElements:=[];
if Control is TWinControl then
for i := 0 to TWinControl(Control).ControlCount-1 do
DisableVclStyles(TWinControl(Control).Controls[i]);
end;
And finally use on this way
DisableVclStyles(SaveTextFileDialog1.GetPanel);
SaveTextFileDialog1.Execute;
RTTI
Another option is use the RTTI to access the private Vcl components.
var
LRttiContext : TRttiContext;
LRttiField :TRttiField;
begin
LRttiContext:=TRttiContext.Create;
for LRttiField in LRttiContext.GetType(SaveTextFileDialog1.ClassType).GetFields do
if LRttiField.FieldType.IsInstance and LRttiField.FieldType.AsInstance.MetaclassType.ClassNameIs('TPanel') then
DisableVclStyles(TPanel(LRttiField.GetValue(SaveTextFileDialog1).AsObject));
SaveTextFileDialog1.Execute;
end;

Is it possible? TCollection descendant to implement storage of TPanel containers with arbitrary content

I'm new to component development in Delphi, therefore want to know, is it possible to implement my task at all.
I need to create a visual component (user control) based on TScrollBox, which will represent a bunch of TPanel, all that panels will be aligned as "Top" inside that TScrollBox and can have different Height. It has to act as TCollection (add, delete. reorder), and must allow users to add other controls into these panels at designtime.
I've created these classes for component:
type
TPanelsGrid = class;
TPanelsGridItem = class(TCollectionItem)
private
FPanel: TPanel;
procedure SetPanel(Value: TPanel);
function GetGrid: TPanelsGrid;
protected
function GetDisplayName: string; override;
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
// This is my TPanel object that should be used at designtime
// I thought "stored True" will serialize it automatically but I was wrong
property Panel: TPanel read FPanel write SetPanel stored True;
end;
TPanelsGridItems = class(TCollection)
private
FPanelsGrid: TPanelsGrid;
protected
function GetItem(Index: Integer): TPanelsGridItem;
procedure SetItem(Index: Integer; Value: TPanelsGridItem);
function GetOwner: TPersistent; override;
procedure Update(Item: TCollectionItem); override;
public
property EditorsGrid: TPanelsGrid read FPanelsGrid;
property Items[Index: Integer]: TPanelsGridItem
read GetItem write SetItem; default;
constructor Create(PanelsGrid: TPanelsGrid);
function Add: TPanelsGridItem;
procedure Delete(Index: Integer);
end;
TPanelsGrid = class(TScrollBox)
private
FItems: TPanelsGridItems;
procedure SetItems(Value: TPanelsGridItems);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Items: TPanelsGridItems read FItems write SetItems;
end;
This component is working ok at designtime, I can add-delete panels in stack, when I'm dropping some control (e.g. TCheckbox) on any panel, it's displayed as "owned by that panel": e.g. I can't drag this checkbox out of panel.
But this checkbox isn't stored in DFM-file and isn't displayed in "Structure" window.
I guess there must be some manual serialization-deserialization of TPanel's content, but I have no idea how to do that. Can't find any example on Internet. Plase give me some guideline, if such implementation is possible at all.
Addition:
This is how my DFM-file fragment looks like after adding 3 panels into grid:
object PanelsGrid1 : TPanelsGrid
Left = 8
Top = 8
Width = 536
Height = 382
Anchors = [akLeft, akTop, akRight, akBottom]
TabOrder = 0
Items = <
item
end
item
end
item
end>
end
As you can see, all items are empty but I dropped there a checkbox and radiobutton into item #3.
After all I decided to give up using TCollection, because during testing of DefineProperties method I has consistent IDE crash. I think TCollection just wasn't designed for such task.
I founded an appropriate implementation inside Delphi sources inside of control ExtCtrls.TCustomCategoryPanelGroup. It maintains the stack of panels which can be added or removed both at design time and runtime. I created my own classes, using the source code of TCustomCategoryPanelGroup and TCustomCategoryPanel and it works as I want.
I think you can look at the TMS Poly List control
The TMS Advanced Poly List components
offer an extremely versatile and
flexible architecture to create
virtually any possible lists of items
in user interfaces. This is seen
typically but not limited to the new
Office 2010 application menu. Contrary
to most user interface list controls,
where a list consists of items of the
same type or a collection of items of
the same type, the TMS Advanced Poly
List components can hold polymorph
items. All items just need to descend
from the base class TCustomItem and
any inherited items can be added. TMS
Advanced Poly List components come
with a large set of prebuilt list
items but custom item classes can be
added by either descending of the
TCustomItem base class or any of the
classes already provided. There are
item classes to show as list section
item, text item with HTML formatting,
text item with buttons, item with
expand/collaps behaviour, item with
image and many more. Items can be
added in the polymorph lists either at
design time, with a rich design time
editor and at runtime via code.
Make sure your child panels have names. You can override TCollection.Notify and if Action is cnAdded, make sure the panel has an name.

Resources