Modifying VCL Component CODE - delphi

I need to change the functionality in a component. What do you do when you get a "you can't override this" message, or you need to change the code in a private method ("method does not exist in base class" message), rendering it impossible to descend the component?

If I face that problem,
I first try to inherit from the component, or its CustomXXX ancestor and see if that fixes the problem. If that doesn't,
I go deeper, i.e. try to intercept the messages that come in. That can be done dynamically. If that turns out to be too deep, because the code that has to be built on that is too extensive, or if I still have to access items I can't access,
I try hacks. One hack is to copy the component and the dependent code to a new unit with a different name, rename the component and modify what needs to be modified.
Sometimes I only need to redo one or two methods to make my new behaviour possible.
Never forget to give the unit a different name and the component a different name too (possibly inheriting from the original component or one of its ancestors, so they remain in the same hierarchy). Do never modify the original sources and then recompile the VCL. That is a maintenance nightmare.
I am no fan of interposer classes, i.e. classes that get the same name but different behaviour than the original classes, inheriting from the original. Their functionality depends on the order of inclusion in the uses clause, and that seems rahter flaky to me. I can't recommend that.
But what I do greatly depends on the problem. I don't think one can (or should) give a blanket advice that covers all situations.
But my main advice: do not modify the original units, always put the new code in a new unit and use a new class name. That way the original and the modified versions can peacefully co-exist, also in the IDE.

There are some (mainly hacky) options when it comes to modifying private methods or behavior therein:
modify the original source, recompile the unit and use the changed dcu as suggested here; never did this but I think this can cause you a good headache when your code uses the new dcu but other VCL code don't
often component behavior is controlled by numerous window messages - look if you can achieve your change by modifying the reaction on some of these messages; you can override the message handling methods (the ones with the message keyword) even if they are declared private and you can also replace the WndProc
you can use hacks like this one which is tinkering with casting
you could use some detour mechanism as described in the answers here
Or you can get another component.

Related

Adding declarations and definitions in the IDE for runtime components

I'm dealing with creating a number of components at runtime and setting their methods. Is there a way to add the declartion and definition stubs for a component without adding it to the form and clicking the corresponding event on the events tab?
I know I can add a component temporarily to create the stub, or sometimes the help files list the declaration. But there are a lot of features in the IDE that I have not found or used.
Replying to comments:
What I do currently is workable I just wondered if there was a shortcut I was missing. To reiterate as some did not understand; I am using component AwesomeComponent in my application. I only need to create instances of AwesomeComponent at runtime, so it is not on my form (or perhaps is non-visual). AmesomeComponent has an event DoSomething. That event has a number of parameters. In order to assign AmesomeComponent.OnDoSOmething at runtime, I need to code the event, but I don't have direct access to the procedure stub, I have to find it somewhere in documentation or create it on a dummy form to cut and paste it. I didn't know if there was an IDE feature that was smart enough to say "Ok, I recognize this name (AwesomeComponent.OnDoSOmething) as an event and create a stub for it. It must do essentially the same thing when double-clicking on the Events tab in the IDE.

How to rename a Form Name in Delphi XE7?

