Browse Source

More work on Menu.

pull/58/head
Steven Kirk 11 years ago
parent
commit
80064e8712
  1. 22
      Perspex.Controls/Menu.cs
  2. 58
      Perspex.Controls/MenuItem.cs
  3. 19
      Perspex.Controls/MenuItemAccessKeyHandler.cs
  4. 82
      Perspex.Input/AccessKeyHandler.cs
  5. 14
      Perspex.Input/IMainMenu.cs
  6. 5
      Perspex.SceneGraph/VisualTree/VisualExtensions.cs

22
Perspex.Controls/Menu.cs

@ -72,7 +72,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Closes the menu. /// Closes the menu.
/// </summary> /// </summary>
public void CloseMenu() public void Close()
{ {
foreach (MenuItem i in this.GetLogicalChildren()) foreach (MenuItem i in this.GetLogicalChildren())
{ {
@ -86,10 +86,11 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Opens the menu in response to the Alt/F10 key. /// Opens the menu in response to the Alt/F10 key.
/// </summary> /// </summary>
public void OpenMenu() public void Open()
{ {
this.SelectedIndex = 0; this.SelectedIndex = 0;
((IInputElement)this.SelectedItem)?.Focus(); this.SelectedMenuItem.Focus();
this.IsOpen = true;
} }
/// <summary> /// <summary>
@ -138,7 +139,7 @@ namespace Perspex.Controls
protected override void OnLostFocus(RoutedEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
{ {
base.OnLostFocus(e); base.OnLostFocus(e);
//this.CloseMenu(); //this.Close();
} }
/// <summary> /// <summary>
@ -151,12 +152,7 @@ namespace Perspex.Controls
base.OnKeyDown(e); base.OnKeyDown(e);
if (this.IsOpen && e.Key == Key.Escape) if (menuWasOpen)
{
this.CloseMenu();
e.Handled = true;
}
else if (menuWasOpen)
{ {
// If a menu item was open and we navigate to a new one with the arrow keys, open // If a menu item was open and we navigate to a new one with the arrow keys, open
// that menu and select the first item. // that menu and select the first item.
@ -199,7 +195,7 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void Deactivated(object sender, EventArgs e) private void Deactivated(object sender, EventArgs e)
{ {
this.CloseMenu(); this.Close();
} }
/// <summary> /// <summary>
@ -208,7 +204,7 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void OnMenuClick(RoutedEventArgs e) private void OnMenuClick(RoutedEventArgs e)
{ {
this.CloseMenu(); this.Close();
} }
/// <summary> /// <summary>
@ -224,7 +220,7 @@ namespace Perspex.Controls
if (!this.IsLogicalParentOf(control)) if (!this.IsLogicalParentOf(control))
{ {
this.CloseMenu(); this.Close();
} }
} }
} }

58
Perspex.Controls/MenuItem.cs

@ -277,10 +277,6 @@ namespace Perspex.Controls
} }
break; break;
default:
e.Handled = this.HandleAccessKey(e.Text);
break;
} }
if (!passStraightToParent) if (!passStraightToParent)
@ -289,60 +285,6 @@ namespace Perspex.Controls
} }
} }
private bool HandleAccessKey(string text)
{
text = text.ToUpper();
if (!this.IsSubMenuOpen)
{
var match = this.GetVisualDescendents()
.OfType<AccessText>()
.FirstOrDefault(x => x.AccessKey.ToString().ToUpper() == text);
if (match != null)
{
if (this.HasSubMenu)
{
this.IsSubMenuOpen = true;
}
else
{
this.RaiseEvent(new RoutedEventArgs(ClickEvent));
}
return true;
}
}
else
{
var match = this.popup.PopupRoot.GetVisualDescendents()
.OfType<AccessText>()
.FirstOrDefault(x => x.AccessKey.ToString().ToUpper() == text)
?.GetVisualAncestors()
.OfType<MenuItem>()
.FirstOrDefault();
if (match != null)
{
var item = (MenuItem)this.ItemContainerGenerator.GetItemForContainer(match);
if (item.HasSubMenu)
{
item.SelectedIndex = 0;
item.IsSubMenuOpen = true;
}
else
{
item.RaiseEvent(new RoutedEventArgs(ClickEvent));
}
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Called when the pointer enters the <see cref="MenuItem"/>. /// Called when the pointer enters the <see cref="MenuItem"/>.
/// </summary> /// </summary>

19
Perspex.Controls/MenuItemAccessKeyHandler.cs

@ -92,14 +92,19 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object sender, KeyEventArgs e) protected virtual void OnKeyDown(object sender, KeyEventArgs e)
{ {
var text = e.Text.ToUpper(); if (!string.IsNullOrWhiteSpace(e.Text))
var focus = this.registered
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)
.FirstOrDefault()?.Item2;
if (focus != null)
{ {
focus.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); 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));
}
e.Handled = true;
} }
} }
} }

82
Perspex.Input/AccessKeyHandler.cs

