locked
VS2010/MPF/C# Default KeyProcessor in a hosted TextView RRS feed

  • Question

  • Hi,

    I'm using a hosted textview in the manner shown by the RegexEditor sample ( http://editorsamples.codeplex.com/wikipage?title=Regex%20Editor%20Implementation )

    The sample has a custom KeyProcessor service which is provided by a MEF component

    internal sealed class RegexEditorKeyProcessorProvider

     

    which in turn uses a KeyTypeConverter fair enough- with a regex editor you may only want a limited set of keys?In my case, I'd rather just use Visual Studio's default (is their one) key processor. I have international users and with the code where it is at the moment I'm soon going to have to extend the keytype converter to understand international keyboards, which seems folly.I tried just disabling the mef  export, but then the keyboard does nothing.

    How do I get default behaviour back in a hosted textview?

     

    Thanks,

     

     

    • Edited by Joeul Wednesday, March 9, 2011 4:55 PM improved subject
    Wednesday, March 9, 2011 12:05 PM

Answers

    • Marked as answer by Joeul Thursday, April 21, 2011 12:37 PM
    Monday, April 4, 2011 6:43 PM
  • >A benefit of properly routed messages perhaps?

    Likely, I looked over a bit of the code you had based your original solution on, I think you could take what I am doing when I call IVsFilterKeys and do it inside a KeyProcessor you provide and simply leave the priority command target in place (to handle the resultant messages), and change your code to respond to the commands (which is what the real editor/language services do, all typed input is translated into TYPECHAR and 'completion' type commands since different languages trigger intellisense on different symbol combinations (i.e. C++ i '.' and/or '->', C# is just '.', etc..)

    Though if whatever you have is working without providing a KeyProcessor that works too :) 

    Looking at the KeyProcessor solution I can see why it works for him, he is simply manually converting the raw keys into editor commands and calling EditorOperations.DoX to direct the command at the editor (see: RegexEditorKeyProcessor.cs under Regex Editor->src->Regex Editor->UI).

    My way of simply passing the QS/Exec to the editor after IVsFilterKeys has done the command mapping is essentially the same thing, but more robust as it handles both users remapping commands (i.e. the example from CodePlex assumes that copy is always going to be Ctrl+C for instance) any command the editor knows about (even commands from third party extensions). His only handles the few-ish commands he has setup handling for, though it is likely quite sufficient in his case (and maybe in yours as well).

    Ryan

    • Marked as answer by Joeul Thursday, April 21, 2011 12:37 PM
    Monday, April 18, 2011 7:02 PM
  • The problem is around the fact that the editor is a far more complex / extensible control than say a WPF rich edit control. The shell has no way of realizing random modal dialogs are perhaps command targets. What I imagine you need to do is register a temporary priority command target while your dialog is up that will pass all QueryStatus and Exec calls to the editor for processing.

    Ryan

    • Marked as answer by Victor_Chen Thursday, March 17, 2011 8:48 AM
    Wednesday, March 9, 2011 7:07 PM

All replies

  • Are you hosting the editor in a tool window?  Basically all keystrokes in the editor are turned into commands and handled by the editor that way, so for it to work properly VS has to be able to 'find' the editor in its command route.

    Ryan

    Wednesday, March 9, 2011 4:51 PM
  • No, I'm hosting in a modal dialog that pops up as a custom editor from a property grid in the DSL Tools - hence not using a fully fledged code window. Though I see that for templates Resharper manages to do something more like using a regular document window.

    I borrowed the code from the RegexEditor Sample, I _think_ that it's just the launch conditions of the modal dialog that are different.

    The editor is in a project package of its own, with a vsix and all. I'm instantiating its service from a UITypeEditor with MEF:


                componentModel = (IComponentModel) Package.GetGlobalService(typeof (SComponentModel));
                var serviceFromMef = componentModel.GetService<MyEditorService>();

    from then on it's as in the sample.

     

    // Create the regex editor
          IContentType regexContentType = this.ContentTypeRegistryService.GetContentType(MyContentType.ContentTypeName);
    
          ITextBuffer textBuffer = this.TextBufferFactoryService.CreateTextBuffer(pattern, regexContentType);
          // TODO: RC1
          IWpfTextView view = this.TextEditorFactoryService.CreateTextView(textBuffer);
          IWpfTextViewHost editor = this.TextEditorFactoryService.CreateTextViewHost(view, true);
          editor.TextView.Properties.AddProperty(TextViewEventManager.Key, new TextViewEventManager(editor.TextView));
          HideTextViewMargins(editor);
    

    I'd found this https://github.com/jaredpar/VsVim/blob/master/VsVim/KeyboardInputRouting.txt which explains the order.

    So as far as I'm aware I'm asking VS for the textbuffer and the view.

    The keyprocessor is provided with

    [Export(typeof(IKeyProcessorProvider))]
      [Name("My Editor Key Processor")]
      [Order(After="default")]
      [ContentType(MyContentType.ContentTypeName)]
    	[TextViewRole(PredefinedTextViewRoles.Document)]
    	internal sealed class MyEditorKeyProcessorProvider : IKeyProcessorProvider
      {
        [Import(typeof(IEditorOperationsFactoryService))]
        private IEditorOperationsFactoryService EditorOperationsFactoryService { get; set; }
    
        public KeyProcessor GetAssociatedProcessor(IWpfTextView wpfTextView)
        {
          // Create the key processor only if the target buffer is of content type "customalgo"
          if (wpfTextView.TextBuffer.ContentType.TypeName == MyContentType.ContentTypeName)
          {
            IEditorOperations editorOperations = this.EditorOperationsFactoryService.GetEditorOperations(wpfTextView);
    
            return new MyEditorKeyProcessor(editorOperations);
          }
    
          return null;
        }
    
      }
    

    And I've played with different values of Order.

     

    The problems (maintainability-internationalisation etc) though come from the KeyProcessors use of KeyTypeConverter that in the sample is American-centric.(i.e. 2/@ vs 2/" )

    It feels like I have a whole lot of code to avoid doing the default - when the default is exactly what I want!

     

     

     

    Wednesday, March 9, 2011 5:14 PM
  • The problem is around the fact that the editor is a far more complex / extensible control than say a WPF rich edit control. The shell has no way of realizing random modal dialogs are perhaps command targets. What I imagine you need to do is register a temporary priority command target while your dialog is up that will pass all QueryStatus and Exec calls to the editor for processing.

    Ryan

    • Marked as answer by Victor_Chen Thursday, March 17, 2011 8:48 AM
    Wednesday, March 9, 2011 7:07 PM
  • Thanks Ryan, I guess I'll try that then.:-)

    I get it from an historical point of view, it just seems mad that with this MEF IKeyProcessorProvider extension point the only implementation I can find is KeyProcessor - which looks like a null object.

     


    Wednesday, March 23, 2011 1:44 PM
  • >I get it from an historical point of view, it just seems mad that with this MEF IKeyProcessorProvider extension point the only implementation I can find is KeyProcessor - which looks like a null object.

    I don't really know a lot about the editor extensibility space, but command routing is a fairly fundamental aspect of how VS operates, it isn't really a 'historical point of view' except that we strive to not break every existing extender out there on a new release. I am not sure what the sample you refer to is doing, but if it is hosting an editor outside of a shell provided window (toolwindow or document window) then it is likely doing what I suggested above, there is no MEF magic to get around the fundamental way command routing works in VS, in fact I don't think there even could be.

    Ryan

    Wednesday, March 23, 2011 2:45 PM
  • What I think I don't understand is that the code is asking a the visual studio TextEditorFactoryService for the text view, so it surprises me that it's no hooked into the command routing.

    If you know of a 'nicer' way that I could get an editor window to edit values in a property grid then I'm all ears! Maybe I should be creating regular document windows with just my string as backing, and some code to update the dsl store on save, rather than a modal dialog. But then I wasn't sure of how the UITypeEditor would interact, and there's some oddities about visual studio then thinking a modal dialog is open, and all that initially seemed more daunting - especially once I found this sample that superficially did what I was after.

    Sorry if 'historical point of view' sounded loaded/had negative connotations, that's not my intention at all!

     

    Thursday, March 24, 2011 11:28 AM
  • >What I think I don't understand is that the code is asking a the visual studio TextEditorFactoryService for the text view, so it surprises me that it's no hooked into the command routing.

    The problem is that (TextEditorFactoryService) is the same way EVERYONE creates a text view, and no one else NEEDS to be explicitly hooked into the command route because if you are hosting inside a document/toolwindow you already will be hooked in based on activation. The problem arises for you because you are hosting inside a window VS knows nothing about (i.e. one it didn't create) and our command routing is based (heavily) on activation (a VS concept, somewhat like focus but not 100% the same).

    >If you know of a 'nicer' way that I could get an editor window to edit values in a property grid then I'm all ears!

    There really isn't good support for a window that isn't a tool window/document window, and what you describe isn't either.  Is hosting the editor necessary? That is a very heavyweight way to get a textbox :)  Do you require the other services the full-fledged editor offers?

    Ryan

    Thursday, March 24, 2011 2:44 PM
  • Thanks Ryan,

    It is indeed a heavyweight way to get a textbox :-) But, the reason I'm using the hosted view is because I wanted the intellisense and syntax highlighting abilities , and ultimately probably snippets support as well: all for a custom language used in parts of the graphical DSL (because languages nested within languages are fun :-S ). These so far have been fairly straightforward - thanks to that regex sample. 

    It seemed slightly silly to be using the DSL designer in Visual Studio - and then spawn off a dialog with a custom control (ICSharpCode/Actipro/Scintilla whatever) to get all those lovely editing extensions. Perhaps that is the easiest way in the end. But I haven't yet had a chance to evaluate the work involved in a Priority Command target yet.

    Thursday, March 31, 2011 10:19 AM
  • It is fairly lightweight, you need to implement IOleCommandTarget and simply forward the incoming requests on both QueryStatus and Exec to your hosted editor instance (cast to IOleCommandTarget).  The only other possibly tricky thing is that the shell might not be aware that editor keybindings should be considered while we are in this modal state, I would need to play around with a similar scenario (hosting the editor in a modal dialog) to see the best way to make this happen...it may be non-trivial as pre-processing would have no idea about this modal dialog and may not even be active while we are in a nested message loop (i.e. modal dialog).  Your dialog may need to pre-process messages using something like IVsFilterKeys2::TranslateAcceleratorEx.

    I may be able to whip up an example of this this weekend (I am already probable going to try a quick sample to show someone how to host the editor in a tool window (also not a mainline scenario) so it should be easy to take that sample and tweak it to show how one would host the editor in a modal dialog.

    Ryan

    Thursday, March 31, 2011 5:12 PM
  • If you are able to I'll be very grateful, and I'm sure I wouldn't be the only one.

    It did just strike me that perhaps the point of the MEF'd keyprocessor in the sample is just to show the joys of MEF - but given that it's already easily showing that with all the intellisense ...

     

    Friday, April 1, 2011 8:45 AM
    • Marked as answer by Joeul Thursday, April 21, 2011 12:37 PM
    Monday, April 4, 2011 6:43 PM
  • Thanks Ryan, I'll check it out ASAP. Copy it verbatim and stick your picture on a monitor in true Cargo Cult style :)

     

    Tuesday, April 5, 2011 4:09 PM
  • Hahah excellent, the comment wasn't directed at you per se, just in general :)  It is a pet peeve when I see code and ask someone 'why is it doing X' and they say 'I don't know, it was doing that in this other place where I copied the code from' :)

    Ryan

    Tuesday, April 5, 2011 4:58 PM
  • <blockquote>'I don't know, it was doing that in this other place where I copied the code from' :)</blockquote>

    Oh don't get me started on that. I've inherited more than my fair share of projects with comments like that, with "magic" working parts. :-D

    Ahh, this is brilliant Ryan! I now need to rework my intellisense a little as all the triggering was on keyup keydown rather than the commands and I had a slightly hacky solution for intellisense on patterns like thing.subthing.propertyofsubthing - now it's popping up with solution as soon as you type 't' of thing. But it looks like the filter may be working better than it had previously. A benefit of properly routed messages perhaps?

    And also thanks to this I identified the cause of visual studio getting confused about having a modal dialog open : my popup was just a window, not a dialogwindow.

    Thanks, I owe you a pint if you're ever in London.

     


    Monday, April 18, 2011 8:36 AM
  • >A benefit of properly routed messages perhaps?

    Likely, I looked over a bit of the code you had based your original solution on, I think you could take what I am doing when I call IVsFilterKeys and do it inside a KeyProcessor you provide and simply leave the priority command target in place (to handle the resultant messages), and change your code to respond to the commands (which is what the real editor/language services do, all typed input is translated into TYPECHAR and 'completion' type commands since different languages trigger intellisense on different symbol combinations (i.e. C++ i '.' and/or '->', C# is just '.', etc..)

    Though if whatever you have is working without providing a KeyProcessor that works too :) 

    Looking at the KeyProcessor solution I can see why it works for him, he is simply manually converting the raw keys into editor commands and calling EditorOperations.DoX to direct the command at the editor (see: RegexEditorKeyProcessor.cs under Regex Editor->src->Regex Editor->UI).

    My way of simply passing the QS/Exec to the editor after IVsFilterKeys has done the command mapping is essentially the same thing, but more robust as it handles both users remapping commands (i.e. the example from CodePlex assumes that copy is always going to be Ctrl+C for instance) any command the editor knows about (even commands from third party extensions). His only handles the few-ish commands he has setup handling for, though it is likely quite sufficient in his case (and maybe in yours as well).

    Ryan

    • Marked as answer by Joeul Thursday, April 21, 2011 12:37 PM
    Monday, April 18, 2011 7:02 PM
  • I think you could take what I am doing when I call IVsFilterKeys and do it inside a KeyProcessor you provide and simply leave the priority command target in place (to handle the resultant messages), and change your code to respond to the commands (which is what the real editor/language services do

    Yes, I see. The calls to IVsFilterKeys was probably the main thing I was after On that note - I want to have the window close on ESC or accept on CTRL + ENTER. But obviously with the FilterThreadMessage in place these keys get mapped/ swallowed. I'd tried setting handled to false for these FilterThreadMessage  but that's obviously a wrong place as then my intellisense loses the cancel :) 

    Do these tend to get mapped to actual commands I can process, do you know?

    My way of simply passing the QS/Exec to the editor after IVsFilterKeys has done the command mapping is essentially the same thing, but more robust as it handles both users remapping commands (i.e. the example from CodePlex assumes that copy is always going to be Ctrl+C for instance) any command the editor knows about (even commands from third party extensions). His only handles the few-ish commands he has setup handling for, though it is likely quite sufficient in his case (and maybe in yours as well).

    Ryan

    Not quite in mine - that's why I posted this question in the first place :) Thanks again
    Tuesday, April 19, 2011 8:49 AM
  • If you go to Tools->Customize and click the Keyboard button on the bottom of the dialog, then change the Use New Shortcut In combo of the Tools->Option page that is opened to 'Text Editor', put your focus into the "Press Shortcut Keys' text box and hit the key combos you are interested in it can tell you if the editor binds the given key combos.  In my profile (General) it looks like the bindings are as follows (I imagine these are likely the same across all profiles):

    Esc -> Edit.SelectionCancel (guidVSStd2k, ECMD_CANCEL)

    Ctrl+Enter -> Edit.LineOpenAbove (guidVSStd2k, ECMD_OPENLINEABOVE)

    Ryan

    Tuesday, April 19, 2011 6:22 PM