Label color not changing with FindComponent - delphi

I have a lot of labels in my form and I have to change the color to all of them, so I thought to use a for loop + the FindComponent method.
procedure TForm1.RadioButton1Click(Sender: TObject);
var i:shortint;
begin
for i:=16 to 27 do
begin
TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
end;
Label85.Font.Color:=clYellow;
Label104.Font.Color:=clYellow;
end;
I'm using lazarus and I have this kind of error: identifier idents no member "Font" . By the way as you can see Label104.Font.Color:=clYellow; works (for example). How could I solve this?

TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
should obviously read
TLabel(FindComponent('Label'+IntToStr(i))).Font.Color:=clYellow;

Your code shouldn't even compile, because your parentheses are out of place:
TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
The closing parenthesis after clYellow should be with the other two after the IntToStr(i)) and before the .Font.
TLabel(FindComponent('Label'+IntToStr(i))).Font.Color:=clYellow;
Your code is pretty risky, though. It makes an assumption that it will find the label (which may fail if the label gets renamed or deleted in the future). You're much safer to check first before using the result of FindComponent:
procedure TForm1.RadioButton1Click(Sender: TObject);
var
i: Integer;
TempComp: TComponent;
begin
for i := 16 to 27 do
begin
TempComp := FindComponent('Label' + IntToStr(i));
if TempComp <> nil then
(TempComp as TLabel).Font.Color:=clYellow;
end;
Label85.Font.Color :=clYellow;
Label104.Font.Color :=clYellow;
end;
(The last two lines are safe, as the compiler will tell you if those labels get renamed or deleted; it can't do so in the TLabel(FindComponent()) case, because it can't tell at compile time which labels you'll be accessing.)

Related

Do I need to free these frames and if yes is it the right way?

Do I have to free these two frames in that case ? And if yes, is it the right way. I have a form in which there can be two differents frames that I can call with two buttons as you can see on the picture. I can call the one I want depending on the button I press.
Here are the procedures :
procedure TChercherDesAppelsForm.OuvrirFrameChercherAppelOnClick(Sender: TObject);
begin
if Assigned(FrameStatistiquesAppels) then
begin
FrameStatistiquesAppels.Free;
end;
FrameChercherAppels := TChercherAppelsFrame.Create(nil);
FrameChercherAppels.Top:=35;
FrameChercherAppels.Parent:= ChercherDesAppelsForm;
end;
procedure TChercherDesAppelsForm.OuvrirFrameStatistiquesAppelsOnClick(Sender: TObject);
begin
if Assigned(FrameChercherAppels) then
begin
FrameChercherAppels.Free;
end;
FrameStatistiquesAppels := TStatistiquesAppelsFrame.Create(nil);
FrameStatistiquesAppels.Top:=35;
FrameStatistiquesAppels.Parent:= ChercherDesAppelsForm;
end;
There are three issues with your code:
procedure TChercherDesAppelsForm.OuvrirFrameChercherAppelOnClick(Sender: TObject);
begin
if Assigned(FrameStatistiquesAppels) then
begin
FrameStatistiquesAppels.Free;
end;
FrameChercherAppels := TChercherAppelsFrame.Create(nil);
FrameChercherAppels.Top:=35;
FrameChercherAppels.Parent:= ChercherDesAppelsForm;
end;
First, you never need to check if a variable is assigned before you free it, because TObject.Free does that. Basically, X.Free does if Assigned(X) then X.Destroy, so if Assigned(X) then X.Free does if Assigned(X) then if Assigned(X) then X.Destroy with does one test too many.
Second, your code will free the "statistics" frame, but it will not set the pointer FrameStatistiquesAppels to nil. Hence, you end up with a dangling pointer.
This is very bad in your case, because if you invoke OuvrirFrameChercherAppelOnClick two times in a row, you will invoke Free on a dangling pointer. Indeed, the second time, FrameStatistiquesAppels is non-nil (it still points to the address of the old object which no longer exists) but it doesn't point to a valid object.
In addition, and this is the third point, if you invoke this method two times in a row, you will have created two "apple" frames, but you only have a pointer to the second one. Hence, you have leaked the first one. If you press this button N times, you will have leaked N − 1 objects.
You should be able to see the memory increase each time.
The following version resolves all these issues:
procedure TChercherDesAppelsForm.OuvrirFrameChercherAppelOnClick(Sender: TObject);
begin
FreeAndNil(FrameStatistiquesAppels);
FreeAndNil(FrameChercherAppels);
FrameChercherAppels := TChercherAppelsFrame.Create(nil);
FrameChercherAppels.Top := 35;
FrameChercherAppels.Parent:= ChercherDesAppelsForm;
end;
Of course, the second snippet will need to be updated in the same way.
All this being said, wouldn't it be simpler to create both frames once at application startup (you could even place them on the form at design time) and then only toggle their visibilities?
procedure TChercherDesAppelsForm.OuvrirFrameChercherAppelOnClick(Sender: TObject);
begin
FrameStatistiquesAppels.Hide;
FrameChercherAppels.Show;
end;

I want to know how to "fill" the TStings defined in CollectLangString?

What is the "engine" under TLang...
TLang is ok in my small project but with larger project It is difficult to manage. I try to figure how it works. I've fund many proc and functions in FMX.Types. I've focus on: CollectLangStart, CollectLangFinish and CollectLangStrings. Calling those function can be compiled but I don't know where and when this TStrings is filled, the TStrings stay empty. The documentation talk about "scene" but it is very limited.
TStyleManager.UpdateScenes must be called between CollectLangStart and copying CollectLangStrings
var
Str: TStrings;
begin
CollectLangStart;
TStyleManager.UpdateScenes;
Str := TStringList.Create;
try
Str.Assign(CollectLangStrings);
Str.SaveToFile(ExtractFilePath(ParamStr(0)) + 'lang.lng');
finally
Str.Free;
CollectLangFinish;
end;
end;

if then else loop problem delphi

I'm still a beginner and I have been trying to solve this problem by my self but I guess I 'm out of luck. I think it is probably quite simple but here's the deal.
I have 3 checkboxes. Each one writes a specific line in a text file when a button is pressed but if none are selected. I want a message to be displayed. But what happens there is that the message pops out even if one checkbox is checked. Here's the code: (btw, feel free to suggest any other code that would make it easier/clearer)
if cbSCV.Checked then
WriteLn(permFile, 'scv');
if cbMP.Checked then
WriteLn(permFile, 'mp');
if cbBTK.Checked then
WriteLn(permFile, 'btk');
if not (cbBTK.Checked) and not (cbMP.Checked) and not (cbBTK.Checked) then
showmessage('Choose at least 1 option.');
try replacing the if sentence to
if not (cbBTK.Checked) and not (cbMP.Checked) and not (cbSCV.Checked) then
because you are checking the cbBTK.checked value twice
For what it's worth I'd probably reverse the logic and write the troublesome test like this:
if not (cbBTK.Checked or cbMP.Checked or cbSCV.Checked) then
To complement #soid's answer: I'd probably write it like this:
procedure TForm1.CheckIt;
var
Count: Integer;
procedure HandleCheckBox(ACheckBox: TCheckBox; const AID: string);
begin
if ACheckBox.Checked then
begin
WriteLn(permFile, AID);
Inc(Count);
end;
end;
begin
Count := 0;
HandleCheckBox(cbSCV, 'scv');
HandleCheckBox(cbMP, 'mp');
HandleCheckBox(cbBTK, 'btk');
if Count = 0 then
ShowMessage('Choose at least 1 option.');
end;
This is a few more lines but it is IMHO less error prone and more "automatic" if you later need a fourth or fifth checkbox.
I would rewrite it like this:
if cbSCV.Checked then WriteLn(permFile, 'scv');
if cbMP .Checked then WriteLn(permFile, 'mp' );
if cbBTK.Checked then WriteLn(permFile, 'btk');
if not (cbSCV.Checked) and
not (cbMP .Checked) and
not (cbBTK.Checked) then
showmessage('Choose at least 1 option.');
This takes the same number of lines but places the repeated elements together to make it easy to read the whole construct quickly and spot places where you are not following the pattern. Your bug, which we have all had in our code, is easier to see if it is written like this.
Hmmm. For those things I like a set-based approach.
One way is this
type
TEnumSomething = (esSCV, esMP, esBTK);
TSomethingSet = set of TEnumSomething;
{var section}
var
Conj: TSomethingSet;
{code section}
Conj := [];
if cbSCV.checked then
begin
Conj := conj + [esSCV];
WriteLn(permFile, 'scv');
end;
{do this for the other 2 checkboxes}
If Conj = [] then ShowMessage('');
You can also make Conj an form field and make checkboxes
set/unset this on their OnClick event.
Warning: maybe some syntax detail is missing, I'm not on delphi IDE now...
I probably wouldn't rewrite it like this, but hey, this is fun. I'm at work and I don't have Delphi here, so this is just sample code. Generics!
type
TCheckBoxDict: TDictionary<String, TCheckBox>;
var
Dict: TCheckBoxDict;
function HandleCheckBoxes(ADict: TCheckBoxDict) : boolean;
var
Key: String;
CheckBox: TCheckBox;
begin
Result := false;
for Key in ADict.Keys do
if ADict.Items[Key].Checked then
begin
WriteLn(permFile, Key);
Result := true;
end;
end;
begin
Dict := TCheckBoxDict.Create;
Dict.Add('scv', cbSCV);
Dict.Add('mp', cbMP);
Dict.Add('btk', cbBTK);
if not HandleCheckBoxes(Dict) then
ShowMessage('Choose at least one option');
Dict.Destroy;
end;

Improve speed of own debug visualizer for Delphi 2010

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;

Is it memory safe to provide an object as a function result?

Here I provide simple piece of code.
function GetStringList:TStringList;
var i:integer;
begin
Result:=TStringList.Create;
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
end;
procedure ProvideStringList(SL:TStringList);
var i:integer;
Names:TStringList;
begin
Names:=TStringList.Create;
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
Names.Free;
end;
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
SL.Assign(GetStringList);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
And now the question: what will happen to result object in function GetStringList:Tstringlist, which is created, but never freed? (I call 2 times Create and only 1 time Free)
Is it memory safe to provide objects by function or should I use procedures to do this task, where object creation and destroying is simply handled (procedure ProvideStringlist)? I call 2 times Create and 2 times Free.
Or is there another solution?
Thanx in advance
Lyborko
Is it memory safe to provide an object as a function result?
It is possible, but it needs attention from the implementor and the call.
Make it clear for the caller, the he controls the lifetime of the returned object
Make shure you don't have a memory leak when the function fails.
For example:
function CreateBibleNames: TStrings;
begin
Result := TStringList.Create;
try
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Free;
raise;
end;
end;
But in Delphi the most commen pattern for this is:
procedure GetBibleNames(Names: TStrings);
begin
Names.BeginUpdate;
try
//perhaps a Names.Clear here
//but I don't use it often because the other
//way is more flexible for the caller
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
finally
Names.EndUpdate;
end;
end;
so the caller code can look like this:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := CreateBibleNames;
try
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
and the other, more common version:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := TStringList.Create;
try
GetBibleNames(Names);
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
(I have no compiler at the moment, so perhaps there are some errors)
I don't know what you mean by safe, but it is common practice. The caller of the function becomes responsible for freeing the returned object:
var
s : TStringList;
begin
s := GetStringList;
// stuff
s.free;
end;
Memory safety is a stricter variant of type safety. For memory safety, you typically need a precise garbage collector and a type system which prevents certain kinds of typecasts and pointer arithmetic. By this metric, Delphi is not memory safe, whether you write functions returning objects or not.
These are the very kinds of questions I grappled with in my early days of Delphi. I suggest you take your time with it:
write test code with debug output
trace your code step-by-step
try different options and code constructs
and make sure you understand the nuances properly;
The effort will prove a great help in writing robust code.
Some comments on your sample code...
You should get into the habit of always using resource protection in your code, even in simple examples; and especially since your question pertains to memory (resource) protection.
If you name a function GetXXX, then there's no reason for anyone to suspect that it's going to create something, and they're unlikely to protect the resource. So careful naming of methods is extremely important.
Whenever you call a method that creates something, assume it's your responsibility to destroy it.
I noticed some code that would produce Hints from the compiler. I recommend you always eliminate ALL Hints & Warnings in your programs.
At best a Hint just means some arbitrary redundant code (excesses of which make maintenance more difficult). More likely it implies you haven't finished something, or rushed it and haven't finished testing/checking.
A Warning should always be taken seriously. Even though sometimes the compiler's concern is a logical impossibility in the specific situation, the warning may indicate some subtle language nuance that you're not aware of. The code can always be rewritten in a more robust fashion.
I have seen many examples of poor resource protection where there is a compiler warning giving a clue as to the problem. So check them out, it will aid in the learning.
If an exception is raised in a method that returns a new object, care should be taken to ensure there isn't a memory leak as a result.
//function GetStringList:TStringList;
function CreateStringList:TStringList; //Rename method lest it be misinterpreted.
//var i: Integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
begin
Result := TStringList.Create;
try //Protect the memory until this method is done; as it can **only** be done by **this** method!
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Destroy; //Note Destroy is fine because you would not get here if the line: Result := TStringList.Create; failed.
raise; //Very important to re-raise the exception, otherwise caller thinks the method was successful.
end;
end;
A better name for the following would be PopulateStringList or LoadStringList. Again, resource protection is required, but there is a simpler option as well.
procedure ProvideStringList(SL:TStringList);
var //i:integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
Names:TStringList;
begin
Names:=TStringList.Create;
try //ALWAYS protect local resources!
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
finally //Finally is the correct choice here
Names.Free; //Destroy would also be okay.
end;
end;
However; in the above code, creating a temporary stringlist is overkill when you could just add the strings directly to the input object.
Depending on how the input stringlist is used, it is usually advisable to enclose a BeginUpdate/EndUpdate so that the changes can be handled as a batch (for performance reasons). If your method is general purpose, then you have no idea of the origin of the input, so you should definitely take the precaution.
procedure PopulateStringList(SL:TStringList);
begin
SL.BeginUpdate;
try //YES BeginUpdate must be protected like a resource
SL.Add('Adam');
SL.Add('Eva');
SL.Add('Kain');
SL.Add('Abel');
finally
SL.EndUpdate;
end;
end;
our original code below had a memory leak because it called a method to create an object, but did not destroy. However, because the method that created the object was called GetStringList, the error is not immediately obvious.
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
//SL:=TStringList.Create; This is wrong, your GetStringList method creates the object for you.
//SL.Assign(GetStringList);
SL := CreateStringList; //I also used the improved name here.
try //Don't forget resource protection.
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
The only error in your final snippet was the lack of resource protection. The technique used is quite acceptable, but may not be ideally suited to all problems; so it helps to also be familiar with the previous technique.
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
try //Be like a nun (Get in the habit)
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
No, it is not "memory safe". When you create an object, someone has to free it.
Your first example leaks memory:
SL:=TStringList.Create;
SL.Assign(GetStringList); // <-- The return value of GetStringList is
// used, but not freed.
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
The second example works fine, but you don't have to create and free an additional temporary instance (Names)
In general, the second example is slightly better, because it is obvious, who is responsible for the creation and destruction of the list. (The caller) In other situations, a returned object must be freed by the caller or perhaps it's forbidden. You can't tell from the code. If you must do so, it's good practice to name your methods accordingly. (CreateList is better than GetList).
It is the usage that is the leak, not the construct itself.
var sl2 : TStringlist;
sl2:=GetStringList;
sl.assign(sl2);
sl2.free;
is perfectly fine, or easier even,
sl:=getstringlist;
// no assign, thus no copy, one created one freed.
sl.free;
In btn1Click you should do:
var sl2: TStringList;
sl2 := GetStringList:
SL.Assign(sl2);
sl2.Free;
In btn2Click you don't have to create an instance of SL before calling ProvideStringList to not create a memory leak.
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);

Resources