I've got a custom frame I need to inherit from in multiple projects. This frame includes some code and some components, and it resides somewhere on disk, in it's own project directory. I don't want to COPY it to the Object Repository folder, that doesn't seem right to me: I'd end up having two copies of the form, one in my Mercurial-backed repository, one in Delphi's Object Repository. Absolutely not a good idea.
What I want is to have my frame in a Package and have the Package do all that's required to make the frame known to the IDE and allow the IDE to create new siblings of the given frame, without actually adding the frame to every single project.
What I've done so far, problems I've encountered, solutions I tried:
- I added my frame to a package, registered my frame using both
RegisterClass
andRegisterNoIcon.
Problem: When I go into some other project and try to open a derived frame for editing it says it can't find my original frame. - To fix problem "1" I figured I'd have to register my frame as an Custom Module. So I called
RegisterCustomModule(TMyFrameName, TCustomModule)
. Problem: From the 'other' project I open a derived frame, the IDE doesn't create the components on my original frame and the IDE is complaining about one of the "inherited" components missing. - To fix "2" I thought I'd give the IDE a helping hand by calling
InitInheritedComponent(Self, TFrame)
. This helped, when I tried opening the frame in the 'other' project everything got re-created and I was able to see the frame as I expected. Problem: when I save the frame it forgets all about the inherited components, treats every single component as a new component added to this particular frame. If I look into the saved DFM everything starts with "object", nothing starts with "inherited" as I'd expect.
Unfortunately I got stuck on problem "3". I tried digging into Classes.pas, ToolsAPI, DesignIntf and DesignEditors but didn't find anything helpful. Apparently the "inherited" attribute I was hoping to see in the DFM is generated by TWriter when it's "TWriter.Ancestor" property is assigned before streaming a TComponent, but there's no way for me to set it up, the IDE needs to set it up. And I can't convince the IDE to do it for me.
Here are the cumulated, relevant parts of code:
TTestFrame = class(TFrame)
public
constructor Create(Owner:TComponent);override;
end;
constructor TTestFrame.Create(Owner: TComponent);
begin
inherited;
if csDesignInstance in ComponentState then InitInheritedComponent(Self, TFrame);
end;
procedure Register;
begin
RegisterClass(TTestFrame);
RegisterNoIcon([TTestFrame]);
RegisterCustomModule(TTestFrame, TCustomModule);
end;
Any ideas, besideds "give up and put your stuff into Object Repository"? Thanks!
Edit
Why I need to do this and why solutions that depend on actual path names getting written into my project's files don't work: I want to support Branching: when one branches it's reasonable to expect multiple versions of the same project being "alive" in different directories on the same machine. The corollary, I can't have multiple versions of the same project in the same place at the same time.
To make sure this works I decided to make my projects not depend on where the live, and to enforce this I have everyone on our team Clone (Mercurial terminology) or Check Out (SVN terminology) our projects in different directories. A hard-coded path on my system will not be good on my colleague's system: if any of us makes the mistake of hard-coding any sort of path into the application, it will not be long before it brakes on one of us, so the error gets fixed.
This is of course a problem with forms and frames that are part of some library (so they're not in our project's directory) that we need to inherit from! In order to get IDE support when working with those files we need to temporarily add them to the project and we need not forget removing them after we're done. If we forget and push/check in changes, the changes would brake the build for our colleagues (because they have the libraries checked out at different locations).
In order to solve this I attempted adding those frames and forms to a design time package (packages are loaded using full-path into the IDE, but the path is not part of the project's files so it's OK). Unfortunately this failed, and that's wyhy I posted this question.
There are a couple of aspects to this problem:
Enabling different project branches to use different versions of your own packages
D:\\Whatever\\Version1\xxx.bpl
to$(MyLib)\\xxx.bpl
.Using frames from a package at design time
Register;
procedure to the LibraryFrame unit, and putRegisterComponents('MyFrames', [TLibraryFrame]);
in its implementation.Inheriting from a frame in a package
Now that all of the above is set up, the Frame from the package is still not available to be inherited from in any other project than the library in which it was "created". However, it is now fairly simple to make it available for inheritance. Simply add
LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},
to your project's dpr and the frame will now show up in the "inheritable items" list when you use "Add New | Other" to add something to your project.Unfortunately however, when you select it, the IDE will complain:
Cannot open file "D:\Whatever\SecondFormReusingFrame\LibraryFrame_fr.pas". The system cannot find the file specified.
It obviously now expects the LibraryFrame in the project's folder instead of trying to find it in the MyLib folder. The IDE also redlines the TLibraryFrame type in the form although ctrl-clicking it does bring up the correct file...
Changing
LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},
to$(MyLib)\LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},
helps in as much as the IDE now no longer complains about not finding the LibraryFrame_fr.pas. But it does have the effect that when you save everything, the IDE "kindly" changes it to a relative path. In my case..\FrameToBeReusedSecond\LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},
which defeats the entire object of the exercise as it reintroduces dependency on path names albeit partly.However, leaving
LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},
in the dpr is not really an option either. When you reload the project the IDE complains about not finding the frame even though it can be found on the search path.Also leaving it like that and ignoring the IDE complaint when deriving from this frame, is not really an option. The IDE does not add a corresponding dfm... And while you could edit the uses in the dpr from
MyOwnFrame_fr in 'MyOwnFrame_fr.pas'
toMyOwnFrame_fr in 'MyOwnFrame_fr.pas' {LibraryFrame1}
and add a dfm stubb manually, the IDE still gets confused with not being able to find LibraryFrame_fr.pas ...All in all Cosmin, it seems the IDE is just a bit too set in it what it wants and expects. I think what you want is achievable but only if you are willing and able to have your library folders always in the same relative location to your projects.
Uh, what's wrong with simply adding the frame you want to reuse to the project in which you want to reuse it?
e.g.
Edit
If you do not want to include the hardcoded path to the frame in the dpr, there is always the ploy of using the IDE's environment variables.
When we created the build process out the last company I worked for, we wanted to do exactly the same thing. We use subversion and had a project for our shared components which contained a (Finalbuilder) project to build all our shared packages.
I was then going to use a similar technique to Marjan's (MyLib) variable. And launch Delphi from a batch file with the latest set of components.
In the end we found it wasn't necessary. The published properties of our main frames changed so infrequently that we just used one installed set of packages say in
c:\BDS\Components\D12\Bpl (This could be different from developer to developer)
But the code that was sitting in
c:\BDS\MyProject\Shared was always linked in when compiling because that was on the relative search path for when building the project
c:\BDS\MyProjectExperimental would use the correctly branched code when building as well.
We actually got around the repository problem by using CodeSmith (a code generator) to generate the frames, with the added bonus that they would be put in the right folder with correct naming conventions (and any branch specific changes as well). We did this as a time saver for us but we (by chance) avoided this problem all together.
The CodeSmith templates took a little bit of time to set up for the first frame, but then we easily adapted it to create other subclasses without too many brain cycles (for things such as dataModules).
cfEditFrame.dfm.cst
cfEditFrame.cst - The one script we had to call to generate a new subclassed frame
There may be other source code generators out there that do this just as well, if not better. We had CodeSmith and therefore used it. Hopefully the above files are formatted OK. I'm a SO newbie and the hopefully rendering of html like code is correct.
As far as having both properties and components on these frames you want to inherit from you need to do one strange thing. You need to do it in two steps. First you need to add the frame properties in the first class and THEN add the components in a subclass of that.
For example we add the custom properties in cfBaseEditFrames.TcfBaseEditFrame.
We then subclass that in cfEditFrames.TcfEditFrame = class(TcfBaseEditFrame). This is where we add our components (in our example a TActionList and TImageList)
When registering these in a package we added
RegisterCustomModule(TcfBaseEditFrame, TWinControlCustomModule);
We then ensure this package is in our project group and there is no problem opening up newly subclassed frames.
One last note, from memory it was important that the descendant frame (TcfEditFrame) was the one that added components. You couldn't add components in TcfBaseEditFrame and properties in TcfEditFrame.
BaseEditFrames.pas
BaseEditFrames.dfm
EditFrames.pas
EditFrames.dfm
FramePackage.dpk
You need to install this design time package. You could then have a frame in another project like the following
EditFrameDescendants.pas
EditFrameDescendants.dfm
You should be able to open up EditFrameDescendant, edit its amazing "NewFormProperty" and add actions to to its action list. Works for me....Good luck