Graph Tree Reordering in VBA


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
    



18 Responses to “Graph Tree Reordering in VBA”

  1. sercio

    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

  2. Sinan

    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!

    • Tesak

      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

          • Tesak

            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

  3. Mihai Gabriel Szabo

    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?!

    • Tesak

      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

  4. Roberto

    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 ;)

      • 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

    • Tesak

      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.

      • Roberto

        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.

      • Roberto

        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?

        • Tesak

          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

Leave a Reply to Sinan

Click here to cancel reply.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>