Delphi RTTI Object Inspector - class

I'm trying to build a simplified object inspector for a drawing app I'm writing.
I'm trying to dynamically get RTTI for the selected object and its child objects. If a given property is a class (tkClass), I want to call GetRTTIObject recursively, handing that property as the object to get the "subproperties" for it (i.e. BaseObj.Brush.Color or BaseObj.Pen.Width etc.). I suspect I want to pass the instance of that object and that it will be painfully obvious when someone points out what that is. How do I get an instance to pass to my function? Or should I be looking at TRttiInstance for properties that are classes....?
I know it works at "level 0", because I can pass in BaseObject.Brush to my first call of GetRTTIObject and I get a list of TBrush properties. How can I drill down recursively?
I seem to get a pointer of some kind with Value := GetPropValue(AObj, Prop.Name);
Do I dereference that somehow to get my instance...?
Regards,
Rob
The simplified test class is defined:
TBaseClass = class(TObject)
private
FFont: TFont;
FBrush: TBrush;
FPen: TPen;
FCaption: String;
FFloat1: Real;
FInt1: Integer;
published
property Font: TFont Read FFont Write FFont;
property Brush: TBrush Read FBrush Write FBrush;
property Pen: TPen Read FPen Write FPen;
property Caption: String Read FCaption Write FCaption;
property Float1: Real Read FFloat1 Write FFloat1;
property Int1: Integer Read FInt1 Write FInt1;
end;
My RTTI procedure is:
procedure TfrmMain.GetRTTIClass(AClass: TClass; Items: TStrings; Indent: Integer);
var
LContext: TRttiContext;
LType: TRttiType;
Prop: TRttiProperty;
PropString: String;
PropInfo: PPropInfo;
Tabs: String;
I: Integer;
Value: Variant;
begin
LContext := TRttiContext.Create();
try
for I := 0 to Indent do
Tabs := Tabs + ' '; //chr(9)
Log(Format('Get RTTI (Class) for "%s"', [AClass.ClassName]));
LType := LContext.GetType(AClass.ClassInfo);
Items.Add(Tabs + 'RTTI for: ' + Ltype.Name);
Items.Add(Tabs + 'Package Name: ' + LType.Package.Name);
Items.Add(Tabs + '-- Properties --');
for Prop in LType.GetProperties do
begin
PropString := 'property: ' + Prop.Name;
PropInfo := GetPropInfo(AClass, Prop.Name);
PropString := PropString + ': ' + GetEnumName(TypeInfo(TTypeKind), Ord(Prop.PropertyType.TypeKind));
if propInfo <> nil then begin
PropString := PropString + ': ' + PropInfo^.PropType^.Name;
case propInfo.PropType^.Kind of
tkClass: begin
PropString := PropString + ' (Class)' ; // ' GetProp Value: ' + IntToHex(PropInfo.GetProc, 8); // Items.Add('--- Get RTTI ---');(Class)';
Log(Format('GetRTTI: %s (%s)', [Prop.Name, PropInfo^.PropType^.Name]));
// TODO: Get a reference to the object and call GetRTTI
// TODO: Or change function to work from classtype rather than object
// GetRTTIObject(### WHAT GOES HERE?!?!?, Items, Indent + 1);// := PropString + ' Class';
end;
end;
end;
Items.Add(Tabs + PropString);
end;
finally
LContext.Free;
end;
end;
Oops!!
I see I put the wrong function in.....the one in question takes a TObject and the assignment is:
LType := LContext.GetType((AObject.ClassInfo); (AObject.ClassType also seems to work...)....
Not at my dev station just now, but think everything else is the same after that....

Problem in your example that TBrash have property TBitMap, TBitMap have TCanvas, TCanvas have TBrash. Call of function GetRTTIClass will be infinite recursive. But if make condition for getting RTTI only one time for each class it is possible to fix your function.
uses System.Generics.Collections;
var ListClasses:TList<TClass>;
LContext : TRttiContext;
implementation
procedure TfrmMain.FormCreate(Sender: TObject);
begin
LContext := TRttiContext.Create();
ListClasses:=TList<TClass>.Create;
end;
procedure TfrmMain.GetRTTIClass(AClass: TClass; Items: TStrings; Indent: Integer);
var
LType: TRttiType;
Prop: TRttiProperty;
PropString: String;
Tabs: String;
I: Integer;
begin
if ListPrinted.Contains(AClass) then Exit
else ListPrinted.Add(AClass);
for I := 0 to Indent do Tabs := Tabs + ' ';
LType := LContext.GetType(AClass.ClassInfo);
Items.Add(Tabs + 'RTTI for: ' + Ltype.Name);
Items.Add(Tabs + 'Package Name: ' + LType.Package.Name);
Items.Add(Tabs + '-- Properties --');
for Prop in LType.GetProperties do begin
PropString := 'property: ' + Prop.Name;
PropString := PropString + ': ' + GetEnumName(TypeInfo(TTypeKind), Ord(Prop.PropertyType.TypeKind))+' '+Prop.PropertyType.Name;
Items.Add(Tabs + PropString);
case Prop.PropertyType.Handle^.Kind of
tkClass: begin
GetRTTIClass(Prop.PropertyType.Handle^.TypeData^.ClassType, Items,Indent+2);
end;
end;
end;
procedure TfrmMain.btn1Click(Sender: TObject);
begin
GetRTTIClass(TBaseClass, Items,0);
end;

Ok, I have made some modifications to the procedure. Parsing the class isn't quite sufficient. I need handles to instances.
To recursively call my procedure (the one that takes an object, not a class as the first parameter), I need the instance of the sub-object (AObj.Font, for example). I can acquire that with:
case Prop.PropertyType.TypeKind of
tkClass: begin
SubObj := GetObjectProp(AObj, Prop.Name);
GetRTTIObject2(SubObj, Tree, ChildNode, Indent + 2);
end;
end;
So simple, really, once I wrapped my head around it.
Still going to vote the other answer as the solution, since it gave good pointers on avoiding another pitfall. :)

