Yet another TFrame IDE-registered-component question from me. Thanks for all the help, fellow programmers. : )
Playing around with Darrian's TFrame inheritance suggestion here:
Specifics:
Basically, I have a TFrame-based component that I've registered to the IDE, and it has worked wonderfully. I'm now developing a few "sister" components which will share a great deal of the existing component's non-visual functionality and properties. It makes sense, then, to move a lot of that to a parent/superclass which both the new and the old components can then inherit from.
What is the best way to "refactor" TFrame inheritance in this way? (This may apply to TForm-class descendants too, not sure). What are the caveats and things to watch out for?
Example:
I tried, for example, creating a new TFrame, with nothing on it, and calling that frame TMyBaseFrame. Then modified the class definition of my existing component (Let's call it TMyFrameTreeView) to inherit from that rather than TFrame.
It compiled fine, but when I tried dropping it on a form, I got "ClientHeight not found" (or "ClientHeight property not found"), and it wouldn't drop on the form. Deleting ClientHeight and ClientWidth from the related DFM wreaked havoc, and they ended up replaced upon resizing anyway. I noticed ExplicitHeight and ExplicitWidth in descendent classes, and am thinking that relates to property-value overrides from inherited values, but am not sure. Recreating an entirely new frame via New -> Inherited Items, and then copying everything over, hasn't yielded great results yet either.
Final Note
I realize this could get messy quickly, with streaming DFM files and multiple generations of descendants, etc.... which is part of why I'm asking for the overall "things to look out for" conceptual aspect, but also giving a specific real-world simpler version of the problem as well (which seems to me, ought to be doable).
I've created a little test package to hack around in learning attempts, and am learning a great deal, but it's slow-going, and any guidance/insight from you Delphi "Jedi Masters" out there would be MOST appreciated. : )
Answer update later:
Both of the answers below were helpful. As well, creating a "Base Frame Class" which has NO changes from the normal TFrame, and THEN inheriting from that before adding any properties, methods, etc. seems to stabilize the inheritance streaming tremendously. Not sure why, but so far it has.
In addition to changing base class of TMyFrameTreeView to TMyBaseFrame change the first word in the dfm file for TMyFrameTreeView from object to inherited.
I'm now developing a few "sister"
components which will share a great
deal of the existing component's
non-visual functionality and
properties. It makes sense, then, to
move a lot of that to a
parent/superclass which both the new
and the old components can then
inherit from.
What is the best way to "refactor"
TFrame inheritance in this way?
The crux of your text above perhaps is "component's non-visual functionality". So, in this case, IMHO it's best to separate the visual and non-visual layers.
So, perhaps it's better to use a decorator:
TMySharedEngine = class(Whatever)
property LinkedFrame: TFrame;
property P1;
property P2;
...
procedure Proc1;
procedure Proc2;
... //etc.
end;
and in your 'sister' frames to use instances of it:
var
TMyFrame1 = class(TFrame)
...
FDecorator: TMySharedEngine;
...
public
property MySharedPart: TMySharedEngine read FDecorator;
constructor Create(AOwner: TComponent); override;
...
end;
constructor TMyFrame1.(AOwner: TComponent); override;
begin
inherited;
FDecorator:=TMySharedEngine.Create; //ok, ok do not forget to Free it .Destroy
FDecorator.LinkedFrame:=Self;
...
end;
OTOH, if you want to use your approach you can use Visual Form Inheritance (as Darian suggested) or (more flexible) you can do it by hand: Create, using the IDE the following frames: TBaseFrame, TChildFrame1, TChildFrame2 ... etc. Now go on TChildFrame1's unit and change by hand it's class definition from TChildFrame1 = class(TFrame) to TChildFrame1 = class(TBaseFrame). Compile. It should work. It's recommended though that when you'll do this trick TBaseFrame to be empty in order to avoid possible small quirks (feature collisions etc.)
HTH.
Related
I have a datamodule for my frame, which uses a global instance.(dmData)
The data components are linked to the datasources on the dmData instance
now I want to use a datamodule instance that is private to a frame, because I want to have multiple instances of the form which contains the frame showing at the same time.
I can't figure out how to make that happen, either in code or in designing.
in the frame, I am creating the datamodule as dmLocalData := tdmData.Create(self), but in design I don't have the option to link dmLocalData, only the option to link to dmData(so all my data controls are blank (except for that ONE that has a local datasource that gets set in code)
I mean, in code, I could manually go through each component one by one and change the datasource, but thinking there really has to be a better way, the maintenance on that would be pretty much horrendous.
Any ideas about a better way?
Actually there is a way to avoid hand-wiring the controls for a dynamically created datamodule. In short - override the datamodules CreateNew constructor like this:
constructor TMainDM.CreateNew(AOwner: TComponent; Dummy: Integer);
begin
Dummy := -1;
inherited;
end;
This avoids that multiple instances of the datamodule get different names and thus the references are resolved as expected. As the datamodules are private to the frame anyway, there is no need for them to have globally unique names.
A much longer and more detailed explanation can be found in these two articles, which use a quite similar task as an example:
Tweaking DFM Loading (update)
Tweaking DFM Loading
I am in the progress of trying to learn a few new tricks in order to better organize some of the sourcecode in my units in Delphi.
I have noticed that some functions or methods I access appear to be classes inside classes, but I have not yet been successful in making a working class inside a class, although it compiles fine, I still get error messages when executing the code.
What I would like to do is to make kind of a tree with functions, procedures and values to set or get. I would be grateful if somebody could help me out a little bit with an example.
I have today some classes that are Types.
I then assign the types to a variable:
something=TSomething
and Then for something to happen I write "something.action".
My aim is to go further, and define sub-functions or/and sub-procedures.
Lets say I have three or four classes. TSnippet, TAction1, TAction2, TSubAction1, Etc.
I would like to use or assign these to a single variable and use them like:
Snippet.Action1.SubAction1.Organize(param1,param2);
Snippet.Action2.SubAction2.Returns='SomeString';
Snippet.Action1.SubAction1.SomeProcedure;
Is anybody able to help me with a useful example as in how to write code for this approach to work?
And also.. does anybydy know how such an implementation of code will affect CPYCycles needed in order to execute code versus the old fashioned method of having thousands of procedures with all different names, but more direct access (it feels like more direct access).
As of my first text was maybe a bit unclear, this follows.
I would like to make use of the editors automatic suggestions of procedures/functions available in order to simplify programming a little bit.
I started to make a Class for this, and it works great.
Consider a classname "Data". What can we do with data? We can Edit, Add, Delete, Save and Load.
Ok. This is my first Class.
Consider then another Class: "Encrypt". We can do DES, DES3, HASH, BITSHUFFLE.
I can go on with a third Class: "Compress". We can do LZW, ZIP, RAR, MP3, MPG, MP4, etc.
Instead of using these as 3 different classes, I would like to combine them in one, yet keeping them separate.
I would like to make a kind of an OwnerClass for the other classes. We can call this "MyStuff"
Whenever I type "MyStuff." in the editor, I should get up a list of "Data, Encrypt, Compress". Further, When I then choose "Compress", the next list for that class' procedures and functions will list up.
The classes may have some local variables, but the main functionality will be towards global arrays.
Maybe there are other ways of achieving this. I don't know.
My basic aim is to be able to categorize and put together routines that belong together.
This is already done in Units, but this does not help with the automatic list from the editor.
Thank you in advance.
Morten.
I think I understand what it is you're asking, after your edit.
What you're calling "classes in classes" are called properties and methods; they're other classes, variables, or procedures/functions that are declared in their containing class. The "list" you're talking about is called Code Insight, and it shows you the available properties and methods of the class you're referencing in your code at that point.
This should do something like you describe, and give you an idea of how to implement it in your own code:
unit MyStuffUnit;
interface
uses
SysUtils;
type
TEncryptionType = (etDES, etDES3, etHASH, etBITSHUFFLE);
TMyStuffEncryption = class(TObject)
private
FEncryptType: TEncryptionType;
public
constructor Create;
published
property EncryptionType: TEncryptionType read FEncryptType
write FEncryptType;
end;
TCompressionType = (ctLZW, ctZIP, ctRAR, ctMP3, ctMPG, ctMP4);
TMyStuffCompression = class(TObject)
private
FCompressionType: TCompressionType;
public
constructor Create;
published
property CompressionType: TCompressionType read FCompressionType
write FCompressionType;
end;
TMyStuff = class(TObject)
private
FCompression: TMyStuffCompression;
FEncryption: TMyStuffEncryption;
public
constructor Create;
destructor Destroy; override;
published
property Compression: TMyStuffCompression read FCompression
write FCompression;
property Encryption: TMyStuffEncryption read FEncryption
write FEncryption;
end;
implementation
constructor TMyStuffEncryption.Create;
begin
inherited;
FEncryptType := etDES;
end;
constructor TMyStuffCompression.Create;
begin
inherited;
FCompressionType := ctLZW;
end;
constructor TMyStuff.Create;
begin
inherited;
FCompression := TMyStuffCompression.Create;
FEncryption := TMyStuffEncryption.Create;
end;
destructor TMyStuff.Destroy;
begin
FCompression.Free;
FEncryption.Free;
inherited;
end;
end.
If you create an instance of TMyStuff in your code, you should be able to type MyStuff. and get the option of choosing Compression or Encryption. Choosing Compression should allow you to set the CompressionType property.
This should be enough to get you going. :-) Remember that Delphi includes the source code for the VCL and RTL in almost all editions, so you always have that to look at for examples. (They're not always the very best examples, but they should give you ideas on how to do things.)
This question already has answers here:
Delphi Enterprise: how can I apply the Visitor Pattern without circular references?
(4 answers)
Closed 8 years ago.
Is there a way of getting around circular unit references in Delphi?
Maybe a newer version of delphi or some magic hack or something?
My delphi project has 100 000+ lines of code mostly based on singleton classes. I need to refactor this, but that would mean several months of "circular reference" hell :)
I've been maintaining close to a million lines of legacy code for the past 10 years so I understand your pain!
In the code that I maintain, when I've encountered circular uses, I frequently have found that they are caused by constants or type definitions in unit A that are needed by unit B. (Sometimes it's also a small bit of code (or even, global variables) in Unit A that is also needed by unit B.
In this situation (when I'm lucky!) I can carefully extract those parts of the code into a new unit C that contains the constants, type definitions, and shared code. Then units A and B use unit C.
I post the above with some hesitance because I'm not an expert on software design and realize there are many others here who are far more knowledgeable than I am. Hopefully, though, my experience will be of some use to you.
It seems you have quite serious code design issues. Besides many signs of such issues, one is the circular unit reference. But as you said: you cannot refactor all the code.
Move all what is possible to the implementation section. They are allowed to have circular references.
To simplify this task you can use 3rd party tools. I would recommend
Peganza Pascal Analyzer - it will suggest what you can move to the implementation section. And will give you many more hints to improve your code quality.
Use the implementation section uses whenever possible, and limit what's in the interface uses clause to what has to be visible in the interface declarations.
There is no "magic hack". Circular references would cause an endless loop for the compiler (unit A requires compiling unit B which requires compiling unit A which requires compiling unit B, etc.).
If you have a specific instance where you think you cannot avoid circular references, edit your post and provide the code; I'm sure someone here can help you figure out how to get it fixed.
There is many ways to avoid circular references.
Delegates.
Way too often, an object will execute some code that should be done in an event instead than being done by the object itself. Whether it is because the programmer working on the project was too short on time(aren't we always?), didn't have enough experience/knowledge or was just lazy, some code like this eventually end up in applications. Real world exemple : TCPSocket component that directly update some visual component on the application's MainForm instead of having the main form register a "OnTCPActivity" procedure on the component.
Abstract Classes/Interfaces. Using either of them allow to remove a direct dependance between many units. An abstract class or an interface can be declared alone in its own unit, limiting dependancies to a maximum. Exemple: Our application has a debug form. It has uses on pretty much the whole application as it displays information from various area of the application. Even worse, every form that allows to show the debug form will also also end up requiring all the units from the debug form. A better approach would be to have a debug form which is essentially empty, but that has the capacity to register "DebugFrames".
TDebugFrm.RegisterDebugFrame(Frame : TDebugFrame);
That way, the TDebugFrm has no dependancies of its own (Except than on the TDebugFrame class). Any and all unit that requires to show the debug form can do so without risking to add too many dependancies either.
There are many other exemple... I bet it could fill a book of its own. Designing a clean class hierarchy in a time efficient fashion is pretty hard to do and it comes with experience. Knowing the tools available to achieve it and how to use them is the 1st step to achieve it. But to answer your question... There is no 1-size-fit-all answer to your question, it's always to be taken on a case by case basis.
Similar Question: Delphi Enterprise: how can I apply the Visitor Pattern without circular references?
The solution presented by Uwe Raabe uses interfaces to resolve the circular dependency.
Modelmaker Code Explorer has a really nice wizard for listing all the uses, including cycles.
It requires that your project compiles.
I agree with the other posters that it is a design issue.
You should carefully look at your design, and remove unused units.
At DelphiLive'09, I did a session titled Smarter code with Databases and data aware controls which contains quite few tips on good design (not limited to DB apps).
--jeroen
I found a solution that doesn't need the use of Interfaces but may not resolve every issues of the circular reference.
I have two classes in two units: TMap and TTile.
TMap contains a map and display it using isometric tiles (TTile).
I wanted to have a pointer in TTile to point back on the map. Map is a class property of TTile.
Class Var FoMap: TObject;
Normaly, you will need to declare each corresponding unit in the other unit... and get the circular reference.
Here, how I get around it.
In TTile, I declare map to be a TObject and move Map unit in the Uses clause of the Implementation section.
That way I can use map but need to cast it each time to TMap to access its properties.
Can I do better? If I could use a getter function to type cast it. But I will need to move Uses Map in the Interface section.... So, back to square one.
In the Implementation section, I did declare a getter function that is not part of my class. A Simple function.
Implementation
Uses Map;
Function Map: TMap;
Begin
Result := TMap(TTile.Map);
End;
Cool, I thought. Now, every time I need to call a property of my Map, I just use Map.MyProperty.
Ouch! Did compile! :) Did not work the expected way. The compiler use the Map property of TTile and not my function.
So, I rename my function to aMap. But my Muse spoke to me. NOOOOO! Rename the Class Property to aMap... Now I can use Map the way I intented it.
Map.Size; This call my little function, who typecast aMap as TMap;
Patrick Forest
I gave a previous answer but after some thinking and scratching I found a better way to solve the circular reference problem. Here my first unit who need a pointer on an object TB define in unit B.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, b, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FoB: TB;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
FoB := TB.Create(Self);
showmessage(FoB.owner.name);
end;
end.
Here the code of the Unit B where TB has a pointer on TForm1.
unit B;
interface
Uses
dialogs, Forms;
type
TForm1 = class(TForm);
TB = class
private
FaOwner: TForm1;
public
constructor Create(aOwner: TForm);
property owner: TForm1 read FaOwner;
end;
implementation
uses unit1;
Constructor TB.create(aOwner: TForm);
Begin
FaOwner := TForm1(aOwner);
FaOwner.Left := 500;
End;//Constructor
end.
And here why it compiles. First Unit B declare the use of Unit1 in the implementation section. Resolving immediately the circular reference unit between Unit1 et Unit B.
But to allow Delphi to compile, I need to give him something to chew on the declaration of FaOwner: TForm1. So, I add stub class name TForm1 who match the declaration of TForm1 in Unit1.
Next, when come the time to call the constructor, TForm1 is able to pass itself has the parameter. In the constructor code, I need to typecast the aOwner parameter to Unit1.TForm1. And voilĂ , FaOwner his set to point on my form.
Now, if the class TB need to use FaOwner internally, I don't need to typecast it every time
to Unit1.TForm1 because both declaration are the same. Note that you could set the declaration of to constructor to
Constructor TB.create(aOwner: TForm1);
but when TForm1 will call the constructor and pass itself has a parameter, you will need to typecast it has b.TForm1. Otherwise Delphi will throw an error telling that both TForm1 are not compatible. So each time you call the TB.constructor you will need to typecast to the appropriate TForm1. The first solution, using a common ancestor, his better. Write the typecast once and forget it.
After I posted it, I realized that I made a mistake telling that both TForm1 were identical. They are not Unit1.TForm1 has components and methods that are unknown to B.TForm1. Has long TB doesn't need to use them or just need to use the commonality given by TForm you're okay. If you need to call something particular to UNit1.TForm1 from TB, you will need to typecast it to Unit1.TForm1.
I try it and test it with Delphi 2010 and it compiled and worked.
Hope it will help and spare you some headache.
In a class declaration, you can press Ctrl+Space to get a list of virtual methods in the baseclass that you can override.
This list seems to be very limited, though. Ex.
TMyBaseClass = class(TInterfacedObject)
protected
procedure mymethod; virtual;
end;
TMyClass = class(TMyBaseClass)
protected
{Ctrl+Space here...}
end;
In TMyClass, I get methods from TInterfacedObject and TObject, but not from TMyBaseClass. Why is that?
-Vegar
EDIT: Forgot my delphi version... I'm using 2007.
Because sometimes these IDE tools work and sometimes they don't (and afaik this is not a problem that is unique to Delphi/RAD Studio).
For example, if you have other edits outstanding which mean that the IDE cannot adequately parse your source up to the current insertion point, then it is likely that such things will be "broken".
In such cases I personally try a quick Ctrl+F9 to see if there are compilation errors that I perhaps may not be aware of. 9 times out of 10 there are, and fixing those then fixes the code insight behaviour.
Recreating your example verbatim in a new unit, I get "mymethod" in my code completion list as expected, so is it possible that in your actual case that your base class and your derived class are actually separated by other code, perhaps even in separate units, and that that other code contains errors "between" the two declarations at the time at which you are trying to invoke code completion?
As a more general point, ime it's better to treat such things as "nice to haves when they work" but to try to develop habits and practices that don't rely on them. In this case the only substitute is knowledge of the methods to be overridden, which isn't much help I admit.
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.