From fc0d97e1cc9c03f076037adeb05b0445c90247ef Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 28 Jan 2016 18:28:25 +0100 Subject: [PATCH 01/19] First step for KeyTipAdorner and KeyTipService refactoring Some special cases from OnPreviewKeyDown in KeyTipAdorner are not implemented yet --- Fluent.Ribbon/Adorners/KeyTipAdorner.cs | 241 ++++-------------------- Fluent.Ribbon/Metro/Native/Constants.cs | 3 +- Fluent.Ribbon/Services/KeyTipService.cs | 48 ++--- 3 files changed, 62 insertions(+), 230 deletions(-) diff --git a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs index 07eac8755..b1ad10722 100644 --- a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs +++ b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs @@ -46,24 +46,15 @@ internal class KeyTipAdorner : Adorner private readonly KeyTipAdorner parentAdorner; KeyTipAdorner childAdorner; - // Focused element - private IInputElement focusedElement; - private readonly Visibility[] backupedVisibilities; private readonly UIElement keyTipElementContainer; // Is this adorner attached to the adorned element? private bool attached; - private HwndSource attachedHwndSource; - - // Current entered chars - string enteredKeys = ""; // Designate that this adorner is terminated private bool terminated; - private DispatcherTimer timerFocusTracking; - private AdornerLayer adornerLayer; #endregion @@ -245,94 +236,16 @@ public void Attach() return; } - // Focus current adorned element - // Keyboard.Focus(adornedElement); - this.focusedElement = Keyboard.FocusedElement; - - if (this.focusedElement != null) - { - this.Log("Focus Attached to {0}", this.focusedElement); - this.focusedElement.LostKeyboardFocus += this.OnFocusLost; - this.focusedElement.PreviewKeyDown += this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput += this.OnFocusedElementPreviewTextInput; - } - else - { - this.Log("[!] Focus Setup Failed"); - } - - GetTopLevelElement(this.oneOfAssociatedElements).PreviewMouseDown += this.OnInputActionOccured; - - // Clears previous user input - this.enteredKeys = ""; - this.FilterKeyTips(this.enteredKeys); + this.FilterKeyTips(string.Empty); // Show this adorner this.adornerLayer.Add(this); - // Hookup window activation - this.attachedHwndSource = ((HwndSource)PresentationSource.FromVisual(this.oneOfAssociatedElements)); - if (this.attachedHwndSource != null) - { - this.attachedHwndSource.AddHook(this.WindowProc); - } - - // Start timer to track focus changing - if (this.timerFocusTracking == null) - { - this.timerFocusTracking = new DispatcherTimer(DispatcherPriority.ApplicationIdle, Dispatcher.CurrentDispatcher) - { - Interval = TimeSpan.FromMilliseconds(50) - }; - this.timerFocusTracking.Tick += this.OnTimerFocusTrackingTick; - } - - this.timerFocusTracking.Start(); - this.attached = true; this.Log("Attach end"); } - private void OnTimerFocusTrackingTick(object sender, EventArgs e) - { - if (this.focusedElement == Keyboard.FocusedElement) - { - return; - } - - this.Log("Focus is changed, but focus lost is not occured"); - - if (this.focusedElement != null) - { - this.focusedElement.LostKeyboardFocus -= this.OnFocusLost; - this.focusedElement.PreviewKeyDown -= this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput -= this.OnFocusedElementPreviewTextInput; - } - - this.focusedElement = Keyboard.FocusedElement; - - if (this.focusedElement != null) - { - this.focusedElement.LostKeyboardFocus += this.OnFocusLost; - this.focusedElement.PreviewKeyDown += this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput += this.OnFocusedElementPreviewTextInput; - } - } - - // Window's messages hook up - private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - // Check whether window is deactivated (wParam == 0) - if ((msg == 6) && (wParam == IntPtr.Zero) && (this.attached)) - { - this.Log("The host window is deactivated, keytips will be terminated"); - this.Terminate(); - } - - return IntPtr.Zero; - } - private void OnDelayAttach(object sender, EventArgs args) { this.Log("Delay attach (control loaded)"); @@ -357,34 +270,13 @@ public void Detach() this.Log("Detach Begin"); - // Remove window hookup - if ((this.attachedHwndSource != null) && (!this.attachedHwndSource.IsDisposed)) - { - // Crashes in a few time if invoke immediately ??? - this.AdornedElement.Dispatcher.BeginInvoke((System.Threading.ThreadStart)(() => this.attachedHwndSource.RemoveHook(this.WindowProc))); - } - // Maybe adorner awaiting attaching, cancel it this.oneOfAssociatedElements.Loaded -= this.OnDelayAttach; - if (this.focusedElement != null) - { - this.focusedElement.LostKeyboardFocus -= this.OnFocusLost; - this.focusedElement.PreviewKeyDown -= this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput -= this.OnFocusedElementPreviewTextInput; - this.focusedElement = null; - } - - GetTopLevelElement(this.oneOfAssociatedElements).PreviewMouseDown -= this.OnInputActionOccured; - // Show this adorner this.adornerLayer.Remove(this); - // Clears previous user input - this.enteredKeys = ""; - this.attached = false; - // Stop timer to track focus changing - this.timerFocusTracking.Stop(); + this.attached = false; this.Log("Detach End"); } @@ -429,95 +321,33 @@ public void Terminate() #region Event Handlers - [SuppressMessage("Microsoft.Maintainability", "CA1502")] - private void OnPreviewKeyDown(object sender, KeyEventArgs e) - { - this.Log("Key Down {0} ({1})", e.Key, e.OriginalSource); - - if (e.IsRepeat - || this.Visibility == Visibility.Hidden) - { - return; - } - - if ((!(this.AdornedElement is ContextMenu) && (!(this.AdornedElement is MenuItem))) && - ((e.Key == Key.Left) || (e.Key == Key.Right) || (e.Key == Key.Up) || (e.Key == Key.Down) || - (e.Key == Key.Enter) || (e.Key == Key.Tab))) - { - this.Visibility = Visibility.Hidden; - } - else if (e.Key == Key.Escape) - { - this.Back(); - e.Handled = true; - } - } - - private void OnFocusedElementPreviewTextInput(object sender, TextCompositionEventArgs e) - { - var keyToSearch = this.enteredKeys + e.Text; - - if (this.IsElementsStartWith(keyToSearch)) - { - this.enteredKeys += e.Text; - - var element = this.TryGetElement(this.enteredKeys); - - if (element != null) - { - this.Forward(element); - } - else - { - this.FilterKeyTips(this.enteredKeys); - } - - e.Handled = true; - } - else - { - System.Media.SystemSounds.Beep.Play(); - } - } - - private void OnInputActionOccured(object sender, RoutedEventArgs e) - { - if (!this.attached) - { - return; - } - - this.Log("Input Action, Keystips will be terminated"); - this.Terminate(); - } - - private void OnFocusLost(object sender, RoutedEventArgs e) - { - if (!this.attached) - { - return; - } - - this.Log("Focus Lost"); - - var previousFocusedElementElement = this.focusedElement; - this.focusedElement.LostKeyboardFocus -= this.OnFocusLost; - this.focusedElement.PreviewKeyDown -= this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput -= this.OnFocusedElementPreviewTextInput; - this.focusedElement = Keyboard.FocusedElement; - - if (this.focusedElement != null) - { - this.Log("Focus Changed from {0} to {1}", previousFocusedElementElement, this.focusedElement); - this.focusedElement.LostKeyboardFocus += this.OnFocusLost; - this.focusedElement.PreviewKeyDown += this.OnPreviewKeyDown; - this.focusedElement.PreviewTextInput += this.OnFocusedElementPreviewTextInput; - } - else - { - this.Log("Focus Not Restored"); - } - } + //[SuppressMessage("Microsoft.Maintainability", "CA1502")] + //private void OnPreviewKeyDown(object sender, KeyEventArgs e) + //{ + // this.Log("Key Down {0} ({1})", e.Key, e.OriginalSource); + + // if (e.IsRepeat + // || this.Visibility == Visibility.Hidden) + // { + // return; + // } + + // if ((this.AdornedElement is ContextMenu == false && this.AdornedElement is MenuItem == false) + // && ((e.Key == Key.Left) + // || (e.Key == Key.Right) + // || (e.Key == Key.Up) + // || (e.Key == Key.Down) + // || (e.Key == Key.Enter) + // || (e.Key == Key.Tab))) + // { + // this.Visibility = Visibility.Hidden; + // } + // else if (e.Key == Key.Escape) + // { + // this.Back(); + // e.Handled = true; + // } + //} #endregion @@ -678,11 +508,10 @@ private UIElement TryGetElement(string keys) } /// - /// Is one of the elements starts with the given chars + /// Determines if an of the keytips contained in this adorner start with /// - /// - /// - public bool IsElementsStartWith(string keys) + /// true if any keytip start with . Otherwise false. + public bool ContainsKeyTipStartingWith(string keys) { foreach (var keyTip in this.keyTips.Where(x => x.IsEnabled)) { @@ -698,7 +527,7 @@ public bool IsElementsStartWith(string keys) } // Hide / unhide keytips relative matching to entered keys - internal void FilterKeyTips(string currentlyEnteredKeys) + internal void FilterKeyTips(string keys) { this.Log("FilterKeyTips"); @@ -713,13 +542,13 @@ internal void FilterKeyTips(string currentlyEnteredKeys) { var content = (string)this.keyTips[i].Content; - if (string.IsNullOrEmpty(currentlyEnteredKeys)) + if (string.IsNullOrEmpty(keys)) { this.keyTips[i].Visibility = this.backupedVisibilities[i]; } else { - this.keyTips[i].Visibility = content.StartsWith(currentlyEnteredKeys, StringComparison.CurrentCultureIgnoreCase) + this.keyTips[i].Visibility = content.StartsWith(keys, StringComparison.CurrentCultureIgnoreCase) ? this.backupedVisibilities[i] : Visibility.Collapsed; } diff --git a/Fluent.Ribbon/Metro/Native/Constants.cs b/Fluent.Ribbon/Metro/Native/Constants.cs index 7ed010571..da7f9791a 100644 --- a/Fluent.Ribbon/Metro/Native/Constants.cs +++ b/Fluent.Ribbon/Metro/Native/Constants.cs @@ -6,6 +6,7 @@ internal static class Constants { public const int MONITOR_DEFAULTTONEAREST = 0x00000002; + public const int WM_ACTIVATE = 0x0006; public const int WM_NCCALCSIZE = 0x83; public const int WM_NCPAINT = 0x85; public const int WM_NCACTIVATE = 0x86; @@ -17,7 +18,7 @@ internal static class Constants public const long WS_MAXIMIZE = 0x01000000; - public const int GCLP_HBRBACKGROUND = -0x0A; + public const int GCLP_HBRBACKGROUND = -0x0A; public const int HTLEFT = 0x0A; public const int HTRIGHT = 0x0B; diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index 5554b5904..0eaf26638 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -112,7 +112,7 @@ public void Attach() return; } - this.window.PreviewKeyDown += this.OnWindowKeyDown; + this.window.PreviewKeyDown += this.OnWindowPreviewKeyDown; this.window.KeyUp += this.OnWindowKeyUp; // Hookup non client area messages @@ -140,7 +140,7 @@ public void Detach() if (this.window != null) { - this.window.PreviewKeyDown -= this.OnWindowKeyDown; + this.window.PreviewKeyDown -= this.OnWindowPreviewKeyDown; this.window.KeyUp -= this.OnWindowKeyUp; this.window = null; @@ -157,23 +157,25 @@ public void Detach() // Window's messages hook up private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - // We must terminate the keytip's adorner chain if: - // - mouse clicks in non client area - // - the window is deactivated - if (((msg >= 161) && (msg <= 173)) || msg == Constants.WM_NCACTIVATE) + // We must terminate the keytip's adorner chain if: + if (msg == Constants.WM_NCACTIVATE // mouse clicks in non client area + || (msg == Constants.WM_ACTIVATE && wParam == IntPtr.Zero) // the window is deactivated + // >= WM_NCLBUTTONDOWN <= WM_NCXBUTTONDBLCLK + || (msg >= 161 && msg <= 173) // mouse click (non client area) + || (msg >= 513 && msg <= 521) // mouse click + ) { if (this.activeAdornerChain != null && this.activeAdornerChain.IsAdornerChainAlive) { this.activeAdornerChain.Terminate(); - this.activeAdornerChain = null; } } return IntPtr.Zero; } - private void OnWindowKeyDown(object sender, KeyEventArgs e) + private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) { if (e.IsRepeat) { @@ -193,7 +195,6 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) && e.SystemKey <= Key.NumPad9) { this.activeAdornerChain?.Terminate(); - this.activeAdornerChain = null; this.ClearUserInput(); return; } @@ -215,7 +216,6 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) //this.ribbon.Focus(); this.activeAdornerChain.Terminate(); - this.activeAdornerChain = null; } else { @@ -226,10 +226,11 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) && this.activeAdornerChain != null) { this.activeAdornerChain.ActiveKeyTipAdorner.Back(); + this.ClearUserInput(); + e.Handled = true; } else { - // Should we show the keytips and immediately react to key? if ((e.Key != Key.System && this.activeAdornerChain == null) || e.SystemKey == Key.Escape || (e.KeyboardDevice.Modifiers != ModifierKeys.Alt && this.activeAdornerChain == null)) @@ -237,6 +238,7 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) return; } + // Should we show the keytips and immediately react to key? if (this.activeAdornerChain == null || this.activeAdornerChain.IsAdornerChainAlive == false || this.activeAdornerChain.AreAnyKeyTipsVisible == false) @@ -250,21 +252,22 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) } string previousInput = this.currentUserInput; + this.currentUserInput += keyConverter.ConvertToString(e.Key == Key.System ? e.SystemKey : e.Key); - if (this.activeAdornerChain.ActiveKeyTipAdorner.Forward(this.currentUserInput, true)) + // 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(); + } + else if (this.activeAdornerChain.ActiveKeyTipAdorner.Forward(this.currentUserInput, true)) { this.ClearUserInput(); e.Handled = true; } else { - // If no key tips match the current input, continue with the previously entered and still correct keys. - if (!this.activeAdornerChain.ActiveKeyTipAdorner.IsElementsStartWith(this.currentUserInput)) - { - this.currentUserInput = previousInput; - } - this.activeAdornerChain.ActiveKeyTipAdorner.FilterKeyTips(this.currentUserInput); e.Handled = true; } @@ -310,7 +313,7 @@ private void ClearUserInput() this.currentUserInput = string.Empty; } - private void RestoreFocuses() + private void RestoreFocus() { if (this.backUpFocusedElement != null) { @@ -323,9 +326,9 @@ private void RestoreFocuses() private void OnAdornerChainTerminated(object sender, EventArgs e) { + this.activeAdornerChain.Terminated -= this.OnAdornerChainTerminated; this.activeAdornerChain = null; - this.RestoreFocuses(); - ((KeyTipAdorner)sender).Terminated -= this.OnAdornerChainTerminated; + this.RestoreFocus(); } private void OnDelayedShow(object sender, EventArgs e) @@ -357,7 +360,6 @@ private void ShowDelayed() this.activeAdornerChain.Terminate(); } - this.activeAdornerChain = null; this.timer.Start(); } @@ -369,7 +371,7 @@ private void Show() if (this.window == null || this.window.IsActive == false) { - this.RestoreFocuses(); + this.RestoreFocus(); return; } From 36525e7873c65952479ca6dbde97064cd9605c7d Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Wed, 3 Feb 2016 22:25:21 +0100 Subject: [PATCH 02/19] Enabling menu navigation and respecting focus on WindowsForms controls #258 --- Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj | 1 + Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj | 1 + Fluent.Ribbon/Internal/FocusWrapper.cs | 54 ++++++++++++++++ Fluent.Ribbon/Internal/NativeMethods.cs | 6 ++ Fluent.Ribbon/Services/KeyTipService.cs | 71 ++++++++++------------ 5 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 Fluent.Ribbon/Internal/FocusWrapper.cs 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 { From 73fd89dc3603ffb58f8f82e7b751c634b796da03 Mon Sep 17 00:00:00 2001 From: maurosampietro Date: Wed, 27 Jan 2016 10:29:38 +0100 Subject: [PATCH 03/19] Windows8 RibbonWindowTitleTextGlowBackground was missing --- .../Themes/Windows8/Controls/Ribbon.xaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Fluent.Ribbon/Themes/Windows8/Controls/Ribbon.xaml b/Fluent.Ribbon/Themes/Windows8/Controls/Ribbon.xaml index cac7180c4..008e14be4 100644 --- a/Fluent.Ribbon/Themes/Windows8/Controls/Ribbon.xaml +++ b/Fluent.Ribbon/Themes/Windows8/Controls/Ribbon.xaml @@ -32,7 +32,21 @@ - + + + + + + Date: Fri, 29 Jan 2016 22:30:17 +0100 Subject: [PATCH 04/19] Code style for .NET XAML parser... --- .../Themes/Generic/Controls/StartScreenTabControlTemplate.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fluent.Ribbon/Themes/Generic/Controls/StartScreenTabControlTemplate.xaml b/Fluent.Ribbon/Themes/Generic/Controls/StartScreenTabControlTemplate.xaml index 138b06e11..64533bdfc 100644 --- a/Fluent.Ribbon/Themes/Generic/Controls/StartScreenTabControlTemplate.xaml +++ b/Fluent.Ribbon/Themes/Generic/Controls/StartScreenTabControlTemplate.xaml @@ -38,7 +38,7 @@ Height="Auto" Content="{TemplateBinding LeftContent}" /> - From 750a9eb9b538dfc84446cea82d55b25d010013a2 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Fri, 29 Jan 2016 23:05:54 +0100 Subject: [PATCH 05/19] Fixes #256 by relying on ItemContainerGenerator instead of invoking async action on dispatcher --- Fluent.Ribbon/Controls/GalleryPanel.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Fluent.Ribbon/Controls/GalleryPanel.cs b/Fluent.Ribbon/Controls/GalleryPanel.cs index e5abc710f..d47648ee3 100644 --- a/Fluent.Ribbon/Controls/GalleryPanel.cs +++ b/Fluent.Ribbon/Controls/GalleryPanel.cs @@ -11,6 +11,7 @@ using System.Windows.Data; using System.Windows.Media; using System.Windows.Threading; + using Fluent.Internal; /// /// Represents panel for Gallery, InRibbonGallery, ComboBox @@ -249,6 +250,7 @@ public GalleryPanel() #region Visual Tree private readonly VisualCollection visualCollection; + private ItemContainerGeneratorAction itemContainerGeneratorAction; /// /// Gets the number of visual child elements within this element. @@ -379,6 +381,11 @@ private void RefreshDispatchered() private void Refresh() { + if (this.itemContainerGeneratorAction == null) + { + this.itemContainerGeneratorAction = new ItemContainerGeneratorAction((ItemContainerGenerator)this.ItemContainerGenerator, this.Refresh); + } + // Clear currently used group containers // and supply with new generated ones foreach (var galleryGroupContainer in this.galleryGroupContainers) @@ -605,9 +612,12 @@ protected override IEnumerator LogicalChildren /// The that raised the event.Provides data for the event. protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) { - base.OnItemsChanged(sender, args); + if (this.itemContainerGeneratorAction?.IsWaitingForGenerator == false) + { + this.itemContainerGeneratorAction?.QueueAction(); + } - this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(this.Refresh)); + base.OnItemsChanged(sender, args); } } } \ No newline at end of file From fafcd3548cbc62fc7c89f52ba895151c09ece4e7 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Fri, 29 Jan 2016 23:08:36 +0100 Subject: [PATCH 06/19] Adding test for #256 to showcase application --- Fluent.Ribbon.Showcase/TestContent.xaml | 8 ++++++-- Fluent.Ribbon.Showcase/TestContent.xaml.cs | 19 ++++++++++++------- .../ViewModels/FontsViewModel.cs | 8 +++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Fluent.Ribbon.Showcase/TestContent.xaml b/Fluent.Ribbon.Showcase/TestContent.xaml index 604487d15..97f773cd8 100644 --- a/Fluent.Ribbon.Showcase/TestContent.xaml +++ b/Fluent.Ribbon.Showcase/TestContent.xaml @@ -503,7 +503,7 @@ x:Name="groupLL"> @@ -513,9 +513,10 @@ + @@ -525,6 +526,9 @@ + + data = new ObservableCollection { "Tahoma", "Segoe UI", "Arial", "Courier New", "Symbol" }; - public string[] FontsData + public ObservableCollection FontsData { get { return this.data; } } From 965134d07c178ba4eae448dc37e997685e1c0aa0 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Fri, 29 Jan 2016 23:09:50 +0100 Subject: [PATCH 07/19] Updating changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index f737ee0da..850626692 100644 --- a/Changelog.md +++ b/Changelog.md @@ -38,6 +38,7 @@ - [#246](../../issues/246) - Bind RibbonGroupBox.DataContext on QuickAccessToolBar - [#251](../../issues/251) - Changing RibbonStatusBar height to 23 and RibbonStatusBarItem foreground to BackstageFontBrush - [#254](../../issues/254) - Basic fix for KeyTips not working when focus is inside a WinForms control + - [#256](../../issues/256) - ComboBox items don't update properly on ItemsSource binding source collection changes - OpenBackstage command was not acting on the correct backstage in a multiple backstage scenario (thanks to @maurosampietro) - ### Enhancements From 6c150eddae4b200394926f3e67aee394cd1ef785 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sun, 31 Jan 2016 11:34:00 +0100 Subject: [PATCH 08/19] Updating changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 850626692..80488ba7b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -39,6 +39,7 @@ - [#251](../../issues/251) - Changing RibbonStatusBar height to 23 and RibbonStatusBarItem foreground to BackstageFontBrush - [#254](../../issues/254) - Basic fix for KeyTips not working when focus is inside a WinForms control - [#256](../../issues/256) - ComboBox items don't update properly on ItemsSource binding source collection changes + - [#257](../../issues/257) - Windows8 RibbonWindowTitleTextGlowBackground was missing - OpenBackstage command was not acting on the correct backstage in a multiple backstage scenario (thanks to @maurosampietro) - ### Enhancements From 1a8e789a3545e2bf364700f9a0d07c4f6988523a Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sun, 31 Jan 2016 21:42:57 +0100 Subject: [PATCH 09/19] Updating ControlzEx to latest version. Should help with #238 --- Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj | 4 ++-- Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj | 4 ++-- Fluent.Ribbon/packages.config | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj index 340e32459..d849e4726 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net40\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev034\lib\net40\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net40\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev034\lib\net40\System.Windows.Interactivity.dll True diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj index 3ed70b8f7..d42ccfb74 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net45\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev034\lib\net45\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net45\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev034\lib\net45\System.Windows.Interactivity.dll True diff --git a/Fluent.Ribbon/packages.config b/Fluent.Ribbon/packages.config index 2f91fc74a..94bd03248 100644 --- a/Fluent.Ribbon/packages.config +++ b/Fluent.Ribbon/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file From 8a5139367b146741847a1b8d6415d52f4cbedbe1 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Mon, 1 Feb 2016 19:26:33 +0100 Subject: [PATCH 10/19] Removing unused test content --- Fluent.Ribbon.Showcase/TestContent.xaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Fluent.Ribbon.Showcase/TestContent.xaml b/Fluent.Ribbon.Showcase/TestContent.xaml index 97f773cd8..3b329edbc 100644 --- a/Fluent.Ribbon.Showcase/TestContent.xaml +++ b/Fluent.Ribbon.Showcase/TestContent.xaml @@ -978,16 +978,6 @@ - - - From 5e25aed450431c9e0b10bf04fd8c030f24c82fd4 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Tue, 2 Feb 2016 21:19:52 +0100 Subject: [PATCH 11/19] As long as we are in preview phase we should use nuget package from CI-build of ControlzEx --- Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj | 4 ++-- Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj | 4 ++-- Fluent.Ribbon/packages.config | 2 +- NuGet.Config | 20 ++++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 NuGet.Config diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj index d849e4726..55fc76e66 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev034\lib\net40\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev035\lib\net40\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev034\lib\net40\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev035\lib\net40\System.Windows.Interactivity.dll True diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj index d42ccfb74..0e8263f99 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev034\lib\net45\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev035\lib\net45\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev034\lib\net45\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev035\lib\net45\System.Windows.Interactivity.dll True diff --git a/Fluent.Ribbon/packages.config b/Fluent.Ribbon/packages.config index 94bd03248..7b136d596 100644 --- a/Fluent.Ribbon/packages.config +++ b/Fluent.Ribbon/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..3d6a5a7bf --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1d111bf4505bee612708a8ec5945b2f78dc10f4b Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 4 Feb 2016 18:48:15 +0100 Subject: [PATCH 12/19] Fixes #263 by introducing ParentBackstage to BackstageTabControl --- Changelog.md | 1 + Fluent.Ribbon.Showcase/TestContent.xaml | 46 ++++++++++++++++++- Fluent.Ribbon/Controls/BackstageTabControl.cs | 36 ++++++++++++++- Fluent.Ribbon/Internal/UIHelper.cs | 34 ++++++++++++++ .../Controls/BackstageTabControl.xaml | 16 +++---- .../Controls/BackstageTabControl.xaml | 14 +++--- .../Controls/BackstageTabControl.xaml | 19 ++++---- 7 files changed, 137 insertions(+), 29 deletions(-) diff --git a/Changelog.md b/Changelog.md index 80488ba7b..17b959d0f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -40,6 +40,7 @@ - [#254](../../issues/254) - Basic fix for KeyTips not working when focus is inside a WinForms control - [#256](../../issues/256) - ComboBox items don't update properly on ItemsSource binding source collection changes - [#257](../../issues/257) - Windows8 RibbonWindowTitleTextGlowBackground was missing + - [#263](../../issues/263) - Changing theme from backstage is broken - OpenBackstage command was not acting on the correct backstage in a multiple backstage scenario (thanks to @maurosampietro) - ### Enhancements diff --git a/Fluent.Ribbon.Showcase/TestContent.xaml b/Fluent.Ribbon.Showcase/TestContent.xaml index 3b329edbc..8c1945f10 100644 --- a/Fluent.Ribbon.Showcase/TestContent.xaml +++ b/Fluent.Ribbon.Showcase/TestContent.xaml @@ -143,6 +143,47 @@ + + + + + Office 2010 Silver + Office 2010 Black + Office 2010 Blue + Office 2013 White + Windows8 White + + + + + + + + + + + + + + + + + + + + + + + - /// Represents Backstage tab control. /// @@ -189,6 +191,23 @@ public Brush ItemsPanelBackground #endregion + /// + /// Gets or sets the + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Backstage ParentBackstage + { + get { return (Backstage)this.GetValue(ParentBackstageProperty); } + set { this.SetValue(ParentBackstageProperty, value); } + } + + /// + /// for + /// + public static readonly DependencyProperty ParentBackstageProperty = + DependencyProperty.Register(nameof(ParentBackstage), typeof(Backstage), typeof(BackstageTabControl), new PropertyMetadata(null)); + #endregion #region Constructors @@ -200,11 +219,11 @@ public Brush ItemsPanelBackground static BackstageTabControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(typeof(BackstageTabControl))); - StyleProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceStyle))); + StyleProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(null, OnCoerceStyle)); } // Coerce object style - static object OnCoerceStyle(DependencyObject d, object basevalue) + private static object OnCoerceStyle(DependencyObject d, object basevalue) { if (basevalue == null) { @@ -227,6 +246,19 @@ public BackstageTabControl() HasDropShadow = false }; this.ContextMenu.Opened += delegate { this.ContextMenu.IsOpen = false; }; + + this.Loaded += this.HandleLoaded; + this.Unloaded += this.HandleUnloaded; + } + + private void HandleLoaded(object sender, RoutedEventArgs e) + { + this.ParentBackstage = UIHelper.GetParent(this); + } + + private void HandleUnloaded(object sender, RoutedEventArgs e) + { + this.ParentBackstage = null; } #endregion diff --git a/Fluent.Ribbon/Internal/UIHelper.cs b/Fluent.Ribbon/Internal/UIHelper.cs index 01ae4934e..cad9cbe28 100644 --- a/Fluent.Ribbon/Internal/UIHelper.cs +++ b/Fluent.Ribbon/Internal/UIHelper.cs @@ -60,6 +60,10 @@ public static TChildItem FindVisualChild(DependencyObject parent) wh return null; } + /// + /// Gets all visual children of . + /// + /// public static IEnumerable GetVisualChildren(DependencyObject parent) { var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent); @@ -74,5 +78,35 @@ public static IEnumerable GetVisualChildren(DependencyObject p } } } + + /// + /// Finds the parent control of type . + /// First looks at the visual tree and then at the logical tree to find the parent. + /// + /// The found visual/logical parent or null. + public static T GetParent(DependencyObject element) + where T : DependencyObject + { + var item = element; + + while (item != null + && item is T == false) + { + item = VisualTreeHelper.GetParent(item); + } + + if (item == null) + { + item = element; + + while (item != null && + item is T == false) + { + item = LogicalTreeHelper.GetParent(item); + } + } + + return (T)item; + } } } \ No newline at end of file diff --git a/Fluent.Ribbon/Themes/Office2010/Controls/BackstageTabControl.xaml b/Fluent.Ribbon/Themes/Office2010/Controls/BackstageTabControl.xaml index 78445ef55..98db29c83 100644 --- a/Fluent.Ribbon/Themes/Office2010/Controls/BackstageTabControl.xaml +++ b/Fluent.Ribbon/Themes/Office2010/Controls/BackstageTabControl.xaml @@ -1,6 +1,6 @@  @@ -71,7 +71,7 @@ - + - + diff --git a/Fluent.Ribbon/Themes/Windows8/Controls/BackstageTabControl.xaml b/Fluent.Ribbon/Themes/Windows8/Controls/BackstageTabControl.xaml index 96ce6346e..66a4dc9c9 100644 --- a/Fluent.Ribbon/Themes/Windows8/Controls/BackstageTabControl.xaml +++ b/Fluent.Ribbon/Themes/Windows8/Controls/BackstageTabControl.xaml @@ -71,7 +71,7 @@ - + - + From c72c205bcd4600fea5291bb5603554b94ff2b04c Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Fri, 5 Feb 2016 16:45:40 +0100 Subject: [PATCH 13/19] Adding missing access modifier to field --- Fluent.Ribbon/Services/KeyTipService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index 08ebb5bf4..b11a6a0eb 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -24,8 +24,8 @@ internal class KeyTipService // Is KeyTips Actived now private KeyTipAdorner activeAdornerChain; - // This element must be remembered to restore it - FocusWrapper backUpFocusedControl; + // This element must be remembered to restore focus + private FocusWrapper backUpFocusedControl; // Window where we attached private Window window; From 7f5228ef5b736535956df4f25cd2ab736a1674f5 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Fri, 5 Feb 2016 16:47:12 +0100 Subject: [PATCH 14/19] Fixes wrong location of keytips in backstage when animations are enabled #258 --- Fluent.Ribbon/Adorners/KeyTipAdorner.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs index b1ad10722..30e4cb01b 100644 --- a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs +++ b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs @@ -788,25 +788,17 @@ private void UpdateKeyTipPositions() elementSize.Height / 2.0 + 2, elementSize.Height / 2.0 + 2), this.AdornedElement); } - else if (this.associatedElements[i] is BackstageTabItem) - { - // BackstageButton Exclusive Placement - var keyTipSize = this.keyTips[i].DesiredSize; - var elementSize = this.associatedElements[i].DesiredSize; - this.keyTipPositions[i] = this.associatedElements[i].TranslatePoint( - new Point( - 5, - elementSize.Height / 2.0 - keyTipSize.Height), this.AdornedElement); - } else if (((FrameworkElement)this.associatedElements[i]).Parent is BackstageTabControl) { // Backstage Items Exclusive Placement var keyTipSize = this.keyTips[i].DesiredSize; var elementSize = this.associatedElements[i].DesiredSize; - this.keyTipPositions[i] = this.associatedElements[i].TranslatePoint( + var parent = (UIElement)((FrameworkElement)this.associatedElements[i]).Parent; + var positionInParent = this.associatedElements[i].TranslatePoint(default(Point), parent); + this.keyTipPositions[i] = parent.TranslatePoint( new Point( - 20, - elementSize.Height / 2.0 - keyTipSize.Height), this.AdornedElement); + 5, + positionInParent.Y + (elementSize.Height / 2.0 - keyTipSize.Height)), this.AdornedElement); } else { From 1cd90f2e301facc59f2c48e166fec0bae64ae263 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sat, 6 Feb 2016 09:37:57 +0100 Subject: [PATCH 15/19] Removing dead code and improving debug log output --- Fluent.Ribbon/Adorners/KeyTipAdorner.cs | 69 +++++-------------------- 1 file changed, 14 insertions(+), 55 deletions(-) diff --git a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs index 30e4cb01b..80b9702b9 100644 --- a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs +++ b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Windows; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Threading; - -namespace Fluent +namespace Fluent { + using System; + using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Windows; using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Media; using Fluent.Internal; /// @@ -319,38 +315,6 @@ public void Terminate() #endregion - #region Event Handlers - - //[SuppressMessage("Microsoft.Maintainability", "CA1502")] - //private void OnPreviewKeyDown(object sender, KeyEventArgs e) - //{ - // this.Log("Key Down {0} ({1})", e.Key, e.OriginalSource); - - // if (e.IsRepeat - // || this.Visibility == Visibility.Hidden) - // { - // return; - // } - - // if ((this.AdornedElement is ContextMenu == false && this.AdornedElement is MenuItem == false) - // && ((e.Key == Key.Left) - // || (e.Key == Key.Right) - // || (e.Key == Key.Up) - // || (e.Key == Key.Down) - // || (e.Key == Key.Enter) - // || (e.Key == Key.Tab))) - // { - // this.Visibility = Visibility.Hidden; - // } - // else if (e.Key == Key.Escape) - // { - // this.Back(); - // e.Handled = true; - // } - //} - - #endregion - #region Static Methods private static AdornerLayer GetAdornerLayer(UIElement element) @@ -435,16 +399,10 @@ public bool Forward(string keys, bool click) return true; } - // Forward to the next element - private void Forward(UIElement element) - { - this.Forward(element, true); - } - // Forward to the next element private void Forward(UIElement element, bool click) { - this.Log("Forwarding"); + this.Log("Forwarding to {0}", element); this.Detach(); @@ -529,7 +487,7 @@ public bool ContainsKeyTipStartingWith(string keys) // Hide / unhide keytips relative matching to entered keys internal void FilterKeyTips(string keys) { - this.Log("FilterKeyTips"); + this.Log("FilterKeyTips with {0}", keys); // Backup current visibility of key tips for (var i = 0; i < this.backupedVisibilities.Length; i++) @@ -931,10 +889,11 @@ private void Log(string format, params object[] args) var headeredControl = this.AdornedElement as IHeaderedControl; if (headeredControl != null) { - name += string.Format(" ({0})", headeredControl.Header); + name += $" ({headeredControl.Header})"; } - Debug.WriteLine("[" + name + "] " + string.Format(format, args), "KeyTipAdorner"); + var formatted = string.Format(format, args); + Debug.WriteLine($"{$"[{name}] "}{formatted}", "KeyTipAdorner"); } #endregion From 9dc52759a49a5a662c6b696613586fb517c0311e Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sat, 6 Feb 2016 09:45:24 +0100 Subject: [PATCH 16/19] Using ?. instead of null check --- Fluent.Ribbon/Adorners/KeyTipAdorner.cs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs index 80b9702b9..c19685423 100644 --- a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs +++ b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs @@ -295,20 +295,11 @@ public void Terminate() this.Detach(); - if (this.parentAdorner != null) - { - this.parentAdorner.Terminate(); - } + this.parentAdorner?.Terminate(); - if (this.childAdorner != null) - { - this.childAdorner.Terminate(); - } + this.childAdorner?.Terminate(); - if (this.Terminated != null) - { - this.Terminated(this, EventArgs.Empty); - } + this.Terminated?.Invoke(this, EventArgs.Empty); this.Log("Termination"); } @@ -362,10 +353,7 @@ private static UIElement GetTopLevelElement(UIElement element) public void Back() { var control = this.keyTipElementContainer as IKeyTipedControl; - if (control != null) - { - control.OnKeyTipBack(); - } + control?.OnKeyTipBack(); if (this.parentAdorner != null) { From 9a1b3ea962339f03cdd0226da396e80bff9b5326 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sat, 6 Feb 2016 09:46:05 +0100 Subject: [PATCH 17/19] Simplifying code --- Fluent.Ribbon/Services/KeyTipService.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index b11a6a0eb..60d3e1cde 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -189,7 +189,6 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) && e.SystemKey <= Key.NumPad9) { this.activeAdornerChain?.Terminate(); - this.ClearUserInput(); return; } @@ -201,14 +200,10 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) { this.ShowDelayed(); } - else if (this.activeAdornerChain != null - && this.activeAdornerChain.IsAdornerChainAlive) - { - this.activeAdornerChain.Terminate(); - } else { - this.ClearUserInput(); + this.activeAdornerChain?.Terminate(); + return; } } else if (e.Key == Key.Escape @@ -325,6 +320,7 @@ private void OnAdornerChainTerminated(object sender, EventArgs e) { this.activeAdornerChain.Terminated -= this.OnAdornerChainTerminated; this.activeAdornerChain = null; + this.ClearUserInput(); this.RestoreFocus(); } From 1acbccaa74d12bdc241d3ccc69606d84274dc553 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sat, 6 Feb 2016 09:58:15 +0100 Subject: [PATCH 18/19] Terminating keytips on unknown keys/keyboard navigation request #258 --- Fluent.Ribbon/Services/KeyTipService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index 60d3e1cde..d175c8b06 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -229,6 +229,14 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) // Don't do anything and let WPF handle the rest if (isKeyRealInput == false) { + // This block is a "temporary" fix for keyboard navigation not matching the office behavior. + // If someone finds a way to implement it properly, here is your starting point. + // In office: If you navigate by keyboard (in menus) and keytips are shown they are shown or hidden based on the menu you are in. + // Implementing navigation the way office does would require complex focus/state tracking etc. so i decided to just terminate keytips and not restore focus. + { + this.backUpFocusedControl = null; + this.activeAdornerChain?.Terminate(); + } return; } From 559c920a7b1ff63ebdd16ad56a381ccc88975ff6 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Sat, 6 Feb 2016 10:24:19 +0100 Subject: [PATCH 19/19] Updating changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 17b959d0f..79204044e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -51,6 +51,7 @@ - [#207](../../issues/207) - Enable DragMove on unused RibbonTabControl space like in Office 2013 - [#230](../../issues/230) - Option to disable the "Minimize"-Ribbon Button & Behavior - [#242](../../issues/242) - Add start screen like in office 2013 and upwards + - [#258](../../issues/258) - Refactoring of KeyTipService and KeyTipAdorner ## 3.6.1