Insert existing TLabels into a dynamically created array of TLabels - delphi

I'm trying to create a dynamic array of TLabels, then insert already existing TLabels created in the IDE into it so I can then use the array in the code.
My purpose is to use this method for several similar processes.
I would like to make it by using loops.
I saw this thread and it was very helpful to understand about creating arrays of TLabels and populating them with TLabels for the desired purposes, but I couldnt find the particular solution for the case when the labels are already created.
Use variables for object name in Delphi
Basically, what I'm trying to automate is this:
var
LabelArray : array of TLabel;
SetLength(LabelArray, 17);
LabelArray[0] := M2;
LabelArray[1] := M3;
.
.
LabelArray[16] := M18
M2 to M18 are 17 labels that are the TLabels already created and positioned on the Form.

You can use the form's FindComponent method to add the labels to your array.
The code below depends on your having declared LabelArray as a private variable at the form level, so that it exists and is visible when the form is created and the code executes. It also hard-codes in 17 as the number of labels, so if you delete or rename a label you'll have an empty spot in the array - I did not include any error checking to make sure a label is found before putting it into the array and the element could end up nil if a label is missing.
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(LabelArray, 17);
{
I use Low() and High() here to avoid having multiple places where
you would have to change the code if you end up adding or removing
labels in the future.
Also note that the index into the array starts at 0, so the code that
calls Format() adjusts that index by 2 to start at M2 instead of M0.
}
for i := Low(LabelArray) to High(LabelArray) do
LabelArray[i] := TLabel(FindComponent(Format('M%d', [i + 2])));
end;

If your labels cannot be found using a common name scheme, but you simply want to get all labels on the same panel / group box etc. into the array, you can use the Controls[] property of the parent control:
SetLength(LabelArray, ParentCtrl.ControlCount);
cnt := 0;
for i := 0 to ParentCtrl.ControlCount - 1 do begin
if ParentCtrl.Controls[i] is TLabel then begin
LabelArray[cnt] := TLabel(ParentCtrl.Controls[i]);
Inc(cnt);
end;
end;
SetLength(LabelArray, cnt);
ParentCtrl is the panel / groupbox or even the form itself that is the common parent of all the labels you are interested in.
If you simply want all labels on a form, even if they are located on other controls, you can use the Components[] property of the form in the same way.

Related

Delphi - Getting property values with GetPropValue()

I'm using Delphi's GetPropValue() function to get values ​​of certain properties of some objects of type TControl. Everything works correctly when I get simple property values ​​such as Value, Opacity, etc, but as I'm using firemonkey there are some extended properties, such as RotationCenter, it has RotationCenter.X and RotationCenter.Y, or even properties of text within TextSettings, in these properties with sub-types I can not get the values.
In this example I get the values ​​correctly:
If IsPublishedProp (Component_cc, 'Value') then
EditValue.Text: = GetPropValue (Component_cc, 'Value', true);
Where Component_cc:TControl; And is created dynamically, it can also be any type of Firemonkey component (so far everything is okay, everything works).
When I need to use the form below, it does not work.
If IsPublishedProp (Component_cc, 'RotationCenter.X') then
EditRotationCenterX.Text: = GetPropValue (CC_component, 'RotationCenter.X', true);
Does anyone know a way to get these properties extended by this function?
First, the CC_component's RotationCenter property is actually an instance of TPosition class which decends from TPersistent.
Second, you cannot use dotted notation when calling IsPublishedProp.
You can use GetObjectProp to first retrieve the internal TPosition instance and then access the X property from there:
(Assume a simple FMX application with one form that contains a TButton called Button1 and a TEdit called EditRotationCenterX.)
procedure TForm1.Button1Click(Sender: TObject);
var
CC_component : TComponent;
CC_component_RotationCenter : TPosition;
begin
CC_component := Button1;
if IsPublishedProp(CC_component, 'RotationCenter') then
begin
CC_component_RotationCenter := TPosition(GetObjectProp(CC_component, 'RotationCenter'));
EditRotationCenterX.Text := CC_component_RotationCenter.X.ToString;
end
end;
Update, for a property of type Set:
For a Set type property, you will need to retrieve its ordinal value using GetOrdProp. This will be the array of bits that represent which elements are included in the current value. Then, you simply test if the appropriate bit is set. This is the method I would prefer.
Alternatively you can use GetSetProp which will return a text representation of the elements in the Set's current value. For example, if the Set's value is [TCorner.BottonLeft, TCorner.TopRight] the you will get back the string value "TopRight,BottonLeft". You then check to see if the name of your target element appears anywhere in the returned string. This method is susceptible to failure if the Delphi RTL or FMX libraries are ever changed in the future.
(This example adds a TRectangle shape called Rectangle1 and a TCheckBox called cbCornerBottonRight to the simple FMX App from above:)
procedure TForm1.Button1Click(Sender: TObject);
var
CC_component : TComponent;
CC_component_Corners : nativeint;
CC_component_CornersAsString : string;
begin
CC_component := Rectangle1;
if IsPublishedProp(CC_component, 'Corners') then
begin
// Using this method will make your code less sensitive to
// changes in the ordinal values of the Set's members or
// changes to names of the enumeration elements.
//
CC_component_Corners := GetOrdProp(CC_component,'Corners');
cbCornerBottonRight.IsChecked := ((1 shl ord(TCorner.BottomRight)) and CC_component_Corners) <> 0;
// This approach may break if the names of the elements of
// the TCorner enumeration are ever changed. (BTW, they have
// been in the past: "cvTopLeft", "cvTopRight", "cvBottomLeft",
// and "cvBottomRight" are now deprecated)
//
CC_component_CornersAsString := GetSetProp(CC_component,'Corners');
cbCornerBottonRight.IsChecked := CC_component_CornersAsString.IndexOf('BottomRight') >= 0;
end;
end;
When speaking about the old RTTI, you can do this. You need to go deeper in the structure. Ask for the X property the TPosition object:
var
O: TObject;
X: Integer;
begin
if PropIsType(Component_cc, 'RotationCenter', tkClass) then
begin
O := GetObjectProp(Component_cc, 'RotationCenter');
if Assigned(O) and PropIsType(O, 'X', tkInteger) then
X := GetOrdProp(O, 'X');
end;
end;

