diff --git a/ConsoleMenu/ConsoleMenu.cs b/ConsoleMenu/ConsoleMenu.cs index f3f72d0..c193604 100644 --- a/ConsoleMenu/ConsoleMenu.cs +++ b/ConsoleMenu/ConsoleMenu.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; [assembly: InternalsVisibleTo("ConsoleMenuTests")] @@ -68,7 +70,7 @@ private IReadOnlyList Titles get { ConsoleMenu? current = this; - List titles = new List(); + List titles = new(); while (current != null) { titles.Add(current.config.Title ?? ""); @@ -83,7 +85,10 @@ private IReadOnlyList Titles /// /// Don't run this method directly. Just pass a reference to this method. /// - public static void Close() => throw new InvalidOperationException("Don't run this method directly. Just pass a reference to this method."); + /// Cancellation token. + /// A representing the asynchronous operation. + /// Thrown if this method is called directly. + public static Task Close(CancellationToken cancellationToken) => throw new InvalidOperationException("Don't run this method directly. Just pass a reference to this method."); /// /// Close the menu before or after a menu action was triggered. @@ -94,7 +99,7 @@ public void CloseMenu() } /// - /// Adds a menu item into this instance. + /// Adds a menu action into this instance. /// /// Name of menu item. /// Action to call when menu item is chosen. @@ -116,12 +121,43 @@ public ConsoleMenu Add(string name, Action action) child.parent = this; } + this.menuItems.Add(name, (_) => + { + action(); + return Task.CompletedTask; + }); + return this; + } + + /// + /// Adds an asynchronous menu action into this instance. + /// + /// Name of menu item. + /// Action to call when menu item is chosen. + /// This instance with added menu item. + public ConsoleMenu Add(string name, Func action) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (action.Target is ConsoleMenu child && action == child.ShowAsync) + { + child.parent = this; + } + this.menuItems.Add(name, action); return this; } /// - /// Adds a menu item into this instance. + /// Adds a menu action into this instance. /// /// Name of menu item. /// Action to call when menu item is chosen. @@ -138,12 +174,38 @@ public ConsoleMenu Add(string name, Action action) throw new ArgumentNullException(nameof(action)); } - this.menuItems.Add(name, () => action(this)); + this.menuItems.Add(name, (_) => + { + action(this); + return Task.CompletedTask; + }); + return this; + } + + /// + /// Adds an asynchronous menu action into this instance. + /// + /// Name of menu item. + /// Action to call when menu item is chosen. + /// This instance with added menu item. + public ConsoleMenu Add(string name, Func action) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + this.menuItems.Add(name, (cancellationToken) => action(this, cancellationToken)); return this; } /// - /// Adds range of menu items into this instance. + /// Adds range of menu actions into this instance. /// /// Menu items to add. /// This instance with added menu items. @@ -162,6 +224,26 @@ public ConsoleMenu AddRange(IEnumerable> menuItems) return this; } + /// + /// Adds range of asynchronous menu actions into this instance. + /// + /// Menu items to add. + /// This instance with added menu items. + public ConsoleMenu AddRange(IEnumerable>> menuItems) + { + if (menuItems is null) + { + throw new ArgumentNullException(nameof(menuItems)); + } + + foreach (var item in menuItems) + { + this.Add(item.Item1, item.Item2); + } + + return this; + } + /// /// Applies an configuration action on this instance. /// @@ -206,7 +288,20 @@ public void Show() this.Console, new List(this.Titles), this.config, - this.closeTrigger).Show(); + this.closeTrigger).ShowAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + + /// + /// Displays the menu in console. + /// + public async Task ShowAsync(CancellationToken cancellationToken = default) + { + await new ConsoleMenuDisplay( + this.menuItems, + this.Console, + new List(this.Titles), + this.config, + this.closeTrigger).ShowAsync(cancellationToken); } /// diff --git a/ConsoleMenu/ConsoleMenu.csproj b/ConsoleMenu/ConsoleMenu.csproj index 97cfa1b..b7e33d8 100644 --- a/ConsoleMenu/ConsoleMenu.csproj +++ b/ConsoleMenu/ConsoleMenu.csproj @@ -6,11 +6,10 @@ lechu445 ConsoleMenu-simple A simple, highly customizable, DOS-like console menu - - added collection initializer -- added ConsoleMenu.Configure(MenuConfig config) + - added async API https://github.com/lechu445/ConsoleMenu true - 2.5.0 + 2.6.0 https://github.com/lechu445/ConsoleMenu console, menu, simple LICENSE.txt @@ -38,7 +37,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ConsoleMenu/ConsoleMenuDisplay.cs b/ConsoleMenu/ConsoleMenuDisplay.cs index 21b7466..94f2390 100644 --- a/ConsoleMenu/ConsoleMenuDisplay.cs +++ b/ConsoleMenu/ConsoleMenuDisplay.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace ConsoleTools; @@ -30,12 +32,12 @@ public ConsoleMenuDisplay( this.noSelectorLine = new string(' ', this.config.Selector.Length); } - public void Show() + public async Task ShowAsync(CancellationToken token) { var selectedItem = this.menuItems.GetSeletedItem(); if (selectedItem != null) { - selectedItem.Action.Invoke(); + await selectedItem.AsyncAction.Invoke(token); return; } @@ -48,6 +50,7 @@ public void Show() while (true) { + token.ThrowIfCancellationRequested(); do { if (this.config.ClearConsole) @@ -137,7 +140,7 @@ public void Show() this.console.WriteLine(); this.console.ForegroundColor = currentForegroundColor; this.console.BackgroundColor = currentBackgroundColor; - var action = this.menuItems.CurrentItem.Action; + var action = this.menuItems.CurrentItem.AsyncAction; if (action == ConsoleMenu.Close) { this.menuItems.UnsetSelectedIndex(); @@ -145,7 +148,7 @@ public void Show() } else { - action(); + await action(token).ConfigureAwait(false); if (this.closeTrigger.IsOn()) { this.menuItems.UnsetSelectedIndex(); diff --git a/ConsoleMenu/ItemsCollection.cs b/ConsoleMenu/ItemsCollection.cs index cb07360..a4f1895 100644 --- a/ConsoleMenu/ItemsCollection.cs +++ b/ConsoleMenu/ItemsCollection.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace ConsoleTools; @@ -28,7 +30,7 @@ public MenuItem CurrentItem set => this.menuItems[this.currentItemIndex] = value; } - public void Add(string name, Action action) + public void Add(string name, Func action) { this.menuItems.Add(new MenuItem(name, action, this.menuItems.Count)); } diff --git a/ConsoleMenu/MenuConfig.cs b/ConsoleMenu/MenuConfig.cs index bdf5a95..0dc250f 100644 --- a/ConsoleMenu/MenuConfig.cs +++ b/ConsoleMenu/MenuConfig.cs @@ -1,41 +1,41 @@ -#pragma warning disable SA1401 // Fields should be private +#pragma warning disable SA1401 // Fields should be private using System; using System.Collections.Generic; namespace ConsoleTools; - -/// -/// Menu configuration. + +/// +/// Menu configuration. /// public class MenuConfig -{ - /// - /// Initializes a new instance of the class. - /// - public MenuConfig() - { - } - - internal MenuConfig(MenuConfig config) - { - this.ArgsPreselectedItemsKey = config.ArgsPreselectedItemsKey; - this.ArgsPreselectedItemsValueSeparator = config.ArgsPreselectedItemsValueSeparator; - this.ClearConsole = config.ClearConsole; - this.EnableBreadcrumb = config.EnableBreadcrumb; - this.EnableFilter = config.EnableFilter; - this.EnableWriteTitle = config.EnableWriteTitle; - this.FilterPrompt = config.FilterPrompt; - this.ItemBackgroundColor = config.ItemBackgroundColor; - this.ItemForegroundColor = config.ItemForegroundColor; - this.SelectedItemBackgroundColor = config.SelectedItemBackgroundColor; - this.SelectedItemForegroundColor = config.SelectedItemForegroundColor; - this.Selector = config.Selector; - this.Title = config.Title; - this.WriteBreadcrumbAction = config.WriteBreadcrumbAction; - this.WriteHeaderAction = config.WriteHeaderAction; - this.WriteItemAction = config.WriteItemAction; - this.WriteTitleAction = config.WriteTitleAction; - } +{ + /// + /// Initializes a new instance of the class. + /// + public MenuConfig() + { + } + + internal MenuConfig(MenuConfig config) + { + this.ArgsPreselectedItemsKey = config.ArgsPreselectedItemsKey; + this.ArgsPreselectedItemsValueSeparator = config.ArgsPreselectedItemsValueSeparator; + this.ClearConsole = config.ClearConsole; + this.EnableBreadcrumb = config.EnableBreadcrumb; + this.EnableFilter = config.EnableFilter; + this.EnableWriteTitle = config.EnableWriteTitle; + this.FilterPrompt = config.FilterPrompt; + this.ItemBackgroundColor = config.ItemBackgroundColor; + this.ItemForegroundColor = config.ItemForegroundColor; + this.SelectedItemBackgroundColor = config.SelectedItemBackgroundColor; + this.SelectedItemForegroundColor = config.SelectedItemForegroundColor; + this.Selector = config.Selector; + this.Title = config.Title; + this.WriteBreadcrumbAction = config.WriteBreadcrumbAction; + this.WriteHeaderAction = config.WriteHeaderAction; + this.WriteItemAction = config.WriteItemAction; + this.WriteTitleAction = config.WriteTitleAction; + } /// default: Console.ForegroundColor public ConsoleColor SelectedItemBackgroundColor = Console.ForegroundColor; @@ -47,11 +47,11 @@ internal MenuConfig(MenuConfig config) public ConsoleColor ItemBackgroundColor = Console.BackgroundColor; /// default: Console.ForegroundColor - public ConsoleColor ItemForegroundColor = Console.ForegroundColor; - + public ConsoleColor ItemForegroundColor = Console.ForegroundColor; + /// default: () => Console.WriteLine("Pick an option:") - public Action WriteHeaderAction = () => Console.WriteLine("Pick an option:"); - + public Action WriteHeaderAction = () => Console.WriteLine("Pick an option:"); + /// default: (item) => Console.Write("[{0}] {1}", item.Index, item.Name) public Action WriteItemAction = item => Console.Write("[{0}] {1}", item.Index, item.Name); diff --git a/ConsoleMenu/MenuItem.cs b/ConsoleMenu/MenuItem.cs index 4d9fd74..8eabcbb 100644 --- a/ConsoleMenu/MenuItem.cs +++ b/ConsoleMenu/MenuItem.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; namespace ConsoleTools; @@ -8,12 +10,12 @@ namespace ConsoleTools; /// public sealed class MenuItem { - internal MenuItem(string name, Action action, int index) + internal MenuItem(string name, Func action, int index) { Debug.Assert(index >= 0); this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.Action = action ?? throw new ArgumentNullException(nameof(action)); + this.AsyncAction = action ?? throw new ArgumentNullException(nameof(action)); this.Index = index; } @@ -24,8 +26,22 @@ internal MenuItem(string name, Action action, int index) /// /// Gets or sets an action of the menu item that will be called when the item is called. + /// If you get asynchronous action, it will be converted to synchronous, so better use getter. /// - public Action Action { get; set; } + public Action Action + { + get => () => this.AsyncAction(CancellationToken.None).GetAwaiter().GetResult(); + set => this.AsyncAction = (_) => + { + value(); + return Task.CompletedTask; + }; + } + + /// + /// Gets or sets an action of the menu item that will be called when the item is called. + /// + public Func AsyncAction { get; set; } /// /// Gets an index of the menu item. diff --git a/ConsoleMenu/PublicAPI.Unshipped.txt b/ConsoleMenu/PublicAPI.Unshipped.txt index 078cd5e..fd914c4 100644 --- a/ConsoleMenu/PublicAPI.Unshipped.txt +++ b/ConsoleMenu/PublicAPI.Unshipped.txt @@ -1,7 +1,10 @@ ConsoleTools.ConsoleMenu ConsoleTools.ConsoleMenu.Add(string! name, System.Action! action) -> ConsoleTools.ConsoleMenu! ConsoleTools.ConsoleMenu.Add(string! name, System.Action! action) -> ConsoleTools.ConsoleMenu! +ConsoleTools.ConsoleMenu.Add(string! name, System.Func! action) -> ConsoleTools.ConsoleMenu! +ConsoleTools.ConsoleMenu.Add(string! name, System.Func! action) -> ConsoleTools.ConsoleMenu! ConsoleTools.ConsoleMenu.AddRange(System.Collections.Generic.IEnumerable!>! menuItems) -> ConsoleTools.ConsoleMenu! +ConsoleTools.ConsoleMenu.AddRange(System.Collections.Generic.IEnumerable!>!>! menuItems) -> ConsoleTools.ConsoleMenu! ConsoleTools.ConsoleMenu.CloseMenu() -> void ConsoleTools.ConsoleMenu.Configure(ConsoleTools.MenuConfig! config) -> ConsoleTools.ConsoleMenu! ConsoleTools.ConsoleMenu.Configure(System.Action! configure) -> ConsoleTools.ConsoleMenu! @@ -34,7 +37,9 @@ ConsoleTools.MenuConfig.WriteTitleAction -> System.Action! ConsoleTools.MenuItem ConsoleTools.MenuItem.Action.get -> System.Action! ConsoleTools.MenuItem.Action.set -> void +ConsoleTools.MenuItem.AsyncAction.get -> System.Func! +ConsoleTools.MenuItem.AsyncAction.set -> void ConsoleTools.MenuItem.Index.get -> int ConsoleTools.MenuItem.Name.get -> string! ConsoleTools.MenuItem.Name.set -> void -static ConsoleTools.ConsoleMenu.Close() -> void \ No newline at end of file +static ConsoleTools.ConsoleMenu.Close(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! \ No newline at end of file diff --git a/ConsoleMenuSampleApp/Program.cs b/ConsoleMenuSampleApp/Program.cs index a23d3cd..c6dd7d5 100644 --- a/ConsoleMenuSampleApp/Program.cs +++ b/ConsoleMenuSampleApp/Program.cs @@ -1,11 +1,13 @@ using System; +using System.Threading; +using System.Threading.Tasks; using ConsoleTools; namespace ConsoleMenuSampleApp { public class Program { - private static void Main(string[] args) + private static async Task Main(string[] args) { var commonConfig = new MenuConfig { @@ -30,6 +32,7 @@ private static void Main(string[] args) .Add("Sub_Two", () => SomeAction("Sub_Two")) .Add("Sub_Three", () => SomeAction("Sub_Three")) .Add("Sub_Four", () => SomeAction("Sub_Four")) + .Add("Sub_Five", async (cancellationToken) => await SomeAction2(cancellationToken)) .Add("Sub_Close", ConsoleMenu.Close) .Add("Sub_Action then Close", (thisMenu) => { SomeAction("Closing action..."); thisMenu.CloseMenu(); }) .Add("Sub_Exit", () => Environment.Exit(0)) @@ -56,7 +59,8 @@ private static void Main(string[] args) config.EnableBreadcrumb = true; }); - menu.Show(); + var token = new CancellationTokenSource(7000).Token; + await menu.ShowAsync(token); } private static void SomeAction(string text) @@ -65,5 +69,14 @@ private static void SomeAction(string text) Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } + + private static async Task SomeAction2(CancellationToken token) + { + Console.WriteLine("start delay..."); + await Task.Delay(2000, token); + Console.WriteLine("end delay"); + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } } } diff --git a/ConsoleMenuTests/ConsoleMenuTests.csproj b/ConsoleMenuTests/ConsoleMenuTests.csproj index d7f1d6f..641c347 100644 --- a/ConsoleMenuTests/ConsoleMenuTests.csproj +++ b/ConsoleMenuTests/ConsoleMenuTests.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive