Looping all components on a TabSheet - delphi

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
....

Related

Insert existing TLabels into a dynamically created array of TLabels

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.

Delphi: TreeView elements vs. Form position changing

We have found something is seems to be a bug(?), and cause bug in our code.
Delphi XE3, Win32. Two forms, the main have button:
procedure TForm4.Button1Click(Sender: TObject);
begin
with TForm1.Create(Application) do
begin
ShowModal;
Release;
end;
end;
The Form1 is doing this:
procedure TForm1.FormCreate(Sender: TObject);
var
i, j: integer;
mn: TTreeNode;
begin
for i := 1 to 10 do
begin
mn := TreeView1.Items.Add(nil, 'M' + IntToStr(i));
for j := 1 to 10 do
begin
TreeView1.Items.AddChild(mn, 'C' + IntToStr(j));
end;
end;
Position := poDesigned;
beep;
Caption := IntToStr(TreeView1.Items.Count);
end;
After this I get 0 elements in caption.
But when I have a button in that form with this code...
procedure TForm1.Button1Click(Sender: TObject);
begin
Caption := IntToStr(TreeView1.Items.Count);
end;
...
Then I can see the good number (110 elements).
If I write TreeView1.Handleneeded after Position changing, the count is also good.
The problem is based on RecreateWnd, which calls DestroyHandles.
But they will be repaired only in Show (in the Activate event I can see good result too).
TreeView is a special control, because the tree elements are children, and the count is calculated by on them, no matter it have real sub-object list.
The main problem that ReCreateWnd called often by another methods to, so it can cause problems in another sections too, and I cannot put HandleNeeded before all .Count calculation.
(We have special base form that correct the Position to poDesigned if it was poScreenCenter to it can be positionable later. This happens after FormCreate call, in an inner method. We found this problem only with these kind of forms, but later we could reproduce it in a simple code too)
So the question is - what is the global solution to this problem?
(Did you experience this in XE5 too?)
Thank you for all help, info, doc.
The Form's HWND is getting destroyed when you set the Position. That also destroys all child HWNDs. The TreeView's HWND has not been re-created yet when you read its Count, which is why it reports 0. Calling TreeView.HandleNeeded after setting the Position forces the TreeView to re-create its HWND immediately, which will re-load any TreeNodes that had beencached internally when the TreeView's HWND was destroyed (but only if the TreeView.CreateWndRestores property is True, which it is by default).
A TreeView stores its child nodes inside of its HWND. Reading the Items.Count merely asks the HWND how many nodes it has, so if there is no HWND then the Count will be 0. TTreeView does not keep its own list of TTreeNode objects, it merely assigns them as user-defined data in the physical nodes themselves. When nodes are removed from the tree, TTreeView frees the associated TTreeNode objects. In the case of HWND recreation, TTreeView caches the TTreeNode data and then re-assigns it back to new nodes when the HWND is re-created. But again, it does not keep track of the TTreeNode objects.
What TTreeView could have done is store the current number of nodes during HWND destruction, and then have Items.Count return that value if the HWND has not been re-created yet. But alas, TTreeView does not do that. But you could implement that manually by subclassing TTreeView to intercept the CreateWnd() and DestroyWnd() methods, and then write your own function that returns the actual Items.Count when HandleAllocated is true and returns your cached value if it is false.
If the Form is visible to the user then its HWND (and the HWND of its children) will be available, since a control is not visible without an HWND, so Items.Count will be available if the TTreeView is visible to the user. If its HWND is ever destroyed while visible, the VCL will re-create the HWND immediately so the user does not see a missing control. However, if the Form (or TreeView) is not visible to the user when the HWND is destroyed, the HWND will not be re-created until it is actually needed when the Form/TreeView is made visible again.
Since you are forcing the Position to always be poDesigned at the time of Form creation, why not just set the Position to poDesigned at design-time? Otherwise, you can simply set the Position before populating the TreeView instead of afterwards, at least:
procedure TForm1.FormCreate(Sender: TObject);
var
i, j: integer;
mn: TTreeNode;
begin
Position := poDesigned; // <-- here
for i := 1 to 10 do
begin
mn := TreeView1.Items.Add(nil, 'M' + IntToStr(i)); // <-- first call to Add() forces HWND recreation if needed
for j := 1 to 10 do
begin
TreeView1.Items.AddChild(mn, 'C' + IntToStr(j));
end;
end;
Beep;
Caption := IntToStr(TreeView1.Items.Count); // <-- correct value reported
end;
This happens in all versions of Delphi. This is simply how things work in the VCL.
On a side note, you should use Free() instead of Release(). Release() is meant to be used only when a Form needs to Free() itself, such as in an event handler, by delaying the Free() until the Form becomes idle. Since the form is already closed and idle by the time ShowModal() exits, it is safe to Free() the Form immediately.
I think you are over-reacting. You state:
The main problem that ReCreateWnd called often by another methods to, so it can cause problems in another sections too, and I cannot put HandleNeeded before all .Count calculation.
But then in comments you say that these other scenarios are:
CMCtlD3Changed CMSysColorChange BORDERSTYLE SetAxBorderStyle SetBorderIcons Dock SetPosition SetPopupMode set_PopupParent RecreateAsPopup ShowModal SetMainFormOnTaskBar
Indeed these methods will cause window recreation. But why does that matter for you? Do you routinely assign to MainFormOnTaskBar and then immediately request the count of items in your tree view? Do you change Ctl3D regularly? Are you changing BorderIcons dynamically? I very much doubt it.
So I think you need to tackle the immediate problem which relates to the timing of the setting of Position. I would deal with that by making sure that Position is set before you populate the tree view. Do that by either setting Position at design time, or simply before you populate the tree view.
Of course, there may well be other problem related to window creation. You'll need to treat them on a case by case basis. I suspect that you are hoping for some sort of magic switch that just makes this issue disappear. I don't believe that there is one. If you try to read the item count before the handle is created, then you will suffer from this problem. The solution is not to read the item count before the handle is created.