Related

Delphi 10.4. Fast Report 6. REST application. Print Failure

I have created a REST server in Delphi using WebBroker. My intention is to use it as a label printer. A client prepares and sends a JSON request detailing the printer name, Fast Report & variables. The server reads the JSON, creates a tFrxReport object loads the requisite report and populates the variables.
This all works admirably, except it will not print to a physical printer. If I select OneNote as my destination, the label is saved to the desktop. If I select a network attached printer, no label emerges.
I have tried PrintOptions.ShowDialog:=True The print dialog shows, indicating the correct printer, but it does not print.
If anyone has any experience, could you point me in the right direction please?
function processJson(itm : sat; jtr : tJsonTextReader): sat;
var
idx : integer;
//itm : sat; // simple array type [idx, 'val1', 'val2']
begin
setlength(itm,0);
idx:=0;
while jtr.Read do
begin
if jtr.TokenType = tJsonToken.PropertyName then
begin
setlength(itm, length(itm)+1);
itm[idx].st_idx := idx;
itm[idx].st_code := jtr.Value.ToString; // property name
jtr.Read;
itm[idx].st_desc := jtr.Value.AsString; // property value
inc(idx);
end;
end;
processJson := itm;
end;
function getPrinterInfo(pnam: string):printinfo_type;
var
ptr : printinfo_type;
idx : integer;
begin
ptr.idx := -1; //default printer
ptr.name := trim(pnam);
for idx := 0 to Printer.Printers.Count - 1 do
if AnsiContainsText(Printer.Printers[idx], ptr.name) then
ptr.idx := idx;
result := ptr;
end;
procedure Ttfdq.tfdqactLabelAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
post : simpleArray_type;
pdx, idx, iitm : integer;
jtr : tJsonTextReader;
sr : tStringReader;
pish : string;
fr : tFrxReport;
thePtr : printinfo_type;
itm : sat;
tstprt : boolean;
begin
d.myHost := 'http://' + Request.host + ':' + intToStr(Request.ServerPort);
d.hostIP := Request.host;
d.Request := Request;
d.Response := Response;
d.remAddr := Request.RemoteAddr;
post := explode(Request.Content);
tstprt := false;
pdx := isset(post, 'json');
pish:='';
if (pdx >=0) then
begin
sr := tStringReader.Create(post[pdx].st_desc);
jtr := tJsonTextReader.Create(sr);
while jtr.read do
begin
if jtr.TokenType = tJsonToken.StartObject then
itm := processJson(itm, jtr);
end;
if fileexists(itm[2].st_desc) then
begin
thePtr := getPrinterInfo(itm[1].st_desc);
fr := tFrxReport.Create(nil);
fr.LoadFromFile(itm[2].st_desc);
// pre load any vars so report does not fail
for idx := 0 to fr.Variables.Count-1 do
fr.Variables.Items[iitm].Value := frText('');
for idx := 4 to High(itm) do
begin
pish := pish + 'index of '+itm[idx].st_code+' = '+ intToStr (fr.Variables.IndexOf(itm[idx].st_code))+'<br>';
iitm := fr.Variables.IndexOf(itm[idx].st_code);
if iitm > -1 then
fr.Variables.Items[iitm].Value := frText(itm[idx].st_desc);
end;
if fr.PrepareReport then
begin
//fr.ShowPreparedReport;
fr.PrintOptions.Printer := thePtr.name;
fr.PrintOptions.PrnOutFileName := 'Trace Label';
fr.PrintOptions.ShowDialog := tstprt;
fr.ShowProgress := tstprt;
fr.Print;
end;
fr.Free;
end;
Response.Content := pish ;
end
else
begin
Response.Content := '<html>' +
'<head><title>Label List</title></head>' +
'<body>This is only used by print serve clients</p>'+
'</body>' +
'</html>';
end;
end;
The problem lies here:
fr.PrintOptions.PrnOutFileName := 'Trace Label';
I erroneously thought that would add a description in the print queue. What it actually did is send the report into limbo :)

