Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Online post: https://pastebin.com/K4KcHS5u
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- // An advanced multi-menu alternative to my simple console menu code: https://pastebin.com/9NmGvV4y
- namespace ConsoleMenuTest
- {
- internal class Program
- {
- #region Constructors
- private static void Main(string[] args)
- {
- GenerateMenuItems();
- GeneratePromptItems();
- _mainMenu.Show();
- }
- #endregion Constructors
- #region Members
- private static void GenerateMenuItems()
- {
- _itemsMenu.Items.Add(new ConsoleMenuItem(" < Back ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- {
- _mainMenu.Show();
- _itemsMenu.Status = null;
- }
- }));
- _itemsMenu.Items.Add(new ConsoleMenuItem(" Add Item ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- {
- _itemsMenu.Items.Add(new ConsoleMenuItem($" Option {_itemsMenuAddedItemsCount} ", (cc, kk) => {
- if (kk.Key == ConsoleKey.Enter)
- _itemsMenu.Status = $"\n\n You selected {cc.Text.Trim()}.";
- }));
- }
- _itemsMenuAddedItemsCount++;
- }));
- }
- private static void GeneratePromptItems()
- {
- // Row 0.
- _promptMenu.Items.Add(new ConsoleMenuItem(" < Back ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- _mainMenu.Show();
- }));
- #region Generate a square-ish arrangement of rows and columns
- var random = new Random();
- // Generate names.
- const int ITEMS_COUNT = 23;
- var names = new List<string>(ITEMS_COUNT);
- for (var i = 1; i <= ITEMS_COUNT; i++)
- names.Add($" Item_{random.Next(1, 1000)} ");
- var ITEMS_PER_ROW = (int)Math.Sqrt(ITEMS_COUNT); // Sqrt gives the side-length of a square whose area equals the items count, but conversion to int (or rounding with Math.Floor()) will likely result in a "square-ish" rectangle
- // Depending on the length of each item you might want to clamp the max number of items per row.
- //const int MAX_ITEMS_PER_ROW = 3;
- //ITEMS_PER_ROW = Math.Min(MAX_ITEMS_PER_ROW, ITEMS_PER_ROW);
- var row = new List<ConsoleMenuItem>(ITEMS_PER_ROW);
- for (int i = 0, j = 0; i < names.Count; i++, j++)
- {
- if (j >= ITEMS_PER_ROW)
- {
- // Add filled row.
- _promptMenu.Items.Add(row);
- row = new List<ConsoleMenuItem>(ITEMS_PER_ROW);
- j = 0;
- }
- row.Add(new ConsoleMenuItem(names[i], (c, k) => {
- ProcessPromptKeyboardInput(c, k);
- }));
- }
- // Add partially filled row.
- _promptMenu.Items.Add(row);
- #endregion Generate a square-ish arrangement of rows and columns
- // Row N.
- _promptMenu.Items.Add(new ConsoleMenuItem(" Save ", (c, k) => {
- switch (k.Key)
- {
- case ConsoleKey.Enter:
- Console.WriteLine($"\n\n You selected {c.Text.Trim()}.");
- Pause();
- break;
- case ConsoleKey.Backspace:
- if (_promptArguments.Count > 0)
- _promptArguments.RemoveAt(_promptArguments.Count - 1);
- UpdatePromptStatus();
- break;
- }
- }));
- }
- private static void ProcessPromptKeyboardInput(ConsoleMenuItem c, ConsoleKeyInfo k)
- {
- switch (k.Key)
- {
- case ConsoleKey.Enter:
- _promptArguments.Add(c.Text);
- UpdatePromptStatus();
- break;
- case ConsoleKey.Backspace:
- if (_promptArguments.Count > 0)
- _promptArguments.RemoveAt(_promptArguments.Count - 1);
- UpdatePromptStatus();
- break;
- }
- }
- private static void UpdatePromptStatus()
- {
- _promptMenu.Status = PROMPT_TEXT;
- foreach (var argument in _promptArguments)
- _promptMenu.Status += argument.TrimEnd();
- }
- private static void Pause()
- {
- // Press any key to continue.
- if (Debugger.IsAttached == false)
- {
- Console.WriteLine(); // Add a blank line after programs main output.
- Console.Write("Press any key to continue . . . ");
- Console.ReadKey();
- }
- }
- #endregion Members
- #region Fields
- #region Main menu
- private static ConsoleMenu _mainMenu = new ConsoleMenu(" Main Menu:", new JaggedList<ConsoleMenuItem> {
- // Row 0.
- new ConsoleMenuItem(" Items ", (c, k) => {
- if(k.Key == ConsoleKey.Enter)
- _itemsMenu.Show();
- }),
- // Row 1.
- new ConsoleMenuItem(" Options ", (c, k) => {
- if(k.Key == ConsoleKey.Enter)
- _optionsMenu.Show();
- }),
- // Row 2.
- new ConsoleMenuItem(" Prompt ", (c, k) => {
- if(k.Key == ConsoleKey.Enter)
- _promptMenu.Show();
- }),
- // Row 3.
- new ConsoleMenuItem(" Exit ", (c, k) => {
- if(k.Key == ConsoleKey.Enter)
- {
- Console.WriteLine();
- Environment.Exit(0);
- }
- }),
- });
- #endregion Main menu
- #region Items menu
- private static ConsoleMenu _itemsMenu = new ConsoleMenu("Items Menu:");
- private static int _itemsMenuAddedItemsCount = 0;
- #endregion Items menu
- #region Options menu
- private static ConsoleMenu _optionsMenu = new ConsoleMenu("Options Menu:", new JaggedList<ConsoleMenuItem> {
- // Row 0.
- new ConsoleMenuItem(" < Back ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- _mainMenu.Show();
- }),
- // Row 1.
- {
- new ConsoleMenuItem(" Option 1 ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- c.Toggle();
- }) { IsToggled = false },
- new ConsoleMenuItem(" Option 2 ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- c.Toggle();
- }) { IsToggled = false },
- new ConsoleMenuItem(" Option 3 ", (c, k) => {
- if (k.Key == ConsoleKey.Enter)
- c.Toggle();
- }) { IsToggled = false }
- },
- });
- #endregion Options menu
- #region Prompt menu
- private static readonly string PROMPT_TEXT = $"\n\n $ {System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.exe";
- private static ConsoleMenu _promptMenu = new ConsoleMenu("Prompt Menu: (Highlight an entry and press Enter to append and Backspace to delete from the end.") { Status = PROMPT_TEXT };
- private static List<String> _promptArguments = new List<string>();
- #endregion Prompt menu
- #endregion Fields
- }
- public class ConsoleMenu
- {
- #region Properties
- #region Items
- private JaggedList<ConsoleMenuItem> _items;
- public JaggedList<ConsoleMenuItem> Items
- {
- get { return _items; }
- private set
- {
- if (value == null)
- throw new NullReferenceException(nameof(Items));
- _items = value;
- }
- }
- #endregion Items
- #region SelectedColumn
- private int _selectedColumn = 0;
- public int SelectedColumn
- {
- get
- {
- // Clamp the selected column to allow error-free navigation across uniquely sized rows in the jagged collection.
- _selectedColumn = Math.Max(0, Math.Min(Items[SelectedRow].Count - 1, _selectedColumn));
- return _selectedColumn;
- }
- set
- {
- if (value < 0 ||
- value >= Items[SelectedRow].Count)
- throw new ArgumentOutOfRangeException(nameof(SelectedColumn), $"Value '{value}' must be in the range [{0}, {Items[SelectedRow].Count - 1}].");
- _selectedColumn = value;
- }
- }
- #endregion SelectedColumn
- #region SelectedRow
- private int _selectedRow = 0;
- public int SelectedRow
- {
- get
- {
- // Clamp the selected row in case the collection was modified.
- _selectedRow = Math.Max(0, Math.Min(Items.Count - 1, _selectedRow));
- return _selectedRow;
- }
- set
- {
- if (value < 0 ||
- value >= Items.Count)
- throw new ArgumentOutOfRangeException(nameof(SelectedRow), $"Value '{value}' must be in the range [{0}, {Items.Count - 1}].");
- _selectedRow = value;
- }
- }
- #endregion SelectedRow
- #region SelectedItem
- public ConsoleMenuItem SelectedItem
- {
- get
- {
- if (Items.Count == 0)
- return null;
- return Items[SelectedRow][SelectedColumn];
- }
- }
- #endregion SelectedItem
- #region Status
- public string Status { get; set; } = null;
- #endregion Status
- #region Title
- public string Title { get; set; }
- #endregion Title
- #endregion Properties
- #region Events
- #region ConsoleKeyEvent
- public event EventHandler<ConsoleKeyEventArgs> ConsoleKeyEvent;
- protected virtual void OnConsoleKeyEvent(ConsoleKeyEventArgs e)
- {
- if (e == null)
- throw new ArgumentNullException(nameof(e));
- ConsoleKeyEvent?.Invoke(this, e);
- }
- #endregion ConsoleKeyEvent
- #region PreConsoleKeyEvent
- public event EventHandler<PreConsoleKeyEventArgs> PreConsoleKeyEvent;
- protected virtual bool OnPreConsoleKeyEvent(PreConsoleKeyEventArgs e)
- {
- if (e == null)
- throw new ArgumentNullException(nameof(e));
- PreConsoleKeyEvent?.Invoke(this, e);
- return e.Cancel;
- }
- #endregion PreConsoleKeyEvent
- #endregion Events
- #region Constructors
- public ConsoleMenu(string title)
- : this(title, new JaggedList<ConsoleMenuItem>())
- {
- }
- public ConsoleMenu(string title, JaggedList<ConsoleMenuItem> items)
- {
- Title = title;
- Items = items;
- }
- #endregion Constructors
- #region Members
- /// <summary>
- /// Display menu.
- /// </summary>
- public void Show()
- {
- if (_currentMenu != this)
- {
- var firstRun = _currentMenu == null;
- _currentMenu = this;
- if (firstRun == false) // If a menu-drawing loop is running.
- return;
- }
- while (true)
- {
- DrawMenu(_currentMenu);
- ProcessKeyboardInput(_currentMenu);
- }
- }
- /// <summary>
- /// Draw menu title and items.
- /// </summary>
- /// <param name="menu">The <see cref="ConsoleMenu"/> to draw.</param>
- /// <exception cref="ArgumentNullException"></exception>
- private static void DrawMenu(ConsoleMenu menu)
- {
- if (menu == null)
- throw new ArgumentNullException(nameof(menu));
- Console.Clear();
- #region Draw title
- if (string.IsNullOrEmpty(menu.Title) == false)
- Console.WriteLine(menu.Title);
- #endregion Draw title
- #region Measure horizontal items
- var LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH = 0;
- for (var row = 0; row < menu.Items.Count; row++)
- {
- var columnsCount = menu.Items[row].Count;
- if (columnsCount == 1) // If only one item in this row.
- continue;
- for (var column = 0; column < columnsCount; column++)
- {
- var item = menu.Items[row][column];
- LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH = Math.Max(LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH, item.Text.Length);
- }
- }
- #endregion Measure horizontal items
- #region Draw items
- for (var row = 0; row < menu.Items.Count; row++)
- {
- var columnsCount = menu.Items[row].Count;
- for (var column = 0; column < columnsCount; column++)
- {
- var menuItem = menu.Items[row][column];
- if (menuItem == null)
- throw new NullReferenceException($"Null {nameof(ConsoleMenuItem)} detected.");
- string rowIndent;
- if (row == 0) // If first item.
- rowIndent = " ";
- else if (column == 0) // If a vertical item.
- rowIndent = "\n ";
- else // If a horizontal item.
- rowIndent = null;
- Console.Write(rowIndent);
- var isSelected = (row == menu.SelectedRow && column == menu.SelectedColumn);
- if (isSelected)
- {
- // Apply selected item colors.
- Console.ForegroundColor = ConsoleColor.Black;
- Console.BackgroundColor = ConsoleColor.White;
- }
- if (menuItem.IsToggled.HasValue)
- {
- if (menuItem.IsToggled.Value)
- Console.Write(" [x] ");
- else
- Console.Write(" [ ] ");
- }
- Console.Write(menuItem.Text);
- if (isSelected)
- {
- // Restore normal item colors.
- Console.ForegroundColor = ConsoleColor.White;
- Console.BackgroundColor = ConsoleColor.Black;
- }
- if (columnsCount > 1)
- {
- var alignmentOutdent = new string(' ', LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH - menuItem.Text.Length);
- Console.Write(alignmentOutdent);
- }
- }
- }
- #endregion Draw items
- #region Draw status
- if (string.IsNullOrEmpty(menu.Status) == false)
- Console.Write(menu.Status);
- #endregion Draw status
- }
- /// <summary>
- /// Navigate and execute menu items based on keyboard input.
- /// </summary>
- /// <param name="menu"></param>
- /// <exception cref="ArgumentNullException"></exception>
- private static void ProcessKeyboardInput(ConsoleMenu menu)
- {
- if (menu == null)
- throw new ArgumentNullException(nameof(menu));
- var consoleKeyInfo = Console.ReadKey();
- var cancel = menu.OnPreConsoleKeyEvent(new PreConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
- if (cancel)
- return;
- var rows = menu.Items.Count;
- if (rows == 0)
- return;
- var columns = menu.Items[menu.SelectedRow].Count;
- switch (consoleKeyInfo.Key)
- {
- case ConsoleKey.UpArrow:
- // Loop around backwards.
- menu.SelectedRow = (menu.SelectedRow - 1 + rows) % rows;
- break;
- case ConsoleKey.DownArrow:
- // Loop around forwards.
- menu.SelectedRow = (menu.SelectedRow + 1) % rows;
- break;
- case ConsoleKey.LeftArrow:
- // Loop around backwards.
- menu.SelectedColumn = (menu.SelectedColumn - 1 + columns) % columns;
- break;
- case ConsoleKey.RightArrow:
- // Loop around forwards.
- menu.SelectedColumn = (menu.SelectedColumn + 1) % columns;
- break;
- default:
- menu.SelectedItem.Execute(consoleKeyInfo);
- break;
- }
- menu.OnConsoleKeyEvent(new ConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
- }
- #endregion Members
- #region Fields
- private static ConsoleMenu _currentMenu = null;
- #endregion Fields
- }
- #region Types
- public class ConsoleMenuItem
- {
- #region Properties
- #region Code
- public Action<ConsoleMenuItem, ConsoleKeyInfo> Code { get; set; }
- #endregion Code
- #region IsToggled
- public bool? IsToggled { get; set; } = null;
- #endregion IsToggled
- #region Tag
- public object Tag { get; set; } = null;
- #endregion Tag
- #region Text
- private string _text;
- public string Text
- {
- get { return _text; }
- set
- {
- if (string.IsNullOrEmpty(value))
- throw new ArgumentNullException(nameof(Text));
- _text = value;
- }
- }
- #endregion Text
- #endregion Properties
- #region Constructors
- public ConsoleMenuItem(string text)
- : this(text, null)
- {
- }
- public ConsoleMenuItem(string text, Action<ConsoleMenuItem, ConsoleKeyInfo> code)
- {
- Text = text;
- Code = code;
- }
- #endregion Constructors
- #region Members
- /// <summary>
- /// Execute menu item code.
- /// </summary>
- /// <param name="consoleKeyInfo"></param>
- public void Execute(ConsoleKeyInfo consoleKeyInfo)
- {
- var code = Code;
- if (code == null)
- return;
- code(this, consoleKeyInfo);
- }
- /// <summary>
- /// Toggle menu item state.
- /// </summary>
- public void Toggle()
- {
- if (IsToggled.HasValue == false)
- IsToggled = true;
- else
- IsToggled ^= true;
- }
- #endregion Members
- }
- public class ConsoleKeyEventArgs : EventArgs
- {
- #region Properties
- public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
- public ConsoleMenuItem ConsoleMenuItem { get; private set; }
- #endregion Properties
- #region Constructors
- public ConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
- {
- if (consoleMenuItem == null)
- throw new ArgumentNullException(nameof(consoleMenuItem));
- if (consoleKeyInfo == null)
- throw new ArgumentNullException(nameof(consoleKeyInfo));
- ConsoleKeyInfo = consoleKeyInfo;
- ConsoleMenuItem = consoleMenuItem;
- }
- #endregion Constructors
- }
- public class PreConsoleKeyEventArgs : CancelEventArgs
- {
- #region Properties
- public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
- public ConsoleMenuItem ConsoleMenuItem { get; private set; }
- #endregion Properties
- #region Constructors
- public PreConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
- {
- if (consoleMenuItem == null)
- throw new ArgumentNullException(nameof(consoleMenuItem));
- if (consoleKeyInfo == null)
- throw new ArgumentNullException(nameof(consoleKeyInfo));
- ConsoleKeyInfo = consoleKeyInfo;
- ConsoleMenuItem = consoleMenuItem;
- }
- #endregion Constructors
- }
- public class JaggedList<T> : List<List<T>>
- {
- #region Members
- /// <summary>
- /// Add a variable-length array of <see cref="T"/> items to the end of the <see cref="JaggedList{T}"/>.
- /// </summary>
- /// <param name="items">A variable-length array of <see cref="T"/> items.</param>
- /// <example>
- /// var jaggedList = new JaggedList<int> {
- /// 1,
- /// { 2, 3 },
- /// { 5 },
- /// };
- /// jaggedList.Add(6);
- /// jaggedList.Add(7, 8);
- /// jaggedList.Add(new int[] { 9, 10, 11 });
- /// </example>
- public void Add(params T[] items)
- {
- Add(new List<T>(items));
- }
- /// <summary>
- /// Searches for the specified <see cref="T"/> item and returns a tuple containing the zero-based row and column of the first occurrence of the value within the entire <see cref="JaggedList{T}"/>.
- /// </summary>
- /// <param name="value">The object to locate in the <see cref="JaggedList{T}"/>. The value can be null for reference types.</param>
- /// <returns>A tuple containing the zero-based row and column of the first occurrence of the value within the entire <see cref="JaggedList{T}"/> if found, otherwise null.</returns>
- public Tuple<int, int> IndexOf(T value)
- {
- for (int row = 0, column; row < Count; row++)
- {
- for (column = 0; column < this[row].Count; column++)
- {
- var item = this[row][column];
- if ((item == null && value == null) ||
- (item != null && item.Equals(value)))
- return Tuple.Create(row, column);
- }
- }
- return null;
- }
- #endregion Members
- }
- #endregion Types
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement