From fc0d97e1cc9c03f076037adeb05b0445c90247ef Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 28 Jan 2016 18:28:25 +0100 Subject: [PATCH] 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; }