How to let all items in multi-level PopupMenu act as one radiogroup?

I have a PopupMenu with submenus and only one item in total shall be checked at a time. GroupIndex and RadioItem properties do not work outside of the respective submenus as far as I have tried.
I have found this piece of code to check a PopupMenu and its direct sub-components but I haven't had any luck with creating a popup-wide variety of this.
I need a solution that is fast - the PopupMenu has 4x14 entries, always iterating through all menus and subentries can't be the best solution for this, I suppose.
Is there a simple property for this that I am missing or is the rocky path of iteration my only option?
Add all 56 items as actions to one ActionList and give all GroupIndex properties the same value.
Now, add menu-items, sub-menu's and sub-sub-menu's in any tree-like fashion and link each of them to an action. Checking one menu-item, wherever positioned, will automatically uncheck all others.
Et voilà!
NGLN's answer is better, but if you really don't want or don't like to use an ActionList, then this routine will also do:
procedure CheckMenuItem(Item: TMenuItem);
procedure UncheckMenu(Menu: TMenuItem; GroupIndex: Byte);
var
I: Integer;
begin
if Menu.RadioItem and (Menu.GroupIndex = GroupIndex) then
Menu.Checked := False;
for I := 0 to Menu.Count - 1 do
UncheckMenu(Menu[I], GroupIndex);
end;
begin
if (not Item.Checked) and Item.RadioItem and (Item.GroupIndex <> 0) then
begin
UncheckMenu(Item.GetParentMenu.Items, Item.GroupIndex);
Item.Checked := True;
end;
end;

How to merge two menus in a MDI application

