From 9c10acdd684ad111fe1480217fcabb7dd131066a Mon Sep 17 00:00:00 2001 From: Gabor Nemeth <37656640+gabor-nemeth@users.noreply.github.com> Date: Wed, 20 Mar 2019 18:31:00 +0100 Subject: [PATCH] [macOS] enhance menu (#4718) fixes #2618 --- Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs | 75 +++++++++++++++++++++- .../SeparatorMenuItem.cs | 10 +++ .../Xamarin.Forms.ControlGallery.MacOS.csproj | 3 +- .../Extensions/NSMenuExtensions.cs | 38 ++++++++--- .../FormsApplicationDelegate.cs | 11 +++- 5 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs diff --git a/Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs b/Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs index ac4940d..6281a17 100644 --- a/Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs +++ b/Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using AppKit; using CoreGraphics; using Foundation; @@ -18,7 +19,7 @@ namespace Xamarin.Forms.ControlGallery.MacOS { ObjCRuntime.Runtime.MarshalManagedException += (sender, args) => { - Console.WriteLine(args.Exception.ToString()); + Console.WriteLine(args.Exception); }; var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled; @@ -35,12 +36,15 @@ namespace Xamarin.Forms.ControlGallery.MacOS get { return _window; } } + public override void DidFinishLaunching(NSNotification notification) { Forms.Init(); FormsMaps.Init(); var app = new App(); + SetupMenu(app); + // When the native control gallery loads up, it'll let us know so we can add the nested native controls MessagingCenter.Subscribe(this, NestedNativeControlGalleryPage.ReadyForNativeControlsMessage, AddNativeControls); MessagingCenter.Subscribe(this, Bugzilla40911.ReadyToSetUp40911Test, SetUp40911Test); @@ -52,6 +56,75 @@ namespace Xamarin.Forms.ControlGallery.MacOS base.DidFinishLaunching(notification); } + void SetupMenu(Application app) + { + NativeMenuItemCreator = (menuItem) => + { + if (menuItem is SeparatorMenuItem) + { + return NSMenuItem.SeparatorItem; + } + + var nsMenuItem = new NSMenuItem { Title = menuItem.Text }; + if (menuItem.Text == "Small icons") + { + nsMenuItem.State = NSCellStateValue.On; + nsMenuItem.Activated += CheckableNsMenuItem_Activated; + } + + return nsMenuItem; + }; + + var menu = new Menu(); + + var appMenu = new Menu(); + + appMenu.Items.Add(new MenuItem { Text = "App menu test" }); + menu.Add(appMenu); + + var fileMenu = new Menu { Text = "File" }; + fileMenu.Items.Add(new MenuItem { Text = "New" }); + fileMenu.Items.Add(new MenuItem { Text = "Open" }); + menu.Add(fileMenu); + + var viewMenu = new Menu { Text = "View" }; + var smallIconsMenuItem = new MenuItem { Text = "Small icons" }; + smallIconsMenuItem.Clicked += CheckableMenuItem_Clicked; + viewMenu.Items.Add(smallIconsMenuItem); + viewMenu.Items.Add(new MenuItem { Text = "Large icons" }); + viewMenu.Items.Add(new SeparatorMenuItem()); + viewMenu.Items.Add(new MenuItem { Text = "List", IsEnabled = false }); + viewMenu.Items.Add(new MenuItem { Text = "Details" }); + menu.Add(viewMenu); + + Element.SetMenu(app, menu); + + void CheckableNsMenuItem_Activated(object sender, EventArgs e) + { + // switch state for checkable menu item + var nsMenuItem = sender as NSMenuItem; + nsMenuItem.State = nsMenuItem.State == NSCellStateValue.On ? NSCellStateValue.Off : NSCellStateValue.On; + } + + void CheckableMenuItem_Clicked(object sender, EventArgs e) + { + var menuItem = sender as MenuItem; + var alert = new NSAlert { MessageText = $"{menuItem.Text} has been clicked!" }; + alert.RunModal(); + } + } + + protected override void SetupMainAppMenu(NSMenu nativeMenu) + { + base.SetupMainAppMenu(nativeMenu); + + // reorder menu items - it can be needed if we add submenus as they are added after simple items + var appMenu = nativeMenu.ItemAt(0); + var testMenuItem = appMenu.Submenu.Items.Last(); + appMenu.Submenu.RemoveItem(testMenuItem); + appMenu.Submenu.InsertItem(testMenuItem, 0); + } + void AddNativeControls(NestedNativeControlGalleryPage page) { if (page.NativeControlsAdded) diff --git a/Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs b/Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs new file mode 100644 index 0000000..7e754ef --- /dev/null +++ b/Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs @@ -0,0 +1,10 @@ + +namespace Xamarin.Forms.ControlGallery.MacOS +{ + /// + /// Represents a separator menu item + /// + public class SeparatorMenuItem : MenuItem + { + } +} diff --git a/Xamarin.Forms.ControlGallery.MacOS/Xamarin.Forms.ControlGallery.MacOS.csproj b/Xamarin.Forms.ControlGallery.MacOS/Xamarin.Forms.ControlGallery.MacOS.csproj index 4560462..b3d0ff0 100644 --- a/Xamarin.Forms.ControlGallery.MacOS/Xamarin.Forms.ControlGallery.MacOS.csproj +++ b/Xamarin.Forms.ControlGallery.MacOS/Xamarin.Forms.ControlGallery.MacOS.csproj @@ -1,4 +1,4 @@ - + Debug @@ -103,6 +103,7 @@ + GalleryPages\AdvancedOpenGLGallery.cs diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSMenuExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSMenuExtensions.cs index a67efba..ece6de7 100644 --- a/Xamarin.Forms.Platform.MacOS/Extensions/NSMenuExtensions.cs +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSMenuExtensions.cs @@ -6,34 +6,52 @@ namespace Xamarin.Forms.Platform.macOS.Extensions { internal static class NSMenuExtensions { - public static NSMenu ToNSMenu(this Menu menus, NSMenu nsMenu = null) + public static NSMenu ToNSMenu(this Menu menus, NSMenu nsMenu = null, Func menuItemCreator = null) { if (nsMenu == null) nsMenu = new NSMenu(menus.Text ?? ""); + nsMenu.AutoEnablesItems = false; + foreach (var menu in menus) { - var menuItem = new NSMenuItem(menu.Text ?? ""); - var subMenu = new NSMenu(menu.Text ?? ""); - menuItem.Submenu = subMenu; + NSMenuItem menuItem = null; + NSMenu subMenu = null; + if (string.IsNullOrEmpty(menu.Text)) // handle menu with empty Text as the application menu + { + menuItem = nsMenu.Items.FirstOrDefault(); + subMenu = menuItem?.Submenu; + } + if (menuItem == null) + { + menuItem = new NSMenuItem(menu.Text ?? ""); + menuItem.Submenu = subMenu = new NSMenu(menu.Text ?? ""); + } + foreach (var item in menu.Items) { - var subMenuItem = item.ToNSMenuItem(); + var subMenuItem = item.ToNSMenuItem(menuItemCreator: menuItemCreator); GetAccelerators(subMenuItem, item); subMenu.AddItem(subMenuItem); item.PropertyChanged += (sender, e) => (sender as MenuItem)?.UpdateNSMenuItem(subMenuItem, new string[] { e.PropertyName }); } - nsMenu.AddItem(menuItem); - menu.ToNSMenu(subMenu); + if (!nsMenu.Items.Contains(menuItem)) + nsMenu.AddItem(menuItem); + menu.ToNSMenu(subMenu, menuItemCreator); } return nsMenu; } - public static NSMenuItem ToNSMenuItem(this MenuItem menuItem, int i = -1) + public static NSMenuItem ToNSMenuItem(this MenuItem menuItem, int i = -1, Func menuItemCreator = null) { - var nsMenuItem = new NSMenuItem(menuItem.Text ?? ""); + NSMenuItem nsMenuItem = null; + if (menuItemCreator == null) + nsMenuItem = new NSMenuItem(menuItem.Text ?? ""); + else + nsMenuItem = menuItemCreator(menuItem); if (i != -1) nsMenuItem.Tag = i; + nsMenuItem.Enabled = menuItem.IsEnabled; nsMenuItem.Activated += (sender, e) => ((IMenuItemController)menuItem).Activate(); if (!string.IsNullOrEmpty(menuItem.Icon)) @@ -42,6 +60,8 @@ namespace Xamarin.Forms.Platform.macOS.Extensions return nsMenuItem; } + + public static void UpdateNSMenuItem(this MenuItem item, NSMenuItem menuItem, string[] properties) { foreach (var property in properties) diff --git a/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs index d85d12a..2e84ea0 100644 --- a/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs +++ b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs @@ -12,6 +12,7 @@ namespace Xamarin.Forms.Platform.MacOS bool _isSuspended; static int _storyboardMainMenuCount; + public Func NativeMenuItemCreator { get; set; } public abstract NSWindow MainWindow { get; } protected override void Dispose(bool disposing) @@ -116,9 +117,15 @@ namespace Xamarin.Forms.Platform.MacOS Log.Warning("FormsApplicationDelegate", "Please provide a Main.storyboard to handle menus"); return; } - + ClearNSMenu(nsMenu); - Element.GetMenu(_application).ToNSMenu(nsMenu); + SetupMainAppMenu(nsMenu); + } + + protected virtual void SetupMainAppMenu(NSMenu nativeMenu) + { + var menu = Element.GetMenu(_application); + menu.ToNSMenu(nativeMenu, NativeMenuItemCreator); } static void ClearNSMenu(NSMenu menu) -- 2.7.4