Test if document is idle

Test if document is idle

TA.Fehr
Advocate Advocate
958 Views
4 Replies
Message 1 of 5

Test if document is idle

TA.Fehr
Advocate
Advocate

Hopefully this is a simple one.

 

I've got an add-in that runs in the background and periodically initiates a save command.

However, I have noticed that sometimes it runs while a dialog box, or text editor, is open. When the save command runs during this time Inventor crashes. I've checked this post and this one, but none of them work.

 

I can't find any way to test if Inventor has an open dialog box. When the leader textbox is open, the "IsIdle" still returns True. 

 

Is there a way to test for open dialog boxes? Or active modal forms?

0 Likes
Accepted solutions (1)
959 Views
4 Replies
Replies (4)
Message 2 of 5

JamieVJohnson2
Collaborator
Collaborator

I was playing around with similar stuff some time ago, only for a different reason.  Here are some scraps of that playing around; ultimately abandoned.  In these routines I was experimenting with accessing inventor via the Windows handle collection.  Perhaps if there is a dialog box, open, it will have its own handle, you can detect.  Using the Running Object Table won't work because it only lists one object for each 'type' due to its use of the hash table collection.  So in short it only gives you 1 out of the many.

 

    'Public Function ListInventorChildWindows(ByVal hwnd As IntPtr, ByVal lParam As Int32) As Boolean
    '    Dim WindowTitle As String, Ret As Integer
    '    Dim ClassName As String
    '    ClassName = Space(255)
    '    Ret = GetClassName(hwnd, ClassName, 255)
    '    ClassName = ClassName.Substring(0, Ret)
    '    'Debug.Print("[" & hwnd.ToString & "]  " & ClassName)
    '    Dim comObject As Object = Nothing
    '    Dim iaObject As Object = GetInventorAppFromChildComObject(comObject, hwnd)
    '    If iaObject IsNot Nothing Then
    '        Dim inAp As Inventor.Application = TryCast(iaObject, Inventor.Application)
    '        If inAp IsNot Nothing Then invAppCollection.Add(inAp)
    '    End If
    '    If ClassName.Contains("Afx:00007FF789EB0000:8:0000000000010005:0000000000000000:") Then
    '        Ret = GetWindowTextLength(hwnd)
    '        WindowTitle = Space(Ret)
    '        GetWindowText(hwnd, WindowTitle, Ret + 1)
    '        'Debug.Print("Window Text: " & WindowTitle)
    '        FoundWindows.Add(hwnd)
    '    End If
    '    Return True
    'End Function

    'Private Sub ListDebug()
    '    'calling code 
    '    FoundWindows = New List(Of IntPtr)
    '    Dim WindowHandle As IntPtr = GetDesktopWindow()
    '    EnumChildWindows(WindowHandle, AddressOf ListInventorChildWindows, 0)
    '    Dim guid As New Guid("{B6B5DC40-96E3-11d2-B774-0060B0F159EF}")
    '    Dim InventorApplication As Inventor.Application
    '    Dim InvObject As Object = Nothing
    '    For i As Integer = 0 To FoundWindows.Count - 1
    '        AccessibleObjectFromWindow(FoundWindows(i), OBJID_CLIENT, guid, InvObject)
    '        If Not InvObject Is Nothing Then
    '            InventorApplication = InvObject.Application
    '        End If
    '    Next
    'End Sub

Also if you are making Inventor do the work and not the user then there is invapp.SilentMode that will suppress all dialog boxes.

Jamie Johnson : Owner / Sisu Lissom, LLC https://sisulissom.com/
0 Likes
Message 3 of 5

TA.Fehr
Advocate
Advocate

thanks @JamieVJohnson2,

I didn't use your code, but you gave me an idea that I'm testing out for the moment.

 

I set up a class to get the child windows under a specific process:

