API using a modeless form in and add-in

I have created a VB .NET addin that loads a form and/or adds a feature manager tab control. When I load the form from the menu, it works fine except that if I am typing in a textbox, any key that has a shortcut defined in SolidWorks is captured and executed and is never sent to the form's textbox. Other keys work fine If I load the form as a modal form using .ShowDialog, it works fine, but I need to allow the user to interact with SolidWorks while the form is open and a modal form won't allow that, so I need to show the form modeless using .Show. However, if I do that the unwanted behavior occurs.

The funny thing is that if I put the same control in a Feature Manager Tree tab, it works fine and the shortcut keys don't trigger solidworks keyboard shortcuts and the letters pressed are entered into the textbox.

If anyone knows a way to disable shortcut keys at least while a text box has the focus, please let me know.

Reply to
Mark Reimer
Loading thread data ...

I haven't run into this, but what I do for the situations you described is have a main modal dialog box called with .Show vbmodal, then I add a button(s) that the user must click to make their respective selections in the SW graphica area. Upon click of the button, the API hides the main dialog box and then opens a sub-ordinate dialog box with the .Show vbmodless command. All that this subordinate contains is a label describing what the user is expected to select and OK & CANCEL buttons (for the user to select once they have made their selections). Once the user selects the OK & CANCEL button, the subordinate box is hidden, appripriate commands are performed, and the main modal dialog box called again with .Show vbmodal. Not as elegant as what you're looking for, but it'll work.

I have heard that it is possible to make macros (Add-ins?) that will appear in the Feature Tree just like native SW commands do, but I have not tried anything like that yet.

Ken

Reply to
Tin Man

I've dealt with this in my C++ addins. What happens is that SW gets a chance to process keyboard shortcuts (accelerators) before your addin processes the windows messages for the keypress. To fix this, you need to have your own message handler function inserted into the chain of message hooks. This handler basically checks to see if the message is for your dialog, and if so handles the message and then sets it to null before calling the next hook. I can give you the code I used to fix the problem, but it's all in C++ so it won't do you much good for a VB.NET addin. You might try searching on the internet for a solution in VB as this is not just a SW problem but is a problem with any addin type app that has a modeless dialog in a host application. That's how I found a solution in C++.

Jonathan Anderson

Reply to
Jonathan Anderson

Well, I've been searching all over and have not found anything for VB.NET so far. I'll take the C++ code if you have it even though its been a few years since my last C++ project (Pre .NET) I might be able to translate it into a VB equivalent. You'd think this would be easy to find, but it sure hasn't been for me.

I have found that VB6 add-ins and my old VC++ 6 add-in work fine without any special message handlers, so its just .NET.

--Mark

Reply to
Mark Reimer

I don't know that it's a .NET problem, but I think it's a problem with the newer style COM addins. When I converted my addins from the MFC extension style to the newer style is when I had this problem. Although I'm using VC++ .NET, I'm not using the .NET runtime or anything. Anyway, here's the code:

I have a variable and a function in my addin class as follows: static LRESULT CALLBACK MessageHook(int code, WPARAM wParam, LPARAM lParam); static HHOOK m_hook;

In the ConnectToSW function I have a line: m_hook = SetWindowsHookEx(WH_GETMESSAGE, MessageHook, NULL, GetCurrentThreadId()); That puts my function in the message hook list.

In the DisconnectFromSW function I have: if (m_hook != NULL) UnhookWindowsHookEx(m_hook); to clean up when it's done.

