Browse Source

Added access key support to AccessKeyHandler.

pull/58/head
Steven Kirk 11 years ago
parent
commit
576ee1500d
  1. 18
      Perspex.Controls/MenuItem.cs
  2. 51
      Perspex.Controls/Primitives/AccessText.cs
  3. 78
      Perspex.Input/AccessKeyHandler.cs
  4. 13
      Perspex.Input/IAccessKeyHandler.cs
  5. 3
      Perspex.Interactivity/RoutedEvent.cs
  6. 5
      Perspex.SceneGraph/IVisual.cs
  7. 1
      Perspex.SceneGraph/Perspex.SceneGraph.csproj
  8. 8
      Perspex.SceneGraph/Visual.cs

18
Perspex.Controls/MenuItem.cs

@ -89,6 +89,7 @@ namespace Perspex.Controls
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged);
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
}
/// <summary>
@ -419,6 +420,23 @@ namespace Perspex.Controls
this.popup.Closed += this.PopupClosed;
}
/// <summary>
/// Called when the menu item's access key is pressed.
/// </summary>
/// <param name="e">The event args.</param>
private void AccessKeyPressed(RoutedEventArgs e)
{
if (this.HasSubMenu)
{
this.SelectedIndex = 0;
this.IsSubMenuOpen = true;
}
else
{
this.RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
/// <summary>
/// Closes all submenus of the menu item.
/// </summary>

51
Perspex.Controls/Primitives/AccessText.cs

@ -8,6 +8,10 @@ namespace Perspex.Controls.Primitives
{
using System;
using Perspex.Media;
using Perspex.Input;
using Perspex.Rendering;
/// <summary>
/// A text block that displays a character prefixed with an underscore as an access key.
@ -20,6 +24,11 @@ namespace Perspex.Controls.Primitives
public static readonly PerspexProperty<bool> ShowAccessKeyProperty =
PerspexProperty.RegisterAttached<AccessText, Control, bool>("ShowAccessKey", inherits: true);
/// <summary>
/// The access key handler for the current window.
/// </summary>
private IAccessKeyHandler accessKeys;
/// <summary>
/// Initializes static members of the <see cref="AccessText"/> class.
/// </summary>
@ -104,6 +113,36 @@ namespace Perspex.Controls.Primitives
return result.WithHeight(result.Height + 1);
}
/// <summary>
/// Called when the control is attached to a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
{
base.OnAttachedToVisualTree(root);
this.accessKeys = (root as IInputRoot)?.AccessKeyHandler;
if (this.accessKeys != null && this.AccessKey != 0)
{
this.accessKeys.Register(this.AccessKey, this);
}
}
/// <summary>
/// Called when the control is detached from a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot root)
{
base.OnDetachedFromVisualTree(root);
if (this.accessKeys != null && this.AccessKey != 0)
{
this.accessKeys.Unregister(this);
this.accessKeys = null;
}
}
/// <summary>
/// Returns a string with the first underscore stripped.
/// </summary>
@ -129,18 +168,24 @@ namespace Perspex.Controls.Primitives
/// <param name="text">The new text.</param>
private void TextChanged(string text)
{
var key = (char)0;
if (text != null)
{
int underscore = text.IndexOf('_');
if (underscore != -1 && underscore < text.Length - 1)
{
this.AccessKey = text[underscore + 1];
return;
key = text[underscore + 1];
}
}
this.AccessKey = (char)0;
this.AccessKey = key;
if (this.accessKeys != null && this.AccessKey != 0)
{
this.accessKeys.Register(this.AccessKey, this);
}
}
}
}

78
Perspex.Input/AccessKeyHandler.cs

