One of the most annoying CATIA features, especially when you work with large assemblies is reordering of specification tree in Products. Even as a programmer there is almost nothing you can do about it, because there is no direct way of reordering components in specification tree. Such a function has not been exposed to CATIA API yet. There are a few tools available on the internet and maybe you already use some of them. You may have even thought how the hell they work. So stop wondering, today I am going to show you how they (might) works :).
Most (probably all of them) make use of the Windows Automation API in particular Microsoft UI Automation. What is it? Here is a description from the official Microsoft website:
Microsoft UI Automation is an accessibility framework that enables Windows applications to provide and consume programmatic information about user interfaces (UIs). It provides programmatic access to most UI elements on the desktop.
And this is exactly what we need to reorder our specification tree. To get a programmatic access to CATIA and its Graph tree reordering window and to simulate user interaction.
Before we start
Unfortunately we can not write our automation module directly in CATIA VBA editor. Reason is that during code execution User Interface become blocked and unresponsive and because we need an interactivity, we have to access the CATIA application from outside. The best way is to use your favorite application from the MS Office package. My favorite application is Excel.
Before we can use UI Automation library we have to establish a reference to the UIAutomationCore.dll. To do this go to Tools -> References in your VBA editor and choose UIAutomationClient library.
Coding
We start with a procedure and step by step I'll be adding new lines with some explanation to help you to understand at least some basic background about UI Automation programming. If you do not have a time to follow, just go to the end of this post and there you can find a complete code of the (standard) module.
Option Explicit
Sub CATMain()
Dim CATIA, doc, prod, sel
Set CATIA = GetObject(, "CATIA.Application") ' get CATIA Application
Set doc = CATIA.ActiveDocument
Set prod = doc.Product
Set sel = doc.Selection
' select top product in a CATIA tree
sel.Clear
sel.Add prod
' launch "Graph tree reordering" command
CATIA.StartCommand "Graph tree reordering"
...
End Sub
In this initial part we get our CATIA.Application object and set also other related variables for document, product and selection. In order to successfully pass this part of the code CATIA have to be running and active document has to be an assembly (product). In a next step we have added the top product node into CATIA selection object and with CATIA.StartCommand we launch "Graph tree reordering" command. At this moment a Graph tree reordering window should appear.
...
Dim winAutomation As CUIAutomation
Set winAutomation = New CUIAutomation
Dim desktop As IUIAutomationElement
' get reference to the root element (desktop)
Set desktop = winAutomation.GetRootElement
Dim allWindowsCond As IUIAutomationCondition
' retrieves a predefined condition that selects all elements
Set allWindowsCond = winAutomation.CreateTrueCondition
Dim childs As IUIAutomationElementArray
' find all elements & put them into element array
Set childs = desktop.FindAll(TreeScope_Children, allWindowsCond)
Dim i As Long, currChild As IUIAutomationElement
Dim catiaWindow As IUIAutomationElement
' loop through all element and find CATIA by window name which contains "CATIA V5" string
For i = 0 To childs.Length - 1
Set currChild = childs.GetElement(i)
If InStr(currChild.CurrentName, "CATIA V5") Then
Set catiaWindow = currChild ' set main catia window
End If
'Debug.Print currChild.CurrentName, currChild.CurrentClassName
Next
...
As a next step we initialize UI Automation library. To start a search for our CATIA window we need to obtain a root element. Root element is the desktop. We can obtain this element by using the GetRootElement method. We can search for descendant elements by using methods, such as FindFirst and FindAll. To retrieve UI Automation elements, you must specify a condition. A condition is a set of criteria that defines the elements that you want to retrieve. The simplest condition is the true condition, which is a predefined object that specifies that all elements in the search scope are to be returned. You can obtain an interface to the true condition by using CreateTrueCondition.
In the rest of the code we find all children of desktop root element using allWindowsCond (a True condition to return all children of desktop element) and we put them into array. In a following loop, we perform a search for main CATIA window by window caption. If any of the desktop children contains string "CATIA V5" (it is likely our CATIA window) we store it in catiaWindow variable. If you have opened more windows with this caption only the last one is stored. Therefore, avoid having more windows with such a caption, because instead of CATIA you can easily catch other windows (like notepad with CATIA V5.txt opened)
...
Dim graphWinCond As IUIAutomationCondition
Set graphWinCond = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "Graph tree reordering")
Dim graphWin As IUIAutomationElement
'wait for Graph window to open and get it
Do
Set graphWin = catiaWindow.FindFirst(TreeScope_Children, graphWinCond)
' do not freeze application in case of infinite loop
DoEvents
Loop While graphWin Is Nothing
' get OK button
Dim btnOKCondition As IUIAutomationCondition, btnOk As IUIAutomationElement
Set btnOKCondition = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "OK")
Set btnOk = graphWin.FindFirst(TreeScope_Children, btnOKCondition)
' get Move Down button
Dim btnMoveDownCondition As IUIAutomationCondition, btnMoveDown As IUIAutomationElement
Set btnMoveDownCondition = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "Move Down")
Set btnMoveDown = graphWin.FindFirst(TreeScope_Descendants, btnMoveDownCondition)
...
After we have identified our main CATIA window, we try to find our already opened Graph tree reordering dialog window. This window is one the children of the main CATIA window and we could narrow our search only to catiaWindow element. We create a new property condition and with FindFirst method we perform a search of the element again by window name, which is now a string "Graph tree reordering". We put this method in a loop because at this moment Graph tree reordering dialog could be still in a process of opening and search could fail.
Finally, we identify buttons we need for reordering such as Move Down button and OK button to close reordering dialog.
...
' control pattern definition (button click)
Dim patMoveDown As IUIAutomationInvokePattern, patOK As IUIAutomationInvokePattern
Set patMoveDown = btnMoveDown.GetCurrentPattern(UIA_InvokePatternId)
Set patOK = btnOk.GetCurrentPattern(UIA_InvokePatternId)
For i = 1 To prod.Products.Count - 1
' button click events invoked
patMoveDown.Invoke
Next
patOK.Invoke
...
To simulate a button click we make use of IUIAutomationInvokePattern interface. We create invoke patterns for both buttons and in a following loop, we move first element in the tree to the last position by Invoke method of Move Down button. At the end we close the dialog window by calling Invoke method of the OK button.
And here is the complete source code of GraphTreeReordering module:
Option Explicit
Sub CATMain()
Dim CATIA, doc, prod, sel
Set CATIA = GetObject(, "CATIA.Application") ' get CATIA Application
Set doc = CATIA.ActiveDocument
Set prod = doc.Product
Set sel = doc.Selection
' select top product in a CATIA tree
sel.Clear
sel.Add prod
' launch "Graph tree reordering" command
CATIA.StartCommand "Graph tree reordering"
Dim winAutomation As CUIAutomation
Set winAutomation = New CUIAutomation
Dim desktop As IUIAutomationElement
' get reference to the root element (desktop)
Set desktop = winAutomation.GetRootElement
Dim allWindowsCond As IUIAutomationCondition
' retrieves a predefined condition that selects all elements
Set allWindowsCond = winAutomation.CreateTrueCondition
Dim childs As IUIAutomationElementArray
' find all elements & put them into element array
Set childs = desktop.FindAll(TreeScope_Children, allWindowsCond)
Dim i As Long, currChild As IUIAutomationElement
Dim catiaWindow As IUIAutomationElement
' loop through all element and find CATIA by window name which contains "CATIA V5" string
For i = 0 To childs.Length - 1
Set currChild = childs.GetElement(i)
If InStr(currChild.CurrentName, "CATIA V5") Then
Set catiaWindow = currChild ' set main catia window
End If
'Debug.Print currChild.CurrentName, currChild.CurrentClassName
Next
Dim graphWinCond As IUIAutomationCondition
Set graphWinCond = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "Graph tree reordering")
Dim graphWin As IUIAutomationElement
'wait for Graph window to open and get it
Do
Set graphWin = catiaWindow.FindFirst(TreeScope_Children, graphWinCond)
' do not freeze application in case of infinite loop
DoEvents
Loop While graphWin Is Nothing
' get OK button
Dim btnOKCondition As IUIAutomationCondition, btnOk As IUIAutomationElement
Set btnOKCondition = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "OK")
Set btnOk = graphWin.FindFirst(TreeScope_Children, btnOKCondition)
' get Move Down button
Dim btnMoveDownCondition As IUIAutomationCondition, btnMoveDown As IUIAutomationElement
Set btnMoveDownCondition = winAutomation.CreatePropertyCondition(UIA_NamePropertyId, "Move Down")
Set btnMoveDown = graphWin.FindFirst(TreeScope_Descendants, btnMoveDownCondition)
' control pattern definition (button click)
Dim patMoveDown As IUIAutomationInvokePattern, patOK As IUIAutomationInvokePattern
Set patMoveDown = btnMoveDown.GetCurrentPattern(UIA_InvokePatternId)
Set patOK = btnOk.GetCurrentPattern(UIA_InvokePatternId)
For i = 1 To prod.Products.Count - 1
' button click events invoked
patMoveDown.Invoke
Next
patOK.Invoke
End Sub
Hello Tesak, Hello Roberto
tesak ;
I ran the run code, thank you. Very good,
I only need the first column level , second column Part number .(I donr need stair stucturre).
I try edit your code.
Roberto your code is interesting.
Roberto can you send me the code written as bass?
Which platform are you on? I am using Vba on Catia V5R24.
I would be glad if you can send it.
thank you
Hello Tesak,
It is really helpful, thank you very much!
But I always get a an error, says:
”’
Run-time error ’91’:
Object variable or With block variable not set.
”’
Do you have an idea, where could be the problem?
Thanks again!
Hi Sinan,
I am sorry to hear it, what kind of CATIA release do you use? What is a language version of your CATIA?
Regards
Hi Tesak,
I am using CATIAV5 R26 and VBA 7.1.1033.
Thanks
Sinan
More important maybe it is the language version of your CATIA. Application supports only English mutation.
I am using CATIA in english, but Windows10 is not in english. You mean could it be the Problem?
Well, I tested it only in version R21. Windows version should not be an issue, the most probable cause is, that the names of the Graph Tree Reordering window and its components (OK button, Apply button, etc …) are different than in lower CATIA releases. These names are used to identify Graph Tree Reordering dialog & it’s components to simulate the user interaction. However, I do not have a chance to test it in higher CATIA versions.
There is something I didn’t fully get. Do we talk about SmartSort application or do you refer to a script above?
Regards,
Tesak
Nice script. I need to order the InstanceName of the part and have like tones of subassemblies. How about letting the user pick the Assembly/SubAssembly first then, scan for parts and other subassemblies and then sort them?!
Hi Mihai,
thanks for your feedback. Well, it sounds like a good idea. I will try to implement it when I have time.
Regards,
Tesak
Thank you very much for the help.
I’ve already made it work, I use a “ListView” with the order as I want, but instead of using the “Move Down” button, I use the “Free Move” button, which is faster in large sets.
If you want, I send you the code of how I did it by e-mail, or I put it here.
Without your help I would not have gotten it ;)
Hi Roberto,
nice to hear it, I am happy for you :)
Please, feel free to post it here.
Hello.
This is the new code I use with listview.
The speed of this is that I put the items from above to below, and if anyone is in his position he jumps. It’s very fast. I’ve been testing it for a while and correcting some bugs, and now it works perfect for me.
Private Sub BtnEjecutar_Click(sender As Object, e As EventArgs) Handles btnEjecutar.Click
Dim doc, prod, sel
CATIA = GetObject(, "CATIA.Application")
doc = CATIA.ActiveDocument
prod = doc.Product
sel = doc.Selection
' select top product in a CATIA tree
sel.Clear
sel.Add(prod)
CATIA.StartCommand("Graph tree reordering")
' Variables
Dim desktop As AutomationElement
Dim allWindowsCond As Condition
Dim childs As AutomationElementCollection
Dim childs2 As AutomationElementCollection
Dim graphWinCond As Condition
Dim graphWin As AutomationElement
Dim i As Long
Dim b As Long
Dim currChild2 As AutomationElement
Dim catiaWindow As AutomationElement
Dim Nombre As String = ""
Dim Contador As Integer = 0
Dim ContadorLista As Integer = 0
Dim ContadorCatia As Integer = 0
Dim ContadorDiferencia As Integer = 0
desktop = AutomationElement.RootElement
allWindowsCond = Condition.TrueCondition
childs = desktop.FindAll(TreeScope.Children, allWindowsCond)
Try
For i = 0 To childs.Count - 1
If InStr(childs.Item(i).Current.Name, "CATIA V5") Then
catiaWindow = childs.Item(i) ' set main catia window
End If
Next
Catch ex As Exception
MsgBox("Se produjo un error al obtener la aplicacion de Catia. Error: " & vbCrLf & ex.Message)
End Try
Try
graphWinCond = New PropertyCondition(AutomationElement.NameProperty, "Graph tree reordering")
Do
graphWin = catiaWindow.FindFirst(TreeScope.Children, graphWinCond)
System.Windows.Forms.Application.DoEvents()
Loop While graphWin Is Nothing
Catch ex As Exception
MsgBox("Se produjo un error al obtener la ventana ''Graph tree reordering''. Error: " & vbCrLf & ex.Message)
End Try
Try
' Obtener todos los botones del cuadro
Dim btnOKCondition As Condition
Dim btnOk As AutomationElement
btnOKCondition = New PropertyCondition(AutomationElement.NameProperty, "OK")
btnOk = graphWin.FindFirst(TreeScope.Children, btnOKCondition)
Dim patOK As InvokePattern
patOK = btnOk.GetCurrentPattern(InvokePattern.Pattern)
Dim btnMoveFreeMoveCondition As Condition
Dim btnMoveFreeMove As AutomationElement
btnMoveFreeMoveCondition = New PropertyCondition(AutomationElement.NameProperty, "Free Move")
btnMoveFreeMove = graphWin.FindFirst(TreeScope.Descendants, btnMoveFreeMoveCondition)
Dim patMoveFreeMove As InvokePattern
patMoveFreeMove = btnMoveFreeMove.GetCurrentPattern(InvokePattern.Pattern)
childs = graphWin.FindAll(TreeScope.Children, allWindowsCond)
For i = 0 To childs.Count - 1
currChild = childs.Item(i)
If currChild.Current.Name = "FrameWindow" Then
childs2 = currChild.FindAll(TreeScope.Children, allWindowsCond)
If childs2.Item(1).Current.Name = "FrameList" Then
childs2 = childs2.Item(1).FindAll(TreeScope.Children, allWindowsCond)
currChild2 = childs2.Item(1)
childs2 = currChild2.FindAll(TreeScope.Children, allWindowsCond)
ContadorDiferencia = childs2.Count - lvOrdenNuevo.Items.Count
ContadorLista = lvOrdenNuevo.Items.Count - 1
ContadorCatia = ContadorLista + ContadorDiferencia
For Contador = 0 To ContadorLista
Nombre = lvOrdenNuevo.Items.Item(Contador).Text
For b = 0 To ContadorCatia
If childs2.Item(b).Current.Name = Nombre Then
If b = Contador + ContadorDiferencia Then Exit For
childs2.Item(b).SetFocus()
SendKeys.Send(Chr(32))
patMoveFreeMove.Invoke()
childs2.Item(Contador + ContadorDiferencia).SetFocus()
SendKeys.Send(Chr(32))
childs2 = currChild2.FindAll(TreeScope.Children, allWindowsCond)
Exit For
End If
Next
Next
patOK.Invoke()
End If
End If
Next
Catch ex As Exception
If Nombre "" Then
MsgBox("Se produjo un error en la pieza: " & vbCrLf & Nombre & vbCrLf & "Error: " & ex.Message)
Else
MsgBox("Se produjo el siguiente error: " & vbCrLf & ex.Message)
End If
End Try
End Sub
Hi Roberto,
thanks for your post. I am sure someone will consider it useful.
Regards,
Tesak
Hello.
This work well on vba in Excel, but in vs2017, vb.net, this don’t work.
How can i port this to vs??????
Hello Roberto,
at first, you have to add references to libraries UIAutomationClient and UIAutomationTypes. Then there are some syntactical differences between VBA and VB.NET so basically you have to remove all Set statements and put parenthesis around method arguments. Also, UIAutomation types have slightly different names, eg. IUIAutomationElement in VBA is AutomationElement in VB.NET, or IUIAutomationCondition in VBA is Condition in VB.NET. In general, it is not difficult to port it to VB.NET, but you have to be familiar with VB.NET environment.
Thank you.
I will try to do it in VB to see if I get it.
I have a program in VB with several macros, and I wanted to include it without having to use excel.
Hello,
I have a problem with this:
Dim CATIA, doc, prod, sel
CATIA = GetObject(, “CATIA.Application”) ‘ get CATIA Application
doc = CATIA.ActiveDocument
prod = doc.Product
sel = doc.Selection
‘ select top product in a CATIA tree
sel.Clear
sel.Add(prod)
‘ launch “Graph tree reordering” command
CATIA.StartCommand(“Graph tree reordering”)
‘ Variables
Dim desktop As AutomationElement
Dim allWindowsCond As Condition
Dim childs As AutomationElementCollection
Dim childs2 As AutomationElementCollection
Dim graphWinCond As Condition
Dim graphWin As AutomationElement
Dim i As Long
Dim b As Long
Dim currChild2 As AutomationElement
Dim catiaWindow As AutomationElement
‘Dim winAutomation As CUIAutomation
‘winAutomation = New CUIAutomation
desktop = AutomationElement.RootElement
allWindowsCond = Condition.TrueCondition
childs = desktop.FindAll(TreeScope.Children, allWindowsCond)
For i = 0 To childs.Count – 1
If InStr(childs.Item(i).Current.Name, “CATIA V5”) Then
catiaWindow = childs.Item(i) ‘ set main catia window
End If
Next
graphWinCond = New PropertyCondition(UIA_NamePropertyId, “Graph tree reordering”)
In the last line, it gives me an error:
System.ArgumentNullException: ‘The value can not be null.
Parameter name: property ‘
How can i solve this?
Hi Roberto,
there are different constants in VB.NET, you have to change this line slightly:
graphWinCond = New PropertyCondition(AutomationElement.NameProperty, "Graph tree reordering")
But you are on the right track :) …
Regards,
Tesak