| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- #nullable enable
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using Microsoft.Extensions.Logging;
- using Serilog;
- using Serilog.Core;
- using Serilog.Events;
- using ILogger = Microsoft.Extensions.Logging.ILogger;
- namespace UICatalog.Scenarios;
- [ScenarioMetadata ("Menus", "Illustrates MenuBar, Menu, and MenuItem")]
- [ScenarioCategory ("Controls")]
- [ScenarioCategory ("Menus")]
- [ScenarioCategory ("Shortcuts")]
- public class Menus : Scenario
- {
- public override void Main ()
- {
- Logging.Logger = CreateLogger ();
- Application.Init ();
- Runnable app = new ();
- app.Title = GetQuitKeyAndName ();
- ObservableCollection<string> eventSource = new ();
- var eventLog = new ListView
- {
- Title = "Event Log",
- X = Pos.AnchorEnd (),
- Width = Dim.Auto (),
- Height = Dim.Fill (), // Make room for some wide things
- SchemeName = "Runnable",
- Source = new ListWrapper<string> (eventSource)
- };
- eventLog.Border!.Thickness = new (0, 1, 0, 0);
- MenuHost menuHostView = new ()
- {
- Id = "menuHostView",
- Title = $"Menu Host - Use {PopoverMenu.DefaultKey} for Popover Menu",
- X = 0,
- Y = 0,
- Width = Dim.Fill ()! - Dim.Width (eventLog),
- Height = Dim.Fill (),
- BorderStyle = LineStyle.Dotted
- };
- app.Add (menuHostView);
- menuHostView.CommandNotBound += (o, args) =>
- {
- if (o is not View sender || args.Handled)
- {
- return;
- }
- Logging.Debug ($"{sender.Id} CommandNotBound: {args?.Context?.Command}");
- eventSource.Add ($"{sender.Id} CommandNotBound: {args?.Context?.Command}");
- eventLog.MoveDown ();
- };
- menuHostView.Accepting += (o, args) =>
- {
- if (o is not View sender || args.Handled)
- {
- return;
- }
- Logging.Debug ($"{sender.Id} Accepting: {args?.Context?.Source?.Title}");
- eventSource.Add ($"{sender.Id} Accepting: {args?.Context?.Source?.Title}: ");
- eventLog.MoveDown ();
- };
- menuHostView.ContextMenu!.Accepted += (o, args) =>
- {
- if (o is not View sender || args.Handled)
- {
- return;
- }
- Logging.Debug ($"{sender.Id} Accepted: {args?.Context?.Source?.Text}");
- eventSource.Add ($"{sender.Id} Accepted: {args?.Context?.Source?.Text}: ");
- eventLog.MoveDown ();
- };
- app.Add (eventLog);
- Application.Run (app);
- app.Dispose ();
- Application.Shutdown ();
- }
- /// <summary>
- /// A demo view class that contains a menu bar and a popover menu.
- /// </summary>
- public class MenuHost : View
- {
- internal PopoverMenu? ContextMenu { get; private set; }
- public MenuHost ()
- {
- CanFocus = true;
- BorderStyle = LineStyle.Dashed;
- AddCommand (
- Command.Context,
- ctx =>
- {
- ContextMenu?.MakeVisible ();
- return true;
- });
- MouseBindings.ReplaceCommands (MouseFlags.Button3Clicked, Command.Context);
- KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);
- AddCommand (
- Command.Cancel,
- ctx =>
- {
- if (App?.Popover?.GetActivePopover () as PopoverMenu is { Visible: true } visiblePopover)
- {
- visiblePopover.Visible = false;
- }
- return true;
- });
- MouseBindings.ReplaceCommands (MouseFlags.Button1Clicked, Command.Cancel);
- Label lastCommandLabel = new ()
- {
- Title = "_Last Command:",
- X = 15,
- Y = 10
- };
- View lastCommandText = new ()
- {
- X = Pos.Right (lastCommandLabel) + 1,
- Y = Pos.Top (lastCommandLabel),
- Height = Dim.Auto (),
- Width = Dim.Auto ()
- };
- Add (lastCommandLabel, lastCommandText);
- AddCommand (Command.New, HandleCommand);
- HotKeyBindings.Add (Key.F2, Command.New);
- AddCommand (Command.Open, HandleCommand);
- HotKeyBindings.Add (Key.F3, Command.Open);
- AddCommand (Command.Save, HandleCommand);
- HotKeyBindings.Add (Key.F4, Command.Save);
- AddCommand (Command.SaveAs, HandleCommand);
- HotKeyBindings.Add (Key.A.WithCtrl, Command.SaveAs);
- AddCommand (
- Command.Quit,
- ctx =>
- {
- Logging.Debug ("MenuHost Command.Quit - RequestStop");
- Application.RequestStop ();
- return true;
- });
- HotKeyBindings.Add (Application.QuitKey, Command.Quit);
- AddCommand (Command.Cut, HandleCommand);
- HotKeyBindings.Add (Key.X.WithCtrl, Command.Cut);
- AddCommand (Command.Copy, HandleCommand);
- HotKeyBindings.Add (Key.C.WithCtrl, Command.Copy);
- AddCommand (Command.Paste, HandleCommand);
- HotKeyBindings.Add (Key.V.WithCtrl, Command.Paste);
- AddCommand (Command.SelectAll, HandleCommand);
- HotKeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
- // BUGBUG: This must come before we create the MenuBar or it will not work.
- // BUGBUG: This is due to TODO's in PopoverMenu where key bindings are not
- // BUGBUG: updated after the MenuBar is created.
- Application.KeyBindings.Remove (Key.F5);
- Application.KeyBindings.Add (Key.F5, this, Command.Edit);
- var menuBar = new MenuBar
- {
- Title = "MenuHost MenuBar"
- };
- MenuHost host = this;
- menuBar.EnableForDesign (ref host);
- base.Add (menuBar);
- Label lastAcceptedLabel = new ()
- {
- Title = "Last Accepted:",
- X = Pos.Left (lastCommandLabel),
- Y = Pos.Bottom (lastCommandLabel)
- };
- View lastAcceptedText = new ()
- {
- X = Pos.Right (lastAcceptedLabel) + 1,
- Y = Pos.Top (lastAcceptedLabel),
- Height = Dim.Auto (),
- Width = Dim.Auto ()
- };
- Add (lastAcceptedLabel, lastAcceptedText);
- // MenuItem: AutoSave - Demos simple CommandView state tracking
- // In MenuBar.EnableForDesign, the auto save MenuItem does not specify a Command. But does
- // set a Key (F10). MenuBar adds this key as a hotkey and thus if it's pressed, it toggles the MenuItem
- // CB.
- // So that is needed is to mirror the two check boxes.
- var autoSaveMenuItemCb = menuBar.GetMenuItemsWithTitle ("_Auto Save").FirstOrDefault ()?.CommandView as CheckBox;
- Debug.Assert (autoSaveMenuItemCb is { });
- CheckBox autoSaveStatusCb = new ()
- {
- Title = "AutoSave Status (MenuItem Binding to F10)",
- X = Pos.Left (lastAcceptedLabel),
- Y = Pos.Bottom (lastAcceptedLabel)
- };
- autoSaveStatusCb.CheckedStateChanged += (_, _) => { autoSaveMenuItemCb!.CheckedState = autoSaveStatusCb.CheckedState; };
- if (autoSaveMenuItemCb is { })
- {
- autoSaveMenuItemCb.CheckedStateChanged += (_, _) => { autoSaveStatusCb!.CheckedState = autoSaveMenuItemCb.CheckedState; };
- }
- base.Add (autoSaveStatusCb);
- // MenuItem: Enable Overwrite - Demos View Key Binding
- // In MenuBar.EnableForDesign, the overwrite MenuItem specifies a Command (Command.EnableOverwrite).
- // Ctrl+W is bound to Command.EnableOverwrite by this View.
- // Thus when Ctrl+W is pressed the MenuBar never sees it, but the command is invoked on this.
- // If the user clicks on the MenuItem, Accept will be raised.
- CheckBox enableOverwriteStatusCb = new ()
- {
- Title = "Enable Overwrite (View Binding to Ctrl+W)",
- X = Pos.Left (autoSaveStatusCb),
- Y = Pos.Bottom (autoSaveStatusCb)
- };
- // The source of truth is our status CB; any time it changes, update the menu item
- var enableOverwriteMenuItemCb = menuBar.GetMenuItemsWithTitle ("Overwrite").FirstOrDefault ()?.CommandView as CheckBox;
- enableOverwriteStatusCb.CheckedStateChanged += (_, _) => enableOverwriteMenuItemCb!.CheckedState = enableOverwriteStatusCb.CheckedState;
- menuBar.Accepted += (o, args) =>
- {
- if (args.Context?.Source is MenuItem mi && mi.CommandView == enableOverwriteMenuItemCb)
- {
- Logging.Debug ($"menuBar.Accepted: {args.Context.Source?.Title}");
- // Set Cancel to true to stop propagation of Accepting to superview
- args.Handled = true;
- // Since overwrite uses a MenuItem.Command the menu item CB is the source of truth
- enableOverwriteStatusCb.CheckedState = ((CheckBox)mi.CommandView).CheckedState;
- lastAcceptedText.Text = args?.Context?.Source?.Title!;
- }
- };
- HotKeyBindings.Add (Key.W.WithCtrl, Command.EnableOverwrite);
- AddCommand (
- Command.EnableOverwrite,
- ctx =>
- {
- // The command was invoked. Toggle the status Cb.
- enableOverwriteStatusCb.AdvanceCheckState ();
- return HandleCommand (ctx);
- });
- base.Add (enableOverwriteStatusCb);
- // MenuItem: EditMode - Demos App Level Key Bindings
- // In MenuBar.EnableForDesign, the edit mode MenuItem specifies a Command (Command.Edit).
- // F5 is bound to Command.EnableOverwrite as an Applicatio-Level Key Binding
- // Thus when F5 is pressed the MenuBar never sees it, but the command is invoked on this, via
- // a Application.KeyBinding.
- // If the user clicks on the MenuItem, Accept will be raised.
- CheckBox editModeStatusCb = new ()
- {
- Title = "EditMode (App Binding to F5)",
- X = Pos.Left (enableOverwriteStatusCb),
- Y = Pos.Bottom (enableOverwriteStatusCb)
- };
- // The source of truth is our status CB; any time it changes, update the menu item
- var editModeMenuItemCb = menuBar.GetMenuItemsWithTitle ("EditMode").FirstOrDefault ()?.CommandView as CheckBox;
- editModeStatusCb.CheckedStateChanged += (_, _) => editModeMenuItemCb!.CheckedState = editModeStatusCb.CheckedState;
- menuBar.Accepted += (o, args) =>
- {
- if (args.Context?.Source is MenuItem mi && mi.CommandView == editModeMenuItemCb)
- {
- Logging.Debug ($"menuBar.Accepted: {args.Context.Source?.Title}");
- // Set Cancel to true to stop propagation of Accepting to superview
- args.Handled = true;
- // Since overwrite uses a MenuItem.Command the menu item CB is the source of truth
- editModeMenuItemCb.CheckedState = ((CheckBox)mi.CommandView).CheckedState;
- lastAcceptedText.Text = args?.Context?.Source?.Title!;
- }
- };
- AddCommand (
- Command.Edit,
- ctx =>
- {
- // The command was invoked. Toggle the status Cb.
- editModeStatusCb.AdvanceCheckState ();
- return HandleCommand (ctx);
- });
- base.Add (editModeStatusCb);
- // Set up the Context Menu
- ContextMenu = new ()
- {
- Title = "ContextMenu",
- Id = "ContextMenu"
- };
- ContextMenu.EnableForDesign (ref host);
- ContextMenu.Visible = false;
- // Demo of PopoverMenu as a context menu
- // If we want Commands from the ContextMenu to be handled by the MenuHost
- // we need to subscribe to the ContextMenu's Accepted event.
- ContextMenu!.Accepted += (o, args) =>
- {
- Logging.Debug ($"ContextMenu.Accepted: {args.Context?.Source?.Title}");
- // Forward the event to the MenuHost
- if (args.Context is { })
- {
- //InvokeCommand (args.Context.Command);
- }
- };
- ContextMenu!.VisibleChanged += (sender, args) =>
- {
- if (ContextMenu!.Visible)
- { }
- };
- // Add a button to open the contextmenu
- var openBtn = new Button { X = Pos.Center (), Y = 4, Text = "_Open Menu", IsDefault = true };
- openBtn.Accepting += (s, e) =>
- {
- e.Handled = true;
- Logging.Trace ($"openBtn.Accepting - Sending F9. {e.Context?.Source?.Title}");
- NewKeyDownEvent (menuBar.Key);
- };
- Add (openBtn);
- //var hideBtn = new Button { X = Pos.Center (), Y = Pos.Bottom (openBtn), Text = "Toggle Menu._Visible" };
- //hideBtn.Accepting += (s, e) => { menuBar.Visible = !menuBar.Visible; };
- //appWindow.Add (hideBtn);
- //var enableBtn = new Button { X = Pos.Center (), Y = Pos.Bottom (hideBtn), Text = "_Toggle Menu.Enable" };
- //enableBtn.Accepting += (s, e) => { menuBar.Enabled = !menuBar.Enabled; };
- //appWindow.Add (enableBtn);
- autoSaveStatusCb.SetFocus ();
- return;
- // Add the commands supported by this View
- bool? HandleCommand (ICommandContext? ctx)
- {
- lastCommandText.Text = ctx?.Command!.ToString ()!;
- Logging.Debug ($"lastCommand: {lastCommandText.Text}");
- return true;
- }
- }
- /// <inheritdoc/>
- protected override void Dispose (bool disposing)
- {
- if (ContextMenu is { })
- {
- ContextMenu.Dispose ();
- ContextMenu = null;
- }
- base.Dispose (disposing);
- }
- }
- private const string LOGFILE_LOCATION = "./logs";
- private static readonly string _logFilePath = string.Empty;
- private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
- private static ILogger CreateLogger ()
- {
- // Configure Serilog to write logs to a file
- _logLevelSwitch.MinimumLevel = LogEventLevel.Verbose;
- Log.Logger = new LoggerConfiguration ()
- .MinimumLevel.ControlledBy (_logLevelSwitch)
- .Enrich.FromLogContext () // Enables dynamic enrichment
- .WriteTo.Debug ()
- .WriteTo.File (
- _logFilePath,
- rollingInterval: RollingInterval.Day,
- outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
- .CreateLogger ();
- // Create a logger factory compatible with Microsoft.Extensions.Logging
- using ILoggerFactory loggerFactory = LoggerFactory.Create (
- builder =>
- {
- builder
- .AddSerilog (dispose: true) // Integrate Serilog with ILogger
- .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
- });
- // Get an ILogger instance
- return loggerFactory.CreateLogger ("Global Logger");
- }
- }
|