Skip to content

Commit

Permalink
Enabling menu navigation and respecting focus on WindowsForms controls
Browse files Browse the repository at this point in the history
  • Loading branch information
batzen committed Feb 3, 2016
1 parent fc0d97e commit 36525e7
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 38 deletions.
1 change: 1 addition & 0 deletions Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<Compile Include="Converters\SpinnerTextToValueConverter.cs" />
<Compile Include="Helpers\WindowSteeringHelper.cs" />
<Compile Include="IHeaderedControl.cs" />
<Compile Include="Internal\FocusWrapper.cs" />
<Compile Include="Internal\ItemsControlHelper.cs" />
<Compile Include="Internal\UIHelper.cs" />
<Compile Include="Services\ContextMenuService.cs" />
Expand Down
1 change: 1 addition & 0 deletions Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<Compile Include="Converters\SpinnerTextToValueConverter.cs" />
<Compile Include="Helpers\WindowSteeringHelper.cs" />
<Compile Include="IHeaderedControl.cs" />
<Compile Include="Internal\FocusWrapper.cs" />
<Compile Include="Internal\ItemsControlHelper.cs" />
<Compile Include="Internal\UIHelper.cs" />
<Compile Include="Services\ContextMenuService.cs" />
Expand Down
54 changes: 54 additions & 0 deletions Fluent.Ribbon/Internal/FocusWrapper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
6 changes: 6 additions & 0 deletions Fluent.Ribbon/Internal/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>Add and remove a native WindowStyle from the HWND.</summary>
/// <param name="_hwnd">A HWND for a window.</param>
/// <param name="removeStyle">The styles to be removed. These can be bitwise combined.</param>
Expand Down
71 changes: 33 additions & 38 deletions Fluent.Ribbon/Services/KeyTipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}

/// <summary>
Expand All @@ -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
Expand Down Expand 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
Expand All @@ -228,6 +217,7 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e)
this.activeAdornerChain.ActiveKeyTipAdorner.Back();
this.ClearUserInput();
e.Handled = true;
return;
}
else
{
Expand All @@ -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
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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
{
Expand Down

0 comments on commit 36525e7

Please sign in to comment.