API using a modeless form in and add-in

Discussion in 'SolidWorks' started by Mark Reimer, Jul 30, 2005.

  1. Mark Reimer

    Mark Reimer Guest

    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.
     
    Mark Reimer, Jul 30, 2005
    #1
  2. Mark Reimer

    Tin Man Guest

    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
     
    Tin Man, Jul 31, 2005
    #2
  3. 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
     
    Jonathan Anderson, Aug 1, 2005
    #3
  4. Mark Reimer

    Mark Reimer Guest

    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
     
    Mark Reimer, Aug 2, 2005
    #4
  5. 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);
    }
     
    Jonathan Anderson, Aug 2, 2005
    #5
  6. Mark Reimer

    Mark Reimer Guest

    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
     
    Mark Reimer, Aug 3, 2005
    #6
  7. 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.
     
    Jonathan Anderson, Aug 3, 2005
    #7
  8. Mark Reimer

    Mark Reimer Guest

    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
     
    Mark Reimer, Aug 4, 2005
    #8
  9. 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
     
    Jonathan Anderson, Aug 4, 2005
    #9
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.