none
Determining which key caused a bound VBA command to be called RRS feed

  • Question

  • In Word, I have one VBA command that is bound to several different keys. When the command is called, is there any way for it to know which key was pressed?

    What I'm doing is described in more detail at http://social.msdn.microsoft.com/Forums/en/vsto/thread/9fff83f8-0a97-46dc-824a-9ec5abb1d2d6.

    In that example, I'm hooking up a VBA function named CallVSTOMethod to the key Alt+0 via the code:

    Application.KeyBindings.Add(Word.WdKeyCategory.wdKeyCategoryCommand, "CallVSTOMethod",
    Application.BuildKeyCode(Word.WdKey.wdKeyAlt, Word.WdKey.wdKey0));

    If I want this function bound to many keys, I can call this function many times. But is there a way that, from within CallVSTOMethod, I can find out which key caused it to be called? (Unfortunately, it's not very feasible for me to have a different function for each key.)

    There's an optional 5th argument CommandParameter to Add(), but the documentation is skimpy. Can I call Add with a different CommandParameter for each key, and somehow get that from within CallVSTOMethod and use it to determine which key was pressed?

    Tuesday, May 3, 2011 8:19 PM

Answers

  • <<Do you know of any technical reason why they couldn't just do that?>>

    I don't know of any, except that it could possibly interfere with the internal key assignment concept, which in Word is very flexible.

    At a certain level it's often a balance between the philosophies: "Word belongs to the user" and "Word as an application tool" (developer product). There's a tendency for a developer to "usurp" the UI from the user for his product, so that the "out-of-the-box" behavior is lost. The user then complains about Microsoft's product being "buggy" because he doesn't know about or understand what a third-party has done. That is one reason behind the Ribbon design being what it is.

    Anyway, it seems to me that Lars Eric's suggestion would probably work (thanks Lars Eric!) :-)

    FWIW we've been asking for a while now (through the VSTO product) that some way be introduced for the (.NET) developer to link keyboard assignments to non-VBA code. So far, no joy, but it takes a couple of product cycles for things to be implemented into Office...


    Cindy Meister, VSTO/Word MVP
    Thursday, May 5, 2011 6:47 AM
    Moderator

All replies

  • In theory it looks like if you setup the "CallVSTOMethod" to include a reference. Your VBA sub would be ...

    Sub CallVSTOMethod(parm as Integer)

    Each key binding would set this command parm differently. So Alt + 0 might set the parm to 0, Alt +1 might set it to 1 and then your subroutine checks the value.

    If that doesn't work, you might consider setting a DocVariable that the add-in checks and then clears, or a Registry key, or a System Profile String.

    Hope this helps

     

     


    Regards, Rich
    Tuesday, May 3, 2011 9:49 PM
  • I've been experimenting with the parameter, but having trouble getting it to work. One would guess it ought to work that way. The CommandParameter appears to be stored as a string rather than an integer, though.

    I'm not clear about your other suggestions.

    Tuesday, May 3, 2011 10:20 PM
  • Here are two routines that get and set document variables. I use them a lot to pass data between applications and sessions of Word.  The format is from VB Net but obviously they can be converted to C#, VBA, etc.

     

        Function getDocVariable(ByRef vName As String) As String

            Dim aDoc As Document = wApp.ActiveDocument

            getDocVariable = Nothing

            Dim myVar As Microsoft.Office.Interop.Word.Variable

            'get variable and return variable value

            For Each myVar In aDoc.Variables

                If myVar.Name = vName Then getDocVariable = myVar.Value

            Next myVar

            Return getDocVariable

        End Function

     

        Function setDocVariable(ByRef vName As String, ByRef vValue As String) As String

            Dim aDoc As Document = wdoc.ActiveDocument

            Dim myVar As Microsoft.Office.Interop.Word.Variable

            Dim num As Integer = 0

            'get variable and set variable value, if variable doesn't exist create one

            For Each myVar In aDoc.Variables

                If myVar.Name = vName Then

                    myVar.Value = vValue

                    num = myVar.Index

                End If

            Next myVar

            If num = 0 Then

                aDoc.Variables.Add(Name:=vName, Value:=vValue.ToString)

            Else

                aDoc.Variables(num).Value = vValue

            End If

    End Function


    Regards, Rich
    Tuesday, May 3, 2011 10:49 PM
  • I wasn't previously aware of Document.Variables, thanks for the pointer.

    But I don't see how this helps me figure out which key was pressed. Setting a Variable when adding the key bindings won't do any good. I'd need to set it after the key is pressed but before the VBA sub runs in order to retrieve it from the sub...

    Trying to follow the approach of receiving the CommandParameter as an argument to the sub, I run into a couple of problems. First, after the call to Application.KeyBindings.Add, the CommandParameter property of the returned KeyBinding is "". Second, when my sub CallVSTOMethod (with added argument param as Object) is called, the call fails with a popup dialog saying "argument not optional", apparently indicating the sub is being called without arguments.

    The Application.KeyBindings.Add looked like:

    Word.WdKey[] keys = { ... };
    Word.KeyBinding kb;
    int kc;
    object kc2 = System.Type.Missing;
    object param;
    foreach (Word.WdKey key in keys) {
       // Experimenting with CommandParameter (5th arg).
       // But I can't get it to even show up in kb, let alone get
       // passed into the sub.
       kc = Application.BuildKeyCode(key);
       param = key.ToString();
       kb = Application.KeyBindings.Add(
        Word.WdKeyCategory.wdKeyCategoryCommand,
        "CallVSTOMethod", kc, ref kc2, ref param);
    }
      

     


    • Edited by rvsasseen Tuesday, May 3, 2011 11:29 PM code formatting problem
    Tuesday, May 3, 2011 11:26 PM
  • I've also tried, with no success, making thet command parameter pass to the macro.

    The only other thing I can think of is to bind each unique key setting to a separate subroutine in the vba.  Each of these subroutines then pass their information to the CallVSTOModule sub. 

    Hope this helps.


    Regards, Rich
    Wednesday, May 4, 2011 1:21 AM
  • Hi rvsasseen

    There's no way I know of to accomplish this using VBA "out-of-the-box" if you can't assign a separate procedure to each keyboard combination. Those separate procedures could all call the same "core method" that does the actual work. But there's nothing in Word's object model that will tell you which keys were pressed. 

    The CommandParameter parameter is for internal Word commands, only. It's not meant to allow you to assign "something" to a keybinding for a macro that will be passed when the key is pressed. An example of this would be the FileOpen command which will accept a file path as a CommandParameter.

    The only way in Word to know what keys were pressed is to use the Windows API to create "keyboard hooks". I've just done a quick search in the Internet and can't find anything positive about doing this from within VBA in any office application.

    I know it's possible with more powerful programming languages (such as VB.NET or even classical VB6), although I've never done it, myself.

    <<(Unfortunately, it's not very feasible for me to have a different function for each key.)>>

    Why not?


    Cindy Meister, VSTO/Word MVP
    Wednesday, May 4, 2011 6:09 AM
    Moderator
  • Hi Cindy,

    First, thanks for doing what you do.

    You're right that having a different function for each key is not exactly infeasible, it's just not ideal, so I was looking for a cleaner, better solution. I'm trying to catch essentially all keystrokes that would modify the document. At the moment I'm doing all the numbers, letters, keys with punctuation in the non-shift position, plus all those in combination with shift. (In principle I should also catch ctrl+V and other things too.) So my keys[] array currently has 52 elements, and for each I'm doing both kc = Application.BuildKeyCode(key) and kc = Application.BuildKeyCode(key, Word.WdKey.wdKeyShift) and then Application.KeyBindings.Add(Word.WdKeyCategory.wdKeyCategoryCommand, procName, kc). That means I'd have to define 104 functions. But apparently there's no better alternative, so I guess I'll go ahead and do that.

    I had researched how to intercept keystrokes and discovered the options appeared to be this VBA approach and the "low level keyboard hooks" approach, and chose to try the former first as it appeared more straightforward. I'm not using VBA for anything else but this, so I wouldn't need to try to combine VBA and keyboard hooks. In researching this I found that how to intercept keystrokes in Office applications is a very FAQ, and in light of the difficulty of doing so, it's surprising to me that after all these years the Word group has never simply implemented a new keystroke event. Do you know of any technical reason why they couldn't just do that?

    Wednesday, May 4, 2011 6:22 PM
  • Hello,

    I think this code will at least give you a start. It will show you how to determin witch key was used when the macro was called (in addition to Ctlr, Alt, shift but I think that is not important and that is mostly for the keybinding in this case).

    Private Declare Function GetKeyState Lib "user32.dll" (ByVal nKey As Long) As Integer

    Option Explicit

    Sub YourKeyBindingMacro()
        Dim bKkey   As Boolean   ' Check for K key?
        Dim bMkey   As Boolean  ' Check for M key?
       
        bKkey = CBool(GetKeyState(Asc("K")) And &H80)  ' Is the K key down?
        bMkey = CBool(GetKeyState(Asc("M")) And &H80)  ' Is the M key down?
       
        If bKkey Then
            MsgBox "K key was pressed"
        End If
       
    End Sub

    Regards,
    Lars-Eric

    Wednesday, May 4, 2011 9:38 PM
  • <<Do you know of any technical reason why they couldn't just do that?>>

    I don't know of any, except that it could possibly interfere with the internal key assignment concept, which in Word is very flexible.

    At a certain level it's often a balance between the philosophies: "Word belongs to the user" and "Word as an application tool" (developer product). There's a tendency for a developer to "usurp" the UI from the user for his product, so that the "out-of-the-box" behavior is lost. The user then complains about Microsoft's product being "buggy" because he doesn't know about or understand what a third-party has done. That is one reason behind the Ribbon design being what it is.

    Anyway, it seems to me that Lars Eric's suggestion would probably work (thanks Lars Eric!) :-)

    FWIW we've been asking for a while now (through the VSTO product) that some way be introduced for the (.NET) developer to link keyboard assignments to non-VBA code. So far, no joy, but it takes a couple of product cycles for things to be implemented into Office...


    Cindy Meister, VSTO/Word MVP
    Thursday, May 5, 2011 6:47 AM
    Moderator
  • This is a very interesting thread.  One question I have about Lars Eric's ingenious solution is: is there any chance of encountering a timing problem in which the keys which the user presses to launch the  macro are no longer pressed by the time Word begins executing the macro?
    Dan
    Saturday, September 24, 2011 2:50 PM