diff --git a/Perspex.Controls/MenuItem.cs b/Perspex.Controls/MenuItem.cs index 0090aa1d7a..23fe67383f 100644 --- a/Perspex.Controls/MenuItem.cs +++ b/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; /// /// A menu item control. @@ -434,6 +435,7 @@ namespace Perspex.Controls base.OnTemplateApplied(); this.popup = this.GetTemplateChild("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; } + + /// + /// A dependency resolver which returns a . + /// + private class DependencyResolver : IDependencyResolver + { + /// + /// Gets the default instance of . + /// + public static readonly DependencyResolver Instance = new DependencyResolver(); + + /// + /// Disposes of all managed resources. + /// + public void Dispose() + { + } + + /// + /// Gets a service of the specified type. + /// + /// The service type. + /// An optional contract. + /// A service of the requested type. + public object GetService(Type serviceType, string contract = null) + { + if (serviceType == typeof(IAccessKeyHandler)) + { + return new MenuItemAccessKeyHandler(); + } + else + { + return Locator.Current.GetService(serviceType, contract); + } + } + + /// + /// Gets collection of services of the specified type. + /// + /// The service type. + /// An optional contract. + /// A collection of services of the requested type. + public IEnumerable GetServices(Type serviceType, string contract = null) + { + return Locator.Current.GetServices(serviceType, contract); + } + } } } diff --git a/Perspex.Controls/MenuItemAccessKeyHandler.cs b/Perspex.Controls/MenuItemAccessKeyHandler.cs new file mode 100644 index 0000000000..b55d074390 --- /dev/null +++ b/Perspex.Controls/MenuItemAccessKeyHandler.cs @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Perspex.Input; + using Perspex.Interactivity; + + /// + /// Handles access keys within a + /// + public class MenuItemAccessKeyHandler : IAccessKeyHandler + { + /// + /// The registered access keys. + /// + private List> registered = new List>(); + + /// + /// The window to which the handler belongs. + /// + private IInputRoot owner; + + /// + /// Gets or sets the window's main menu. + /// + /// + /// This property is ignored as a menu item cannot have a main menu. + /// + public IMainMenu MainMenu { get; set; } + + /// + /// Sets the owner of the access key handler. + /// + /// The owner. + /// + /// This method can only be called once, typically by the owner itself on creation. + /// + public void SetOwner(IInputRoot owner) + { + Contract.Requires(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); + } + + /// + /// Registers an input element to be associated with an access key. + /// + /// The access key. + /// The input element. + 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)); + } + + /// + /// Unregisters the access keys associated with the input element. + /// + /// The input element. + public void Unregister(IInputElement element) + { + foreach (var i in this.registered.Where(x => x.Item2 == element).ToList()) + { + this.registered.Remove(i); + } + } + + /// + /// Handles a key being pressed in the menu. + /// + /// The event sender. + /// The event args. + 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)); + } + } + } +} diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index 062bf4a636..f38451ed02 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -36,6 +36,7 @@ 4 + diff --git a/Perspex.Controls/Popup.cs b/Perspex.Controls/Popup.cs index 2eb886f5a3..0aa644b60d 100644 --- a/Perspex.Controls/Popup.cs +++ b/Perspex.Controls/Popup.cs @@ -11,6 +11,7 @@ namespace Perspex.Controls using Perspex.Collections; using Perspex.Rendering; using Perspex.VisualTree; + using Splat; /// /// Displays a popup window. @@ -102,6 +103,19 @@ namespace Perspex.Controls set { this.SetValue(ChildProperty, value); } } + /// + /// Gets or sets a dependency resolver for the . + /// + /// + /// This property allows a client to customize the behaviour of the popup by injecting + /// a specialized dependency resolver into the 's constructor. + /// + public IDependencyResolver DependencyResolver + { + get; + set; + } + /// /// Gets or sets a value indicating whether the popup is currently open. /// @@ -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], diff --git a/Perspex.Controls/PopupRoot.cs b/Perspex.Controls/PopupRoot.cs index ec6cc28b8e..5bb34f8522 100644 --- a/Perspex.Controls/PopupRoot.cs +++ b/Perspex.Controls/PopupRoot.cs @@ -34,6 +34,17 @@ namespace Perspex.Controls this.GetObservable(ParentProperty).Subscribe(x => this.InheritanceParent = x); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The dependency resolver to use. If null the default dependency resolver will be used. + /// + public PopupRoot(IDependencyResolver dependencyResolver) + : base(Locator.Current.GetService(), dependencyResolver) + { + } + /// /// Gets the platform-specific window implementation. /// diff --git a/Perspex.Controls/TopLevel.cs b/Perspex.Controls/TopLevel.cs index 0d32fb3c33..1457060bd3 100644 --- a/Perspex.Controls/TopLevel.cs +++ b/Perspex.Controls/TopLevel.cs @@ -90,15 +90,29 @@ namespace Perspex.Controls /// /// The platform-specific window implementation. public TopLevel(ITopLevelImpl impl) + : this(impl, Locator.Current) { - IPlatformRenderInterface renderInterface = Locator.Current.GetService(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The platform-specific window implementation. + /// + /// The dependency resolver to use. If null the default dependency resolver will be used. + /// + public TopLevel(ITopLevelImpl impl, IDependencyResolver dependencyResolver) + { + dependencyResolver = dependencyResolver ?? Locator.Current; + + IPlatformRenderInterface renderInterface = dependencyResolver.GetService(); this.PlatformImpl = impl; - this.accessKeyHandler = Locator.Current.GetService(); - this.inputManager = Locator.Current.GetService(); - this.keyboardNavigationHandler = Locator.Current.GetService(); - this.LayoutManager = Locator.Current.GetService(); - this.renderManager = Locator.Current.GetService(); + this.accessKeyHandler = dependencyResolver.GetService(); + this.inputManager = dependencyResolver.GetService(); + this.keyboardNavigationHandler = dependencyResolver.GetService(); + this.LayoutManager = dependencyResolver.GetService(); + this.renderManager = dependencyResolver.GetService(); 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 styler = dependencyResolver.GetService(); styler.ApplyStyles(this); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);