Browse Source

Use a different approach to access keys in menu.

Create a MenuItemAccessKeyHandler that handles the plain keys without
alt, and inject it into the submenu popups.
pull/58/head
Steven Kirk 11 years ago
parent
commit
018d99d91e
  1. 51
      Perspex.Controls/MenuItem.cs
  2. 106
      Perspex.Controls/MenuItemAccessKeyHandler.cs
  3. 1
      Perspex.Controls/Perspex.Controls.csproj
  4. 16
      Perspex.Controls/Popup.cs
  5. 11
      Perspex.Controls/PopupRoot.cs
  6. 28
      Perspex.Controls/TopLevel.cs

51
Perspex.Controls/MenuItem.cs

@ -7,16 +7,17 @@
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Rendering;
using Perspex.Controls.Templates;
using Perspex.Controls.Presenters;
using Perspex.VisualTree;
using Perspex.Threading;
using Splat;
/// <summary>
/// A menu item control.
@ -434,6 +435,7 @@ namespace Perspex.Controls
base.OnTemplateApplied();
this.popup = this.GetTemplateChild<Popup>("popup");
this.popup.DependencyResolver = DependencyResolver.Instance;
this.popup.PopupRootCreated += this.PopupRootCreated;
this.popup.Opened += this.PopupOpened;
this.popup.Closed += this.PopupClosed;
@ -550,5 +552,52 @@ namespace Perspex.Controls
{
this.SelectedItem = null;
}
/// <summary>
/// A dependency resolver which returns a <see cref="MenuItemAccessKeyHandler"/>.
/// </summary>
private class DependencyResolver : IDependencyResolver
{
/// <summary>
/// Gets the default instance of <see cref="DependencyResolver"/>.
/// </summary>
public static readonly DependencyResolver Instance = new DependencyResolver();
/// <summary>
/// Disposes of all managed resources.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Gets a service of the specified type.
/// </summary>
/// <param name="serviceType">The service type.</param>
/// <param name="contract">An optional contract.</param>
/// <returns>A service of the requested type.</returns>
public object GetService(Type serviceType, string contract = null)
{
if (serviceType == typeof(IAccessKeyHandler))
{
return new MenuItemAccessKeyHandler();
}
else
{
return Locator.Current.GetService(serviceType, contract);
}
}
/// <summary>
/// Gets collection of services of the specified type.
/// </summary>
/// <param name="serviceType">The service type.</param>
/// <param name="contract">An optional contract.</param>
/// <returns>A collection of services of the requested type.</returns>
public IEnumerable<object> GetServices(Type serviceType, string contract = null)
{
return Locator.Current.GetServices(serviceType, contract);
}
}
}
}

106
Perspex.Controls/MenuItemAccessKeyHandler.cs

@ -0,0 +1,106 @@
// -----------------------------------------------------------------------
// <copyright file="MenuItemAccessKeyHandler.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Input;
using Perspex.Interactivity;
/// <summary>
/// Handles access keys within a <see cref="MenuItem"/>
/// </summary>
public class MenuItemAccessKeyHandler : IAccessKeyHandler
{
/// <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>
private IInputRoot owner;
/// <summary>
/// Gets or sets the window's main menu.
/// </summary>
/// <remarks>
/// This property is ignored as a menu item cannot have a main menu.
/// </remarks>
public IMainMenu MainMenu { get; set; }
/// <summary>
/// Sets the owner of the access key handler.
/// </summary>
/// <param name="owner">The owner.</param>
/// <remarks>
/// This method can only be called once, typically by the owner itself on creation.
/// </remarks>
public void SetOwner(IInputRoot owner)
{
Contract.Requires<ArgumentNullException>(owner != null);
if (this.owner != null)
{
throw new InvalidOperationException("AccessKeyHandler owner has already been set.");
}
this.owner = owner;
this.owner.AddHandler(InputElement.KeyDownEvent, this.OnKeyDown);
}
/// <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>
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 a key being pressed in the menu.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object sender, KeyEventArgs e)
{
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(AccessKeyHandler.AccessKeyPressedEvent));
}
}
}
}

