diff --git a/CefSharp.Wpf/CefSharp.Wpf.csproj b/CefSharp.Wpf/CefSharp.Wpf.csproj index 99fecfbc03..9cccaeeaa1 100644 --- a/CefSharp.Wpf/CefSharp.Wpf.csproj +++ b/CefSharp.Wpf/CefSharp.Wpf.csproj @@ -78,6 +78,10 @@ + + + + diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index c3a0baf02d..92ee166b18 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -2,10 +2,8 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -using CefSharp.Internals; -using CefSharp.Wpf.Internals; -using Microsoft.Win32.SafeHandles; using System; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -16,10 +14,12 @@ using System.Windows.Interop; using System.Windows.Media; using System.Windows.Threading; -using System.Runtime.CompilerServices; +using CefSharp.Internals; +using CefSharp.Wpf.Internals; using CefSharp.Wpf.Rendering; +using Microsoft.Win32.SafeHandles; -namespace CefSharp.Wpf +namespace CefSharp.Wpf { /// /// ChromiumWebBrowser is the WPF web browser control @@ -34,10 +34,6 @@ public class ChromiumWebBrowser : ContentControl, IRenderWebBrowser, IWpfWebBrow /// private HwndSource source; /// - /// The source hook - /// - private HwndSourceHook sourceHook; - /// /// The tooltip timer /// private DispatcherTimer tooltipTimer; @@ -94,6 +90,10 @@ public class ChromiumWebBrowser : ContentControl, IRenderWebBrowser, IWpfWebBrow /// user attempts to set after browser created) /// private IRequestContext requestContext; + /// + /// Handles keypress and text typing + /// + private IWpfKeyboardHandler wpfKeyboardHandler; /// /// A flag that indicates whether or not the designer is active @@ -207,6 +207,26 @@ public IRequestContext RequestContext /// /// The find handler. public IFindHandler FindHandler { get; set; } + /// + /// Legacy keyboard handler uses WindowProc callback interceptor to handle keypress events. + /// Only use this method if you find problems with the default keyboard handling mechanism. + /// This might be removed in the future. + /// + public bool UseLegacyKeyboardHandler + { + get + { + return wpfKeyboardHandler is WpfLegacyKeyboardHandler; + } + set + { + if (value != UseLegacyKeyboardHandler) { + wpfKeyboardHandler.Dispose(); + wpfKeyboardHandler = CreateWpfKeyboardHandler(this, value); + wpfKeyboardHandler.Setup(source); + } + } + } /// /// Event handler for receiving Javascript console messages being sent from web pages. @@ -470,6 +490,8 @@ private void NoInliningConstructor() BrowserSettings = new BrowserSettings(); BitmapFactory = new InteropBitmapFactory(); + wpfKeyboardHandler = CreateWpfKeyboardHandler(this); + PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality); @@ -584,7 +606,8 @@ protected virtual void Dispose(bool isDisposing) Cef.RemoveDisposable(this); - RemoveSourceHook(); + wpfKeyboardHandler.Dispose(); + source = null; } } @@ -1530,8 +1553,8 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA var notifyDpiChanged = DpiScaleFactor > 0 && !DpiScaleFactor.Equals(matrix.M11); DpiScaleFactor = source.CompositionTarget.TransformToDevice.M11; - sourceHook = SourceHook; - source.AddHook(sourceHook); + + wpfKeyboardHandler.Setup(source); if (notifyDpiChanged && browser != null) { @@ -1566,7 +1589,7 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA } else if (args.OldSource != null) { - RemoveSourceHook(); + wpfKeyboardHandler.Dispose(); var window = args.OldSource.RootVisual as Window; if (window != null) @@ -1618,18 +1641,6 @@ private void OnWindowLocationChanged(object sender, EventArgs e) UpdateBrowserScreenLocation(); } - /// - /// Removes the source hook. - /// - private void RemoveSourceHook() - { - if (source != null && sourceHook != null) - { - source.RemoveHook(sourceHook); - source = null; - } - } - /// /// Create the underlying Browser instance, can be overriden to defer control creation /// The browser will only be created when size > Size(0,0). If you specify a positive @@ -1838,60 +1849,6 @@ private Popup CreatePopup() return newPopup; } - /// - /// WindowProc callback interceptor. Handles Windows messages intended for the source hWnd, and passes them to the - /// contained browser as needed. - /// - /// The source handle. - /// The message. - /// Additional message info. - /// Even more message info. - /// if set to true, the event has already been handled by someone else. - /// IntPtr. - protected virtual IntPtr SourceHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) - { - if (handled) - { - return IntPtr.Zero; - } - - switch ((WM)message) - { - case WM.SYSCHAR: - case WM.SYSKEYDOWN: - case WM.SYSKEYUP: - case WM.KEYDOWN: - case WM.KEYUP: - case WM.CHAR: - case WM.IME_CHAR: - { - if (!IsKeyboardFocused) - { - break; - } - - if (message == (int)WM.SYSKEYDOWN && - wParam.ToInt32() == KeyInterop.VirtualKeyFromKey(Key.F4)) - { - // We don't want CEF to receive this event (and mark it as handled), since that makes it impossible to - // shut down a CefSharp-based app by pressing Alt-F4, which is kind of bad. - return IntPtr.Zero; - } - - if (browser != null) - { - browser.GetHost().SendKeyEvent(message, wParam.CastToInt32(), lParam.CastToInt32()); - handled = true; - } - - break; - } - } - - return IntPtr.Zero; - } - - /// /// Converts a .NET Drag event to a CefSharp MouseEvent /// /// The instance containing the event data. @@ -2005,7 +1962,7 @@ protected override void OnPreviewKeyDown(KeyEventArgs e) { if (!e.Handled) { - OnPreviewKey(e); + wpfKeyboardHandler.HandleKeyPress(e); } base.OnPreviewKeyDown(e); @@ -2020,37 +1977,24 @@ protected override void OnPreviewKeyUp(KeyEventArgs e) { if (!e.Handled) { - OnPreviewKey(e); + wpfKeyboardHandler.HandleKeyPress(e); } base.OnPreviewKeyUp(e); } /// - /// Handles the event. + /// Handles the event. /// - /// The instance containing the event data. - private void OnPreviewKey(KeyEventArgs e) + /// The instance containing the event data. + protected override void OnPreviewTextInput(TextCompositionEventArgs e) { - // As KeyDown and KeyUp bubble, it appears they're being handled before they get a chance to - // trigger the appropriate WM_ messages handled by our SourceHook, so we have to handle these extra keys here. - // Hooking the Tab key like this makes the tab focusing in essence work like - // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. - // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. - if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up - || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right - || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + if (!e.Handled) { - var modifiers = e.GetModifiers(); - var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); - var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); - - if(browser != null) - { - browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); - e.Handled = true; - } + wpfKeyboardHandler.HandleTextInput(e); } + + base.OnPreviewTextInput(e); } /// @@ -2323,6 +2267,18 @@ private bool InternalIsBrowserInitialized() return Interlocked.CompareExchange(ref browserInitialized, 0, 0) == 1; } + private static IWpfKeyboardHandler CreateWpfKeyboardHandler(ChromiumWebBrowser owner, bool useLegacyHandler = false) + { + if (useLegacyHandler) + { + return new WpfLegacyKeyboardHandler(owner); + } + else + { + return new WpfKeyboardHandler(owner); + } + } + //protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) //{ // base.OnManipulationDelta(e); diff --git a/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs b/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs new file mode 100644 index 0000000000..3038f83b33 --- /dev/null +++ b/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs @@ -0,0 +1,20 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; +using System.Windows.Input; +using System.Windows.Interop; + +namespace CefSharp.Wpf.Internals +{ + /// + /// Implement this interface to handle events related to keyboard input. + /// + internal interface IWpfKeyboardHandler : IDisposable + { + void Setup(HwndSource source); + void HandleKeyPress(KeyEventArgs e); + void HandleTextInput(TextCompositionEventArgs e); + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/VirtualKeys.cs b/CefSharp.Wpf/Internals/VirtualKeys.cs new file mode 100644 index 0000000000..34f3b35e7a --- /dev/null +++ b/CefSharp.Wpf/Internals/VirtualKeys.cs @@ -0,0 +1,405 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +namespace CefSharp.Wpf.Internals +{ + /// + /// Enumeration for virtual keys taken from http://www.pinvoke.net/default.aspx/Enums/VirtualKeys.html + /// + public enum VirtualKeys : ushort + { + /// + LeftButton = 0x01, + /// + RightButton = 0x02, + /// + Cancel = 0x03, + /// + MiddleButton = 0x04, + /// + ExtraButton1 = 0x05, + /// + ExtraButton2 = 0x06, + /// + Back = 0x08, + /// + Tab = 0x09, + /// + Clear = 0x0C, + /// + Return = 0x0D, + /// + Shift = 0x10, + /// + Control = 0x11, + /// + Menu = 0x12, + /// + Pause = 0x13, + /// + CapsLock = 0x14, + /// + Kana = 0x15, + /// + Hangeul = 0x15, + /// + Hangul = 0x15, + /// + Junja = 0x17, + /// + Final = 0x18, + /// + Hanja = 0x19, + /// + Kanji = 0x19, + /// + Escape = 0x1B, + /// + Convert = 0x1C, + /// + NonConvert = 0x1D, + /// + Accept = 0x1E, + /// + ModeChange = 0x1F, + /// + Space = 0x20, + /// + Prior = 0x21, + /// + Next = 0x22, + /// + End = 0x23, + /// + Home = 0x24, + /// + Left = 0x25, + /// + Up = 0x26, + /// + Right = 0x27, + /// + Down = 0x28, + /// + Select = 0x29, + /// + Print = 0x2A, + /// + Execute = 0x2B, + /// + Snapshot = 0x2C, + /// + Insert = 0x2D, + /// + Delete = 0x2E, + /// + Help = 0x2F, + /// + N0 = 0x30, + /// + N1 = 0x31, + /// + N2 = 0x32, + /// + N3 = 0x33, + /// + N4 = 0x34, + /// + N5 = 0x35, + /// + N6 = 0x36, + /// + N7 = 0x37, + /// + N8 = 0x38, + /// + N9 = 0x39, + /// + A = 0x41, + /// + B = 0x42, + /// + C = 0x43, + /// + D = 0x44, + /// + E = 0x45, + /// + F = 0x46, + /// + G = 0x47, + /// + H = 0x48, + /// + I = 0x49, + /// + J = 0x4A, + /// + K = 0x4B, + /// + L = 0x4C, + /// + M = 0x4D, + /// + N = 0x4E, + /// + O = 0x4F, + /// + P = 0x50, + /// + Q = 0x51, + /// + R = 0x52, + /// + S = 0x53, + /// + T = 0x54, + /// + U = 0x55, + /// + V = 0x56, + /// + W = 0x57, + /// + X = 0x58, + /// + Y = 0x59, + /// + Z = 0x5A, + /// + LeftWindows = 0x5B, + /// + RightWindows = 0x5C, + /// + Application = 0x5D, + /// + Sleep = 0x5F, + /// + Numpad0 = 0x60, + /// + Numpad1 = 0x61, + /// + Numpad2 = 0x62, + /// + Numpad3 = 0x63, + /// + Numpad4 = 0x64, + /// + Numpad5 = 0x65, + /// + Numpad6 = 0x66, + /// + Numpad7 = 0x67, + /// + Numpad8 = 0x68, + /// + Numpad9 = 0x69, + /// + Multiply = 0x6A, + /// + Add = 0x6B, + /// + Separator = 0x6C, + /// + Subtract = 0x6D, + /// + Decimal = 0x6E, + /// + Divide = 0x6F, + /// + F1 = 0x70, + /// + F2 = 0x71, + /// + F3 = 0x72, + /// + F4 = 0x73, + /// + F5 = 0x74, + /// + F6 = 0x75, + /// + F7 = 0x76, + /// + F8 = 0x77, + /// + F9 = 0x78, + /// + F10 = 0x79, + /// + F11 = 0x7A, + /// + F12 = 0x7B, + /// + F13 = 0x7C, + /// + F14 = 0x7D, + /// + F15 = 0x7E, + /// + F16 = 0x7F, + /// + F17 = 0x80, + /// + F18 = 0x81, + /// + F19 = 0x82, + /// + F20 = 0x83, + /// + F21 = 0x84, + /// + F22 = 0x85, + /// + F23 = 0x86, + /// + F24 = 0x87, + /// + NumLock = 0x90, + /// + ScrollLock = 0x91, + /// + NEC_Equal = 0x92, + /// + Fujitsu_Jisho = 0x92, + /// + Fujitsu_Masshou = 0x93, + /// + Fujitsu_Touroku = 0x94, + /// + Fujitsu_Loya = 0x95, + /// + Fujitsu_Roya = 0x96, + /// + LeftShift = 0xA0, + /// + RightShift = 0xA1, + /// + LeftControl = 0xA2, + /// + RightControl = 0xA3, + /// + LeftMenu = 0xA4, + /// + RightMenu = 0xA5, + /// + BrowserBack = 0xA6, + /// + BrowserForward = 0xA7, + /// + BrowserRefresh = 0xA8, + /// + BrowserStop = 0xA9, + /// + BrowserSearch = 0xAA, + /// + BrowserFavorites = 0xAB, + /// + BrowserHome = 0xAC, + /// + VolumeMute = 0xAD, + /// + VolumeDown = 0xAE, + /// + VolumeUp = 0xAF, + /// + MediaNextTrack = 0xB0, + /// + MediaPrevTrack = 0xB1, + /// + MediaStop = 0xB2, + /// + MediaPlayPause = 0xB3, + /// + LaunchMail = 0xB4, + /// + LaunchMediaSelect = 0xB5, + /// + LaunchApplication1 = 0xB6, + /// + LaunchApplication2 = 0xB7, + /// + OEM1 = 0xBA, + /// + OEMPlus = 0xBB, + /// + OEMComma = 0xBC, + /// + OEMMinus = 0xBD, + /// + OEMPeriod = 0xBE, + /// + OEM2 = 0xBF, + /// + OEM3 = 0xC0, + /// + OEM4 = 0xDB, + /// + OEM5 = 0xDC, + /// + OEM6 = 0xDD, + /// + OEM7 = 0xDE, + /// + OEM8 = 0xDF, + /// + OEMAX = 0xE1, + /// + OEM102 = 0xE2, + /// + ICOHelp = 0xE3, + /// + ICO00 = 0xE4, + /// + ProcessKey = 0xE5, + /// + ICOClear = 0xE6, + /// + Packet = 0xE7, + /// + OEMReset = 0xE9, + /// + OEMJump = 0xEA, + /// + OEMPA1 = 0xEB, + /// + OEMPA2 = 0xEC, + /// + OEMPA3 = 0xED, + /// + OEMWSCtrl = 0xEE, + /// + OEMCUSel = 0xEF, + /// + OEMATTN = 0xF0, + /// + OEMFinish = 0xF1, + /// + OEMCopy = 0xF2, + /// + OEMAuto = 0xF3, + /// + OEMENLW = 0xF4, + /// + OEMBackTab = 0xF5, + /// + ATTN = 0xF6, + /// + CRSel = 0xF7, + /// + EXSel = 0xF8, + /// + EREOF = 0xF9, + /// + Play = 0xFA, + /// + Zoom = 0xFB, + /// + Noname = 0xFC, + /// + PA1 = 0xFD, + /// + OEMClear = 0xFE + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs b/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs new file mode 100644 index 0000000000..9986d5f155 --- /dev/null +++ b/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs @@ -0,0 +1,100 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System.Windows.Input; +using System.Windows.Interop; + +namespace CefSharp.Wpf.Internals +{ + internal class WpfKeyboardHandler : IWpfKeyboardHandler + { + /// + /// The owner browser instance + /// + private readonly ChromiumWebBrowser owner; + + public WpfKeyboardHandler(ChromiumWebBrowser owner) + { + this.owner = owner; + } + + public void Setup(HwndSource source) + { + // nothing to do here + } + + public void Dispose() + { + // nothing to do here + } + + public void HandleKeyPress(KeyEventArgs e) + { + var browser = owner.GetBrowser(); + var key = e.SystemKey != Key.None ? e.SystemKey : e.Key; + if (browser != null) + { + int message; + int virtualKey = 0; + var modifiers = e.GetModifiers(); + + switch (key) + { + case Key.LeftAlt: + case Key.RightAlt: + virtualKey = (int) VirtualKeys.Menu; + break; + + case Key.LeftCtrl: + case Key.RightCtrl: + virtualKey = (int) VirtualKeys.Control; + break; + + case Key.LeftShift: + case Key.RightShift: + virtualKey = (int) VirtualKeys.Shift; + break; + + default: + virtualKey = KeyInterop.VirtualKeyFromKey(key); + break; + } + + if (e.IsDown) + { + message = (int)(e.SystemKey != Key.None ? WM.SYSKEYDOWN : WM.KEYDOWN); + } else { + message = (int)(e.SystemKey != Key.None ? WM.SYSKEYUP : WM.KEYUP); + } + + browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); + } + + // Hooking the Tab key like this makes the tab focusing in essence work like + // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. + // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. + // Prevent keyboard navigation using arrows and home and end keys + if (key == Key.Tab || key == Key.Home || key == Key.End || key == Key.Up + || key == Key.Down || key == Key.Left || key == Key.Right + || (key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + { + e.Handled = true; + } + } + + public void HandleTextInput(TextCompositionEventArgs e) + { + var browser = owner.GetBrowser(); + if (browser != null) + { + var browserHost = browser.GetHost(); + for (int i = 0; i < e.Text.Length; i++) + { + browserHost.SendKeyEvent((int)WM.CHAR, e.Text[i], 0); + } + e.Handled = true; + } + } + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs b/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs new file mode 100644 index 0000000000..7e9ae2002c --- /dev/null +++ b/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs @@ -0,0 +1,136 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; +using System.Windows.Input; +using System.Windows.Interop; +using CefSharp.Internals; + +namespace CefSharp.Wpf.Internals +{ + internal class WpfLegacyKeyboardHandler : IWpfKeyboardHandler + { + /// + /// The source hook + /// + private HwndSourceHook sourceHook; + + /// + /// The source + /// + private HwndSource source; + + /// + /// The owner browser instance + /// + private readonly ChromiumWebBrowser owner; + + public WpfLegacyKeyboardHandler(ChromiumWebBrowser owner) + { + this.owner = owner; + } + + public void Setup(HwndSource source) + { + this.source = source; + if (source != null) + { + sourceHook = SourceHook; + source.AddHook(SourceHook); + } + } + + public void Dispose() + { + if (source != null && sourceHook != null) + { + source.RemoveHook(sourceHook); + } + source = null; + } + + /// + /// WindowProc callback interceptor. Handles Windows messages intended for the source hWnd, and passes them to the + /// contained browser as needed. + /// + /// The source handle. + /// The message. + /// Additional message info. + /// Even more message info. + /// if set to true, the event has already been handled by someone else. + /// IntPtr. + protected IntPtr SourceHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (handled) + { + return IntPtr.Zero; + } + + switch ((WM)message) + { + case WM.SYSCHAR: + case WM.SYSKEYDOWN: + case WM.SYSKEYUP: + case WM.KEYDOWN: + case WM.KEYUP: + case WM.CHAR: + case WM.IME_CHAR: + { + if (!owner.IsKeyboardFocused) + { + break; + } + + if (message == (int)WM.SYSKEYDOWN && + wParam.ToInt32() == KeyInterop.VirtualKeyFromKey(Key.F4)) + { + // We don't want CEF to receive this event (and mark it as handled), since that makes it impossible to + // shut down a CefSharp-based app by pressing Alt-F4, which is kind of bad. + return IntPtr.Zero; + } + + var browser = owner.GetBrowser(); + if (browser != null) + { + browser.GetHost().SendKeyEvent(message, wParam.CastToInt32(), lParam.CastToInt32()); + handled = true; + } + + break; + } + } + + return IntPtr.Zero; + } + + public void HandleKeyPress(KeyEventArgs e) + { + // As KeyDown and KeyUp bubble, it appears they're being handled before they get a chance to + // trigger the appropriate WM_ messages handled by our SourceHook, so we have to handle these extra keys here. + // Hooking the Tab key like this makes the tab focusing in essence work like + // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. + // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. + if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up + || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right + || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + { + var modifiers = e.GetModifiers(); + var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); + var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); + var browser = owner.GetBrowser(); + + if (browser != null) + { + browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); + e.Handled = true; + } + } + } + + public void HandleTextInput(TextCompositionEventArgs e) + { + // nothing to do here + } + } +}