diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj index 95ca4e3e9..340e32459 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj @@ -91,6 +91,7 @@ + diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj index 688fcadc2..3ed70b8f7 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj @@ -91,6 +91,7 @@ + diff --git a/Fluent.Ribbon/Internal/FocusWrapper.cs b/Fluent.Ribbon/Internal/FocusWrapper.cs new file mode 100644 index 000000000..3538390e6 --- /dev/null +++ b/Fluent.Ribbon/Internal/FocusWrapper.cs @@ -0,0 +1,54 @@ +namespace Fluent.Internal +{ + using System; + using System.Windows; + using System.Windows.Input; + + internal class FocusWrapper + { + private readonly IInputElement inputElement; + private readonly IntPtr handle; + + private FocusWrapper(IInputElement inputElement) + { + this.inputElement = inputElement; + } + + private FocusWrapper(IntPtr handle) + { + this.handle = handle; + } + + public void Focus() + { + if (this.inputElement != null) + { + this.inputElement.Focus(); + return; + } + + if (this.handle != IntPtr.Zero) + { + NativeMethods.SetFocus(this.handle); + return; + } + } + + public static FocusWrapper GetWrapperForCurrentFocus() + { + if (Keyboard.FocusedElement != null) + { + return new FocusWrapper(Keyboard.FocusedElement); + } + + var handle = NativeMethods.GetFocus(); + + if (handle != IntPtr.Zero) + { + return new FocusWrapper(handle); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Fluent.Ribbon/Internal/NativeMethods.cs b/Fluent.Ribbon/Internal/NativeMethods.cs index 4d9236a61..0187f8485 100644 --- a/Fluent.Ribbon/Internal/NativeMethods.cs +++ b/Fluent.Ribbon/Internal/NativeMethods.cs @@ -207,6 +207,12 @@ internal static void PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lP [DllImport("user32.dll")] internal static extern int SetWindowLong(IntPtr hWnd, GWL nIndex, WS dwNewLong); + [DllImport("user32.dll")] + internal static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + internal static extern IntPtr SetFocus(IntPtr hWnd); + /// Add and remove a native WindowStyle from the HWND. /// A HWND for a window. /// The styles to be removed. These can be bitwise combined. diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index 0eaf26638..08ebb5bf4 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -25,7 +25,8 @@ internal class KeyTipService // Is KeyTips Actived now private KeyTipAdorner activeAdornerChain; // This element must be remembered to restore it - IInputElement backUpFocusedElement; + FocusWrapper backUpFocusedControl; + // Window where we attached private Window window; @@ -117,10 +118,7 @@ public void Attach() // Hookup non client area messages this.attachedHwndSource = (HwndSource)PresentationSource.FromVisual(this.window); - if (this.attachedHwndSource != null) - { - this.attachedHwndSource.AddHook(this.WindowProc); - } + this.attachedHwndSource?.AddHook(this.WindowProc); } /// @@ -146,12 +144,8 @@ public void Detach() this.window = null; } - // Hookup non client area messages - if (this.attachedHwndSource != null - && this.attachedHwndSource.IsDisposed == false) - { - this.attachedHwndSource.RemoveHook(this.WindowProc); - } + // Unhook non client area messages + this.attachedHwndSource?.RemoveHook(this.WindowProc); } // Window's messages hook up @@ -210,11 +204,6 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) else if (this.activeAdornerChain != null && this.activeAdornerChain.IsAdornerChainAlive) { - // Focus ribbon - this.backUpFocusedElement = Keyboard.FocusedElement; - this.ribbon.Focusable = true; - //this.ribbon.Focus(); - this.activeAdornerChain.Terminate(); } else @@ -228,6 +217,7 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) this.activeAdornerChain.ActiveKeyTipAdorner.Back(); this.ClearUserInput(); e.Handled = true; + return; } else { @@ -238,6 +228,15 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) return; } + var actualKey = e.Key == Key.System ? e.SystemKey : e.Key; + var isKeyRealInput = ((actualKey >= Key.A && actualKey <= Key.Z) || (actualKey >= Key.D0 && actualKey <= Key.D9) || (actualKey >= Key.NumPad0 && actualKey <= Key.NumPad9)); + + // Don't do anything and let WPF handle the rest + if (isKeyRealInput == false) + { + return; + } + // Should we show the keytips and immediately react to key? if (this.activeAdornerChain == null || this.activeAdornerChain.IsAdornerChainAlive == false @@ -251,25 +250,28 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) return; } - string previousInput = this.currentUserInput; - - this.currentUserInput += keyConverter.ConvertToString(e.Key == Key.System ? e.SystemKey : e.Key); + var previousInput = this.currentUserInput; + this.currentUserInput += keyConverter.ConvertToString(actualKey); // If no key tips match the current input, continue with the previously entered and still correct keys. if (this.activeAdornerChain.ActiveKeyTipAdorner.ContainsKeyTipStartingWith(this.currentUserInput) == false) { this.currentUserInput = previousInput; System.Media.SystemSounds.Beep.Play(); + e.Handled = true; + return; } else if (this.activeAdornerChain.ActiveKeyTipAdorner.Forward(this.currentUserInput, true)) { this.ClearUserInput(); e.Handled = true; + return; } else { this.activeAdornerChain.ActiveKeyTipAdorner.FilterKeyTips(this.currentUserInput); e.Handled = true; + return; } } } @@ -315,13 +317,8 @@ private void ClearUserInput() private void RestoreFocus() { - if (this.backUpFocusedElement != null) - { - this.backUpFocusedElement.Focus(); - this.backUpFocusedElement = null; // Release the reference, so GC can work - } - - this.ribbon.Focusable = false; + this.backUpFocusedControl?.Focus(); + this.backUpFocusedControl = null; } private void OnAdornerChainTerminated(object sender, EventArgs e) @@ -343,28 +340,20 @@ private void OnDelayedShow(object sender, EventArgs e) private void ShowImmediatly() { - this.timer.Stop(); - this.backUpFocusedElement = Keyboard.FocusedElement; - - // Focus ribbon - this.ribbon.Focusable = true; - //this.ribbon.Focus(); - this.Show(); } private void ShowDelayed() { - if (this.activeAdornerChain != null) - { - this.activeAdornerChain.Terminate(); - } + this.activeAdornerChain?.Terminate(); this.timer.Start(); } private void Show() { + this.timer.Stop(); + // Check whether the window is // - still present (prevents exceptions when window is closed by system commands) // - still active (prevents keytips showing during Alt-Tab'ing) @@ -375,6 +364,11 @@ private void Show() return; } + this.backUpFocusedControl = FocusWrapper.GetWrapperForCurrentFocus(); + + // Focus ribbon + this.ribbon.Focus(); + this.ClearUserInput(); this.activeAdornerChain = new KeyTipAdorner(this.ribbon, this.ribbon, null); @@ -435,9 +429,10 @@ private ApplicationMenu GetApplicationMenu() private void DirectlyForwardToSpecialControl(DependencyObject specialControl) { var keys = KeyTip.GetKeys(specialControl); + if (string.IsNullOrEmpty(keys) == false) { - this.activeAdornerChain.Forward(KeyTip.GetKeys(specialControl), false); + this.activeAdornerChain.Forward(keys, false); } else {