I'm trying to iterate between all components inside a TTabsheet. Problem is, inside this tab there are only a memo and a edit, but my code iterate between components in all form. What am I'm missing ?
var i : integer;
begin
with PageControl1.ActivePage do
for i := 0 to componentcount-1 do
begin
// componentcount should be 2, but actually is 95
components[i].doSomething;
end;
end;
I had something like this, where a button click caused code to traverse over all the controls that were on a tabsheet that was on a page control, using the components array. Later on I changed it to the following, that uses the controls array of the given tabsheet.
procedure TShowDocsByRank.CleanBtnClick(Sender: TObject);
var
i: integer;
begin
for i:= 0 to tabsheet1.controlcount - 1 do
if tabsheet1.controls[i] is TLabeledEdit
then TLabeledEdit (tabsheet1.controls[i]).text:= ''
else if tabsheet1.controls[i] is TComboBox
then TComboBox (tabsheet1.controls[i]).text:= '-';
end;
Perhaps 'with' might have something to do about it. You cant really tell what the return value for 'componentcount' will be (might even return number of components for the form itself?).
Related
I have been trying to get a list of menu sub-items from a standard Windows application using the UIAutomationCore library imported as a TLB into Delphi - i.e.
File -> New | Exit
Help -> About
I can get the application menu, and then the top-level items into a list (i.e. in the example above, 'File' and 'Help', but I cannot get a list of ANY controls that are under these menuitems. My code is as below - the FElement represents the actual menuitem I am checking.
The length of the collection returned by FindAll is always 0. I have tried expanding the menuitem prior to this code, but it seems to have no effect.
UIAuto.CreateTrueCondition(condition);
FItems := TObjectList<TAutomationMenuItem>.create;
self.Expand;
sleep(3000);
// Find the elements
self.FElement.FindAll(TreeScope_Descendants, condition, collection);
collection.Get_Length(length);
for count := 0 to length -1 do
begin
collection.GetElement(count, itemElement);
itemElement.Get_CurrentControlType(retVal);
if (retVal = UIA_MenuItemControlTypeId) then
begin
item := TAutomationMenuItem.Create(itemElement);
FItems.Add(item);
end;
end;
I can see examples of this in C#, and they are not really doing anything different from the code above (as far as I can see)
Thanks in advance
Update : It looks very similar to this question
Update2 : In this example it is trying to do this for another Delphi application. However, if I try the same thing on notepad (for example), the same issue occurs.
Update3 : Using Inspect (and then using UI Automation), I have the following structure ...
Name = Exit
Ancestors = File (menu) Form1 (pane)
I have also tried this after expanding the menu (file), and the same thing is happening (or not happening).
I think you have the following two issues:
The menus will not list the sub menu items unless the menu is expanded
If you're trying to automate your own application, you have to do it in a thread.
The following works for me:
// Careful: the code might not be 100% threadsafe, but should work for the purpose of demonstration
const
UIA_MenuItemControlTypeId = 50011;
UIA_ControlTypePropertyId = 30003;
UIA_NamePropertyId = 30005;
UIA_ExpandCollapsePatternId = 10005;
procedure TForm1.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(procedure begin CoInitializeEx(nil, 2); FindItems(true); CoUninitialize; end).Start;
end;
procedure TForm1.FindItems(Recurse: Boolean);
var
UIAuto: TCUIAutomation;
condition: IUIAutomationCondition;
collection: IUIAutomationElementArray;
Length: Integer;
Count: Integer;
itemElement: IUIAutomationElement;
retVal: Integer;
val: WideString;
ExpandCollapsePattern: IUIAutomationExpandCollapsePattern;
FElement: IUIAutomationElement;
begin
UIAuto := TCUIAutomation.Create(nil);
UIAuto.CreateTrueCondition(condition);
// Find the elements
UIAuto.ElementFromHandle(Pointer(Handle), FElement);
FElement.FindAll(TreeScope_Descendants, condition, collection);
collection.Get_Length(length);
for Count := 0 to length - 1 do
begin
collection.GetElement(Count, itemElement);
itemElement.Get_CurrentControlType(retVal);
if (retVal = UIA_MenuItemControlTypeId) then
begin
ItemElement.Get_CurrentName(val);
TThread.Synchronize(nil,
procedure
begin
memo1.lines.Add(val);
end);
itemElement.GetCurrentPattern(UIA_ExpandCollapsePatternId, IInterface(ExpandCollapsePattern));
if Assigned(ExpandCollapsePattern) then
begin
ExpandCollapsePattern.Expand;
if Recurse = True then
FindItems(False);
end;
end;
end;
UIAuto.Free;
end;
Problem
I like to use the TActionManager component as a better way of managing events and building the menu interfaces either using TMainMenu or TActionMainMenuBar, for this though we would be using the TActionMainMenuBar because of the ActionBars property in the TActionManager.
One annoying issue I have is with the image indexes sometimes getting lost and more often then not it means having to go through each ActionBar item and manually entering the image index again, a pain if you are adding/deleting actions and images etc.
To solve this I came up with the idea of iterating through each TActionClientItem and assigning the image index which is determined by its assigned TAction.
Here is what I came up with so far:
procedure TMyComponent.ReassignActionImages;
var
I, J, K: Integer;
Manager: TActionManager;
BarItem: TActionBarItem;
Client: TActionClientItem;
Action: TAction;
begin
for I := 0 to Owner.ComponentCount -1 do
begin
if (Owner.Components[I].ClassType = TActionManager) then
begin
Manager := TActionManager(Owner.Components[I]);
for J := 0 to Manager.ActionBars.Count -1 do
begin
BarItem := Manager.ActionBars.ActionBars[J];
if BarItem.ActionBar <> nil then
begin
for K := 0 to BarItem.Items.Count -1 do
begin
Client := BarItem.Items[K];
if Client.Action <> nil then
begin
Action := TAction(Client.Action);
if Action <> nil then
begin
Client.ImageIndex := Action.ImageIndex;
//ShowMessage('Has Action: ' + Manager.Name + ' - ' + Action.Name + ' - ' + Client.Caption);
end;
end;
end;
end;
end;
end;
end;
end;
While this does seem to work, it does not appear to process child items. I think I need some kind of recursive procedure but I am not sure how to implement that. From what I understand from recursion it basically means running the same procedure within a procedure?
Steps to reproduce lost TActionClientItem image indexes using one scenario:
Create a New Project.
Add TActionManager to Form.
Add TAction to the TActionManager, leave ImageIndex as -1.
Add TActionMainMenuBar to Form.
Select the TActionManager in the Form Designer then edit the ActionBars property.
Add a new TActionBarItem.
In the Object Inspector assign the TActionMainMenuBar as the ActionBar.
Still in the Object Inspector click the Items (TActionClients) property.
Add a new TActionClientItem, give it a caption such as &File.
Click Items (TActionClients) property again.
Add a new TActionClientItem.
In the Object Inspector assign the TAction (eg, Action1) to the Action property.
Drop a TImageList on the Form, and add a bitmap.
Select the TActionManager in the Form Designer again and assign the TImageList to the Images property.
Double Click the TActionManager and for Action1 change the ImageIndex to 0
Run the application, notice there is no image showing.
The &File menu item will also be disabled so just edit the .OnExecute event for the TAction if you wish.
Run again, still no image showing.
Solution for the above scenario:
To make the image visible, edit the ActionBars and find the TActionClientItem manually setting the Image index to 0.
Run the application and the image will show.
So the above is one scenario I can reproduce that shows how the imageindex of a TAction is not updated to reflect the changes in a TActionClientItem.
There are other times that this has happened (usually when adding, deleting or editing TActions and images) where a manual update of the TActionClientItem is required.
This is why I decided to try and make a simple component which would ensure the images are syncronized at all times, and that no matter if the TActionClientItem imageindex is lost or becomes redundant, if it has a TAction assigned that has a imageindex I want that imageindex linked back with the TActionClientItem.
I am actually a bit pleased because for once I am able to solve a not so obvious simple problem!
The solution is to call IterateClients on the TActionManager.ActionBars and by using a callback procedure we can then access each TActionClientItem.
Useful documentation links:
http://docwiki.embarcadero.com/VCL/XE/en/ActnMan.TActionBars
http://docwiki.embarcadero.com/VCL/XE/en/ActnMan.TActionBars_Inherited_Members
http://docwiki.embarcadero.com/VCL/XE/en/ActnMan.TActionClientsCollection.IterateClients
In code this what I managed to do:
TMyComponent = class(TComponent)
protected
procedure Loaded; override;
procedure ActionCallBack(Client: TActionClient);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
and
procedure TMyComponent.Loaded;
procedure SyncImages;
var
I: Integer;
Manager: TActionManager;
begin
for I := 0 to Owner.ComponentCount -1 do
begin
if (Owner.Components[I].ClassType = TActionManager) then
begin
Manager := TActionManager(Owner.Components[I]);
Manager.ActionBars.IterateClients(Manager.ActionBars, ActionCallBack);
end;
end;
end;
begin
inherited Loaded;
SyncImages;
end;
{ ---------------------------------------------------------------------------- }
procedure TMyComponent.ActionCallBack(Client: TActionClient);
begin
if (Client is TActionClientItem) then
begin
with TActionClientItem(Client) do
begin
if Action <> nil then
begin
Caption := TAction(Action).Caption; // if you want to sync caption
ImageIndex := TAction(Action).ImageIndex;
end;
end;
end;
end;
I clone a panel and its contents(A image and a checkbox) 20 times.
Sample of the panel being cloned:
This is the procedure used to clone a whole panel:
procedure TForm1.ClonePanel(pObjectName: Tpanel);
var apanel : Tpanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control itself first
TComponent(apanel) := CloneComponent(pObjectName);
with apanel do
begin
Left := 24;
Top :=64;
end;
//now handle the childcontrols
for i:= 0 to pObjectName.ControlCount-1 do
begin
Ctrl := TComponent(pObjectName.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := apanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
The following is the the code that physically does the cloning(called above):
function TForm1.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 + inttostr(panels);
inc(panels);
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;
So now I want to use the cloned objects but how do I call them in my code?
For example how can I call the checked function of one of the cloned check boxes?
Thanks for your help :)
Others are right and it is better to use frame but if we want just use your code we must fix it first. there is a problem in your code and that is the Inc(panles); position. you must put this line after loop of for i:= 0 to pObjectName.ControlCount-1 do in the ClonePanle procedure, not in the CloneComponent function.
If you fix that, then you can use FindComponent function to access the components that you want as Marko Paunovic said.
For example the name of the component that you put on the first Panel that you defined as the first instance which other cloned panels are cloned from that is TestCheckBox. If you cloned 20 times the Panel that we talked about; you can access the TCheckBox of the 16th Cloned obejct like this and changing it's caption to whatever you want:
(I suppose that the panels variable was 0, when the program started.)
TCheckBox(FindComponent('clone_TestCheckBox15')).Caption:='aaaaa';
I have ComboBox4,ComboBox1 and Button5
When I click Button5 program should remove component selected in combobox4 from the ComboBox4 and ComboBox1 components' list. But I get list out of bounds error with the following code...
procedure TForm1.Button5Click(Sender: TObject);
var
cat : Integer;
trinti: TComponent;
catT : String;
begin
catT := ComboBox4.Text;
cat := ComboBox4.Items.IndexOf(catT);
trinti := ComboBox4.Components[cat];
ComboBox1.Items.BeginUpdate;
ComboBox4.Items.BeginUpdate;
ComboBox4.RemoveComponent(trinti);
ComboBox1.RemoveComponent(trinti);
ComboBox1.Items.EndUpdate;
ComboBox4.Items.EndUpdate;
removeCat(catT);
end;
Please help :(
The Components property, and the RemoveComponent method are the wrong things to use here. These are for ownership and lifetime management. Typically the only thing on your form that owns anything is the form itself. So using Components on the combo box will always results in an error.
Instead you need to use the Items property of the combo box, and its Delete method. It might look like this:
var
Index: Integer;
....
catT := ComboBox4.Text;
Index := ComboBox4.Items.IndexOf(catT);
if Index <> -1 then
ComboBox4.Items.Delete(Index);
I've seen this question asked as part of another question before, so know it can't just be me...if I open a new FireMonkey2 HD app, add a TButton and TStringGrid, then add this to the button on click event, NOTHING happens in the grid when I click the button!
procedure TForm33.Button1Click(Sender: TObject);
var
i: Integer;
begin
for i:= 0 to 6 do
begin
StringGrid1.Cells[0,i] := 'Row:' + IntToStr(i);
end;
stringgrid1.UpdateColumns;
stringgrid1.SetFocus;
end;
Any ideas ?
PS I've also tried using TStringGrid.OnGetValue and it still won't show anything in the StringGrid.
Having looked further into the TStringGrid source code, C is nil so the Cells are never being set.
procedure TStringGrid.SetValue(Col, Row: Integer; const Value: TValue);
var
C: TColumn;
begin
C := Columns[Col];
if Assigned(C) then
begin
C.UpdateRowCount(RowCount);
C.SetCells(Row, Value);
end;
I appears there are no Columns in the "virgin" StringGrid, so how do you add them ? There is a r+w RowCount property but ColCount is readonly...
You must add at least a column to the TStringGrid in order to add data to the cells, you can do this in runtime
StringGrid1.AddObject(TStringColumn.Create(Self));
or in design time using the Items Designer