Reputation: 81
We are working on a task to convert CHAR to NCHAR and VARCHAR to NVARCHAR in our databases. This would require replacing all persistent TStringField
components with TWideStringField
.
Since our app has over 5000 persistent TStringField
components across dozens of forms, I was opposed to having to change these all at design time. I was able to make this work using code that replaces TStringField
components with TWideStringField
at runtime.
My coworker suggested instead to add a type TStringField = class(TWideStringField)
declaration in the PAS file to make Delphi create TWideStringField
for components defined as TStringField
in the DFM.
I swore up and down this could never work, so I wouldn't even try it. ChatGPT agreed:
No, you cannot instantiate a different type for a component that is defined at design time in the DFM file simply by declaring a different type in your PAS file.
Well, it turns out it works! I'm astonished. I have never seen any case where any change in the PAS file could have any effect on the loading of the DFM. So presenting this to ChatGPT, it cheerfully explains why it does work:
When Delphi loads a DFM file, it looks for class names stored in the DFM and uses the class type registered under that name. By declaring:
type TStringField = class(TWideStringField);
before the form class, you're redefining TStringField as an alias for TWideStringField. So when the DFM loader encounters TStringField, it creates an instance of TWideStringField instead, because your TStringField is actually just TWideStringField under a different name.
However, based on its previous answer, I'm nervous about actually using this trick in production code. Is this explanation valid?
It did provide this "Alternative (Safer) Solution":
Instead of redefining TStringField, register a class alias using RegisterClassAlias. This achieves the same trick without messing with type definitions:
initialization RegisterClassAlias(TWideStringField, 'TStringField');
But, when I tried this, it didn't work. I get a "Type Mismatch" error. I was hopeful I could use this since it only requires adding one line to my entire app.
Sorry this got very long winded. Given all of the above, what do you think is the best way to deal with this task?
Upvotes: 4
Views: 129
Reputation: 47819
The alias for TStringField is only used when no class TStringField is registered via RegisterClass. You need to call UnregisterClass(TStringField)
first.
Unfortunately, that would not solve your problem.
When a DFM is loaded, the actual class type of any component inside is looked up by name in the Field Class Table of the container (the form etc.), which is built when compiling. Only when the class type is not found in the Field Class Table, they are looked up from the registered classes and aliases.
The actual classes in the Field Class Table table are sensitive to the current scope. That is why a re-declaration of TStringField before the form declaration works, but registering an alias does not.
That said, the Alternative (Safer) Solution is not suitable in your case.
Upvotes: 3
Reputation: 597916
It is a well known trick to declare a local class in a .pas
file that derives from another class that has been placed on a Form at design time. This is commonly referred to as an "interposer class".
This trick allows you to use a placeholder component at design-time and a more specialized class at run-time (usually for the purpose of overriding virtual methods, etc without having to install the new component in the IDE).
This works because the DFM system doesn't store qualified class names, so when it needs to instantiate a component, it will lookup the class type for the specified class name and find the last-declared class type that is in the current scope.
Upvotes: 6