none
Add-In: Problem with restoring Command.Bindings

    Question

  • I create an add-in for Visual Studio 2005/2008/2010 using Visual Studio 2008 as well as 2010 (SP1) and .NET 3.5.

    Overview

    The add-in uses the so called temporary UI approach, which means to delete all its UI when Visual Studio is closed and recreate it the next time Visual Studio is started. This actually is a workaround to prevent dead UI elements.

    To remove every single UI element of a command, the command has to be deleted. Visual Studio removes any associated UI of the command automatically. Afterwards, the command is created again.

    In order to not lose existing shortcut bindings, I backup the command bindings before I delete the command and assign the same bindings when I create the command again, basically it works like this code:

     

    Command existingCommand = ...;
    
    // keep a reference to bindings, so they don't get lost
    object[] commandBindingsBackup = (object[])existingCommand.Bindings;
    
    // delete the command and any associated UI controls
    existingCommand.Delete();
    
    // re-create the command
    Command newCommand = CreateCommandButton(...);
    
    // re-assign the backuped bindings
    newCommand.Bindings = commandBindingsBackup;
    
    

     

    Problem description

    The problem is when I re-assign the old bindings in Visual Studio 2010. Bindings have to be specified using their localized names, at least in Visual Studio 2005 and 2008. In 2010, this seems to have changed.

    The old binding (commandBindingsBackup[0]) equals "Global::Strg+Umschalt+Alt+O", this is what I get back from the Command.Bindings member. It's the german version of "Global::Ctrl+Shift+Alt+O". However, when I re-assign this same binding to the freshly created command, an ArgumentException is thrown: Wrong Parameter. (Exception of HRESULT: 0x80070057 (E_INVALIDARG)).

    When I assign the english version "Global::Ctrl+Shift+Alt+O" instead, it works as expected.

     

    My question

    How can I keep existing bindings when I re-create a command in Visual Studio 2010? I don't think it's an option to translate whatever I get from Command.Bindings to english and then assign it.

     

    Thanks in advance

    Peter

     

    Sunday, January 15, 2012 12:35 PM

Answers

  • Well, it won't win any beauty contests, but you could do something like this

                IVsShell shell = (IVsShell)GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SVsShell));
                Guid taskListPgGuid = new Guid("4A9B7E50-AA16-11d0-A8C5-00A0C921A4D2");
    
                uint globalKeybindingScopeNameId = 13018U;
                string globalKeybindingScopeName ;
                int res = shell.LoadPackageString(ref taskListPgGuid, globalKeybindingScopeNameId, out globalKeybindingScopeName);
    
                uint textEditorKeybindingScopeNameId = 13022U;
                string textEditorKeybindingScopeName;
                res = shell.LoadPackageString(ref taskListPgGuid, textEditorKeybindingScopeNameId, out textEditorKeybindingScopeName);
    
                uint localizedCtrlKeyId = 13358U;
                string localizedCtrlKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedCtrlKeyId, out localizedCtrlKeyText);
    
                uint localizedAltKeyId = 13359U;
                string localizedAltKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedAltKeyId, out localizedAltKeyText);
    
                uint localizedShiftKeyId = 13360U;
                string localizedShiftKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedShiftKeyId, out localizedShiftKeyText);
    
    


     Then you could use the given localized Global or Text Editor namespace scope fetched above, and use the localized versions of Ctrl, Alt and/or Shift to check the keybinding and then backmap to hard coded English variants in your string before you set the binding.

    Ryan


    Monday, January 16, 2012 6:41 PM

All replies

  • Can you double check this actually worked on version prior to 2010?  This code didn't change in 2010 and I see things like this in the code:

    if(!_wcsicmp(pszTempCurrent, L"CTRL"))
        *pfCTRL = TRUE;
    else if(!_wcsicmp(pszTempCurrent, L"ALT"))
        *pfALT = TRUE;
    else if(!_wcsicmp(pszTempCurrent, L"SHIFT"))
        *pfSHIFT = TRUE;

    So I can't imagine this ever could have worked, unless it was returning non-localized binding text in the past (but as I said none of this code changed in 2010).

    This definetly seems to be a bug, and I can't see any work around since the parsing code is using hard coded string literals to look for 'special' tokens in the input stream from what I can tell via code inspection (I haven't debugged into this, but unless we have a back mapping from 'localized version of CTRL -> "CTRL"' I don't see this code being correct).

    Ryan

    Monday, January 16, 2012 6:01 AM
  • Hi Ryan, I double checked with a german version of Visual Studio 2008 and you are right. Modifiers (Ctrl, Alt, Shift) must be specified in english. However, the scope (Global, Text Editor, etc) must be specified using the localized name. For example it's "Text Editor" in english and "Text-Editor" in german (with a dash in the middle).

    Can you think of any workaround that would do the job? I don't try to win a beauty contest, it should just get the job right.

    Monday, January 16, 2012 6:20 PM
  • Well, it won't win any beauty contests, but you could do something like this

                IVsShell shell = (IVsShell)GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SVsShell));
                Guid taskListPgGuid = new Guid("4A9B7E50-AA16-11d0-A8C5-00A0C921A4D2");
    
                uint globalKeybindingScopeNameId = 13018U;
                string globalKeybindingScopeName ;
                int res = shell.LoadPackageString(ref taskListPgGuid, globalKeybindingScopeNameId, out globalKeybindingScopeName);
    
                uint textEditorKeybindingScopeNameId = 13022U;
                string textEditorKeybindingScopeName;
                res = shell.LoadPackageString(ref taskListPgGuid, textEditorKeybindingScopeNameId, out textEditorKeybindingScopeName);
    
                uint localizedCtrlKeyId = 13358U;
                string localizedCtrlKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedCtrlKeyId, out localizedCtrlKeyText);
    
                uint localizedAltKeyId = 13359U;
                string localizedAltKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedAltKeyId, out localizedAltKeyText);
    
                uint localizedShiftKeyId = 13360U;
                string localizedShiftKeyText;
                res = shell.LoadPackageString(ref taskListPgGuid, localizedShiftKeyId, out localizedShiftKeyText);
    
    


     Then you could use the given localized Global or Text Editor namespace scope fetched above, and use the localized versions of Ctrl, Alt and/or Shift to check the keybinding and then backmap to hard coded English variants in your string before you set the binding.

    Ryan


    Monday, January 16, 2012 6:41 PM
  • Hi Ryan, thanks for the code. I have implemented it as you suggested and it works, hooray!

    What I didn't think about, you can also assign special keys like Delete, Return and Up Arrow for example. These need to be specified in english as well to get this whole thing working. Luckily the translated strings can be found via LoadPackageString as well, so it's just a bit more backmapping.

    Thanks for the help

    Peter



    Tuesday, January 17, 2012 7:50 PM
  • Glad it works, I opened a bug about this internally, we should take English or localized text really.  My gut says it may not be approved for fix as we are fairly late in the release cycle and there is a work around, even though it is kind of ugly.

    The main problem is it relies on the tasklist package being inside msenv.dll and thus sharing its resource dll, it also relies on the resource ids not changing.  Neither of those things would change for the next release for any reason I can imagine, and seem unlikely to change even in the release after that (moving packages between dlls has to have an actual reason), but ideally you wouldn't need to do any of this gross stuff.  Sorry about that.

    Ryan

    Tuesday, January 17, 2012 8:54 PM
  • FWIW, I have opened a bug about this externally through Microsoft Connect:

    https://connect.microsoft.com/VisualStudio/feedback/details/761838/envdte-command-bindings-causes-exception-when-setting-value-from-a-visual-studio-add-in

    It affects several add-ins, including mine.


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/

    Sunday, September 09, 2012 7:23 PM