Public Class Test

        <DllImport("USER32.DLL")>
        Private Shared Function GetShellWindow() As IntPtr
        End Function

        <DllImport("USER32.DLL")>
        Private Shared Function GetWindowText(ByVal hWnd As IntPtr, ByVal lpString As StringBuilder, ByVal nMaxCount As Integer) As Integer
        End Function

        <DllImport("USER32.DLL")>
        Private Shared Function GetWindowTextLength(ByVal hWnd As IntPtr) As Integer
        End Function

        <DllImport("user32.dll", SetLastError:=True)>
        Private Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, <Out()> ByRef lpdwProcessId As UInt32) As UInt32
        End Function

        <DllImport("USER32.DLL")>
        Private Shared Function IsWindowVisible(ByVal hWnd As IntPtr) As Boolean
        End Function

        Private Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Boolean

        <DllImport("USER32.DLL")>
        Private Shared Function EnumWindows(ByVal enumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean
        End Function

        Private hShellWindow As IntPtr = GetShellWindow()
        Private dictWindows As New Dictionary(Of IntPtr, String)
        Private currentProcessID As Integer

        Public Function GetOpenWindowsFromPID(ByVal processID As Integer) As IDictionary(Of IntPtr, String)
            dictWindows.Clear()
            currentProcessID = processID
            EnumWindows(AddressOf enumWindowsInternal, 0)
            Return dictWindows
        End Function

        Private Function enumWindowsInternal(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Boolean
            If (hWnd <> hShellWindow) Then
                Dim windowPid As UInt32
                If Not IsWindowVisible(hWnd) Then
                    Return True
                End If
                Dim length As Integer = GetWindowTextLength(hWnd)
                If (length = 0) Then
                    Return True
                End If
                GetWindowThreadProcessId(hWnd, windowPid)
                If (windowPid <> currentProcessID) Then
                    Return True
                End If
                Dim stringBuilder As New StringBuilder(length)
                GetWindowText(hWnd, stringBuilder, (length + 1))
                dictWindows.Add(hWnd, stringBuilder.ToString)
            End If
            Return True
        End Function
    End Class

Then I before initiating any actions via the API I check for the number of child windows that are open.

Dim processes As Process() = Process.GetProcessesByName("Inventor")
                                For Each process As Process In processes
                                    Dim Test As New Test
                                    Dim windows As IDictionary(Of IntPtr, String) = Test.GetOpenWindowsFromPID(process.Id)
                                    Do Until windows.Count = 1
                                        If windows.Count > 1 Then
                                            Threading.Thread.Sleep(1000)
                                        End If
                                        windows.Clear()
                                        windows = Test.GetOpenWindowsFromPID(process.Id)
                                    Loop
                                Next

This seems to give the values I'm looking for.

The only issue is that if ever the dockable windows are in an undocked state, this will still keep the loop running.

But I'm not sure how to differentiate between a dialog box and a dockable window.

0 Likes
Message 4 of 5

JamieVJohnson2
Collaborator
Collaborator
Accepted solution

Windows should have titles.  Also dockable windows are completely different classes that inherit the standard window, if you could martial it to the inventor dockable window class, or fail too, may give you your answer.  But think about that, the browser is a dockable window, the drawing is a dockable window, the hole command is a dockable window...  If you can get the window name, compare it to your list of open documents to weed it out.  Just don't name a document 'iLogic' Smiley LOL

Jamie Johnson : Owner / Sisu Lissom, LLC https://sisulissom.com/
Message 5 of 5

TA.Fehr
Advocate
Advocate

Once again, thanks for the direction.

 

Because I'm by no means a code guru, I wasn't able to distinguish between the window classes within my add-in. However, because I could get the child windows of the Inventor process, I was able to compare these against the list of loaded dockable windows within the UserInterfaceManager.DockableWindows container.

So whenever the isidle flag = true and there are more than 1 windows showing, I iterate the dockable windows and disregard any that share names with that list and everybody is happy.

 

One odd thing I noticed was that the "isidle" process is not exactly consistent with what windows are open. If I open the hole dialog, isidle = false, if I open the welding dialog, isidle = false. But the leader textbox isidle = true.

 

So we'll run with this and see if I can't break it in the future so I can come back for more help

0 Likes