Delphi: refer to a component from multiple frames

I want to refer to a component (using loop) which exists in multiple frames. The component is the same for all frames.
I have a ComboBox named "comboBoxAccount".
I also have multiple frames (which I instantiate at runtime).
The frames I create are named:
frm1, frm2, frm3
Each frame has a multple TabSheets created at runtime named:
page_1, page_2, page_3
So, I am currently using:
Tfrm1(FindComponent('page_'+i)).comboBoxAccount
Which means I use FindComponent with i in a loop to access all comboBoxAccount in all TabSheets.
The problem is that I have to use casting to make Delphi understand which component I want to access, so I've put Tfrm1( ) in front of FindComponent.
So, if I want to use casting with a loop what can I do?
I could just use:
Tfrm1(FindComponent('page_'+i)).comboBoxAccount
Tfrm2(FindComponent('page_'+i)).comboBoxAccount
Tfrm3(FindComponent('page_'+i)).comboBoxAccount
but is there a way to loop Tfrm without writing them one by one?
page_1, page_2, etc are TabSheets, not frames, but you are casting them to frames, which is wrong.
If each frame has multiple TabSheets on it, and each TabSheet has a ComboBox on it, you would not use FindComponent() at all. You would have to iterate through parent/child Controls[] lists instead, or use FindChildControl().
However, if the frame is the Owner of the ComboBox (which it would be if the ComboBox is added to the frame at design-time), then you would use FindComponent() on the frame itself. But you cannot have multiple child components with the same name, so each ComboBox on each TabSheet would need a unique name:
cb := TComboBox(frm1.FindComponent('comboBoxAccount_'+IntToStr(i)));
Repeating for each index of frm1, then all indexes of frm2, then frm3.
If your ComboBox really is named just comboBoxAccount then I wonder if you really have one parent container that has multiple TabSheets on it, and each TabSheet has a frame on it, and each frame has only one ComboBox named comboBoxAccount on it. If that is the case, you don't need to do any searching at all, just use your existing object pointers directly:
cb := frm1.comboBoxAccount;
cb := frm2.comboBoxAccount;
cb := frm3.comboBoxAccount;
But if you really want to use a loop, then put the frames into a list, eg:
private
Frames: TList;
...
Frames := TList.Create;
Frames.Add(frm1);
Frames.Add(frm2);
Frames.Add(frm3);
...
for I := 0 to Frames.Count-1 do
begin
// assuming TFrm1, TFrm2, etc do not have a common ancestor holding the ComboBox...
cb := TComboBox(TWinControl(Frames[i]).FindComponent('comboBoxAccount'));
...
end;
Or use an array:
private
// assuming TFrm1, TFrm2, etc do not have a common ancestor holding the ComboBox...
Frames: array[0..2] of TWinControl;
...
Frames[0] := frm1;
Frames[1] := frm2;
Frames[2] := frm3;
...
for I := Low(Frames) to High(Frames) do
begin
cb := TComboBox(Frames[i].FindComponent('comboBoxAccount'));
...
end;
Update: given new information you provided, you can find the ComboBox like this instead:
cb := Tfrm1(PageControl[i].Pages[j].FindChildControl('frame_'+IntToStr(i))).comboBoxAccount;
Your while loop is creating multiple tabsheets and frames that have the same Name, which is not valid. I would suggest not naming them at all.
while condition=true do
begin
TabSheet := TTabSheet.Create(PageControl[i]);
TabSheet.Caption := '';
//TabSheet.Name:='tabesheet_'+IntToStr(i);
TabSheet.PageControl := PageControl[i];
TabSheet.PageControl.ActivePageIndex:=0;
{I also create a frame for each TabSheet. In every frame there is the ComboBox that I want to access}
frame := Tfrm1.Create(TabSheet);
//frame.Name:='frame_'+IntToStr(i);
frame.Parent:=TabSheet;
end;
Assuming a frame is the only child of its TabSheet, you can then do this:
cb := Tfrm1(PageControl[i].Pages[j].Controls[0]).comboBoxAccount;

