none
Programmatically editing keyboard maps

    Question

  • Hello,

    I'd like the next version of my product to create an alternate keyboard map (as seen in Tools|Options|Keyboard), make it the default, and edit some of its bindings. I haven't found any way to do this. I've also had a look at the registry (both the system and the user portion), and it seems keyboard maps are not stored there.

    Is there any way to do this? Any DTE interfaces?

    Thanks,

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio


    Friday, August 18, 2006 8:35 PM

Answers

  • Hello,

    In the tradition of answering my own questions, I have found a nice explanation and sample code to do what I mentioned above:

      http://msdn2.microsoft.com/en-us/library/envdte.command.bindings.aspx

    This works nicely in VS.NET 2003: setting the keyboard scheme property to some new value creates a new keyboard scheme based on the current one, and setting the 'bindings' field in the commands changes them.

    This doesn't work on VS2005 though. Well, only part of it does. The property name has been changed from "Scheme" to "SchemeName", but setting it now doesn't create a copy. I guess this results from the general UI changes in which bindings can be changed directly whatever the currently selected scheme, and there is a button to 'reset' the bindings in the UI. The result is that bindings can be changed directly. It would be great if there were a documented way to create a new scheme, but it seems there isn't.

    I planned to switch schemes programmatically when enabling/disabling my plugin, but I will instead resort to modifying individual command bindings.

    Regards,

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio

    Sunday, August 20, 2006 3:51 PM