@ -10,6 +10,7 @@ namespace Perspex.Input
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Perspex.Interactivity; using Perspex.Interactivity;
using Perspex.VisualTree;
/// <summary> /// <summary>
/// Handles access keys for a window. /// Handles access keys for a window.
@ -40,6 +41,11 @@ namespace Perspex.Input
/// </summary> /// </summary>
private bool showingAccessKeys; private bool showingAccessKeys;
/// <summary>
/// Whether to ignore the Alt KeyUp event.
/// </summary>
private bool ignoreAltUp;
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
@ -64,6 +70,7 @@ namespace Perspex.Input
this.owner = owner; this.owner = owner;
this.owner.AddHandler(InputElement.KeyDownEvent, this.OnPreviewKeyDown, RoutingStrategies.Tunnel); this.owner.AddHandler(InputElement.KeyDownEvent, this.OnPreviewKeyDown, RoutingStrategies.Tunnel);
this.owner.AddHandler(InputElement.KeyDownEvent, this.OnKeyDown, RoutingStrategies.Bubble);
this.owner.AddHandler(InputElement.KeyUpEvent, this.OnPreviewKeyUp, RoutingStrategies.Tunnel); this.owner.AddHandler(InputElement.KeyUpEvent, this.OnPreviewKeyUp, RoutingStrategies.Tunnel);
this.owner.AddHandler(InputElement.PointerPressedEvent, this.OnPreviewPointerPressed, RoutingStrategies.Tunnel); this.owner.AddHandler(InputElement.PointerPressedEvent, this.OnPreviewPointerPressed, RoutingStrategies.Tunnel);
} }
@ -98,7 +105,7 @@ namespace Perspex.Input
} }
/// <summary> /// <summary>
/// Handles the Alt key being pressed in the window. /// Called when a key is pressed in the owner window.
/// </summary> /// </summary>
/// <param name="sender">The event sender.</param> /// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
@ -106,19 +113,61 @@ namespace Perspex.Input
{ {
if (e.Key == Key.LeftAlt) if (e.Key == Key.LeftAlt)
{ {
this.owner.ShowAccessKeys = this.showingAccessKeys = true; if (this.MainMenu == null || !this.MainMenu.IsOpen)
{
// When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File").
this.owner.ShowAccessKeys = this.showingAccessKeys = true;
}
else
{
// If the Alt key is pressed and the main menu is open, close the main menu.
this.CloseMenu();
this.ignoreAltUp = true;
}
// We always handle the Alt key.
e.Handled = true;
}
}
/// <summary>
/// Called when a key is pressed in the owner window.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object sender, KeyEventArgs e)
{
bool menuIsOpen = this.MainMenu?.IsOpen == true;
if (e.Key == Key.Escape && menuIsOpen)
{
// When the Escape key is pressed with the main menu open, close it.
this.CloseMenu();
e.Handled = true; e.Handled = true;
} }
else if ((KeyboardDevice.Instance.Modifiers & ModifierKeys.Alt) != 0) else if ((KeyboardDevice.Instance.Modifiers & ModifierKeys.Alt) != 0 || menuIsOpen)
{ {
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.
var text = e.Text.ToUpper(); var text = e.Text.ToUpper();
var focus = this.registered var matches = this.registered
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) .Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)
.FirstOrDefault()?.Item2; .Select(x => x.Item2);
// If the menu is open, only match controls in the menu's visual tree.
if (menuIsOpen)
{
matches = matches.Where(x => this.MainMenu.IsVisualParentOf(x));
}
var match = matches.FirstOrDefault();
if (focus != null) // If there was a match, raise the AccessKeyPressed event on it.
if (match != null)
{ {
focus.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent)); match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent));
e.Handled = true;
} }
} }
} }
@ -133,9 +182,13 @@ namespace Perspex.Input
switch (e.Key) switch (e.Key)
{ {
case Key.LeftAlt: case Key.LeftAlt:
if (this.showingAccessKeys && this.MainMenu != null) if (this.ignoreAltUp)
{
this.ignoreAltUp = false;
}
else if (this.showingAccessKeys && this.MainMenu != null)
{ {
this.MainMenu.OpenMenu(); this.MainMenu.Open();
e.Handled = true; e.Handled = true;
} }
@ -143,7 +196,7 @@ namespace Perspex.Input
case Key.F10: case Key.F10:
this.owner.ShowAccessKeys = this.showingAccessKeys = true; this.owner.ShowAccessKeys = this.showingAccessKeys = true;
this.MainMenu.OpenMenu(); this.MainMenu.Open();
e.Handled = true; e.Handled = true;
break; break;
} }
@ -161,5 +214,14 @@ namespace Perspex.Input
this.owner.ShowAccessKeys = false; this.owner.ShowAccessKeys = false;
} }
} }
/// <summary>
/// Closes the <see cref="MainMenu"/> and performs other bookeeping.
/// </summary>
private void CloseMenu()
{
this.MainMenu.Close();
this.owner.ShowAccessKeys = this.showingAccessKeys = false;
}
} }
} }

14
Perspex.Input/IMainMenu.cs

@ -9,11 +9,21 @@ namespace Perspex.Input
/// <summary> /// <summary>
/// Defines the interface for a window's main menu. /// Defines the interface for a window's main menu.
/// </summary> /// </summary>
public interface IMainMenu public interface IMainMenu : IVisual
{ {
/// <summary>
/// Gets a value indicating whether the menu is open.
/// </summary>
bool IsOpen { get; }
/// <summary>
/// Closes the menu.
/// </summary>
void Close();
/// <summary> /// <summary>
/// Opens the menu in response to the Alt/F10 key. /// Opens the menu in response to the Alt/F10 key.
/// </summary> /// </summary>
void OpenMenu(); void Open();
} }
} }

5
Perspex.SceneGraph/VisualTree/VisualExtensions.cs

@ -117,5 +117,10 @@ namespace Perspex.VisualTree
return visual; return visual;
} }
public static bool IsVisualParentOf(this IVisual visual, IVisual target)
{
return target.GetVisualAncestors().Any(x => x == visual);
}
} }
} }

Loading…
Cancel
Save