Last active
October 31, 2016 10:23
-
-
Save OmerRaviv/69dfd80fd5c13d3caef1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Stops keystrokes on a WPF Window or Adornment from propagating to the Visual Studio code editor. | |
/// | |
/// "The reason that keys like Backspace and Delete do not work in your WPF windows/Adornments is due to Visual Studio's usage of IOleComponentManager and IOleComponent. | |
/// Visual Studio and WinForms both use IOleComponent as a way of tracking the active component in the application. | |
/// | |
/// WPF does not implement IOleComponent or use the IOleComponentManager for its windows. This means that when your WPF window is active, Visual Studio doesn't know that its | |
/// primary component should not be processing command keybindings. Since "Backspace", "Delete", and several other keys are bound to commands for the text editor, | |
/// Visual Studio continues processing those keystrokes as command bindings." | |
/// | |
/// Adapted from code originally received from Microsoft as an answer to Omer's Connect ticket, | |
/// https://connect.microsoft.com/VisualStudio/feedback/details/549866/msdn-visual-studio-extensibility-forum-backspace-tab-and-enter-key-are-not-captured-in-wpf-window-which-exist-in-a-package | |
/// </summary> | |
public class KeystrokeThief | |
{ | |
private readonly IOleComponentManager _manager; | |
private uint _componentCookie; | |
public KeystrokeThief(IOleComponentManager manager) | |
{ | |
if (manager == null) | |
{ | |
throw new ArgumentNullException("manager"); | |
} | |
_manager = manager; | |
} | |
public bool IsStealing { get; private set; } | |
private void RegisterDummyComponent() | |
{ | |
var component = new EmptyOleComponent(); | |
var regInfo = new OLECRINFO { grfcrf = 0U, grfcadvf = 0U, uIdleTimeInterval = 0U }; | |
regInfo.cbSize = (uint)Marshal.SizeOf(regInfo); | |
int result = _manager.FRegisterComponent(component, new[] { regInfo }, out _componentCookie); | |
if (!ErrorHandler.Succeeded(result)) | |
{ | |
throw new InvalidOperationException("Could not register the OleComponent"); | |
} | |
_manager.FOnComponentActivate(_componentCookie); | |
} | |
private void UnregisterDummyComponent() | |
{ | |
_manager.FRevokeComponent(_componentCookie); | |
_componentCookie = 0; | |
} | |
public void StartStealing() | |
{ | |
RegisterDummyComponent(); | |
IsStealing = true; | |
} | |
public void StopStealing() | |
{ | |
UnregisterDummyComponent(); | |
IsStealing = false; | |
} | |
/// <summary> | |
/// The default IOleComponent for Visual Studio will translate keystrokes into | |
/// commands for the active IVsWindowFrame. By activating this component when this window is active, | |
/// it will allow normal keyboard processing without command keybindings. | |
/// </summary> | |
private class EmptyOleComponent : IOleComponent | |
{ | |
public int FContinueMessageLoop(uint uReason, IntPtr pvLoopData, MSG[] pMsgPeeked) | |
{ | |
return VSConstants.S_OK; | |
} | |
public int FDoIdle(uint grfidlef) | |
{ | |
return VSConstants.S_OK; | |
} | |
public int FPreTranslateMessage(MSG[] pMsg) | |
{ | |
return VSConstants.S_OK; | |
} | |
public int FQueryTerminate(int fPromptUser) | |
{ | |
return 1; | |
} | |
public int FReserved1(uint dwReserved, uint message, IntPtr wParam, IntPtr lParam) | |
{ | |
return VSConstants.S_OK; | |
} | |
public IntPtr HwndGetWindow(uint dwWhich, uint dwReserved) | |
{ | |
return IntPtr.Zero; | |
} | |
public void OnActivationChange(IOleComponent pic, int fSameComponent, OLECRINFO[] pcrinfo, int fHostIsActivating, OLECHOSTINFO[] pchostinfo, uint dwReserved) | |
{ | |
} | |
public void OnAppActivate(int fActive, uint dwOtherThreadID) | |
{ | |
} | |
public void OnEnterState(uint uStateID, int fEnter) | |
{ | |
} | |
public void OnLoseActivation() | |
{ | |
} | |
public void Terminate() | |
{ | |
} | |
} | |
/// <summary> | |
/// A WPF window hosted inside Visual Studio. | |
/// This class prevents Visual Studio from stealing backspace, arrow keys, shift/tab and Enter key-presses from our window. | |
/// https://connect.microsoft.com/VisualStudio/feedback/details/549866/msdn-visual-studio-extensibility-forum-backspace-tab-and-enter-key-are-not-captured-in-wpf-window-which-exist-in-a-package?wa=wsignin1.0#details | |
/// </summary> | |
public class OzCodeModelessWindow : Window | |
{ | |
private readonly KeystrokeThief _keystrokeThief; | |
// This constructor needs to be public, because WPF can't find it otherwise. | |
// ReSharper disable MemberCanBeProtected.Global | |
public OzCodeModelessWindow() | |
// ReSharper restore MemberCanBeProtected.Global | |
{ | |
var componentManager = VisualStudioServices.GetService<SOleComponentManager, IOleComponentManager>(); | |
_keystrokeThief = new KeystrokeThief(componentManager); | |
new System.Windows.Interop.WindowInteropHelper(this).Owner = new IntPtr(VisualStudioServices.Dte.MainWindow.HWnd); | |
} | |
protected override void OnActivated(EventArgs e) | |
{ | |
_keystrokeThief.StartStealing(); | |
base.OnActivated(e); | |
} | |
protected override void OnClosed(EventArgs e) | |
{ | |
_keystrokeThief.StopStealing(); | |
base.OnClosed(e); | |
} | |
} | |
public class ExampleAdornment : UserControl | |
{ | |
private readonly KeystrokeThief _thief = new KeystrokeThief(VisualStudioServices.OleComponentManager); | |
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) | |
{ | |
base.OnGotKeyboardFocus(e); | |
if (IsKeyboardFocusWithin && (_thief.IsStealing == false)) | |
{ | |
_thief.StartStealing(); | |
} | |
} | |
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) | |
{ | |
base.OnLostKeyboardFocus(e); | |
if (_thief.IsStealing) _thief.StopStealing(); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment