locked
KeyBinding not working RRS feed

  • Question

  • I have added a few Commands to my VS2010 extension but I cannot get the KeyBinding to work.

    	<KeyBinding guid="guidVSProlog2010CmdSet" id="cmdidDefinition" editor="guidVSStd97" key1="VK_F12" mod1="Control"/>
    
    


    I want the Command to fire when I press Ctrl+F12. What am I doing wrong?

    Regards


    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 2:18 PM

Answers

  • Well, when processing keyboard input the shell looks at all the active binding scopes in a given order looking for someone claiming the given keyboard input it is processing.  So we will look in all the scopes for a binding against Ctrl+F12.  If we find one (we should here it seems) then we will do a QueryStatus against the command to make sure it is enabled, if so we will call Exec on it. 

    I just realized something though. I thought originally your binding was in the context of the editor, but it is actually global (guidVSStd97 is the 'global' binding space). 

    There is already a global binding for Ctrl-F12 for Edit.GotoDefinition (at least in my profile, and I assume probably universaly across all profiles). 

    We don't allow multiple bindings in the same scope for different commands and since the order of merging of VSCT contributions is undefined (i.e. an implementation detail) we don't guarantee who will win if multiple people try to claim the same binding.  You can test if a given binding is in use via Tools->Options->General->Keyboard and putting focus in the 'Press Shortcut Keys' textbox and then seeing if, after pressing the keys you want to test, anything is showing up 'Shortcut currently used by' combo.

    Ryan

    Tuesday, November 15, 2011 4:15 PM

