소스 검색

Added MenuOpened event and others bug fixes. (#1572)

* Added MenuOpened event and others bug fixes.

* Fixing typo.

* Unifying constructors initializations.
BDisp 3 년 전
부모
커밋
ff962b64df
2개의 변경된 파일596개의 추가작업 그리고 75개의 파일을 삭제
  1. 143 75
      Terminal.Gui/Views/Menu.cs
  2. 453 0
      UnitTests/MenuTests.cs

+ 143 - 75
Terminal.Gui/Views/Menu.cs

@@ -47,15 +47,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of <see cref="MenuItem"/>
 		/// Initializes a new instance of <see cref="MenuItem"/>
 		/// </summary>
 		/// </summary>
-		public MenuItem (Key shortcut = Key.Null)
-		{
-			Title = "";
-			Help = "";
-			shortcutHelper = new ShortcutHelper ();
-			if (shortcut != Key.Null) {
-				shortcutHelper.Shortcut = shortcut;
-			}
-		}
+		public MenuItem (Key shortcut = Key.Null) : this ("", "", null, null, null, shortcut) { }
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of <see cref="MenuItem"/>.
 		/// Initializes a new instance of <see cref="MenuItem"/>.
@@ -216,8 +208,7 @@ namespace Terminal.Gui {
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		public MenuBarItem (ustring title, ustring help, Action action, Func<bool> canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
 		public MenuBarItem (ustring title, ustring help, Action action, Func<bool> canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
 		{
 		{
-			SetTitle (title ?? "");
-			Children = null;
+			Initialize (title, null, null, true);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -228,15 +219,7 @@ namespace Terminal.Gui {
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		public MenuBarItem (ustring title, MenuItem [] children, MenuItem parent = null)
 		public MenuBarItem (ustring title, MenuItem [] children, MenuItem parent = null)
 		{
 		{
-			if (children == null) {
-				throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
-			}
-			SetTitle (title ?? "");
-			if (parent != null) {
-				Parent = parent;
-			}
-			SetChildrensParent (children);
-			Children = children;
+			Initialize (title, children, parent);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -247,22 +230,7 @@ namespace Terminal.Gui {
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		public MenuBarItem (ustring title, List<MenuItem []> children, MenuItem parent = null)
 		public MenuBarItem (ustring title, List<MenuItem []> children, MenuItem parent = null)
 		{
 		{
-			if (children == null) {
-				throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
-			}
-			SetTitle (title ?? "");
-			if (parent != null) {
-				Parent = parent;
-			}
-			MenuItem [] childrens = new MenuItem [] { };
-			foreach (var item in children) {
-				for (int i = 0; i < item.Length; i++) {
-					SetChildrensParent (item);
-					Array.Resize (ref childrens, childrens.Length + 1);
-					childrens [childrens.Length - 1] = item [i];
-				}
-			}
-			Children = childrens;
+			Initialize (title, children, parent);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -276,6 +244,33 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public MenuBarItem () : this (children: new MenuItem [] { }) { }
 		public MenuBarItem () : this (children: new MenuItem [] { }) { }
 
 
+		void Initialize (ustring title, object children, MenuItem parent = null, bool isTopLevel = false)
+		{
+			if (!isTopLevel && children == null) {
+				throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
+			}
+			SetTitle (title ?? "");
+			if (parent != null) {
+				Parent = parent;
+			}
+			if (children is List<MenuItem []>) {
+				MenuItem [] childrens = new MenuItem [] { };
+				foreach (var item in (List<MenuItem []>)children) {
+					for (int i = 0; i < item.Length; i++) {
+						SetChildrensParent (item);
+						Array.Resize (ref childrens, childrens.Length + 1);
+						childrens [childrens.Length - 1] = item [i];
+					}
+				}
+				Children = childrens;
+			} else if (children is MenuItem []) {
+				SetChildrensParent ((MenuItem [])children);
+				Children = (MenuItem [])children;
+			} else {
+				Children = null;
+			}
+		}
+
 		//static int GetMaxTitleLength (MenuItem [] children)
 		//static int GetMaxTitleLength (MenuItem [] children)
 		//{
 		//{
 		//	int maxLength = 0;
 		//	int maxLength = 0;
@@ -570,15 +565,10 @@ namespace Terminal.Gui {
 				host.NextMenu (barItems.IsTopLevel || (barItems.Children != null && current > -1 && current < barItems.Children.Length && barItems.Children [current].IsFromSubMenu) ? true : false);
 				host.NextMenu (barItems.IsTopLevel || (barItems.Children != null && current > -1 && current < barItems.Children.Length && barItems.Children [current].IsFromSubMenu) ? true : false);
 				return true;
 				return true;
 			case Key.Esc:
 			case Key.Esc:
-				Application.UngrabMouse ();
-				host.CloseAllMenus ();
+				CloseAllMenus ();
 				return true;
 				return true;
 			case Key.Enter:
 			case Key.Enter:
-				if (barItems.IsTopLevel) {
-					Run (barItems.Action);
-				} else if (current > -1) {
-					Run (barItems.Children [current].Action);
-				}
+				RunSelected ();
 				return true;
 				return true;
 			default:
 			default:
 				// TODO: rune-ify
 				// TODO: rune-ify
@@ -598,6 +588,21 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
+		void RunSelected ()
+		{
+			if (barItems.IsTopLevel) {
+				Run (barItems.Action);
+			} else if (current > -1) {
+				Run (barItems.Children [current].Action);
+			}
+		}
+
+		void CloseAllMenus ()
+		{
+			Application.UngrabMouse ();
+			host.CloseAllMenus ();
+		}
+
 		bool MoveDown ()
 		bool MoveDown ()
 		{
 		{
 			if (barItems.IsTopLevel) {
 			if (barItems.IsTopLevel) {
@@ -630,6 +635,7 @@ namespace Terminal.Gui {
 				}
 				}
 			} while (barItems.Children [current] == null || disabled);
 			} while (barItems.Children [current] == null || disabled);
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
+			host.OnMenuOpened ();
 			return true;
 			return true;
 		}
 		}
 
 
@@ -674,6 +680,7 @@ namespace Terminal.Gui {
 				}
 				}
 			} while (barItems.Children [current] == null || disabled);
 			} while (barItems.Children [current] == null || disabled);
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
+			host.OnMenuOpened ();
 			return true;
 			return true;
 		}
 		}
 
 
@@ -708,6 +715,7 @@ namespace Terminal.Gui {
 				if (item != null && !disabled)
 				if (item != null && !disabled)
 					current = me.Y - 1;
 					current = me.Y - 1;
 				CheckSubMenu ();
 				CheckSubMenu ();
+				host.OnMenuOpened ();
 				return true;
 				return true;
 			}
 			}
 			return false;
 			return false;
@@ -970,13 +978,27 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public event Action<MenuOpeningEventArgs> MenuOpening;
 		public event Action<MenuOpeningEventArgs> MenuOpening;
 
 
+		/// <summary>
+		/// Raised when a menu is opened.
+		/// </summary>
+		public event Action<MenuItem> MenuOpened;
+
 		/// <summary>
 		/// <summary>
 		/// Raised when a menu is closing.
 		/// Raised when a menu is closing.
 		/// </summary>
 		/// </summary>
 		public event Action MenuClosing;
 		public event Action MenuClosing;
 
 
 		internal Menu openMenu;
 		internal Menu openMenu;
-		internal Menu openCurrentMenu;
+		Menu ocm;
+		internal Menu openCurrentMenu {
+			get => ocm;
+			set {
+				if (ocm != value) {
+					ocm = value;
+					OnMenuOpened ();
+				}
+			}
+		}
 		internal List<Menu> openSubMenu;
 		internal List<Menu> openSubMenu;
 		View previousFocused;
 		View previousFocused;
 		internal bool isMenuOpening;
 		internal bool isMenuOpening;
@@ -999,6 +1021,18 @@ namespace Terminal.Gui {
 			return ev;
 			return ev;
 		}
 		}
 
 
+		/// <summary>
+		/// Virtual method that will invoke the <see cref="MenuOpened"/> event if it's defined.
+		/// </summary>
+		public virtual void OnMenuOpened ()
+		{
+			MenuItem mi = null;
+			if (openCurrentMenu.barItems.Children != null) {
+				mi = openCurrentMenu.barItems.Children [openCurrentMenu.current];
+			}
+			MenuOpened?.Invoke (mi);
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Virtual method that will invoke the <see cref="MenuClosing"/>
 		/// Virtual method that will invoke the <see cref="MenuClosing"/>
 		/// </summary>
 		/// </summary>
@@ -1021,7 +1055,7 @@ namespace Terminal.Gui {
 			if (newMenu.Cancel) {
 			if (newMenu.Cancel) {
 				return;
 				return;
 			}
 			}
-			if (newMenu.NewMenuBarItem != null && Menus [index].Title == newMenu.NewMenuBarItem.Title) {
+			if (newMenu.NewMenuBarItem != null) {
 				Menus [index] = newMenu.NewMenuBarItem;
 				Menus [index] = newMenu.NewMenuBarItem;
 			}
 			}
 			int pos = 0;
 			int pos = 0;
@@ -1208,12 +1242,15 @@ namespace Terminal.Gui {
 				return;
 				return;
 			for (int i = openSubMenu.Count - 1; i > index; i--) {
 			for (int i = openSubMenu.Count - 1; i > index; i--) {
 				isMenuClosing = true;
 				isMenuClosing = true;
+				Menu menu;
 				if (openSubMenu.Count - 1 > 0)
 				if (openSubMenu.Count - 1 > 0)
-					openSubMenu [i - 1].SetFocus ();
+					menu = openSubMenu [i - 1];
 				else
 				else
-					openMenu.SetFocus ();
+					menu = openMenu;
+				openCurrentMenu = menu;
+				openCurrentMenu.SetFocus ();
 				if (openSubMenu != null) {
 				if (openSubMenu != null) {
-					var menu = openSubMenu [i];
+					menu = openSubMenu [i];
 					SuperView.Remove (menu);
 					SuperView.Remove (menu);
 					openSubMenu.Remove (menu);
 					openSubMenu.Remove (menu);
 					menu.Dispose ();
 					menu.Dispose ();
@@ -1405,6 +1442,10 @@ namespace Terminal.Gui {
 
 
 		private void ProcessMenu (int i, MenuBarItem mi)
 		private void ProcessMenu (int i, MenuBarItem mi)
 		{
 		{
+			if (selected < 0) {
+				return;
+			}
+
 			if (mi.IsTopLevel) {
 			if (mi.IsTopLevel) {
 				var menu = new Menu (this, i, 0, mi);
 				var menu = new Menu (this, i, 0, mi);
 				menu.Run (mi.Action);
 				menu.Run (mi.Action);
@@ -1419,6 +1460,7 @@ namespace Terminal.Gui {
 				}
 				}
 				openCurrentMenu.CheckSubMenu ();
 				openCurrentMenu.CheckSubMenu ();
 			}
 			}
+			SetNeedsDisplay ();
 		}
 		}
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
@@ -1451,30 +1493,22 @@ namespace Terminal.Gui {
 		{
 		{
 			switch (kb.Key) {
 			switch (kb.Key) {
 			case Key.CursorLeft:
 			case Key.CursorLeft:
-				selected--;
-				if (selected < 0)
-					selected = Menus.Length - 1;
-				break;
+				MoveLeft ();
+				return true;
+
 			case Key.CursorRight:
 			case Key.CursorRight:
-				selected = (selected + 1) % Menus.Length;
-				break;
+				MoveRight ();
+				return true;
 
 
 			case Key.Esc:
 			case Key.Esc:
 			case Key.C | Key.CtrlMask:
 			case Key.C | Key.CtrlMask:
-				//TODO: Running = false;
-				CloseMenu ();
-				if (openedByAltKey) {
-					openedByAltKey = false;
-					LastFocused?.SetFocus ();
-				}
-				break;
+				CloseMenuBar ();
+				return true;
 
 
 			case Key.CursorDown:
 			case Key.CursorDown:
 			case Key.Enter:
 			case Key.Enter:
-				if (selected > -1) {
-					ProcessMenu (selected, Menus [selected]);
-				}
-				break;
+				ProcessMenu (selected, Menus [selected]);
+				return true;
 
 
 			default:
 			default:
 				var key = kb.KeyValue;
 				var key = kb.KeyValue;
@@ -1499,8 +1533,32 @@ namespace Terminal.Gui {
 
 
 				return false;
 				return false;
 			}
 			}
+		}
+
+		void CloseMenuBar ()
+		{
+			CloseMenu ();
+			if (openedByAltKey) {
+				openedByAltKey = false;
+				LastFocused?.SetFocus ();
+			}
+			SetNeedsDisplay ();
+		}
+
+		void MoveRight ()
+		{
+			selected = (selected + 1) % Menus.Length;
+			OpenMenu (selected);
+			SetNeedsDisplay ();
+		}
+
+		void MoveLeft ()
+		{
+			selected--;
+			if (selected < 0)
+				selected = Menus.Length - 1;
+			OpenMenu (selected);
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
-			return true;
 		}
 		}
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
@@ -1564,15 +1622,25 @@ namespace Terminal.Gui {
 						Application.UngrabMouse ();
 						Application.UngrabMouse ();
 						var v = me.View;
 						var v = me.View;
 						Application.GrabMouse (v);
 						Application.GrabMouse (v);
-						var newxy = v.ScreenToView (me.X, me.Y);
-						var nme = new MouseEvent () {
-							X = newxy.X,
-							Y = newxy.Y,
-							Flags = me.Flags,
-							OfX = me.X - newxy.X,
-							OfY = me.Y - newxy.Y,
-							View = v
-						};
+						MouseEvent nme;
+						if (me.Y > -1) {
+							var newxy = v.ScreenToView (me.X, me.Y);
+							nme = new MouseEvent () {
+								X = newxy.X,
+								Y = newxy.Y,
+								Flags = me.Flags,
+								OfX = me.X - newxy.X,
+								OfY = me.Y - newxy.Y,
+								View = v
+							};
+						} else {
+							nme = new MouseEvent () {
+								X = me.X + current.Frame.X,
+								Y = 0,
+								Flags = me.Flags,
+								View = v
+							};
+						}
 
 
 						v.MouseEvent (nme);
 						v.MouseEvent (nme);
 						return false;
 						return false;

+ 453 - 0
UnitTests/MenuTests.cs

@@ -0,0 +1,453 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Terminal.Gui.Views {
+	public class MenuTests {
+		[Fact]
+		public void Constuctors_Defaults ()
+		{
+			var menu = new Menu (new MenuBar (), 0, 0, new MenuBarItem ());
+			Assert.Equal (Colors.Menu, menu.ColorScheme);
+			Assert.True (menu.CanFocus);
+			Assert.False (menu.WantContinuousButtonPressed);
+
+			var menuBar = new MenuBar ();
+			Assert.Equal (0, menuBar.X);
+			Assert.Equal (0, menuBar.Y);
+			Assert.IsType<Dim.DimFill> (menuBar.Width);
+			Assert.Equal (1, menuBar.Height);
+			Assert.Empty (menuBar.Menus);
+			Assert.Equal (Colors.Menu, menuBar.ColorScheme);
+			Assert.True (menuBar.WantMousePositionReports);
+			Assert.False (menuBar.IsMenuOpen);
+
+			menuBar = new MenuBar (new MenuBarItem [] { });
+			Assert.Equal (0, menuBar.X);
+			Assert.Equal (0, menuBar.Y);
+			Assert.IsType<Dim.DimFill> (menuBar.Width);
+			Assert.Equal (1, menuBar.Height);
+			Assert.Empty (menuBar.Menus);
+			Assert.Equal (Colors.Menu, menuBar.ColorScheme);
+			Assert.True (menuBar.WantMousePositionReports);
+			Assert.False (menuBar.IsMenuOpen);
+
+			var menuBarItem = new MenuBarItem ();
+			Assert.Equal ("", menuBarItem.Title);
+			Assert.Null (menuBarItem.Parent);
+			Assert.Empty (menuBarItem.Children);
+
+			menuBarItem = new MenuBarItem (new MenuBarItem [] { });
+			Assert.Equal ("", menuBarItem.Title);
+			Assert.Null (menuBarItem.Parent);
+			Assert.Empty (menuBarItem.Children);
+
+			menuBarItem = new MenuBarItem ("Test", new MenuBarItem [] { });
+			Assert.Equal ("Test", menuBarItem.Title);
+			Assert.Null (menuBarItem.Parent);
+			Assert.Empty (menuBarItem.Children);
+
+			menuBarItem = new MenuBarItem ("Test", new List<MenuItem []> ());
+			Assert.Equal ("Test", menuBarItem.Title);
+			Assert.Null (menuBarItem.Parent);
+			Assert.Empty (menuBarItem.Children);
+
+			menuBarItem = new MenuBarItem ("Test", "Help", null);
+			Assert.Equal ("Test", menuBarItem.Title);
+			Assert.Equal ("Help", menuBarItem.Help);
+			Assert.Null (menuBarItem.Action);
+			Assert.Null (menuBarItem.CanExecute);
+			Assert.Null (menuBarItem.Parent);
+			Assert.Equal (Key.Null, menuBarItem.Shortcut);
+
+			var menuItem = new MenuItem ();
+			Assert.Equal ("", menuItem.Title);
+			Assert.Equal ("", menuItem.Help);
+			Assert.Null (menuItem.Action);
+			Assert.Null (menuItem.CanExecute);
+			Assert.Null (menuItem.Parent);
+			Assert.Equal (Key.Null, menuItem.Shortcut);
+
+			menuItem = new MenuItem ("Test", "Help", Run, () => { return true; }, new MenuItem (), Key.F1);
+			Assert.Equal ("Test", menuItem.Title);
+			Assert.Equal ("Help", menuItem.Help);
+			Assert.Equal (Run, menuItem.Action);
+			Assert.NotNull (menuItem.CanExecute);
+			Assert.NotNull (menuItem.Parent);
+			Assert.Equal (Key.F1, menuItem.Shortcut);
+
+			void Run () { }
+		}
+
+		[Fact]
+		public void Exceptions ()
+		{
+			Assert.Throws<ArgumentNullException> (() => new MenuBarItem ("Test", (MenuItem [])null, null));
+			Assert.Throws<ArgumentNullException> (() => new MenuBarItem ("Test", (List<MenuItem []>)null, null));
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MenuOpening_MenuOpened_MenuClosing_Events ()
+		{
+			var miAction = "";
+			var isMenuClosed = true;
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_New", "Creates new file.", New)
+				})
+			});
+			menu.MenuOpening += (e) => {
+				Assert.Equal ("_File", e.CurrentMenu.Title);
+				Assert.Equal ("_New", e.CurrentMenu.Children [0].Title);
+				Assert.Equal ("Creates new file.", e.CurrentMenu.Children [0].Help);
+				Assert.Equal (New, e.CurrentMenu.Children [0].Action);
+				e.CurrentMenu.Children [0].Action ();
+				Assert.Equal ("New", miAction);
+				e.NewMenuBarItem = new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "Copies the selection.", Copy)
+				});
+			};
+			menu.MenuOpened += (e) => {
+				Assert.Equal ("_Edit", e.Parent.Title);
+				Assert.Equal ("_Copy", e.Title);
+				Assert.Equal ("Copies the selection.", e.Help);
+				Assert.Equal (Copy, e.Action);
+				e.Action ();
+				Assert.Equal ("Copy", miAction);
+			};
+			menu.MenuClosing += () => {
+				Assert.False (isMenuClosed);
+				isMenuClosed = true;
+			};
+			Application.Top.Add (menu);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			isMenuClosed = !menu.IsMenuOpen;
+			Assert.False (isMenuClosed);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.True (isMenuClosed);
+
+			void New () => miAction = "New";
+			void Copy () => miAction = "Copy";
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MouseEvent_Test ()
+		{
+			MenuItem miCurrent = null;
+			Menu mCurrent = null;
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_New", "", null),
+					new MenuItem ("_Open", "", null),
+					new MenuItem ("_Save", "", null)
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", null),
+					new MenuItem ("C_ut", "", null),
+					new MenuItem ("_Paste", "", null)
+				})
+			});
+			menu.MenuOpened += (e) => {
+				miCurrent = e;
+				mCurrent = menu.openCurrentMenu;
+			};
+			Application.Top.Add (menu);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () {
+				X = 10,
+				Y = 0,
+				Flags = MouseFlags.Button1Pressed,
+				View = menu
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", miCurrent.Parent.Title);
+			Assert.Equal ("_Copy", miCurrent.Title);
+
+			Assert.True (mCurrent.MouseEvent (new MouseEvent () {
+				X = 10,
+				Y = 3,
+				Flags = MouseFlags.ReportMousePosition,
+				View = mCurrent
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", miCurrent.Parent.Title);
+			Assert.Equal ("_Paste", miCurrent.Title);
+
+			for (int i = 2; i >= -1; i--) {
+				View view;
+				if (i == -1) {
+					Assert.False (mCurrent.MouseEvent (new MouseEvent () {
+						X = 10,
+						Y = i,
+						Flags = MouseFlags.ReportMousePosition,
+						View = menu
+					}));
+				} else {
+					Assert.True (mCurrent.MouseEvent (new MouseEvent () {
+						X = 10,
+						Y = i,
+						Flags = MouseFlags.ReportMousePosition,
+						View = mCurrent
+					}));
+				}
+				Assert.True (menu.IsMenuOpen);
+				if (i == 2) {
+					Assert.Equal ("_Edit", miCurrent.Parent.Title);
+					Assert.Equal ("C_ut", miCurrent.Title);
+				} else if (i == 1) {
+					Assert.Equal ("_Edit", miCurrent.Parent.Title);
+					Assert.Equal ("_Copy", miCurrent.Title);
+				} else if (i == 0) {
+					Assert.Equal ("_Edit", miCurrent.Parent.Title);
+					Assert.Equal ("_Copy", miCurrent.Title);
+				} else {
+					Assert.Equal ("_Edit", miCurrent.Parent.Title);
+					Assert.Equal ("_Copy", miCurrent.Title);
+				}
+			}
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void KeyBindings_Command ()
+		{
+			var miAction = "";
+			MenuItem mbiCurrent = null;
+			MenuItem miCurrent = null;
+			Menu mCurrent = null;
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_New", "", () => miAction ="New"),
+					new MenuItem ("_Open", "", () => miAction ="Open"),
+					new MenuItem ("_Save", "", () => miAction ="Save"),
+					null,
+					new MenuItem ("_Quit", "", () => miAction ="Quit"),
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", () => miAction ="Copy"),
+					new MenuItem ("C_ut", "", () => miAction ="Cut"),
+					new MenuItem ("_Paste", "", () => miAction ="Paste"),
+					new MenuBarItem ("_Find and Replace", new MenuItem [] {
+						new MenuItem ("F_ind", "", null),
+						new MenuItem ("_Replace", "", null)
+					}),
+					new MenuItem ("_Select All", "", () => miAction ="Select All")
+				}),
+				new MenuBarItem ("_About", "Top-Level", () => miAction ="About")
+			});
+			menu.MenuOpening += (e) => mbiCurrent = e.CurrentMenu;
+			menu.MenuOpened += (e) => {
+				miCurrent = e;
+				mCurrent = menu.openCurrentMenu;
+			};
+			menu.MenuClosing += () => {
+				mbiCurrent = null;
+				miCurrent = null;
+				mCurrent = null;
+			};
+			Application.Top.Add (menu);
+			Application.Begin (Application.Top);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Quit", GetCurrentMenuTitle ());
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Application.MainLoop.MainIteration ();
+			Assert.Equal ("New", miAction);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Application.MainLoop.MainIteration ();
+			Assert.Equal ("About", miAction);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_New", GetCurrentMenuTitle ());
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Copy", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("C_ut", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Edit", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Paste", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("F_ind", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Replace", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("F_ind", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Edit", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Edit", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Select All", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("F_ind", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Edit", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Find and Replace", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Edit", GetCurrenParenttMenuItemTitle ());
+			Assert.Equal ("_Paste", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("C_ut", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_Edit", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("_Copy", GetCurrentMenuTitle ());
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
+			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Application.MainLoop.MainIteration ();
+			Assert.Equal ("Copy", miAction);
+
+
+			string GetCurrentMenuBarItemTitle ()
+			{
+				return mbiCurrent != null ? mbiCurrent.Title.ToString () : "Closed";
+			}
+
+			string GetCurrenParenttMenuItemTitle ()
+			{
+				return miCurrent?.Parent != null ? miCurrent.Parent.Title.ToString () : "None";
+			}
+
+			string GetCurrentMenuTitle ()
+			{
+				return miCurrent != null ? miCurrent.Title.ToString () : "None";
+			}
+		}
+	}
+}