Tool: Delphi 6 Pro
I created a new component in my main components package that is a descendant of TFrame using the Component -> New Component option. When I try to draw the component on a form during design time I get a "Resource {component class name} not found" error. I tried adding the line {$R *.dfm} to the component unit just after the "implementation" declaration and that didn't work. (I did recompile the host package first). I even tried copying over a DFM from another frame and then renaming everything to sync up with the main unit including the DFM file name itself. That didn't work either.
I want to have the TFrame descendant as a Component instead of just creating a new TFrame variant because I want to add properties to it that show up in the Property Editor at design time. Is there a way to make this work?
Thanks in advance.
Related
I would like to add "MyProperty" to TTabSheet and work with it at design time. However if I subclass it I fear I will need to also subclass TPageControl, since it internally creates/manages the TTabSheets.
I believe this would require duplicating the TTabSheet management code to reference TMyTabSheet instead of TTabSheet, since it will need to invoke TMyTabSheet.create. This feels like something I will regret when a new version of Delphi updates TPageControl and I forget to update TMyPageControl.
I am also researching "class helpers" as another option.
Does anyone have advice on how to best accomplish this?
Thanks!
Type Helpers are merely compile-time sugar, they would cause no effect over the already compiled code, that stock VCL is together with TPageControl and TForm. That is a dead-end.
However you have a slight misconception here that TPageControl... internally creates... the TTabSheets. Indeed, when you create the sheets by the means of TPageControl itself, like right-clicking it and selecting "New Tab" that si what happens. But when you create the living form object out of DFM file (or DFM resource in your compiled EXE) that is TForm itself that creates ALL the components, including both page control and its tabsheets.
Just see my answer at How to efficiently let a `ParentFont = False` child control to use same font name as parent? - that would show you how far it is about the owner - TForm, not about TPageControl or other parent components when it comes about selecting specific classes for the actual tabs or other elements..
So you are free to pursue subclassing tabsheets only.
Create the TMyTabsheet = class(TTabSheet) component
Create and install into IDE the design-time package that would introduce new subclassed tab sheet to Delphi Form Editor
In your form put the stock TPageControl and create all the needed tabs regular way
in IDE form Editor right-click over the form free space and in the menu do the "View As Text Alt-F12" command - you would see the text content of your form's DFM file
in those DFM sources find your tab sheets and change their stock TTabSheet class to be your derived sub-class
right-click the text editor and choose "View As Form Alt-F12" command
If all was done correct then Delphi would recreate the form with your new-class sheets now. Find your new properties in Object Inspector and change them.
Now switch to .Pas sources of your form and find the declarations of those tab sheets and change their type too. That is only required if you would have to access your new properties from the Delphi sources. If not you can leave their declaration as TTabSheet in pas-file as your class is direct descendant from it. You may leave those declarations as they were - but then you would have to add RegisterClass(TMyTabSheet); call into the very initialization section at the bottom of your unit, so when the form would construct itself out of the DFM it would be able to find the class implementation by the name. If you would change the declaration (at least one of those) then your form would automagically call all needed RegisterClass before streaming out of DFM. Choose any option you like.
Optionally and later, extend your design-time package to find and hijack IDE Form Editor's right-click menu for TPageControl and add "New My Subclassed Tab" command there. Just to avoid manual post-factum DFM editing. if you would do it often
This feels like something I will regret when a new version of Delphi updates TPageControl
After you created and tuned the form and saved it into DFM - it would be TForm that creates all the components out of the saved DFM-data, and that includes your tabs too. Unless very improbable event EMBT would kill the whole VCL streaming (made back in Delphi 1 in 1995) and redesign it from scratch (killing all the compatibility with existing Delphi sources at once), there should be no problem with forward compatibility with specifying your class in DFM. It is just the standard way VCL is designed - to get specific component types from the DFM.
If I put a component on the form I don't see any code like MyComp:=TMyComponent.Create in unit code. I think the component is created automatically, but when ? And the same happends with the properties that I configured after I put the component on the form. When they are applied at runtime ?
The properties for a form and all the design time components that live on it are streamed in by the framework during the construction of the form. That process is triggered from the form's constructor, in TCustomForm.Create. The pertinent code in there looks like this:
Include(FFormState, fsCreating);
try
if not InitInheritedComponent(Self, TForm) then
raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
The key is the call to InitInheritedComponent. That is a function defined in the Classes unit that does the heavy lifting. In a very broad overview it does the following:
Finds the name of the form class and looks for an RT_RCDATA resource of that name. That resource is the .dfm file.
Once the .dfm resource has been located it is parsed.
The .dfm parsing handles the assignment of properties that you set at design-time in the Object Inspector. For instance the parsing might encounter a line like this: Caption = 'My main form' and it turns that into an assignment of the string 'My main form' to the form's property Caption.
The .dfm file is hierarchical. It contains the properties for the various components and controls that are defined at design-time.
As well as setting the properties of the form's design-time components, the .dfm streaming process also instantiates those components.
In order for all of this to work, the streaming framework relies on RTTI. It knows nothing at all at compile time of your classes and components. Hence the need for RTTI. The streaming framework uses the old style RTTI and in fact that is the reason for the existence of old style RTTI. If ever you wonder about why old style RTTI is the way it is, try to view it from the perspective of having been designed to support streaming.
Information about controls and components as well as their properties that you edit while designing in IDE will be stored in your form .dfm file. Creation of that form in run-time will trigger the process of automatic loading of that .dfm file and all controls and components will be initialized at that time.
This is rather simple explanation of what exactly happens, you can start debugger at form creation line and follow what is happening there, but it is quite longish process and you can easily get lost if you are still learning.
You can find form creation code that Delphi automatically writes in .dpr file of your project.
Application.CreateForm(TForm1, Form1);
My Delphi program builds and compiles fine, however as soon as it is run in the debug mode, I get the following error;
Property ClientHeight does Not Exist
After looking through all of the .DFM file sources, in every form the code is there which is;
ClientHeight = 111
I'm not understanding where I go wrong here?
Your forms would have been saved with a newer version of Delphi. Unfortunately you will need to open each form in the IDE and save it again to clear the newer properties. There is a tool that can help you called DFMCheck (http://andy.jgknet.de/blog/ide-tools/dfmcheck/). This is an add on that will go through all of your forms and tell you about any problems with the forms that will only show up at runtime.
The reason why you are seeing the problem is this. Delphi saves the forms with all of the properties. It uses streaming to load the forms at runtime. When it tries to load a form with properties that don't exist then you will get an error like this as the streaming system is trying to set a property on a component when the property doesn't exist.
I know this is old thread, but hopefully this will help others that has this problem.
In cases like this where your class inheriteds from other and you know the properties are there, just re-publish them .Add a published section and add them again for example:
published
property ClientWidth;
property ClientHeight;
This then forces the compiler to compile these typeinfo for parts where the parents classes might have forward declarations and thus , resolve your issue.
Hope it helps somebody, took me 3 days to get to the solution eventually.
Same bug happens in modern Delphi (e.g. Rio 10.3) with FMX frames. After some investigation it revealed to be caused by tweaking TFrame inheritance. Example below:
type
// Declaration of custom type
TFrameEx = class(TFrame) .. {here I override a couple of methods} end;
// Causes a bug (described below)
TMyFrame = class(TFrameEx)
// Works fine
TMyFrame = class(TFrame)
Explanation:
Due to changed type, Delphi was unable to correctly choose TMyFrame type between FMX and VCL. So when the TMyFrame was opened in IDE it would ask to strip out FMX properties (non-existent in VCL, e.g. Size.Width) and add VCL properties (e.g. ClientWidth). When saved, that would make the TMyFrame buggy - it would show the "Property ClientHeight does Not Exist" error in runtime upon init.
Had similar bug. First you need a dfm file for your frame.
When you inherit a frame, the dfm file must starts with "inherited MyFrame: TFRameEx" and NOT "object MyFrame: TFrameEx". Without the inherited, when I did it, it was adding TForm properties and in the editor the frame had TForm events, in Delphi 10.3. So delphi really needs the dfm to find the right type. If you use the ide menu, it will be done automatically. New->Others->inheritables it will create the dfm with the inherited line, create a file with {$R *.dfm} in it and a line in the project source "unitname in '......pas' {MyFrame TFrame};" Or you can do it by hand.
As for the possibility of having multiple frames in the same unit, havent tested it myself but since the line is {$R *.dfm} it might be doable.
wanted it to be a comment for the solution of kromster but cant comment apparently.
In my case, I was inheriting TFrame that was save in Delphi 7, and I change the .dfm to resolve.
The first line: "object" frmMain: TfrmMain
I changed to "inherited", like this: inherited frmMain: TfrmMain
I just created a form within bpl project and place it on repository, named AncForm.
When I made its descendant in a new project (program Inheritance1) named DecForm.
Normally, AncForm will be included in the new project automatically when DecForm just inherited from AncForm.
program Inheritance1;
{$R *.res}
uses
Forms,
cAncForm in 'cAncForm.pas' {AncForm}, //-----> Ancestor ..... Line A
uDecForm in 'uDecForm.pas' {DecForm}; //-----> Descendant ..... Line B
begin
Application.Initialize;
Application.CreateForm(TDecForm, DecForm);
Application.Run;
end.
The question is: is there any way to link the DecForm to AncForm within this project
without the presence of "Line A"?
I mean the AncForm is not visually linked to project but still be able to provide reference to DecForm within IDE, without "error creating form...".
I hope there is a way to fully wrap the ancestor inside BPL.
I would gratefully thanks for any idea.
You need to add the package project in which the ancestor form lives to the same project group as your applications. The IDE will notice that the form is in the package project and not add it to the uses list of the application project.
Every once in a while when I am tweaking my TFrame classes (adding properties, methods, etc), the IDE gets confused and acts as if it thinks the frame is a form, complete with header/caption, borders, etc. Yet, clearly the class is declared as a TFrame descendent. Any ideas as to what causes this, how to prevent, and how to fix?
I'm using Delphi 2007 Pro. Also note (if it matters), the TFrame descendents are typically registered with the IDE (i.e. on the palette) via a design-time package.
Later: Additional "specifics": The frame that I'm having this problem with at the moment is, visually, a VERY basic TFrame (only change from brand new TFrame is size, and background color).
Here's its class declaration:
TBasePanel = class(TFrame)
private
FPanelManager: TPanelManager;
procedure SetPanelManager(const Value: TPanelManager);
protected
procedure Connect; virtual; abstract;
procedure Disconnect; virtual; abstract;
procedure Refresh; virtual;
procedure Requery; virtual; abstract;
published
property PanelManager: TPanelManager read FPanelManager write
SetPanelManager;
This frame is used as a base class for a number of others. I am usually editing it directly from the BPL project it belongs to (because all of these frames install to the palette), rather than as part of an EXE project, with related Forms open etc.
Also, "Embedded designer" is checked in Tools -> Options.
I am saving all DFM files as text rather than binary as well (if that matters at all).
I have encountered the same problem. The following steps solved the problem for us, it might also work for you:
in the IDE: close all forms that use the frame
open the frame, view as text (*.dfm)
the dfm probably begins with object MyFrame: TMyFrameClass
change this to inherited MyFrame: TMyFrameClass
I don't know what caused the problem.
Perhaps you had unchecked the 'Embedded designer' check box? (Tools | Options | Environment Options | VCL Designer). Then, indeed, your frame is shown at design time as a form (with caption, border etc.). Also a concrete code of your problematic TFrame descendant or more details about your case would help.
As far as I know, you have to have both the form and the frame open in the editor when you edit the frame. Else there can be update problems. Although I haven't seen this one.
But I gave up on frames a long time ago because I did not find them very reliable.
Right now I only use them for prototyping, creating a custom component (derived of a panel with the apropriate controls on it).
You may have to register custom module to the IDE
But your additional properties won't work well unless they are in ancestor class.
I have encounter a lot of problems with TFrame and finally came to such workaround that solves all my problems: I create and design frames visually, but use them only by hand-coding.
As a side effect my applications became smaller, because of less dfm-s.