Now, the MessageHook function: LRESULT CALLBACK CPWAddin::MessageHook(int code, WPARAM wParam, LPARAM lParam) { LPMSG lpMsg = (LPMSG) lParam; if (code >= 0 && wParam == PM_REMOVE) { // Don't translate non-input events. if ( (lpMsg->message >= WM_KEYFIRST && lpMsg->message m_pPartDlgs.begin(); while (it != TheApplication->m_pPartDlgs.end()) { // Here's the important part, it checks to make sure the dialog // is actually formed and open, and then checks to see if the // message is for that dialog, and if so tells Windows to ignore // the message. if (IsWindow((*it)->m_hWnd) && (*it)->IsDialogMessage(lpMsg)) { // The value returned from this hookproc is ignored, // and it cannot be used to tell Windows the message has been handled. // To avoid further processing, convert the message to WM_NULL // before returning. lpMsg->message = WM_NULL; lpMsg->lParam = 0; lpMsg->wParam = 0; break; } ++it; } } } // call the next hook in line in Windows return CallNextHookEx(m_hook, code, wParam, lParam); }

Reply to
Jonathan Anderson

Thanks for the C++ code. I've got most of it translated to VB and I'll post it here if I get it working, but I have a couple of questions. I'm trying to find the VB equivalent to the vector iterator and it would help to get a better feel for what's happening here. In the line from the MessageHook function...

std::vector::=ADiterator it =3D TheApplication->m_pPartDlgs.be=ADgin();

.=2E.I think that CInsertPartDlg is the class in your program that creates the modeless dialogs in an array of m_pPartDlgs forms in your application object named TheApplication, is this correct? I only have one modeless dialog, so I would not have to iterate through my dialogs and would just check if the message was from my dialog right?

Then, in the same function you define lpMsg and set it equal to lParam, then if the message is from one of your dialogs, you set lpMsg to Null, but don't do anything with it. I must be missing something or my C++ memory is failing me.

Thanks for your help so far.

--Mark

Reply to
Mark Reimer

The vector and iterator stuff is all C++ standard template library - it's kind of like a container object. If you've just got one window, then you only need to check that and you can get rid of that whole loop. Basically, my application object maintains a list of windows which the messge hook goes through when checking messages. The CInsertPartDlg is my dialog class with the window handle.

The lpMsg and lParam stuff I copied off of a website. With this particular type of message hook (WH_GETMESSAGE - specified in the SetWindowsHookEx call), the wParam is unused and the lParam is a pointer to a MSG structure (docs available on MSDN). The lpMsg = lParam line is assigning a pointer to the MSG structure that is the same as the lParam pointer. So when lpMsg->message is changed, it is changing the value pointed to by lParam, which is then passed on to the CallNextHookEx function.

Hopefully that helps instead of confusing you more. My main language is C++, and I'm not terribly familiar with windows system calls in VB, so I don't really know how it handles the pointers and such.

Reply to
Jonathan Anderson

Well, I finally got it. As promised, here is the VB.NET code. Note that I only have one modeless form, not an MDI application, so I did not create the loop to check each mdiChild, but it should not be too dificult to do.

I have the following in the add-in class...

#Region "Functions and Constants for Modeless Form Message Hook"

Public Const PM_NOREMOVE = &H0 Public Const PM_REMOVE = &H1 Public Const WM_NULL = &H0 Public Const WM_KEYFIRST = &H100 Public Const WM_KEYLAST = &H108 Public Const WH_KEYBOARD = &H2 Public Const WH_GETMESSAGE = &H3

Public Declare Function GetCurrentThreadId Lib "kernel32" () As Integer

Public Declare Function UnhookWindowsHookEx Lib "user32" _ (ByVal hHook As Integer) As Integer

Public Declare Function IsWindow Lib "user32" _ (ByVal hWnd As Integer) As Boolean

Public Declare Function IsDialogMessage Lib "user32" _ (ByVal hWnd As Integer, _ ByRef lpMsg As System.Windows.Forms.Message) As Boolean

Public Declare Function CallNextHookEx Lib "user32" _ (ByVal hHook As Integer, _ ByVal nCode As Integer, _ ByVal wParam As Integer, _ ByRef lParam As System.Windows.Forms.Message) As Integer

Public Delegate Function MessageHookDelegate(ByVal code As Integer, _ ByVal wParam As Integer, _ ByRef lParam As System.Windows.Forms.Message) As Integer

_ Private MessageHookCallback As MessageHookDelegate

Public Declare Function SetWindowsHookEx Lib "user32" _ Alias "SetWindowsHookExA" _ (ByVal idHook As Integer, _ ByVal lpfn As MessageHookDelegate, _ ByVal hmod As Integer, _ ByVal dwThreadId As Integer) As Integer

Public m_hook As Integer #End Region

In the ConnectToSW function I have...

'Create MessageHook Delegate Callback MessageHookCallback = New MessageHookDelegate( _ AddressOf MessageHook) 'Put MessageHook function in the Message Hook list m_hook = SetWindowsHookEx(WH_GETMESSAGE, _ MessageHookCallback, 0, GetCurrentThreadId())

In the DisconnectFromSW function I have...

'Pull our function out of the Message Hook list If (m_hook vbNull) Then UnhookWindowsHookEx(m_hook)

Then our MessageHook Function...

Public Function MessageHook(ByVal code As Integer, _ ByVal wParam As Integer, _ ByRef lParam As System.Windows.Forms.Message) As Integer

If code >= 0 And wParam = PM_REMOVE Then 'Don't translate non-input events If (lParam.Msg >= WM_KEYFIRST) And (lParam.Msg

Reply to
Mark Reimer

Thanks for the code, it looks pretty good from what I can tell. I imaging moving the hook and unhook calls should be fine. You may also consider checking the return value of the SetWindowsHookEx function, but I'm not really sure what you would do on fail - your window would just work like it did without the hook in place.

Jonathan Anderson

Reply to
Jonathan Anderson

PolyTech Forum website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.