I am working on a multiple-device application in Embarcadero Delphi XE7.
Being new to delphi I would like to know how (if possible) can I rename a Form's Name.
e.g. TForm3 to something like frmCustomer.
You can change it from the Delphi Object Inspector - official documentation on how to customize the main form of an application.
You got me a bit confused. You want to rename a class name (TForm3) to frmCustomer. A defacto standard is to prefix all classes with a "T", like TfrmCustomer. Never have a class named frmCustomer, use that for the instance instead (here form3 -> frmCustomer). Generally it's a very good idea to be strict about renaming all controls. Makes things a whole lot easier to maintain.
I prefer to use the built in refactoring (shift-ctrl-e) while having the cursor stand in the """thing" you want renamed, e.g. TForm3. That way the name, that be a class, method or attribute, is changed throughout the project.
I can furthermore recommend you install GExperts which, among many good features, have one to name the components while you place them.

How to modify TCustomEdit so that all descendants inherit the changes?

After reading this question, I was wondering if it is possible to modify TCustomEdit to check for the text width in a way that all of its descendants inherit the changes too?
Most changes you make to TCustomEdit will be inherited by descendants. That's generally how inheritance works. It specifically depends on what kind of changes you make, though:
If you edit StdCtrls.pas, then any changes you make will be inherited by any newly compiled code. One obstacle to this technique is getting Delphi accept the changes to your version of StdCtrls.pas without having to recompile other parts of the library that are difficult or impossible to recompile.
If you patch TCustomEdit methods at run time (by the usual technique of overwriting the first few bytes of the method to direct control to a method of your own), then those changes will also be inherited by descendants.
If you patch the TCustomEdit virtual-method table, then some changes will be inherited, but not all. Virtual calls to your patched methods will use your custom version, but non-virtual calls will continue to use the original version. When a descendant uses inherited, the dispatch of that call to the inherited method is not virtual, so the VMT is not involved, and the descendant will end up calling the original TCustomEdit method.
TCustomEdit is in stdctrls Unit. to test out what changes would be inherited, don't modify the one on the VCL path, copy stdctrls to your main project folder and modify it there. Delphi will find your modified unit first and will use it.
This leaves all other projects and VCL patches using/updating the original unit. Your patched unit would not be affected.
If I am right then modifying it even in the new location would cause a rebuild of the relevant VCL units. So when switching to other projects you will need to do a build so it recompiles with the VCL parts using the original unit in effect "switching back".

What does TDataModule.ClassGroup pseudo-property in Delphi XE2 really do?

I tried to copy and paste a component from one data module into another in Delphi XE2. The component was a Fast Report data source link component. The data module was brand new, just created that second, in XE2.
Someone else had the same problem and reported it on quality central as 106369 and same error message leading me to this mysterious DocWiki entry.
So data modules now have a framework affinity, and a designtime-only pseudo property, which according to the docs:
"Because the ClassGroup pseudo-property is used only by the IDE and is not a compiler-generated property (hence, 'pseudo-property'), ClassGroup is not documented in the compiler-generated Libraries Reference. The page you are reading is the documentation for ClassGroup."
So, the first time I even learn this exists is when it blocks me from copy and pasting components into my data module from an existing set of designtime building blocks that I didn't wish to rebuild from scratch.
Once I change the data module affinity, I can paste stuff into data modules without it bugging me. Thank goodness for Google-that-error or I'd be stuck.
If it is intended to help us write cross platform data modules, and yet it only affects the IDE, according to the documentation, that's inconsistent with the warning that you get when you play with this at designtime, here's the error you get if you change it:
EInvalidType : The following component(s) are not available in the specified
class group. This is likely to cause compile or runtime errors.
frxDBSet.TfrxDBDataset.
What I can't see is how that error message can be correct, and the documentation can also be correct.
The warnings seem to suggest compile, link, and runtime errors if this is set incorrectly. Curious minds who want to know what's really going on, want to know: What is this thing about and why did it get added to the data modules in XE2. I anticipate other people will stumble upon this weird feature, with the feeling that they've stepped in something like dinosaur droppings, and want to know what is up with this feature.
My best answer at this point is that a data module affinity for TPersistent which means, in XE2 lingo, that this data module doesn't want non-visual controls in it, that are VCL-specific. In a future version of Delphi, perhaps a similar marker would allow us to mark forms as being "clean of dependencies on the VCL or windows" too?
Update 1: The .PAS source code of your data module stores this pseudo-property in a way that looks a bit like a compiler directive, like this:
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
I think it is pretty obviously what its intended use is for, and the documentation you link to is pretty extensive about that purpose.
It is meant to prevent VCL-only components from being placed on a FireMonkey-accessible DataModule, and vice versa.
Since TDataModule is initially framework-neutral, only framework-neutral components can be placed on it.
So obviously, your source DataModule has a different affinity than your new DataModule, which is why copy/paste does not work until you change the affinity of the new DataModule to match the affinity of the source DataModule.

Visual Form Inheritance problem. "cannot inherit from (parent form). Contains a component that does not support inheritance"

I have a base form with descendents. The base form has a TElRelDataSource on it (from ElPack v4.00). When I open the descendent form I get an error "cannot inherit from (parent form). Contains a component that does not support inheritance". This is with the ancestor forms open (there are two), to avoid the "ancestor for (parent form) not found" error.
Delphi 7 doesn't (AFAIK) support finalising classes, but this seems to be a standard error message. It's in the index for "Tomes of Delphi: Troubleshooting" but I don't have a copy of that and I'm reluctant to spend $US60 to read an entry like "this is why you should avoid VFI" (being pessimistic about the contents of the book). Other than that, no references in Google or Bing.
This is in code that I've recently inherited from someone who left a while ago and I'm the first person to change it since they left. Which means I can't ask how he created the offending form.
So, how do I get around this error message?
You got yourself into an interesting problem. First of all, you say the parent form already includes the offending DataSource, so you've got to ask yourself a question: How did the original developer manage to put the dataset on the parent form and then create child forms, if creating child forms is rejected by the datasource?
The original code didn't have the TElRelDataSource
This implies you're trying to add it your self. Don't add it, find an other solution that doesn't rely on the TElRelDataSource.
The original code did have it!
The original was probably compiled against a different version of TElRelDataSource, one that did allow inheritance. "Inherability" is controlled by the "csInheritable" component style. That is, if the component doesn't include "csInheritable" in ComponentStyle, the Delphi IDE will not allow visual inheritance. You have a number of options:
Use a different version of TElRelDataSource, maybe the original was compiled against a NEWER version of TElRelDataSource. If you are using the latest TElRelDataSource, the author probably decided it's code can't handle visual inheritance so he/she removed the csInheritable flag from ComponentStyle!
Try making your own derived component and set that style yourself. This is easily done, but if TElRelDataSource's author removed the csInheritable flag for a reason, you'll probably get into trouble.
Normally HeartWare's idea would work just fine; Unfortunately it's not that easy with a DATA SOURCE component: I assume you have DB-Aware controls linked to that data source. If you create the DataSource from code, you'll need to re-create the connections to data-aware controls as well, and that's going to take a lot of "if-s" (or clever use of RTTI).
You may try moving the DataSource to a TDataModule. This is probably more trouble then it's worth it.
One option would be to remove the offending component from the form and allocate it dynamically in the FormCreate event. Depending on how many properties the (presumed) non-visual Data Source component has, this could be fairly simple.

Resources