Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

API using a modeless form in and add-in

171 views
Skip to first unread message

Mark Reimer

unread,
Jul 29, 2005, 8:06:05 PM7/29/05
to
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.

Tin Man

unread,
Jul 30, 2005, 9:49:12 PM7/30/05
to
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

Jonathan Anderson

unread,
Aug 1, 2005, 5:26:36 PM8/1/05
to
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

Mark Reimer

unread,
Aug 1, 2005, 10:32:57 PM8/1/05
to
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

Jonathan Anderson

unread,
Aug 2, 2005, 10:39:20 AM8/2/05
to
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 <= WM_KEYLAST)
)
{
// This loop is just going through a list of modeless dialog boxes
that
// may be open, so I need to check the messages against all the
dialogs
std::vector<CInsertPartDlg*>::iterator it =
TheApplication->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);
}

Mark Reimer

unread,
Aug 3, 2005, 1:47:56 AM8/3/05
to
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<CInsertPartDlg*>::­iterator it =

TheApplication->m_pPartDlgs.be­gin();

...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

Jonathan Anderson

unread,
Aug 3, 2005, 11:34:33 AM8/3/05
to
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.

Mark Reimer

unread,
Aug 4, 2005, 2:40:12 PM8/4/05
to
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

<MarshalAs(UnmanagedType.FunctionPtr)> _
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 <= WM_KEYLAST)
Then
'Make sure MainDialog object exists before we attempt to find
its handle
If Not MainDialog Is Nothing Then
'Determine whether the specified window handle identifies an
existing window
'And determine whether message is intended for the specified
dialog box
If IsWindow(MainDialog.Handle.ToInt32) _
And IsDialogMessage(MainDialog.Handle.ToInt32, lParam) Then
lParam.Msg = WM_NULL
lParam.WParam = IntPtr.Zero
lParam.WParam = IntPtr.Zero
End If
End If
End If
End If
Return CallNextHookEx(m_hook, code, wParam, lParam)
End Function


So far it seems to be working great, but if somebody spots something
wrong with it, please post something here. Also, I may move the Hook
and Unkook to just before and after my form is Shown and Closed to
avoid any unnecessary processing.

Thanks for all your help Johnathan.

--Mark

Jonathan Anderson

unread,
Aug 4, 2005, 3:31:24 PM8/4/05
to
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

0 new messages