@ -7,6 +7,8 @@
namespace Perspex.Input
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Interactivity;
/// <summary>
@ -14,6 +16,20 @@ namespace Perspex.Input
/// </summary>
public class AccessKeyHandler : IAccessKeyHandler
{
/// <summary>
/// Defines the AccessKeyPressed attached event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> AccessKeyPressedEvent =
RoutedEvent.Register<RoutedEventArgs>(
"AccessKeyPressed",
RoutingStrategies.Bubble,
typeof(AccessKeyHandler));
/// <summary>
/// The registered access keys.
/// </summary>
private List<Tuple<string, IInputElement>> registered = new List<Tuple<string, IInputElement>>();
/// <summary>
/// The window to which the handler belongs.
/// </summary>
@ -53,17 +69,58 @@ namespace Perspex.Input
}
/// <summary>
/// Handles the Alt/F10 keys being pressed in the window.
/// Registers an input element to be associated with an access key.
/// </summary>
/// <param name="accessKey">The access key.</param>
/// <param name="element">The input element.</param>
public void Register(char accessKey, IInputElement element)
{
var existing = this.registered.FirstOrDefault(x => x.Item2 == element);
if (existing != null)
{
this.registered.Remove(existing);
}
this.registered.Add(Tuple.Create(accessKey.ToString().ToUpper(), element));
}
/// <summary>
/// Unregisters the access keys associated with the input element.
/// </summary>
/// <param name="element">The input element.</param>
public void Unregister(IInputElement element)
{
foreach (var i in this.registered.Where(x => x.Item2 == element).ToList())
{
this.registered.Remove(i);
}
}
/// <summary>
/// Handles the Alt key being pressed in the window.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftAlt || e.Key == Key.F10)
if (e.Key == Key.LeftAlt)
{
this.owner.ShowAccessKeys = this.showingAccessKeys = true;
e.Handled = true;
}
else if ((KeyboardDevice.Instance.Modifiers & ModifierKeys.Alt) != 0)
{
var text = e.Text.ToUpper();
var focus = this.registered
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)
.FirstOrDefault()?.Item2;
if (focus != null)
{
focus.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent));
}
}
}
/// <summary>
@ -73,13 +130,22 @@ namespace Perspex.Input
/// <param name="e">The event args.</param>
protected virtual void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftAlt || e.Key == Key.F10)
switch (e.Key)
{
if (this.showingAccessKeys && this.MainMenu != null)
{
case Key.LeftAlt:
if (this.showingAccessKeys && this.MainMenu != null)
{
this.MainMenu.OpenMenu();
e.Handled = true;
}
break;
case Key.F10:
this.owner.ShowAccessKeys = this.showingAccessKeys = true;
this.MainMenu.OpenMenu();
e.Handled = true;
}
break;
}
}

13
Perspex.Input/IAccessKeyHandler.cs

@ -24,5 +24,18 @@ namespace Perspex.Input
/// This method can only be called once, typically by the owner itself on creation.
/// </remarks>
void SetOwner(IInputRoot owner);
/// <summary>
/// Registers an input element to be associated with an access key.
/// </summary>
/// <param name="accessKey">The access key.</param>
/// <param name="element">The input element.</param>
void Register(char accessKey, IInputElement element);
/// <summary>
/// Unregisters the access keys associated with the input element.
/// </summary>
/// <param name="element">The input element.</param>
void Unregister(IInputElement element);
}
}

3
Perspex.Interactivity/RoutedEvent.cs

@ -33,7 +33,6 @@ namespace Perspex.Interactivity
Contract.Requires<NullReferenceException>(eventArgsType != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo()));
Contract.Requires<InvalidCastException>(typeof(IInteractive).GetTypeInfo().IsAssignableFrom(ownerType.GetTypeInfo()));
this.EventArgsType = eventArgsType;
this.Name = name;
@ -68,7 +67,6 @@ namespace Perspex.Interactivity
public static RoutedEvent<TEventArgs> Register<TOwner, TEventArgs>(
string name,
RoutingStrategies routingStrategy)
where TOwner : IInteractive
where TEventArgs : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(name != null);
@ -124,7 +122,6 @@ namespace Perspex.Interactivity
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(IInteractive).GetTypeInfo().IsAssignableFrom(ownerType.GetTypeInfo()));
}
public void AddClassHandler<TTarget>(

5
Perspex.SceneGraph/IVisual.cs

@ -32,6 +32,11 @@ namespace Perspex
/// </summary>
bool ClipToBounds { get; }
/// <summary>
/// Gets a value indicating whether this scene graph node and all its parents are visible.
/// </summary>
bool IsEffectivelyVisible { get; }
/// <summary>
/// Gets a value indicating whether this scene graph node is visible.
/// </summary>

1
Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -50,6 +50,7 @@
<Compile Include="Animation\PageSlide.cs" />
<Compile Include="Animation\CrossFade.cs" />
<Compile Include="Animation\IPageTransition.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Media\Brush.cs" />
<Compile Include="Media\Brushes.cs" />
<Compile Include="Media\Color.cs" />

8
Perspex.SceneGraph/Visual.cs

@ -132,6 +132,14 @@ namespace Perspex
set { this.SetValue(ClipToBoundsProperty, value); }
}
/// <summary>
/// Gets a value indicating whether this scene graph node and all its parents are visible.
/// </summary>
public bool IsEffectivelyVisible
{
get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); }
}
/// <summary>
/// Gets a value indicating whether this scene graph node is visible.
/// </summary>

Loading…
Cancel
Save