Looping all components on a TabSheet

I have the following code which shoul loop all components on a given tab on my tabsheet.
I have tried many variants of the same code found on the net, but I simply can't get it to work.
First I check if it is the right tab - that works.
Then I check to see how many components - that doesn't work. It says 0 component even though I now that there are 2 panels with 9 checkboxes total.
procedure TfrmHsUsers.pagUsersClick(Sender: TObject);
var
i: integer;
Fieldname: string;
begin
if pagUsers.Properties.ActivePage.Name = 'tabProgram' then
begin
ShowMessage(IntToStr(pagUsers.Properties.ActivePage.ComponentCount));
for i := 0 to pagUsers.Properties.ActivePage.ComponentCount - 1 do
if (pagUsers.Properties.ActivePage.Components[i]) is TcxDbCheckBox then
begin
Fieldname := TcxDbCheckBox(pagUsers.Properties.ActivePage.Components[i]).DataBinding.DataField;
TcxDbCheckBox(pagUsers.Properties.ActivePage.Components[i]).Enabled := Settings.License.IsEnabled(Fieldname);
end;
end;
end;
Any hints to what might be wrong in my code?
What's wrong is that you are looping over the Components property. That lists the components that are owned by the tab sheet. For components created in the form designer, the form is the owner. So it is expected that pagUsers.Properties.ActivePage.ComponentCount is zero since the only thing on your form that owns anything is the form itself.
What you need to to is use ControlCount and Controls[] to iterate over the children of the tab sheet. Simply replace all use of ComponentCount with ControlCount, and likewise replace Components[] with Controls[].
Note that the ControlCount and Controls[] properties only give the immediate children. Since you have panels you most likely have the panels as children of the tab sheet, and the check boxes as children of the panels. So you need to iterate over the children of the panels.
My answer here shows one way to do that. If you use the code I presented there then your iteration over checkboxes can be written very simply indeed:
TabSheet := pagUsers.Properties.ActivePage;
for CheckBox in TControls.Enumerator<TcxDbCheckBox>(TabSheet) do
....

Delphi. How to Disable/Enable controls without triggering controls events