All replies

  • Hello,

    In the tradition of answering my own questions, I have found a nice explanation and sample code to do what I mentioned above:

      http://msdn2.microsoft.com/en-us/library/envdte.command.bindings.aspx

    This works nicely in VS.NET 2003: setting the keyboard scheme property to some new value creates a new keyboard scheme based on the current one, and setting the 'bindings' field in the commands changes them.

    This doesn't work on VS2005 though. Well, only part of it does. The property name has been changed from "Scheme" to "SchemeName", but setting it now doesn't create a copy. I guess this results from the general UI changes in which bindings can be changed directly whatever the currently selected scheme, and there is a button to 'reset' the bindings in the UI. The result is that bindings can be changed directly. It would be great if there were a documented way to create a new scheme, but it seems there isn't.

    I planned to switch schemes programmatically when enabling/disabling my plugin, but I will instead resort to modifying individual command bindings.

    Regards,

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio

    Sunday, August 20, 2006 3:51 PM
  • I'm still a bit stuck with this. I have implemented the code to do this, modifying bindings dynamically, and although it works great in VS2005, it doesn't in VS.NET 2003. Well, kind of. The modifications I do to the bindings in VS.NET 2003 work, I can see them in Tools|Options|Keyboard, but as soon as I exit and reenter the IDE, they are gone. I have taken care to create a new keyboard scheme (just assigning to the environment property does the trick). I'm actually just removing bindings, rather than adding them, by calling put_Binding with an empty array (I've also tried with a VT_EMPTY variant, doesn't work).

    The tricky part is that the sample linked to from above works great in VS.NET 2003 when run from the macros IDE, and the changes *are* persistent. I'm thinking that I'm doing something wrong with the lifetime of the COM objects (I'm not that much of an expert...). Does the put_Bindings call take ownership of the SafeArray? Do I have to delete it or not? I'd expect it'd just make a copy, but I can not be sure. I'm also thinking that changes might not be persistent until the DTE, Commands, Command, etc... objects are released, but I'm not sure either.

    I've also tried to find some call to store the changes by scanning dteinternal.h, maybe the macros IDE is automatically calling it - but I haven't been able to find anything.

    If there is some specific rule here, I'd be glad to hear it. I will else resort to tracing through the put_Bindings() assembly code, but you can guess it's pretty scary.

    Thanks!

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio

    Wednesday, August 23, 2006 12:37 PM
  • Hi,

    After finding out about IVsGlobals and the DTE Globals object, I tried calling Save() on it, but it doesn't work (it save another unrelated file). Tracing through the code in get_Bindings() and put_Bindings() hasn't been of much help. I've also tried setting the property indirectly through IDispatch, which works, but doesn't achieve persistence either.

    The most promising thing I've seen is a command id called 'cmdidSaveOptions' in the Std97 cmdset, value 959, listed in stdidcmd.h. But I can't find an equivalent DTE text-based command. I'll try to find a way to call this command directly (by GUID and command id).

    I'll be grateful for any help anyone can offer.

    Thanks!

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio

    Wednesday, August 23, 2006 5:30 PM
  • After fiddling with the put_Bindings() argument format, I've found the following (in VS.NET 2003, VS2005 seems to do everything fine):

     - If I try to set it to a VT_BSTR ("Global::F2"), it removes the current setting and the removal is persistent!
     - If I try to set it to a SafeArray of variants containing BSTR (the hopefully correct way), the first time around (when the keyboard scheme has just been set to a writable new one) the current binding is removed but the new binding I'm setting is not added (this is permanent).
     - The second time around, again with a SafeArray containing just a BSTR the binding is actually set (and it is permanent).
     - If set with an empty (0-element)

    I have tried everything I could think of. Doing the modifications from different places in my code, setting safearrays with 1-based or 0-based arrays, arrays of raw BSTRs, using SafeArrayCreate(), SafeArrayCreateVector() and SafeArrayCreateVectorEx(), setting the kbdscheme property again after modifying the bindings, adding and removing the bindings, calling put_Bindings through IDispatch::Execute with the proper DISPID,  etc... all to no avail.

    After spending 3 solid days on this, I am dedicating one last effort to tracing through put_Bindings in assembly language w/o symbols. I hope I can trace it as well when it is called from the macros IDE in order to see the difference, as setting the binding from the macro seems to always work fine. I'm hoping that the call to write the .vsk file is within there, if it's somewhere else, I'm not sure I'll be able to find anything. If I can't fix it, I will resort to providing this functionality on VS2005 only, but I'd really like to get it working in VS.NET 2003.

    If I can verify that setting the binding to a BSTR actually removes the current bindings permanently and reliably, I may provide a lower degree of functionality in VS.NET 2003, as ugly as that is.

    Thanks!

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio
    Friday, August 25, 2006 4:29 PM
  • After more than 10 hours of assembly tracing w/o symbols, several hundred memory dumps, and countles vtbl inspections, I've found out what's happening.

    The put_Bindings method in VS.NET 2003 has a bug in which setting an empty array will actually remove the current bindings, but it will not write the .VSK file. The reason is that the loop that applies the bindings is something like this:

    RemoveCurrentKeyBindings(cmd);
    for (i = lowerbound; i <= upperbound; i++)
    {
      AddKeyBinding(cmd, arrayIdea);
      WriteOutVSKFile();
    }

    Thus, setting the property to an empty array is a non-persistent operation. But, given that the whole file is written out for any keybinding change (with this case as the exception), if you set any other keybinding afterwards, to any command, the changes will be persisted.

    This seems to be fixed in VS2005.

    My current plan is to force writing out the VSK file by setting some other binding to its own current setting. The VSK file is generated from in-memory data in every put_Bindings call, so writing out can be done in any moment.

    On the other hand, documentation is pretty slim wrt this function. First, the function accepts a BSTR (VT_BSTR) or an array of variants containing BSTRs (VT_ARRAY|VT_VARIANT, create the array using VT_VARIANT, and then each variant element must be a VT_BSTR with a keybinding). The sample provided in MSDN works mostly well because it sets a single VT_BSTR.

    Apart from this, the function does not remove the assigned keybinding from any other commands that may be using it. After this is done, general command management and the Tools|Options|Keyboard interface can get a bit confused, which is the source of the problems I mentioned about with setting F2 for File.NewFile (in the Visual C++ 6 base mapping, F2 is set to File.RenameFile, which clashes, so setting it somewhere else creates quite a few problems).

    If a BSTR is passed with an illegal key descriptor (or an empty string, for example), the call fails, but the bindings of that command are removed, although the VSK file is not written out.

    If anyone is interested in a fully commented disassembly of the function, just let me know.

    Regards,

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio
    Saturday, August 26, 2006 1:48 PM
  • Two more important things I discovered later, in case anyone is stumbling into this dark corner of Visual Studio.

    When running under Windows 2000, if you use CComVariant to hold the array of bindings, it is necessary to create the array with SafeArrayCreateEx() or SafeArrayCreateVectorEx(). If you use the non-"Ex" methods, the CComVariant will not pick up the values correctly, and it will fail. This is not Visual Studio specific, of course, rather C++/ATL/WTL specific.

    And, more relevant to the forum, the only way to reliably remove keybindings every time is to call put_Bindings() with an empty array *twice* in a row. Really weird, but I've found cases in which calling it just once will only remove some of the bindings, not all of them. Don't know why, and I'm not going to disassembled the VS2005 version, but be warned that it seems to have fixed it for me.

    It reminds me of the old "sync 3 times before shutting down" Unix trick.

      - Jon
    ----------------------------------------------------
    ViEmu - vi/vim emulation for Visual Studio

    Wednesday, September 20, 2006 2:34 AM