locked
IWpfTextView MouseHover popup window does not capture all keystrokes RRS feed

  • Question

  • I am experimenting with the MouseHover event of IWpfTextView. part of a MouseProcessor extension. The plan is that in the visual studio editor, when the mouse hovers over specific text content, popup a window beneath the text. In this case, the popup window would contain a TextBox and a Button. Click the Button to get a File open dialog.

    The problem is, the TextBox within the popup window does not capture the backspace and arrow left/right cursor keys. Those keystrokes are handled by the visual studio editor window. But my textbox in the popup window does capture keyboard character intput and mouse clicks.

    Is extension code allowed to popup a WPF window in this manner? How to make it so the backspace and arrow keystrokes are captured by the popup windows?

    Here is the xaml code of the popup window.  Using WindowStyle=None and AllowsTransparency=True to get the popup context window effect.

    <Window x:Class="AutoCoder.MouseExtension.Windows.SolutionPathPopupWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            WindowStyle="None"
            ResizeMode="CanResizeWithGrip"
            AllowsTransparency="True"
            Height="80" Width="250">
    
      <Window.ContextMenu>
        <ContextMenu>
          <MenuItem Header="Exit" Name="mnuExit" Click="mnuExit_Click"/>
        </ContextMenu>
      </Window.ContextMenu>
    
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="auto"></RowDefinition>
          <RowDefinition Height="auto"></RowDefinition>
          <RowDefinition Height="auto"></RowDefinition>
          <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
          <TextBlock>Solution path:</TextBlock> 
          <TextBox x:Name="tbSolutionPath" MinWidth="150"></TextBox>
          <Button x:Name="butSolutionPath" Click="butSolutionPath_Click">...</Button>
        </StackPanel>
      </Grid>
    </Window>
    

    Friday, April 6, 2012 11:48 PM

Answers

  • Hmmm, well the problem is that the keys you are seeing aren't being translated to VS commands, so they end up being given to WPF and surface as KeyUp/KeyDown type events.  The keys you AREN'T seeing are mapping to VS commands and being sent through the VS command route, which has nothing to do with nor any knowledge of WPF.  The editor gets these commands as it is plugged into the VS command route.

    It looks like you can use that tracking service, it is a MEF exported service so you can import it from MEF or explicitly request it from the ComponentModelHost service.  Once you have it it looks like you want to call BeginTrackingKeyboard passing in an HWND you want the messages delivered to.  This would likely be the HWND that underlies your popup window (WindowInteropHelper can be used to get the HWND for a WPF window).  It also wants you to give it a collection of messages you want to handle, you likely want something like WM_KEYDOWN/WM_KEYUP.

    Note:  It is very (very, very, very, very) important you ALWAYS call EndTrackingKeyboard when you lose focus/dismiss your UI.  What this component does it take over the entire VS message loop, so while it is active every windows message is going to it not the main VS message loop. If you don't yield back the message processing VS will basically be dead in the water as all messages have been diverted. 

    There are a whole raft of things to worry about if you go down this route, off the top of my head:

    1:  VS Idle tasks are NOT running while a component has hijacked the message loop like this.

    2:  Keybindings will NOT be mapped becuase that happens in our main message loop, which you have hijacked with this component.  So Ctrl-O won't work if focus is in your dialog (for instance).

    3:  There may be some wackiness with users going into menu mode (i.e. Alt-F) because some state tracking/processing for that transition happens in our main message loop.

    4:  You can not show a shell context menu (IVsUIShell::ShowContextMenu) while you are doing this as it needs to become a hijacking component like you are, and there is only one allowed at a time and they do not nest.

    Ryan

    • Marked as answer by Steve Richter Saturday, April 7, 2012 12:34 PM
    Saturday, April 7, 2012 12:54 AM