I have a DataSet (TZQuery), which has several boolean fields, that have TDBCheckBoxes assigned to them.
These CheckBoxes have "OnClick" events assigned to them and they are triggered whenever I change field values (which are assigned to checkboxes).
The problem is that I do not need these events triggerred, during many operations i do with the dataset.
I've tried calling DataSet.DisableControls, but then events are called right after i call DataSet.EnableControls.
So my question is - is there a way to disable triggering Data-aware controls events.
Edit (bigger picture):
If an exception happens while let's say saving data, i have to load the default values (or the values i've had before saving it). Now while loading that data, all these events (TDBCheckBoxes and other data-aware controls) are triggered, which do all sorts of operations which create lag and sometimes even unwanted changes of data, i'm looking for an universal solution of disabling them all for a short period of time.
Building on Guillem's post:
Turn off everything:
Traverse each component on the form with the for-loop, shown below, changing the properties to the desired value.
If you want to later revert back to the original property values, then you must save the original value (as OldEvent is used below.)
Edit: The code below shows the key concept being discussed. If components are being added or deleted at run-time, or if you'd like to use the absolutely least amount of memory, then use a dynamic array, and as Pieter suggests, store pointers to the components rather than indexing to them.
const
MAX_COMPONENTS_ON_PAGE = 100; // arbitrarily larger than what you'd expect. (Use a dynamic array if this worries you.
var
OldEvent: Array[0.. MAX_COMPONENTS_ON_PAGE - 1] of TNotifyEvent; // save original values here
i: Integer;
begin
for i := 0 to ComponentCount - 1 do
begin
if (Components[i] is TCheckBox) then
begin
OldEvent[i] := TCheckBox(Components[i]).OnClick; // remember old state
TCheckBox(Components[i]).OnClick := nil;
end
else if (Components[i] is TEdit) then
begin
OldEvent[i] := TEdit(Components[i]).OnClick; // remember old state
TEdit(Components[i]).OnClick := nil;
end;
end;
Revert to former values
for i := 0 to ComponentCount - 1 do
begin
if (Components[i] is TCheckBox) then
TCheckBox(Components[i]).OnClick := OldEvent[i]
else if (Components[i] is TEdit) then
TEdit(Components[i]).OnClick := OldEvent[i];
end;
There may be a way to fold all of the if-statements into one generic test that answers "Does this component have an OnClickEvent" -- but I don't know what it is.
Hopefully someone will constructively criticize my answer (rather than just down voting it.) But, hopefully what I've shown above will be workable.
One way to do this is following:
var
Event : TNotifyEvent;
begin
Event := myCheckbox.OnClick;
try
myCheckbox.OnClick := nil;
//your code here
finally
myCheckbox.OnClick := Event;
end;
end;
HTH
The internal design of the TCustomCheckBox is that it triggers the Click method every time the Checked property if changed. Be it by actually clicking it or setting it in code. And this is happening here when you call EnableControls because the control gets updated to display the value of the linked field in your dataset.
TButtonControl (which is what TCustomCheckBox inherits from) has the property ClicksDisabled. Use this instead of (or in addition to) the DisableControls/EnableControls call. Unfortunately it is protected and not made public by TCustomCheckBox but you can use a small hack to access it:
type
TButtonControlAccess = class(TButtonControl)
public
property ClicksDisabled;
end;
...
TButtonControlAccess(MyCheckBox1).ClicksDisabled := True;
// do some dataset stuff
TButtonControlAccess(MyCheckBox1).ClicksDisabled := False;
Of course you can put this into a method that checks all components and sets this property if the control inherits from TCustomCheckBox or some other criteria.

TStrings vs TStringList in TCheckListBox

In Delphi 7, I'm using a TCheckListBox. I want it to use a TStringList rather than a TStrings, so I can set Duplicates to dupIgnore, and Sorted to TRUE.
Can I just do this:
Form1 = class(TObject
CheckListBox1: TCheckListBox; // created by the IDE
end;
procedure TForm1.FormCreate
begin
CheckListBox1.Items.Free;
CheckListBox1.Items := TStringList.Create;
CheckListBox1.Items.Sorted := TRUE;
CheckListBox1.Items.Duplicates := dupIgnore;
end;
Is this safe? Any caveats or suggestions?
EDIT: Removed declaration for MyStringList and added .Items to the last two assignment lines.
EDIT 2: Trying to compile the above, it looks like I'd have to cast the two final lines like this:
TStringList(CheckListBox1.Items).Sorted := TRUE;
TStringList(CheckListBox1.Items).Duplicates := dupIgnore;
Although I might be able to get this to run, I'm asking the question because just getting it to run doesn't mean it will always run or is safe.
You don't control what class TCheckListBox uses to store its items. Assigning the Items property a value only assigns its items to the internal storage.
Also, you shouldn't call Items.Free;. TCheckListBox depends on its internal instance of TListBoxStrings.
To answer your edits in your question: Don't hard-cast the Items property to TStringList, either. The typecast is wrong (the instance exposed by Items is not a TStringList) and will only cause problems.
Edit, to suggest a workaround for what you seem to try to achieve: To keep the checklistbox sorted, you can set its Sorted property to True. To avoid duplicates, you can check the list before adding an item in code.

Resources