All replies

  • Hard to say without a complete (minimal) repro.  Your extension integrates with the standard editor?  Focus is in the editor when you hit Ctrl-F12?  Your command handler is reporting from QueryStatus that the command is enabled?

    Ryan

    Tuesday, November 15, 2011 3:56 PM
  • Thanks for answering Ryan and sorry for the little info :(

    The command is actually working if I call it from the menu bar or from the editor context menu. The only thing that's not working is the Ctrl+F12 so that's why I thought of the KeyBinding first. What happens if that shortcut is taken?

    I just created a VSPackage from the VS template and added the KeyBinding. You should be able to repro right away.

    Regards


    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 4:06 PM
  • Well, when processing keyboard input the shell looks at all the active binding scopes in a given order looking for someone claiming the given keyboard input it is processing.  So we will look in all the scopes for a binding against Ctrl+F12.  If we find one (we should here it seems) then we will do a QueryStatus against the command to make sure it is enabled, if so we will call Exec on it. 

    I just realized something though. I thought originally your binding was in the context of the editor, but it is actually global (guidVSStd97 is the 'global' binding space). 

    There is already a global binding for Ctrl-F12 for Edit.GotoDefinition (at least in my profile, and I assume probably universaly across all profiles). 

    We don't allow multiple bindings in the same scope for different commands and since the order of merging of VSCT contributions is undefined (i.e. an implementation detail) we don't guarantee who will win if multiple people try to claim the same binding.  You can test if a given binding is in use via Tools->Options->General->Keyboard and putting focus in the 'Press Shortcut Keys' textbox and then seeing if, after pressing the keys you want to test, anything is showing up 'Shortcut currently used by' combo.

    Ryan

    Tuesday, November 15, 2011 4:15 PM
  • Ok, I see. What do I need to change in order to execute my command in the editor's context?

    I'm also interested id the QueryMethod because I dopn't want my command to be always visible. Where do I write the QueryCommand method?


    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 6:21 PM
  • For the standard (built in) editor I believe you want to use GUID_TextEditorFactory instead of guidVSStd97 in your VSCT KeyBinding element as the value for the editor attribute.

    As for the QueryStatus, it depends.  It needs to be located somewhere that will be in the command route, which is dynamic and somewhat complex, but as a starting point if your package is loaded the QueryStatus should be routed to your package for all commands your package defines.  We never load packages to perform a QueryStatus.

    If you want your command to say always be hidden before your package has been loaded you can attach the DefaultDisable and DefaultInvisible values to your command definition in your VSCT. Otherwise we default to visible and enabled if we can't find any handlers.

    Alternatively you could use VisibilityItems to tie your visibility to some UI context being active.  It is hard to give more specific advice without knowing more about your specific scenario.

    Ryan

    Tuesday, November 15, 2011 6:48 PM
  • Thanks Ryan, you been of great help.

    My specific scenario is this. I'm working on a Prolog editor for Visual Studio (http://visualstudiogallery.msdn.microsoft.com/f7d1e065-149d-417a-878d-26d1aac961fd) my first version had only coloring but now there are a few command I want to add, like "Got to definition" but for Prolog predicates of course.

    So I guess my command should be visible in the editor's context menu if the current file is a .ari (the file extension I'm working with) and then, depending on where the cursor is I should be able to search (Find in files) with the correct regular expression to show where the predicate is defined. Think of it like a partial class on C#, when you execute "Go to definition" go get a small Window with the options of where that class is defined.

    Thanks again for your time, there's not much documentation on these matters.

    Regards,
    Sebastian


    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 7:02 PM
  • Did you add your own custom editor or are you extending the built in editor with say a custom language service that knows about Prolog?

    Edit:  Nevermind, I looked at your link :) 

    It looks like you are extending the built in editor (which is likely the right way to go about things here). 

    In this case you probably don't even need to define your own command as there already is Edit.GotoDefinition defined (globally) which is what all the built in languages use to trigger a Goto Definition. 

    If you are using MEF have a look at this (the important part is the IVsTextViewCreationListener, I don't think you need to bother with any of the rest of it, like the adornment layer or creating an adornment project, etc...  Just do the same thing of exporting the view creation listener in your code (likely listening for your content type) and then add into the command chain like it does in the example), if not we can figure out the same steps to do 'the old fashioned way', which involves calling IVsTextView.AddCommandFilter yourself on the editor instance.

    Your QueryStatus handler would be interested in {guidVSStd97, cmdidGotoDefn} (or {VSConstants.CMDSETID.StandardCmdSet97_guid, VSConstants.VSStd97CmdID.GotoDefn} if you are using MPF).  If you return that the command is enabled/visible for from your IOleCommandTarget then it will appear on the editor context menu and Exec should also hit your handler (at which point you can do anything you want).

    Ryan


    Tuesday, November 15, 2011 7:09 PM
  • I'm extending the built in editor... I based my work on the Ook sample
    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 7:12 PM
  • Yeah, see above (I edited my response after the initial posting :))

    Ryan

    Tuesday, November 15, 2011 7:40 PM
  • Thanks Ryan, that's a lot of info right there.

    I will take a closer read of your answers with my code in front of me to see if I have everything I need. I will come back to let you know how it all went.

    Thanks again

    EDIT: changing editor scope from guidVSStd97 to GUID_TextEditorFactory did the trick!

     


    http://bit.ly/sebagomez
    Tuesday, November 15, 2011 10:06 PM
  • Well that gets you a binding, but if you are supporting Goto Definition like a lot of other languages you should be using the same command, not one of your own definition. 

    Otherwise users would have to know that YOUR Goto Definition was Ctrl-F12 instead of normal F12, and other people's extensions that may say want to invoke Goto Definition against your editor would have to know about your special Goto Definition command, that is a recipe for nightmares.

    Ryan

    Wednesday, November 16, 2011 2:21 AM
  • I did changed my shortcut to F12... is that it? or can I extend the actual command to be available (via Query) and execute whatever I want it to do?
    http://bit.ly/sebagomez
    Wednesday, November 16, 2011 1:44 PM
  • Yes, you can use the existing VS command, otherwise VS itself would have to know about every single language service in order to support Goto Definition across every language (including ones added by third parties), which wouldn't really be feasible.  When a command's status needs to be known or it needs to be executed we look for a handler (and instance of IOleCommandTarget) along the command route.  

    When the editor receives a QueryStatus/Exec request it passes it along to a chain of IOleCommandTargets it knows about, those added via IVsTextView.AddCommandFilter.  If you add a filter to that chain when your language service is active in the given view then you can handle the Goto Definition command and do whatever you like.  

    The advantage to using the built in commands (apart from making your life easier not having to redefine your own versions in the VSCT file) is that third party extensions, if they are automatically invoking commands, are doing it via GUID/int identifiers. Therefore they would be invoke Goto Definition via referring to the shell's Goto Definition command, and it wouldn't work in your window if you had defined your own, even if you had the same text (we don't match commands for execution by text, it is always by GUID/int identifying pair).

    Ryan

    Wednesday, November 16, 2011 3:48 PM
  • Thanks Ryan, I will go that way.

    Do you know of an example I could take a look at about this matter?


    http://bit.ly/sebagomez
    Wednesday, November 16, 2011 4:08 PM
  • No but I could probably whip one up later today, it should be pretty easy.

    Ryan

    Wednesday, November 16, 2011 4:34 PM
  • No but I could probably whip one up later today, it should be pretty easy.

    Ryan

    That'd be awesome!

    http://bit.ly/sebagomez
    Wednesday, November 16, 2011 5:07 PM
  • I mainly followed the instructions from here ignoring the irrelevant parts and tweaking some of the code for my own personal tastes :)

    using System;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Editor;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.Utilities;
    
    using OLEConstants = Microsoft.VisualStudio.OLE.Interop.Constants;
    
    namespace Microsoft.VSPackage1
    {
        [Export(typeof(IVsTextViewCreationListener))]
        [ContentType("text")]
        [TextViewRole(PredefinedTextViewRoles.Editable)]
        class CommandFilterProvider : IVsTextViewCreationListener
        {
            [Import(typeof(IVsEditorAdaptersFactoryService))]
            internal IVsEditorAdaptersFactoryService editorFactory = null;
    
            public void VsTextViewCreated(VisualStudio.TextManager.Interop.IVsTextView textViewAdapter)
            {
                IWpfTextView textView = editorFactory.GetWpfTextView(textViewAdapter);
                if (textView == null)
                    return;
    
                AddCommandFilter(textViewAdapter, new EditorCommandFilter(textView));
            }
    
            void AddCommandFilter(IVsTextView viewAdapter, EditorCommandFilter commandFilter)
            {
                //get the view adapter from the editor factory
                IOleCommandTarget next;
                int hr = viewAdapter.AddCommandFilter(commandFilter, out next);
    
                if (hr == VSConstants.S_OK)
                {
                    if (next != null)
                        commandFilter.SetNextTarget(next);
                }
            }
        }
    
        class EditorCommandFilter : IOleCommandTarget
        {
            private IWpfTextView textView;
            private IOleCommandTarget nextTarget;
    
            public EditorCommandFilter(IWpfTextView textView)
            {
                this.textView = textView;
            }
    
            internal void SetNextTarget(IOleCommandTarget nextTarget)
            {
                this.nextTarget = nextTarget;
            }
    
            public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
            {
                if (pguidCmdGroup == VSConstants.CMDSETID.StandardCommandSet97_guid && nCmdID == (uint)VSConstants.VSStd97CmdID.GotoDefn)
                {
                    System.Windows.Forms.MessageBox.Show("Someone asked me to Goto Definition!");
    
                    //Indicate we handled the Exec so the shell doesn't continue looking for handlers.
                    return (int)VSConstants.S_OK;
                }
                else
                {
                    if (nextTarget != null)
                    {
                        return nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
                    }
                }
    
                return (int)OLEConstants.OLECMDERR_E_NOTSUPPORTED;
            }
    
            public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
            {
                if (pguidCmdGroup == VSConstants.CMDSETID.StandardCommandSet97_guid && prgCmds[0].cmdID == (uint)VSConstants.VSStd97CmdID.GotoDefn)
                {
                    //Say the comand is enabled and that we support it.
                    prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
    
                    //INdicate we handled the QueryStatus so the shell doesn't continue looking for handlers.
                    return VSConstants.S_OK;
                }
                else if (nextTarget != null)
                {
                    return nextTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
                }
    
                return (int)OLEConstants.OLECMDERR_E_NOTSUPPORTED;
            }
        }
    }
    
    

    Add this to a CS file in your project and hit F5.  Open a text file and right click. You will see Goto Definition on the context menu (because of my QueryStatus method above) and when you click on it you will see the messagebox that called in my Exec method. 
    Ryan


    Wednesday, November 16, 2011 9:14 PM
  • Thanks Ryan, you rule!

    I got it all working now... now I just need to make things cleaner... like, get to know if I actually can show the GoToDefinition command depending on the line of code the cursor is on, and of course if the opened file is my type of file (*.ari).

    Thanks again, you've been of great help!

    Best regards
    http://bit.ly/sebagomez
    Friday, November 18, 2011 3:53 AM
  • I believe if you define your own content type for your .ari files, and then change the text view creation listener to be associated with that content type you should only get calls back when .ari files are opened, so you would only end up adding hooks to those editors.  I used "text" just for simplicity.

    As for the other bit, you can use the editor instance or its buffer to get info on where the cursor is and extract the text to see if you can sensibly do a Goto Definition, just be aware that QueryStatus is a hot path in the shell, the menu can't be shown until all the QueryStatus calls for all the items on it return, so don't do anything that will take a long time or you will delay the context menu coming up and annoy users :)

    Ryan

    Friday, November 18, 2011 6:45 AM
  • Thanks Ryan, that's what I did. You've been of great help!

    Finished work (by now) is now online.

    Thanks again.

    Regards


    http://bit.ly/sebagomez
    Sunday, November 20, 2011 1:37 PM