In the post "Adding a form to your addin." we created a simple form/window. But there are problems with those windows. They are never a part of Inventor and therefore always in the way of your Inventor screen or the other way around. (They are in front or at the back of your Inventor Window.) A dockable window can help in those situations.
You can also create dockable windows for aesthetic reasons. It's how Autodesk is also making its commands look and feel.
In the Inventor API help, you will find an example of creating a dockable window. But that example is only valid for Winforms. (Although even that example is not complete.) In this tutorial, I have chosen to create a WPF form. The dockable windows you can create with the Inventor API don't work out of the box with WPF forms. In this post of the tutorial, I will turn the form we created in the post "Adding a form to your addin." into a dockable window/form. That looks something like this:
The dockable window will be created when we start Inventor. therefore we start by changing the "StandardAddInServer". We need to add the following method:
Private Sub CreateDockableWindow(inventor As Inventor.Application)
Dim mySearchForm As New MySearchForm(inventor)
Dim userInterfaceMgr As UserInterfaceManager = inventor.UserInterfaceManager
Dim dockableWindow = userInterfaceMgr.DockableWindows.Add(
Guid.NewGuid().ToString(),
"MyDockableWindow InternalName",
"My dockable window")
DockableWindowChildAdapter.AddWPFWindow(dockableWindow, mySearchForm)
End Sub
In fact, nothing interesting is done here. The WPF form and the dockable window are created here. But binding the 2 together is done in another class. But this method "CreateDockableWindow" needs to be called. We can do that in the method "Activate". That will look like this.
Public Sub Activate(AddInSiteObject As ApplicationAddInSite, FirstTime As Boolean) Implements ApplicationAddInServer.Activate
Try
' initialize the rule class
_myButton = New MyButton(AddInSiteObject.Application)
' initialize the dockable window
CreateDockableWindow(AddInSiteObject.Application)
Catch ex As Exception
' Show a message if any thing goes wrong.
MessageBox.Show($"{ex.Message}{System.Environment.NewLine}{System.Environment.NewLine} {ex.StackTrace}")
If (ex.InnerException IsNot Nothing) Then
MessageBox.Show($"Inner exception message: {ex.InnerException.Message}")
End If
End Try
End Sub
(The complete code of the "StandardAddInServer" class and the entire solution can be found on GitHub)
Now the more interesting class "DockableWindowChildAdapter". The problem with WPF forms combined with dockable windows is the following:
"Textboxes in WFP application do not receive most keyboard input. The only keys which seem to work are the control keys and the spacebar. You can also highlight, copy and paste to text boxes, but you can't type characters." and
"This is a side-effect of modifying the window structure of Dockable Windows." (source) What that exactly means is above my pay grade but smarter people than me have found a solution. I just joined several ideas into 1 class. The code in this class could also be added to the "StandardAddInServer" directly but then we could not reuse the code that easily. the class looks like this:
Imports Inventor
Public Module DockableWindowChildAdapter
'''
''' https://forums.autodesk.com/t5/inventor-forum/dockable-window-with-wpf-controls-don-t-receive-keyboard-input/td-p/9115997
'''
Private Const DLGC_WANTARROWS As UInt32 = &H1
Private Const DLGC_WANTTAB As UInt32 = &H2
Private Const DLGC_WANTALLKEYS As UInt32 = &H4
Private Const DLGC_HASSETSEL As UInt32 = &H8
Private Const DLGC_WANTCHARS As UInt32 = &H80
Private Const WM_GETDLGCODE As UInt32 = &H87
Private _dockableWindow As DockableWindow
Public Sub AddWPFWindow(dockableWindow As DockableWindow, window As Window)
_dockableWindow = dockableWindow
window.WindowStyle = WindowStyle.None
window.WindowState = WindowState.Maximized
window.ResizeMode = ResizeMode.NoResize
window.Show()
Dim win As Window = Window.GetWindow(window)
Dim wih = New Interop.WindowInteropHelper(win)
Dim hWnd As IntPtr = wih.EnsureHandle()
_dockableWindow.AddChild(hWnd)
Interop.HwndSource.FromHwnd(hWnd).AddHook(AddressOf WndProc)
End Sub
Public Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr
If (msg = WM_GETDLGCODE) Then
handled = True
Return New IntPtr(DLGC_WANTCHARS Or DLGC_WANTARROWS Or DLGC_HASSETSEL Or DLGC_WANTTAB Or DLGC_WANTALLKEYS)
End If
Return IntPtr.Zero
End Function
End Module
You should be able to run Inventor now and find the docked window.