InputQuery Formatting Issues

I'm having trouble with InputQuery/InputBox on Delphi XE2.
The input area is out of place (should be under text).
Is there a way to re-align it before making my own input form?
Thank you!
InputQuery() is not designed to be used in this manner. The prompt text is meant to be a short label displayed to the left of the text field (similar to TLabeledEdit). It is not designed to display instructions above the prompts, like you are attempting. This situation would be much better handled by simply creating your own custom Form using whatever controls and layouts you want. For instance, using TDateTimePicker for dates and times, TCheckBox or TRadioGroup to indicate repeats, etc.
However, that being said, InputQuery() is implemented using a custom VCL TForm, so it is technically possible to accomplish what you are trying to achieve. You can use the TScreen.OnActiveFormChange event to gain access to the Form object when it becomes visible, and then you can manipulate it however you want. For example:
procedure TMyForm.ActiveFormChanged(Sender: TObject);
var
Form: TCustomForm;
Prompt: TLabel;
Edit: TEdit;
Ctrl: TControl;
I, J, ButtonTop: Integer;
begin
Form := Screen.ActiveCustomForm;
if (Form = nil) or (Form.ClassName <> 'TInputQueryForm') then Exit;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[i];
if Ctrl is TLabel then
begin
Prompt := TLabel(Ctrl);
end
else if Ctrl is TEdit then
begin
Edit := TEdit(Ctrl);
end;
end;
Edit.SetBounds(Prompt.Left, Prompt.Top + Prompt.Height + 5, Prompt.Width, Edit.Height);
Form.ClientWidth := (Edit.Left * 2) + Edit.Width;
ButtonTop := Edit.Top + Edit.Height + 15;
J := 0;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[i];
if Ctrl is TButton then
begin
Ctrl.SetBounds(Form.ClientWidth - ((Ctrl.Width + 15) * (2-J)), ButtonTop, Ctrl.Width, Ctrl.Height);
Form.ClientHeight := Ctrl.Top + Ctrl.Height + 13;
Inc(J);
end;
end;
end;
procedure TMyForm.DoSomething;
var
value: string;
begin
Screen.OnActiveFormChange := ActiveFormChanged;
try
InputQuery('Enter New Schedule', 'Format: <Second> <Minute> <Hour> <Day_of_the_Month> <Month_of_the_Year> <Day_of_the_Week> <Year>.'#10'Use * for repeating cycles. ex: 0 0 7 * * * * (trigger at 7AM everyday)', value);
finally
Screen.OnActiveFormChange := nil;
end;
end;
Alternatively:
class procedure TScreenEvents.ActiveFormChanged(Sender: TObject);
var
Form: TCustomForm;
Instructions: TLabel;
Ctrl: TControl;
I, J, K, Offset: Integer;
begin
Form := Screen.ActiveCustomForm;
if (Form = nil) or (Form.ClassName <> 'TInputQueryForm') then Exit;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[I];
if Ctrl is TLabel then
begin
Instructions := TLabel.Create(Form);
Instructions.Parent := Form;
Instructions.Caption := 'Format: <Second> <Minute> <Hour> <Day_of_the_Month> <Month_of_the_Year> <Day_of_the_Week> <Year>.'#10'Use * for repeating cycles. ex: 0 0 7 * * * * (trigger at 7AM everyday)';
Instructions.SetBounds(Ctrl.Left, Ctrl.Top, Instructions.Width, Instructions.Height);
Offset := Instructions.Top + Instructions.Height + 5;
Form.ClientWidth := Instructions.Width + (Instructions.Left * 2);
K := 0;
for J := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[J];
if Ctrl <> Instructions then
begin
Ctrl.Top := Ctrl.Top + Offset;
if Ctrl is TEdit then
begin
Ctrl.Width := (Form.ClientWidth - Ctrl.Left - Instructions.Left);
end
else if Ctrl is TButton then
begin
Ctrl.Left := (Form.ClientWidth - (Ctrl.Width + 5) * (2-K));
Inc(K);
end;
end;
end;
Form.ClientHeight := Form.ClientHeight + Offset;
Break;
end;
end;
end;
procedure TMyForm.DoSomething;
var
value: string;
begin
Screen.OnActiveFormChange := ActiveFormChanged;
try
InputQuery('Enter New Schedule', 'Value', value);
finally
Screen.OnActiveFormChange := nil;
end;
end;

Delphi Xe10 Datasnap : Image function sending not working

I have a problem with the send function. My program worked correcly in Xe8 but with the upgrade to Xe10, I have a problem : encryption ?
I have create a simple project datasnap with juste the bugging function like that
procedure TServerMethods1.test3;
VAR
a : string;
begin
a := LoadFileToStr('C:\demo\Bitmaps\Mazak_Matrix.JPG');
GetInvocationMetadata.ResponseContentType := 'image/jpeg';
GetInvocationMetadata.ResponseCode := 200;
GetInvocationMetadata().ResponseContent := a;
GetInvocationMetaData.CloseSession := True;
end;
And the function LoadFileToStr
function LoadFileToStr(const FileName: string): AnsiString;
var
FileStream : TFileStream;
begin
FileStream:= TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if FileStream.Size>0 then
begin
SetLength(Result, FileStream.Size);
FileStream.Read(Pointer(Result)^, FileStream.Size);
end;
finally
FileStream.Free;
end;
end;
Actually, the data was correct until
GetInvocationMetadata().ResponseContent := a;
The responseContent destroy the data, like you can see on the hex editor. An example. The entire file is not corrupt, just a few characters.
Original Picture
After sending by the server
Someone has encountered the same bug? If going directly a stream that works, but it doesn't interest me.
Thanks !
Had same problem. Try something like this.
procedure TAPI.GetFile(UID: string);
var
LFS: TFileStream;
I: Integer;
LByte: Byte;
begin
try
LFS := TFileStream.Create('e:\image.jpg', fmOpenRead);
for I := 0 to Pred(LFS.Size) do
begin
LFS.Read(LByte, 1);
GetInvocationmetaData.ResponseContent := GetInvocationmetaData.ResponseContent + Char(LByte);
end;
GetInvocationmetaData.ResponseContentType := 'image/jpeg';
finally
LFS.Free;
end;
end;

Form resource not found after on-the-fly String-Resource translation

I have a problem which occurs only in a very small customer range and I would like to ask if you might give me a hint where the problem might be. The program works for 98% of the customers. Alas, it is not possible that I work with the customers to debug the issue, because their knowledge of Windows and computers is very basic. It is also not possible that I send multiple versions of the product to them, since they don't even know how to install software (the admins do all the stuff).
First of all, I translate all RT_STRING resources on-the-fly, so that the language-switching in the program also affects hardcoded stuff like "Yes", "No", "Cancel" etc., which would only be possible by compiling 2 EXE files.
The code (I have tried to left away as much unnecessary stuff as possible, but since I don't know where the problem is, I provided as much details for the bug as possible):
The ony-the-fly resource translation
procedure TranslateResources;
var
i: integer;
s: string;
{$IF NOT Declared(FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)}
const
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = $2000;
{$IFEND}
begin
// I copy all resources in a dummy DLL (without code), because
// 1) The resources are the only thing we need when changing the resource module
// 2) If the EXE code/debug sections are too long, BeginUpdateResource() will ruin the performance heavily
FTempFile := IncludeTrailingPathDelimiter(GetTempDirectory) + GetRandomString(8)+'.dll';
// Transfers all resources from ParamStr(0) into the dummy DLL at FTempFile
ReGenerateResourceFile(FTempFile);
// if necessary, remove readonly flag
SetFileAttributes(PChar(FTempFile), FILE_ATTRIBUTE_OFFLINE or
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED or
FILE_ATTRIBUTE_TEMPORARY );
for i := 0 to Length(RTLResStringTranslationArray)-1 do
begin
s := Translate(RTLResStringTranslationArray[i].TranslationID);
if s <> '' then
begin
// Translate the string
UpdateResString(RTLResStringTranslationArray[i].ResStrDescriptor.Identifier, s);
end;
end;
LoadNewResourceModule(FTempFile):
end;
procedure ReGenerateResourceFile(OutputFile: string);
var
hUpd: Cardinal;
rs: TResourceStream;
fs: TFileStream;
begin
// As template we use a dummy DLL which contains no code.
// We will implement all resources from ParamStr(0) into it, before we translate the strings.
rs := TResourceStream.Create(HInstance, 'DUMMYDLL', 'DLL');
fs := TFileStream.Create(OutputFile, fmCreate or fmOpenWrite);
try
fs.CopyFrom(rs, rs.Size)
finally
rs.Free;
fs.Free;
end;
// Transfer resources from our EXE into the dummy DLL file
hUpd := BeginUpdateResource(PChar(OutputFile), true);
try
EnumResourceTypes(hInstance, #_enumResTypesProc, hUpd);
finally
EndUpdateResource(hUpd, false)
end;
end;
// This is based on reinit.pas from Borland's RichEdit example; slightly modified
function LoadNewResourceModule(PatchedFile: string): LongInt;
var
NewInst: Longint;
CurModule: PLibModule;
begin
Result := 0;
// Win95: "Initialization routine failed"
// NewInst := LoadLibrary(PChar(PatchedFile));
NewInst := LoadLibraryEx(PChar(PatchedFile), 0, LOAD_LIBRARY_AS_DATAFILE);
CurModule := LibModuleList;
Result := 0;
while CurModule <> nil do
begin
if CurModule.Instance = HInstance then
begin
if CurModule.ResInstance <> CurModule.Instance then
FreeLibrary(CurModule.ResInstance);
// Win95: ERangeError
CurModule^.ResInstance := NewInst;
Result := NewInst;
Exit;
end;
CurModule := CurModule.Next;
end;
end;
// Based on http://stackoverflow.com/questions/1498658/modifying-a-string-in-resource-of-an-exe
// Modified
procedure UpdateResString(const AStringIdent: Integer; const ANewString: WideString);
var
ResData, TempData: TWordArray;
iSection, iIndexInSection: Integer;
i, iLen, iSkip, iPos: Integer;
begin
// Calculate the resource string area and the string index in that area
iSection := AStringIdent div 16 + 1;
iIndexInSection := AStringIdent mod 16;
ResData := ReadSectionCached(iSection);
// Calculate the position of the string
iLen := Length(ANewString);
iPos := 0;
for i := 0 to iIndexInSection do
begin
if iPos > High(ResData) then
begin
SetLength(ResData, iPos + 1);
ResData[iPos] := 0;
end;
if i <> iIndexInSection then
begin
iSkip := ResData[iPos] + 1;
Inc(iPos, iSkip);
end;
end;
// Put data behind strings into TempData
iSkip := 1{size} + ResData[iPos];
SetLength(TempData, Length(ResData) - (iPos + iSkip));
if Length(TempData) > 0 then
begin
CopyMemory(#TempData[0], #ResData[iPos + iSkip], Length(TempData)*SizeOf(TempData[0]));
end;
SetLength(ResData, iPos + (iLen + 1{size}) + Length(TempData));
// Overwrite string
ResData[iPos] := iLen;
Inc(iPos);
if iLen > 0 then
begin
CopyMemory(#ResData[iPos], #ANewString[1], iLen*SizeOf(ANewString[1]));
Inc(iPos, iLen);
end;
// Append TempData after our new string
if Length(TempData) > 0 then
begin
CopyMemory(#ResData[iPos], #TempData[0], Length(TempData)*SizeOf(TempData[0]));
end;
CacheSet(iSection, ResData);
end;
type
TGlobalData = record
GlobalPtr: Pointer;
Length: integer;
end;
function LoadResourcePtr(hModule: HMODULE; restype, resname: PChar; wIDLanguage: WORD): TGlobalData;
var
hFind, hRes: THandle;
begin
result.GlobalPtr := nil;
result.Length := -1;
hFind := Windows.FindResourceEx(hModule, restype, resname, wIDLanguage);
if hFind = 0 then RaiseLastOSError;
hres := Windows.LoadResource(hModule, hFind);
if hres = 0 then RaiseLastOSError;
result.GlobalPtr := Windows.LockResource(hres);
result.Length := Windows.SizeofResource(hModule, hFind);
end;
function _enumResLangsProc(hmodule: HMODULE; restype, resname: PChar; wIDLanguage: WORD;
lParam: LongInt): BOOL; stdcall;
var
rs: TGlobalData;
begin
rs := LoadResourcePtr(hmodule, restype, resname, wIDLanguage);
UpdateResource(lParam, restype, resname, wIDLanguage, rs.GlobalPtr, rs.Length);
result := true;
end;
function _enumResNamesProc(hmodule: HMODULE; restype, resname: PChar;
lParam: LongInt): BOOL; stdcall;
begin
EnumResourceLanguages(hmodule, restype, resname, #_enumResLangsProc, lParam);
result := true;
end;
function _enumResTypesProc(hmodule: HMODULE; restype: PChar;
lParam: LongInt): BOOL; stdcall;
begin
EnumResourceNames(hmodule, restype, #_enumResNamesProc, lParam);
result := true;
end;
{$R '..\dummydll\dummydll.RES'}
Then I use a wait form:
unit Wait;
interface
uses
...
type
TWaitForm = class(TForm)
...
end;
var
WaitForm: TWaitForm;
implementation
{$R *.dfm}
...
end;
The wait form will be called by dynamically showing the form:
procedure ShowWaitForm;
begin
...
{ I use my own _CreateForm function because it solves many workarounds for
juicy stuff like half-modal windows (which can be hidden without user action),
miscellaneous deadlocks etc. and to allow the form to be shown in a shared PAS file
without the requirement to add it to every DPR file where the WaitForm API is used. }
WaitForm := _CreateForm(TWaitForm, {Application.MainForm}AParent) as TWaitForm;
WaitForm.Show;
...
end;
function _CreateForm(InstanceClass: TCustomFormClass; AParent: TCustomForm): TCustomForm;
var
LOwner: TComponent;
begin
if Assigned(AParent) then
begin
LOwner := AParent;
end
else if Assigned(Application) then
begin
LOwner := Application;
end
else
begin
LOwner := nil;
end;
result := InstanceClass.Create(LOwner);
end;
The error message at 2% of the customers:
Resource TWaitForm was not found
However, other forms are working.
There are 2 theories I can think of:
1) Did the resource translation corrupt the DLL file / part of the RCData section? (Maybe a bug in the WinAPI's UpdateResource ?)
2) Is there a problem with the dynamic showing of the wait form (since other "static" forms are shown?)

Delphi: Creating a TComboBox in a dynamically created form at runtime

Okay, I am working in a project that was originally done in D7. And I am doing double duty here as I am working on fixing bugs in the original code and attempting to port it over to XE3/4. Kinda hard when the original author used some none-open source kits for the project.
But anyways, the app is a scripting/macroing program. As part of the custome scripting/macroing language. There is a ability to create very simple basic forms for user input. The forms are created dynamically at runtime based on the script/macro the script/macro author has created. I have already fixed some bugs in the code for the creation of the forms. But, there is one that I just can not figure out.
When creating a TComboBox for the parent form and setting the Text property AT component creation. The text in the Text property is not displayed.
Here is the code to create the form:
procedure CreateForm(var wFrm: TForm; sName: String);
var
iLoop, iPos, iLen: Integer;
iFormHeight, iFormWidth: Integer;
lh, hresult1, hresult2: Integer;
sWork, sWork2, sLine, CmdName: String;
lstForm, lst: TStringList;
pnl: TPanel;
begin
iFormHeight := 80;
iFormWidth := 400;
hresult1 := 0;
lst := TStringList.Create;
iLoop := lstForms.IndexOf(Trim(UpperCase(sName)));
if iLoop < 0 then
begin
AbortError('Form "' + sName + '" could not be found!');
Exit;
end;
lstForm := TStringList(lstForms.Objects[iLoop]);
for iLoop := 0 to lstForm.Count - 1 do
begin
sLine := lstForm[iLoop];
iPos := Pos('=', sLine);
iLen := Length(sLine);
if iPos = 0 then
continue;
CmdName := Uppercase(Trim(Copy(sLine, 1, iPos - 1)));
sWork2 := Trim(Copy(sLine, iPos + 1, iLen));
if CmdName = 'FORMCAPTION' then
begin
with wfrm do
begin
Caption := Trim(Copy(sLine, iPos + 1, iLen));
Name := Trim(sName);
Height := iFormHeight;
Width := iFormWidth;
Tag := 10;
BorderStyle := bsSizeable;
BorderIcons := [biSystemMenu];
Position := poDesktopCenter;
pnl := TPanel.Create(wfrm);
with pnl do
begin
Parent := wfrm;
Caption := '';
Align := alBottom;
BevelInner := bvNone;
BevelOuter := bvNone;
Height := 30;
end;
with TButton.Create(wfrm) do
begin
Parent := pnl;
Caption := '&OK';
Default := True;
ModalResult := mrOK;
Left := 235;
Top := 0;
end;
with TButton.Create(wfrm) do
begin
Parent := pnl;
Caption := '&Cancel';
Cancel := True;
ModalResult := mrCancel;
Left := 310;
Top := 0;
end;
pnl := TPanel.Create(wfrm);
with pnl do
begin
Parent := wfrm;
Caption := '';
Align := alClient;
BevelInner := bvRaised;
BevelOuter := bvNone;
BorderWidth := 5;
end;
end;
end
else
begin
lst.Clear;
StringToList(sWork2, lst, ':');
if UpperCase(lst[0]) = 'EDITBOX' then
CreateEditBox
else if UpperCase(lst[0]) = 'CHECKBOX' then
CreateCheckBox
else if UpperCase(lst[0]) = 'COMBOBOX' then
CreateComboBox
else if UpperCase(lst[0]) = 'LABEL' then
CreateLabel;
end;
end;
with wfrm do
begin
if hresult1 > 1 then
hresult2 := 5
else
hresult2 := 9;
Tag := Tag + hresult2;
Height := Height + hresult2;
end;
lst.Free;
end;
And here is the specific code to create the TComboBox, w/ TLabel, for the form:
procedure CreateComboBox;
var
iPos: Integer;
begin
with TLabel.Create(wfrm) do
begin
Parent := pnl;
Caption := lst[1];
Left := 15;
if hresult1 > 1 then
hresult2 := 5 * hresult1
else
hresult2 := 3 * hresult1;
Top := wfrm.Tag + hresult2;
Name := 'lbl' + CmdName;
Width := 150;
WordWrap := True;
AutoSize := True;
lh := Height;
end;
hresult1 := Trunc(lh/13);
with TComboBox.Create(wfrm) do
begin
Parent := pnl;
Left := 170;
Width := 200;
if hresult1 > 1 then
hresult2 := 5 * hresult1
else
hresult2 := 3 * hresult1;
Top := wfrm.Tag + hresult2;
Style := csDropDownList;
Name := UpperCase(CmdName);
Text := 'Test Text';
sWork := lst[3];
lst.Clear;
StringToList(sWork, lst, ',');
for iPos := 0 to lst.Count - 1 do
lst[iPos] := lst[iPos];
Items.Assign(lst);
// ItemIndex := 0;
end;
wfrm.Tag := wfrm.Tag + ((hresult1 * 13)+ 13);
wfrm.Height := wfrm.Height + ((hresult1 * 13)+ 13);
TComboBox(wfrm
end;
NOTE: the above procedure is a child procedure of the CreateForm procedure.
The app uses TStringList lists to store the form definition at script/macro runtime. Then the above code retrieves that information to create to form when the author wants the form to be shown. And then creates the form and places the form object into another temporary TStringList list prior to being shown. This is done so that when the user runs the script/macro and enters the information/settings as requested in the form. The author may retrieve the requested information/settings from the form before the form is destroyed.
The form is deleted (if previously created) from tmp TStringList list, created, stored in tmp TStringList list, and shown modally with the following code:
iPos := lstForms.IndexOf(UpperCase(sWVar2));
if iPos < 0 then
begin
AbortError('Could not find form "' + Trim(sWVar2) + '" defined!');
Exit;
end;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos > -1then
begin
TForm(lstFormsTMP.Objects[iPos]).Free;
lstFormsTMP.Delete(iPos);
frm.Free;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos > -1 then
begin
AbortError('Form "' + Trim(sWVar2) + '" was not removed from the lstFormsTMP TStringList.');
Exit;
end;
end;
frm := TForm.Create(frmMain);
CreateForm(frm, sWVar2);
lstFormsTMP.AddObject(Uppercase(sWVar2), frm);
end;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos < 0 then
begin
AbortError('Could not find form "' + Trim(sWVar2) + '" defined!');
Exit;
end;
hndHold := SwitchToHandle(frmMain.Handle);
try
Result := TForm(lstFormsTMP.Objects[iPos]).ShowModal = mrOK;
finally
SwitchToHandle(hndHold);
end;
With the above sets of code the form defined in the running script is created and shown, without to many bugs/errors. But, even though I have hardcoded the text for the TComboBox.Text property. It is not shown. Can anyone shed some lite on why this is the case for me? All other form components, TCheckBox, TEditBox, TLabel, are displayed without any issues, so far. It is just the TComboBox that is causing me to scratch my head in confusion.
NOTE: Eventually the TComboBox.Text property will be dynamically set based on the authors setting for that property in the form component's definition.
Thanks in advance.
EDITED 8/18/2013, to include the following:
The original code also includes the ability to save/load the form component's settings by way of the TIniFile object. The following code is used to save the setting for the TComboBox:
if frm.Components[i] is TCombobox then
iniWork.WriteString(frm.Name, TCombobox(frm.Components[i]).Name, TCombobox(frm.Components[i]).Text)
else
and the following to load the TComboBox setting:
if frm.Components[i] is TCombobox then
begin
TCombobox(frm.Components[i]).ItemIndex := TCombobox(frm.Components[i]).Items.IndexOf(
iniWork.ReadString(frm.Name, TCombobox(frm.Components[i]).Name, TCombobox(frm.Components[i]).Text));
end
With the above code it looks to me like the setting is being save from and loaded back into the TComboBox's Text property. Now when the TComboBox setting is loaded, the form is changed after it has been created and placed, as an object, into the tmp TStringList list and prior to being shown modally. Yet, when the form is shown the Text property, as set by the above load code above, is shown.
It is because of the above that I am confused. Why does it work at this point, after the form is created. Yet not when the form is created?
This is a drop down list because you set the style to csDropDownList. That means that the edit control of the combo box can only display items that are contained in its list control.
For a drop down list combo, setting the Text property has no effect. Instead of using the Text property, you should be specifying ItemIndex.