Anybody knows how to merge two menus with the same name in a MDI application.
More exactly, in the MDI main form I have a menu called 'File' which has a sub-menu called 'Load project'.
In the MDI child form, I have a menu called also 'File' which contains a sub-menu called 'Save project'.
How can I force my application to show both 'Load' and 'Save' sub-menus under the 'File' menu?
-
PS: setting the same GoupIndex value will not work.
To merge your menus use this procedure:
procedure MergeMenus(var SrcMenu, DstMenu: TMainMenu);
var
i, i2, i3: Integer;
Menu: TMenuItem;
begin
for i := 0 to SrcMenu.Items.Count - 1 do
begin
for i2 := 0 to DstMenu.Items.Count - 1 do
begin
if (SrcMenu.Items[i].Name = DstMenu.Items[i2].Name) and
(SrcMenu.Items[i].Count > 0) and (DstMenu.Items[i].Count > 0) then
begin
for i3 := 0 to SrcMenu.Items[i].Count - 1 do
begin
Menu := TMenuItem.Create(DstMenu.Owner);
// copy another properties if necessery
Menu.Name := SrcMenu.Items[i].Items[i3].Name;
Menu.Caption := SrcMenu.Items[i].Items[i3].Caption;
Menu.ShortCut := SrcMenu.Items[i].Items[i3].ShortCut;
Menu.OnClick := SrcMenu.Items[i].Items[i3].OnClick;
DstMenu.Items[i].Add(Menu);
end;
end;
end;
end;
end;
Call it in the OnCreate event of your MDIChildForm like this:
procedure TMDIChild.FormCreate(Sender: TObject);
begin
MergeMenus(YourMainForm.MainMenu1, Self.MainMenu1);
end;
It will work if two different MainMenus will have MenuItems with the same name. Also please note that there is a possible memory leek if your DstMenu does not have an owner (but I guess it have and it is your MDICHildForm).
Manual merging is a matter of calling FormMain.MainMenu.Merge(SubForm.MainMenu) and its counterpart FormMain.MainMenu.UnMerge(SubForm.MainMenu).
You shouldn't need it though, because if the FormStyles of your forms are properly set to fsMDIForm and fsMDIChild, then menu merging should be automatic.
Having said that, I am not sure that what you want is possible using the built in menu merging.
According to the GroupIndex help (and a couple of experiments), menu items from a child forms replace items on the main form with the same GroupIndex. Only when the GroupIndex of a menu item on the child form falls between GroupIndex values on the main form, will the menu be inserted. So, the File menu on your child form will always replace the File menu on the main form. Only if you give the File menu's different GroupIndex values will the File menu of the Main form remain, but then you have two File menu's...
So, I think the only solution would be to insert and remove the menu items of the subform manually, or to have them on the main menu all the time and enable/disable them according to the active MDIChild. Possibly even show/hide them.
Personally I would go for the option of having them around all the time and enabling/disabling them according to the active MDIChild, as I don't like menu items that "bounce around" (change position).

Create an exact copy of TPanel on Delphi5

I have a TPanel pnlMain, where several dynamic TPanels are created (and pnlMain is their Parent) according to user actions, data validations, etc. Every panel contains one colored grid full of strings. Apart from panels, there are some open source arrows components and a picture. Whole bunch of stuff.
Now I want user to be able to print this panel (I asked how to do it on this question), but before printing, user must be presented with a new form, containing copy of pnlMain. On this form user has to do some changes, add few components and then print his customized copy of pnlMain. After printing user will close this form and return to original form with original pnlMain. And – as you can guess – original pnlMain must remain intact.
So is there any clever way to copy whole TPanel and it’s contents? I know I can make it manually iterating through pnlMain.Controls list.
Code based as iterating on child controls, but not bad in anyway ;-)
procedure TForm1.btn1Click(Sender: TObject);
function CloneComponent(AAncestor: TComponent): TComponent;
var
XMemoryStream: TMemoryStream;
XTempName: string;
begin
Result:=nil;
if not Assigned(AAncestor) then
exit;
XMemoryStream:=TMemoryStream.Create;
try
XTempName:=AAncestor.Name;
AAncestor.Name:='clone_' + XTempName;
XMemoryStream.WriteComponent(AAncestor);
AAncestor.Name:=XTempName;
XMemoryStream.Position:=0;
Result:=TComponentClass(AAncestor.ClassType).Create(AAncestor.Owner);
if AAncestor is TControl then TControl(Result).Parent:=TControl(AAncestor).Parent;
XMemoryStream.ReadComponent(Result);
finally
XMemoryStream.Free;
end;
end;
var
aPanel: TPanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control (here Panel1) itself first
TComponent(aPanel) := CloneComponent(pnl1);
with aPanel do
begin
Left := 400;
Top := 80;
end;
//now handle the childcontrols
for i:= 0 to pnl1.ControlCount-1 do begin
Ctrl := TComponent(pnl1.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := aPanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
code from Delphi3000 article
Too much code... ObjectBinaryToText and ObjectTextToBinary do the job nicely using streaming.
Delphi 7 have a code example, don't know 2009 (or 2006, never bothered to look) still have it.
See D5 help file for those functions (don't have d5 available here).
I'd do it by using RTTI to copy all the properties. You'd still have to iterate over all the controls, but when you need to set up the property values, RTTI can help automate the process. You can get an example towards the bottom of this article, where you'll find a link to some helper code, including a CopyObject routine.

Resources