1
Perspex.Controls/Perspex.Controls.csproj

@ -36,6 +36,7 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="MenuItemAccessKeyHandler.cs" />
<Compile Include="Primitives\AccessText.cs" />
<Compile Include="Border.cs" />
<Compile Include="GlobalSuppressions.cs" />

16
Perspex.Controls/Popup.cs

@ -11,6 +11,7 @@ namespace Perspex.Controls
using Perspex.Collections;
using Perspex.Rendering;
using Perspex.VisualTree;
using Splat;
/// <summary>
/// Displays a popup window.
@ -102,6 +103,19 @@ namespace Perspex.Controls
set { this.SetValue(ChildProperty, value); }
}
/// <summary>
/// Gets or sets a dependency resolver for the <see cref="PopupRoot"/>.
/// </summary>
/// <remarks>
/// This property allows a client to customize the behaviour of the popup by injecting
/// a specialized dependency resolver into the <see cref="PopupRoot"/>'s constructor.
/// </remarks>
public IDependencyResolver DependencyResolver
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether the popup is currently open.
/// </summary>
@ -169,7 +183,7 @@ namespace Perspex.Controls
{
if (this.popupRoot == null)
{
this.popupRoot = new PopupRoot()
this.popupRoot = new PopupRoot(this.DependencyResolver)
{
Parent = this,
[~PopupRoot.ContentProperty] = this[~ChildProperty],

11
Perspex.Controls/PopupRoot.cs

@ -34,6 +34,17 @@ namespace Perspex.Controls
this.GetObservable(ParentProperty).Subscribe(x => this.InheritanceParent = x);
}
/// <summary>
/// Initializes a new instance of the <see cref="PopupRoot"/> class.
/// </summary>
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>
public PopupRoot(IDependencyResolver dependencyResolver)
: base(Locator.Current.GetService<IPopupImpl>(), dependencyResolver)
{
}
/// <summary>
/// Gets the platform-specific window implementation.
/// </summary>

28
Perspex.Controls/TopLevel.cs

@ -90,15 +90,29 @@ namespace Perspex.Controls
/// </summary>
/// <param name="impl">The platform-specific window implementation.</param>
public TopLevel(ITopLevelImpl impl)
: this(impl, Locator.Current)
{
IPlatformRenderInterface renderInterface = Locator.Current.GetService<IPlatformRenderInterface>();
}
/// <summary>
/// Initializes a new instance of the <see cref="TopLevel"/> class.
/// </summary>
/// <param name="impl">The platform-specific window implementation.</param>
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>
public TopLevel(ITopLevelImpl impl, IDependencyResolver dependencyResolver)
{
dependencyResolver = dependencyResolver ?? Locator.Current;
IPlatformRenderInterface renderInterface = dependencyResolver.GetService<IPlatformRenderInterface>();
this.PlatformImpl = impl;
this.accessKeyHandler = Locator.Current.GetService<IAccessKeyHandler>();
this.inputManager = Locator.Current.GetService<IInputManager>();
this.keyboardNavigationHandler = Locator.Current.GetService<IKeyboardNavigationHandler>();
this.LayoutManager = Locator.Current.GetService<ILayoutManager>();
this.renderManager = Locator.Current.GetService<IRenderManager>();
this.accessKeyHandler = dependencyResolver.GetService<IAccessKeyHandler>();
this.inputManager = dependencyResolver.GetService<IInputManager>();
this.keyboardNavigationHandler = dependencyResolver.GetService<IKeyboardNavigationHandler>();
this.LayoutManager = dependencyResolver.GetService<ILayoutManager>();
this.renderManager = dependencyResolver.GetService<IRenderManager>();
if (renderInterface == null)
{
@ -158,7 +172,7 @@ namespace Perspex.Controls
this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted());
this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded());
IStyler styler = Locator.Current.GetService<IStyler>();
IStyler styler = dependencyResolver.GetService<IStyler>();
styler.ApplyStyles(this);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);

Loading…
Cancel
Save