All replies

  • maybe use IWpfKeyboardTrackingService ?   How to use that interface?

    http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.language.intellisense.iwpfkeyboardtrackingservice.aspx

    "... Keyboard tracking is necessary because some hosts, such as VisualStudio, do their own keyboard handling, causing inconsistent behavior with WPF elements even when they have keyboard focus. By tracking the keyboard, all keyboard events will be routed to WPF first, giving focused WPF controls a chance to handle keyboard events in a consistent manner. ... "

    Saturday, April 7, 2012 12:29 AM
  • Hmmm, well the problem is that the keys you are seeing aren't being translated to VS commands, so they end up being given to WPF and surface as KeyUp/KeyDown type events.  The keys you AREN'T seeing are mapping to VS commands and being sent through the VS command route, which has nothing to do with nor any knowledge of WPF.  The editor gets these commands as it is plugged into the VS command route.

    It looks like you can use that tracking service, it is a MEF exported service so you can import it from MEF or explicitly request it from the ComponentModelHost service.  Once you have it it looks like you want to call BeginTrackingKeyboard passing in an HWND you want the messages delivered to.  This would likely be the HWND that underlies your popup window (WindowInteropHelper can be used to get the HWND for a WPF window).  It also wants you to give it a collection of messages you want to handle, you likely want something like WM_KEYDOWN/WM_KEYUP.

    Note:  It is very (very, very, very, very) important you ALWAYS call EndTrackingKeyboard when you lose focus/dismiss your UI.  What this component does it take over the entire VS message loop, so while it is active every windows message is going to it not the main VS message loop. If you don't yield back the message processing VS will basically be dead in the water as all messages have been diverted. 

    There are a whole raft of things to worry about if you go down this route, off the top of my head:

    1:  VS Idle tasks are NOT running while a component has hijacked the message loop like this.

    2:  Keybindings will NOT be mapped becuase that happens in our main message loop, which you have hijacked with this component.  So Ctrl-O won't work if focus is in your dialog (for instance).

    3:  There may be some wackiness with users going into menu mode (i.e. Alt-F) because some state tracking/processing for that transition happens in our main message loop.

    4:  You can not show a shell context menu (IVsUIShell::ShowContextMenu) while you are doing this as it needs to become a hijacking component like you are, and there is only one allowed at a time and they do not nest.

    Ryan

    • Marked as answer by Steve Richter Saturday, April 7, 2012 12:34 PM
    Saturday, April 7, 2012 12:54 AM
  • Hmmm, well the problem is that the keys you are seeing aren't being translated to VS commands, so they end up being given to WPF and surface as KeyUp/KeyDown type events.  The keys you AREN'T seeing are mapping to VS commands and being sent through the VS command route, which has nothing to do with nor any knowledge of WPF.  The editor gets these commands as it is plugged into the VS command route.

    It looks like you can use that tracking service, it is a MEF exported service so you can import it from MEF or explicitly request it from the ComponentModelHost service.  Once you have it it looks like you want to call BeginTrackingKeyboard passing in an HWND you want the messages delivered to.  This would likely be the HWND that underlies your popup window (WindowInteropHelper can be used to get the HWND for a WPF window).  It also wants you to give it a collection of messages you want to handle, you likely want something like WM_KEYDOWN/WM_KEYUP.

    Note:  It is very (very, very, very, very) important you ALWAYS call EndTrackingKeyboard when you lose focus/dismiss your UI.  What this component does it take over the entire VS message loop, so while it is active every windows message is going to it not the main VS message loop. If you don't yield back the message processing VS will basically be dead in the water as all messages have been diverted. 

    Huge help Ryan. Thank you.

    IWpfKeyboardTrackingService did the job for me. Had to make sure to call it within the GotKeyboardFocus and LostKeyboardFocus events of the TextBox of the WPF Window. ( for some reason, LostFocus on the TextBox is not called when you click outside the WPF window. )

    [Export(typeof(IMouseProcessorProvider))] [Name("xml mouse processor")] [ContentType("XML")] [TextViewRole(PredefinedTextViewRoles.Interactive)] public class XmlMouseProvider : IMouseProcessorProvider { public IMouseProcessor GetAssociatedProcessor(IWpfTextView WpfTextView) { ... var mh = this.ServiceProvider.GetService(typeof(SComponentModel)) as IComponentModel; if (mh != null) { var ts = mh.GetService<IWpfKeyboardTrackingService>(); } ... } } public partial class SolutionPathPopupWindow : Window { private const uint WM_KEYDOWN = 0x0100; private const uint WM_KEYUP = 0x0101; private const uint WM_CHAR = 0x0102; private const uint WM_SYSKEYDOWN = 0x0104; public IWpfKeyboardTrackingService KeyboardTrackingService { get; set; } bool CurrentlyTrackingKeyboard { get; set; } ...

    void tbSolutionPath_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { if (this.KeyboardTrackingService != null) { Trace.WriteLine("Got keyboard focus"); if (this.CurrentlyTrackingKeyboard == false) { List<uint> msgs = new List<uint>(); msgs.Add(WM_KEYDOWN); msgs.Add(WM_KEYUP); var interHelper = new WindowInteropHelper(this); this.KeyboardTrackingService.BeginTrackingKeyboard(interHelper.Handle, msgs); this.CurrentlyTrackingKeyboard = true; Trace.WriteLine("call BeginTrackingKeyboard"); } } } void tbSolutionPath_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { if (this.CurrentlyTrackingKeyboard == true) { this.KeyboardTrackingService.EndTrackingKeyboard(); this.CurrentlyTrackingKeyboard = false; Trace.WriteLine("call EndTrackingKeyboard"); } } }


    Saturday, April 7, 2012 4:50 PM