I been reading bad things about the with keyword in delphi but, in my opinion, if you don't over use it. It can make your code look simple.
I often put all my TClientDataSets and TFields in TDataModules. So in my forms I had code like this
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
with LongNameDataModule do
begin
LongNameTable1.Insert;
LongNameTable1_Field1.Value := "some value";
LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
LongNameTable1.Post;
end
end;
without the with keyword I have to write the code like this
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
LongNameDataModule.LongNameTable1.Insert;
LongNameDataModule.LongNameTable1_LongNameField1.Value := "some value";
LongNameDataModule.LongNameTable1_LongNameField2.Value :=
LongNameDataModule.LongNameTable2_LongNameField1.Value;
LongNameDataModule.LongNameTable1_LongNameField3.Value :=
LongNameDataModule.LongNameTable3_LongNameField1.Value;
LongNameDataModule.LongNameTable1_LongNameField4.Value :=
LongNameDataModule.LongNameTable4_LongNameField1.Value;
LongNameDataModule.LongNameTable1.Post;
end;
I think is easier to read using the with keyword.
Should I avoid using the with keyword?
The biggest danger of with, outside of pathological conditions like "with A, B, C, D" is that your code can silently change meaning with no notice to you. Consider this example:
with TFoo.Create
try
Bar := Baz;
DoSomething();
finally
Free;
end;
You write this code knowing that Bar is a property of TFoo, and Baz is a property of the type containing the method which has this code.
Now, two years later, some well-meaning developer comes in adds a Baz property to TFoo. Your code has silently changed meaning. The compiler won't complain, but the code is now broken.
The with keyword is a nice feature for making your code more readable but there are some pitfalls.
Debugging:
When using code like this:
with TMyClass.Create do
try
Add('foo');
finally
Free;
end;
There is no way to inspect the properties of this class, so always declare a variable and use the with keyword on that.
Interfaces:
When creating an interface in the with clause it lives till the end of your method:
procedure MemoryHog;
begin
with GetInterfaceThatTakes50MBOfMemory do
Whatever;
ShowMessage('I''m still using 50MB of memory!');
end;
Clarity
When using a class in a with clause that has properties or method names that already exists within the scope, it can fool you easily.
with TMyForm.Create do
Width := Width + 2; //which width in this with is width?
Of course when having duplicate names, you're using the properties and methods of the class declared in your with statement (TMyForm).
The with statement has its place but I have to agree that overuse can lead to ambiguous code. A good rule of thumb is to make sure the code is "more" readable and maintainable after adding the with statement. If you feel you need to add comments to explain the code after adding the statement then it is probably a bad idea. If the code is more readable as in your example then use it.
btw: this was always one of my favorite patterns in Delphi for showing a modal window
with TForm.Create(nil) do
try
ShowModal;
finally
Free;
end
I tend towards baning the with-statement altogether. As previously stated, it can make things complicated, and my experience is that it will. Many times the debugger want evaluate values because of withs, and all to often I find nested withs that lead to code that hard to read.
Brian's code seems readable and nice, but the code would be shorter if you just typecast the sender directly, and you remove all doubt about that component you enable:
TAction(Sender).Enabled := Something;
If you are concerned about typing to much, I prefare to make a temporary referance to the long-named object:
var
t: TTable;
begin
t := theLongNamedDataModule.WithItsLongNamedTable;
t.FieldByName(' ');
end;
I can't se why typing should bother you, though. We Are Typists First, Programmers Second, and code-completion, copy-paste and key-recording can help you be a more effective typist.
update:
Just stumbled over an long article with a little section on with-statements: he with keyword. The most hideous, dangerous, blow-your-own-feet-off feature in the language. :-)
When I first began pascal programming (with TurboPascal!) and learnt as I went, WITH seemed wonderful. As you say, the answer to tedious typing and ideal for those long records. Since Delphi arrived, I've been removing it and encouraging other to drop it - neatly summed-up by Verity at the register
Apart from a reduction in readability there are two main reasons why I'd avoid it:
If you use a class then you dont need it anyway - only records 'seem' to benefit from it.
Using the debugger to follow the code to the declaration with Ctrl-Enter doesnt work.
That said, for readability I still use the syntax:
procedure ActionOnUpdate( Sender : TObject )
begin
With Sender as TAction do
Enabled := Something
end;
I've not seen a better construct.
Your example, of a datamodule access within a button click, is a poorly contrived example in my opinion. The whole need for WITH goes away if you move this code into the data module where it should be. The OnClick then just calls LongNameDataModule.InsertStuff and there is no with needed.
With is a poor device, and you should look at your code to see why you are needing it. You probably did something wrong, or could do it a better way.
As Vegar mentioned, it's just as neat and much more readable, easier to debug, and less prone to stealth issues to use a temporary reference.
So far, I have never found a need to use with. I used to be ambivalent over it, until I took over a project that used the mind bending double with frequently. Questioning whether the original developer intended to reference items in the first with or second, if that ambiguous reference was a with-slip or clumsy code, the torment of trying to debug it, and the knock on effects of extending or modifying classes that use these abominations is just not worth anyone's time.
Explicit code is simply more readable. This way you can have your cake and enjoy eating it.
procedure TMyForm.AddButtonClick(Sender: TObject);
var
dm: TLongNameDataModuleType
begin
dm:=LongNameDataModule;
dm.LongNameTable1.Insert;
dm.LongNameTable1_Field1.Value := "some value";
dm.LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
dm.LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
dm.LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
dm.LongNameTable1.Post;
end;
I'm a firm believer of removing WITH support in Delphi. Your example usage of using a datamodule with named fields is about the only instance I could see it working out. Otherwise the best argument against it was given by Craig Stuntz - which I voted up.
I just like to point out that over time you may eventually (should) rmeove all coding in OnClick events and your code will also eventually migrate away from named fields on datamodules into using classes that wrap this data and the reason to use WITH will go away.
Your question is an excellent example of 'a hammer is not always the solution'.
In this case, 'with' is not your solution: You should move this business logic out of your form into your datamodule.
Not doing so violates Law of Demeter like mghie (Michael Hieke) already commented.
Maybe your example was just illustrative, but if you are actually using code like that in your projects, this is what you should do in stead:
procedure TLongNameDataModule.AddToLongNameTable1(const NewField1Value: string);
begin
LongNameTable1.Insert;
LongNameTable1_Field1.Value := NewField1Value;
LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
LongNameTable1.Post;
end;
And then call it from your form like this:
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
LongNameDataModule.AddToLongNameTable1('some value');
end;
This effectively gets rid of your with statement, and makes your code more maintainable at the same time.
Of course surrounding Delphi strings with single quotes will help making it compile as well ;-)
The main problem with "with" is that you don't know where its scope ends, and you could have multiple overlapping with statements.
I don't think you should avoid using it, as long as your code is readable.
One of the proposals to make it more readable (and less confusing in longer code) was if codegear added the option to allow for aliases in with, and probably allowing multiple withs in one:
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
with LongNameDataModule as dm, dm.LongNameTable1 as t1, dm.LongNameTable2 as t2 do
begin
t1.Insert;
t1.FieldByName('Field1').AsString := 'some value';
t1.FieldByName('Field2').AsString := t2.FieldByName('Field2').AsString;
t1.Post;
dm.Connection.Commit;
end
end;
As far as I'm concerned, With is quite acceptable in the case you give. It certainly improves the code clarity.
The real evil is when you have multiple with's open at once.
Also, my opinion is that what you are using the with on makes a big difference. If it's a truly different object then the with is probably a bad idea. However, I dislike having a lot of variables at one level even when this makes sense--generally data objects that hold an entire very complex data item--generally the entire piece of work the program is designed to work with. (I do not think this case would occur in an app that didn't have such an item.) To make the world clearer I often use records to group related items. I find that almost all withs I use are for accessing such subgroups.
There are many excelent answers here as to why the with statement is bad, so I'll try not to repeat them. I've been using the with statement for years and I'm very much starting to shy away from it. This is partially beause it can be difficult to work out scope, but I've been starting to get into refactoring lately, and none of the automated refactorings work withing a with statement - and automated refactoring is awesome.
Also some time ago I made a video on why the with statement is bad, it's not one of my best works but Here it is
Use with only temporarily (just as you comment-out temporarily).
It helps you write code sketches to get something compiled and running fast. If you consolidate the solution, clean it up! Remove with as you move code to the right place.
The current With statement is "dangerous", but it can be substantially improved:
With TForm1.Create (Nil) Do // New TForm1 instance
Try
LogForm ("); // That same instance as parameter to an outer method
"ShowModal; // Instance.ShowModal
Finally
"Free; // Instance.Free
End;
My proposal is:
No more than one object/record per With header.
Nested Withs not allowed.
Usage of " to indicate the object/record (double quotes are similar
to the ditto mark: http://en.wikipedia.org/wiki/Ditto_mark).
Related
so I was just looking through the Code of our inventory management system
and I saw some Snippet's of one of my Coworkers, all he does in some Functions is
simply open them and insert a command in there for example
procedure TWerkStF.TBBtnStatiClick(Sender: TObject);
begin
inherited;
//
end;
so i am wondering when you should do something like that and when is it usefull, are there some benefits?
This is not useful, and has no benefits. Such a function can, and should, be removed.
The function in the question, by using inherited, simply searches in the super classes for a function of the same name, and if one is found calls it. If one is not found then no action is performed. As a rule, such a function, one that only calls inherited, does not modify the behaviour of the program. You can remove it without changing behaviour.
We use such functions/procedures most times to tell other Co-Workers where they should not change anything, the reason for that is because some events are just not fitting the needs of some Modules, therefore we "warn" each other with empty Functions.
Sounds wiered, i know.
This is similar to this question. I asked "Why?" to the most popular response but I don't know that anyone would ever look at it again. At least not in any timely manner.
Anyway, my question is about best practices for delegating responsibility for creation of objects to functions or procedures, without causing memory leaks. It seems that this:
procedure FillObject(MyObject: TMyObject; SomeParam: Integer);
begin
//Database operations to fill object
end;
procedure CallUsingProcedure();
var
MyObject: TMyObject;
begin
MyObject = TMyObject.Create();
try
FillObject(MyObject, 1);
//use object
finally
MyObject.Free();
end;
end;
is preferred over this:
function CreateMyObject(DBID: Integer): TMyObject;
begin
Result := TMyObject.Create();
try
//Database operations to fill object
except on E: Exception do
begin
Result.Free();
raise;
end;
end;
end;
procedure CallUsingFunction();
var
MyObject: TMyObject;
begin
MyObject = CreateMyObject(1);
try
//use object
finally
MyObject.Free();
end;
end;
Why?
I'm relatively new to Delphi, having previously worked most with Java and PHP, as well as C++, though to a lesser extent. Intuitively, I lean toward the function method because:
It encapsulates the object creation code in the function, rather than create the object separately whenever I want to use the procedure.
I dislike methods that alter their parameters. It's often left undocumented and can make tracing bugs more difficult.
Vague, but admittedly it just "smells" bad to me.
I'm not saying I'm right. I just want to understand why the community chooses this method and if there is good reason for me to change.
Edit:
References to #E-Rock in comments are to me(Eric G). I changed my display name.
One problem is what Ken White wrote: you hand the user of the function an object he or she must free.
Another advantage of procedures is that you can pass several objects of a hierarchy, while a function that creates such an object always generates the same. E.g.
procedure PopulateStrings(Strings: TStrings);
To that procedure, you can pass any kind of TStrings, be it the Lines of a TMemo, the Items of a TListBox or TComboBox or a simple standalone TStringList. If you have a function:
function CreateStrings: TStrings;
You always get the same kind of object back (which object exactly is not known, as TStrings is abstract, so you probably get a TStringList), and must Assign() the contents to the TStrings you want to modify. The procedure is to be preferred, IMO.
Additionally, if you are the author of the function, you can't control whether the object you create is freed, or when. If you write a procedure, that problem is taken off your hands, since the user provides the object, and its lifetime is none of your concern. And you don't have to know the exact type of the object, it must just be of the class or a descendant of the parameter. IOW, it is also much better for the author of the function.
It is IMO seldom a good idea to return an object from a function, for all the reasons given. A procedure that only modifies the object has no dependency on the object and creates no dependency for the user.
FWIW, Another problem is if you do that from a DLL. The object returned uses the memory manager of the DLL, and also the VMT to which it points is in the DLL. That means that code that uses as or is in the user code does not work properly (since is and as use the VMT pointer to check for class identity). If the user must pass an object of his, to a procedure, that problem does not arise.
Update
As others commented, passing an object to a DLL is not a good idea either. Non-virtual functions will call the functions inside the DLL and use its memory manager, which can cause troubles too. And is and as will not work properly inside the DLL either. So simply don't pass objects into or out of a DLL. That goes with the maxime that DLLs should only use POD type parameters (or compound types -- arrays, records -- that only contain POD types) or COM interfaces. The COM interfaces should also only use the same kind of parameters.
Creating the object instance and passing it into another procedure makes it clear which code is responsible for freeing the instance.
In the first case (using a procedure to fill it):
MyObj := TMyObject.Create;
try
// Do whatever with MyObj
finally
MyObj.Free;
end;
This is clear that this block of code is responsible for freeing MyObj when it's finished being used.
MyObj := CreateMyObject(DBID);
What code is supposed to free it? When can you safely free it? Who is responsible for exception handling? How do you know (as a user of someone else's code)?
As a general rule, you should create, use, and free object instances where they're needed. This makes your code easier to maintain, and definitely makes it easier for someone who comes along later and has to try and figure it out. :)
I use a combination of both idioms. Pass the object as an optional parameter and if not passed, create the object. And in either case return the object as the function result.
This technique has (1) the flexibility of the creation of the object inside of the called function, and (2) the caller control of the caller passing the object as a parameter. Control in two meanings: control in the real type of the object being used, and control about the moment when to free the object.
This simple piece of code exemplifies this idiom.
function MakeList(aList:TStrings = nil):TStrings;
var s:TStrings;
begin
s:=aList;
if s=nil then
s:=TSTringList.Create;
s.Add('Adam');
s.Add('Eva');
result:=s;
end;
And here are three different ways to use it
simplest usage, for quick and dirty code
var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
when programmer wants to make more explicit ownership and/or use a custom type
sl2:=MakeList(TMyStringsList.create);
when the object is previously created
sl3:=TMyStringList.Create;
....
MakeList(sl3);
I'm asking this because I'm out of good ideas...hoping for someone else's fresh perspective.
I have a user running our 32-bit Delphi application (compiled with BDS 2006) on a Windows 7 64-bit system. Our software was "working fine" until a couple weeks ago. Now suddenly it isn't: it throws an Access Violation while initializing (instancing objects).
We've had him reinstall all our software--starting all over from scratch. Same AV error. We disabled his anti-virus software; same error.
Our stack tracing code (madExcept) for some reason wasn't able to provide a stack trace to the line of the error, so we've sent a couple error logging versions for the user to install and run, to isolate the line which generates the error...
Turns out, it's a line which instances a simple TStringList descendant (there's no overridden Create constructor, etc.--basically the Create is just instancing a TStringList which has a few custom methods associated with the descendant class.)
I'm tempted to send the user yet another test .EXE; one which just instances a plain-vanilla TStringList, to see what happens. But at this point I feel like I'm flailing at windmills, and risk wearing out the user's patience if I send too many more "things to try".
Any fresh ideas on a better approach to debugging this user's problem? (I don't like bailing out on a user's problems...those tend to be the ones which, if ignored, suddenly become an epidemic that 5 other users suddenly "find".)
EDIT, as Lasse requested:
procedure T_fmMain.AfterConstruction;
begin
inherited;
//Logging shows that we return from the Inherited call above,
//then AV in the following line...
FActionList := TAActionList.Create;
...other code here...
end;
And here's the definition of the object being created...
type
TAActionList = class(TStringList)
private
FShadowList: TStringList; //UPPERCASE shadow list
FIsDataLoaded : boolean;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
procedure DataLoaded;
function Add(const S: string): Integer; override;
procedure Delete(Index : integer); override;
function IndexOf(const S : string) : Integer; override;
end;
implementation
procedure TAActionList.AfterConstruction;
begin
Sorted := False; //until we're done loading
FShadowList := TStringList.Create;
end;
I hate these kind of problems, but I reckon you should focus on what's happening recently BEFORE the object tries to get constructed.
The symptoms you describe sound like typical heap corruption, so maybe you have something like...
An array being written to outside bounds? (turn bounds checking on, if you have it off)
Code trying to access an object which has been deleted?
Since my answer above, you've posted code snippets. This does raise a couple of possible issues that I can see.
a: AfterConstruction vs. modified constructor:
As others have mentioned, using AfterConstruction in this way is at best not idiomatic. I don't think it's truly "wrong", but it's a possible smell. There's a good intro to these methods on Dr. Bob's site here.
b: overridden methods Add, Delete, IndexOf
I'm guessing these methods use the FshadowList item in some way. Is it remotely possible that these methods are being invoked (and thus using FShadowList) before the FShadowList is created? This seems possible because you're using the AfterConstruction methods above, by which time virtual methods should 'work'. Hopefully this is easy to check with a debugger by setting some breakpoints and seeing the order they get hit in.
You should never override AfterConstruction and BeforeDestruction methods in your programs. They are not meant for what you're doing with them, but for low-level VCL hacking (like reference adding, custom memory handling or such).
You should override the Create constructor and Destroy destructor instead and put your initialization code here, like such:
constructor TAActionList.Create;
begin
inherited;
// Sorted := False; // not necessary IMHO
FShadowList := TStringList.Create;
end;
Take a look at the VCL code, and all serious published Delphi code, and you'll see that AfterConstruction and BeforeDestruction methods are never used. I guess this is the root cause of your problem, and your code must be modified in consequence. It could be even worse in future version of Delphi.
Clearly there is nothing suspicious about what TAActionList is doing at time of construction. Even considering ancestor constructors and possible side-effects of setting Sorted := False indicate there shouldn't be a problem. I'm more interested in what's happening inside T_fmMain.
Basically something is happening that causes FActionList := TAActionList.Create; to fail, even though there is nothing wrong in the implementation of TAActionList.Create (a possibility is that the form may have been unexpectedly destroyed).
I suggest you try changing T_fmMain.AfterConstruction as follows:
procedure T_fmMain.AfterConstruction;
begin
//This is safe because the object created has no form dependencies
//that might otherwise need to be initialised first.
FActionList := TAActionList.Create;
//Now, if the ancestor's AfterConstruction is causing the problem,
//the above line will work fine, and...
inherited AfterConstruction;
//... your error will have shifted to one of these lines here.
//other code here
end;
If an environment issue with a component used by your form is causing it destroy the form during AfterConstruction, then it's the assignment of the new TAActionList.Create instance to FActionList that's actually causing the AV. Another way to test would be to first create the object to a local variable, then assign it to the class field: FActionList := LActionList.
Environment problems can be subtle. E.g. We use a reporting component which we discovered requires that a printer driver is installed, otherwise it prevents our application from starting up.
You can confirm the destruction theory by setting a global variable in the form's destructor. Also you may be able to output a stack trace from the destructor to confirm the exact sequence leading to the destruction of the form.
Our software was "working fine" until a couple weeks ago... suddenly become an epidemic that 5 other users suddenly "find".) :
Sounds like you need to do some forensic analysis, not debugging: You need to discover what changed in that user's environment to trigger the error. All the more so if you have other users with the same deployment that don't have the problem (sounds like that's your situation). Sending a user 'things to try' is one of the best ways to erode user confidence very quickly! (If there is IT support at the user site, get them involved, not the user).
For starters, explore these options:
*) If possible, I'd check the Windows Event Log for events that may have occurred on that machine around the time the problem arose.
*) Is there some kind of IT support person on the user's side that you can talk to about possible changes/problems in that user's environment?
*) Was there some kind of support issue/incident with that user around the time the error surfaced that may be connected to it, and/or caused some kind of data or file corruption particular to them?
(As for the code itself, I agree with #Warran P about decoupling etc)
Things to do when MadExcept is NOT Enough (which is rare, I must say):
Try Jedi JCL's JCLDEBUG instead. You might get a stack traceback with it, if you change out MadExcept for JCLDEBUG, and write directly the stack trace to the disk without ANY UI interaction.
Run a debug-viewer like MS/SysInternals debugview, and trace output things like the Self pointers of the objects where the problems are happening. I suspect that somehow an INVALID instance pointer is ending up in there.
Decouple things and refactor things, and write unit tests, until you find the really ugly thing that's trashing you. (Someone suggested heap corruption. I often find heap corruption goes hand in hand with unsafe ugly untested code, and deeply bound UI+model cascading failures.)
Working with my ongoing TFrames-based component set project, I'm coming across various instances where I am wanting to replace one of the TFrame's components (usually non-visual) at runtime with one that is generated dynamically at runtime.
I think I've probably found the answer to my immediate problem here, but in my own digging around and experimenting prior to finding that, it's become clear I've got quite a bit to learn about how Delphi handles object references, particularly with respect to forms/frames, and (in general) class properties which are object references rather than non-pointer values.
A specific example of one experiment is here:
(On a form with three TButtons)
procedure TForm1.Button3Click(Sender: TObject);
var
MyButton : TButton;
begin
MyButton := TButton.Create(Self);
MyButton.Caption := 'New Button';
MyButton.Parent := Form1;
Form1.Button2 := MyButton;
Form1.Repaint;
ShowMessage('Button2 caption = ' + Form1.Button2.Caption);
end;
Doesn't replace Button2 with the created button, but does show both on the form. The ShowMessage results indicates Button2's caption still = "Button2"
I find myself asking questions like, "Is this 'non-replacement' unique to forms, or would that be true for other classes as well?" etc. In short, I've discovered yet another sinkhole of my own ignorance. ;-) I find in working with instances/object references/derefencing/class definitions/class properties etc, that oftentimes things behave exactly as they expect them to, but other times, not at all, and not even close.
It's clear I need to study up on this area. Rather than post stupid question after stupid question revolving around this subject, I thought I'd ask this instead:
What is a really good reference or tutorial for getting a better grasp on the subtle distinctions re: how Delphi handles such things?
Thanks in advance for all your help!
Those fields on the forms are there purely for your convenience when writing code. You can delete them from the .pas file and they'd still show up. The form's layout is defined in the DFM, and the form object holds an internal list of references to the controls placed on it, just like any other visual control.
I don't know about tutorials on the subject, but I do know how you can replace a button. You've got it mostly right, but you also have to free Form1.Button2 before you overwrite the reference. That will cause the button to remove itself from the form's control list as part of its destruction process. Or, if you want to save the button somewhere instead of destroying it, call Form1.RemoveControl(Button2); instead.
When trying a new component for which there's no documentation, I need to go through its methods, properties, and events to try to figure out what it can do. Doing this through the IDE's Object Inspector is a bit tedious.
Is there a utility that presents this list in a more readable format?
Thank you.
When I want to know what something can do, I read the source code. The class declaration will contain a succinct list of all the methods and properties, unless there is a lot of inheritance. The definitions will tell you want the methods do.
Another thing is to declare a variable of the type you're interested in, type its name and a period, and then press Ctrl+Space to let Class Completion show you everything you can do.
As the others said, use the source. Also an UML tool will help.
But if you don't want to use this, you can use this procedure (you need Delphi 2010 for this, and be sure to add RTTI to your 'Uses' clause):
procedure DumpProps(aObject: TObject; aList: TStringList);
var
RttiContext: TRttiContext;
RttiType: TRttiType;
I: Integer;
n: integer;
props: TArray<TRttiProperty>;
begin
aList.Clear; //it must be <> nil
RttiType := RttiContext.GetType(aObject.ClassType);
props:=RttiType.GetProperties;
with aList do
begin
Append('');
Append('==========');
Append('Begin Dump');
Append('----------');
for I := Low(props) to High(props) do
begin
try
Append(props[i].Name+': '); //odd construction to see if the Getter blows
n:=Count-1;
Strings[n]:=Strings[n]+props[i].GetValue(aObject).AsString;
except
on E: Exception do
Strings[n]:=Strings[n]+' >>> ERROR! <<< '+E.Message;
end;
end;
end;
end;
The above you can use either at run-time, either if you build a Menu Wizard, you can have your info at design time.
HTH
You can use the Class Browser that comes with GExperts.
I would also recommend to build a Model diagram with the IDE or ModelMaker. It helps to see the visual relations.
In the immortal words of Obi Wan Kenobi -- "Use the source".
There is no substitute for reading and understanding the source code of a component (or anything) to understand what it does and what it is up to.
Source code is the Lingua Franca of programming.
Look at the .hpp that is generated for C++Builder support. It resembles the interface section of a Delphi unit.
I just use code completion. If you can't figure out what the component does from the names of the properties and methods, then it's probably poorly designed anyway, and you're better off not using it. Also, since you're asking the question, I'm guessing you do not have the source. If you don't, again, I wouldn't use the component. You're only storing trouble for yourself.
There is RTTI...