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
    



Leave a 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>