Advertisement
FocusedWolf

C#: Advanced Console Menu

Mar 14th, 2023 (edited)
842
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 33.78 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8.  
  9. // POSTED ONLINE: https://pastebin.com/K4KcHS5u
  10.  
  11. namespace ConsoleMenuAdvanced
  12. {
  13.     internal class Program
  14.     {
  15.         #region Constructors
  16.  
  17.         private static void Main(string[] args)
  18.         {
  19.             _mainMenu.Show();
  20.         }
  21.  
  22.         #endregion Constructors
  23.  
  24.         #region Members
  25.  
  26.         private static void UpdatePromptStatus()
  27.         {
  28.             _promptMenu.Status = PROMPT_TEXT;
  29.             foreach (string argument in _promptArguments)
  30.                 _promptMenu.Status += $" {argument}";
  31.         }
  32.  
  33.         #endregion Members
  34.  
  35.         #region Fields
  36.  
  37.         #region Main menu
  38.  
  39.         private static ConsoleMenu _mainMenu = new ConsoleMenu("Main Menu:", new JaggedList<ConsoleMenuItem> {
  40.             // Row 0.
  41.             new ConsoleMenuItem("Items", (c, k) => {
  42.                 if(k.Key == ConsoleKey.Enter)
  43.                     _itemsMenu.Show();
  44.             }),
  45.  
  46.             // Row 1.
  47.             new ConsoleMenuItem("Options", (c, k) => {
  48.                 if(k.Key == ConsoleKey.Enter)
  49.                     _optionsMenu.Show();
  50.             }),
  51.  
  52.             // Row 2.
  53.             new ConsoleMenuItem("Prompt", (c, k) => {
  54.                 if(k.Key == ConsoleKey.Enter)
  55.                     _promptMenu.Show();
  56.             }),
  57.  
  58.             // Row 3.
  59.             new ConsoleMenuItem("Exit", (c, k) => {
  60.                 if(k.Key == ConsoleKey.Enter)
  61.                 {
  62.                     Console.WriteLine();
  63.                     Environment.Exit(0);
  64.                 }
  65.             }),
  66.         });
  67.  
  68.         #endregion Main menu
  69.  
  70.         #region Items menu
  71.  
  72.         private static ConsoleMenu _itemsMenu = new ConsoleMenu("Items Menu:", new Func<JaggedList<ConsoleMenuItem>>(() =>
  73.         {
  74.             JaggedList<ConsoleMenuItem> items = new JaggedList<ConsoleMenuItem>();
  75.  
  76.             // Row 0.
  77.             items.Add(new ConsoleMenuItem("< Back", (c, k) =>
  78.             {
  79.                 if (k.Key == ConsoleKey.Enter)
  80.                 {
  81.                     _mainMenu.Show();
  82.                     _itemsMenu.Status = null;
  83.                 }
  84.             }));
  85.  
  86.             // Row 1.
  87.             items.Add(new ConsoleMenuItem("Add Item", (c, k) =>
  88.             {
  89.                 if (k.Key == ConsoleKey.Enter)
  90.                 {
  91.                     items.Add(new ConsoleMenuItem($"Option {items.Count - 2}", (cc, kk) =>
  92.                     {
  93.                         if (kk.Key == ConsoleKey.Enter)
  94.                             ConsoleMenu.DisplayMessage($"You selected {cc.Text.Trim()}.");
  95.                     }));
  96.                 }
  97.             }));
  98.  
  99.             return items;
  100.         })());
  101.  
  102.         #endregion Items menu
  103.  
  104.         #region Options menu
  105.  
  106.         private static ConsoleMenuToggleItem _disableableConsoleMenuToggleItem = new ConsoleMenuToggleItem("Disabled Option", false) { Indent = " ┗━", IsEnabled = false };
  107.  
  108.         private static ConsoleMenu _optionsMenu = new ConsoleMenu("Options Menu:", new JaggedList<ConsoleMenuItem> {
  109.             // Row 0.
  110.             new ConsoleMenuItem("< Back", (c, k) => {
  111.                 if (k.Key == ConsoleKey.Enter)
  112.                     _mainMenu.Show();
  113.             }),
  114.  
  115.             // Row 1.
  116.             {
  117.                 new ConsoleMenuToggleItem("Option 1", false, (c,k) => {
  118.                     _disableableConsoleMenuToggleItem.IsEnabled = c.IsToggled;
  119.                     _disableableConsoleMenuToggleItem.Text = c.IsToggled ? "Enabled Option" : "Disabled Option";
  120.                 }),
  121.                 new ConsoleMenuToggleItem("Option 2", false)
  122.             },
  123.  
  124.             // Row 2.
  125.             {
  126.                 _disableableConsoleMenuToggleItem
  127.             },
  128.  
  129.             // Row 3.
  130.             {
  131.                 new ConsoleMenuSelectorItem(new[] { "Safe", "ONE", "On" }, 1, (c, k) => {
  132.                     for (int i = 0; i < c.Selections.Count; i++)
  133.                         if (i == c.SelectedIndex)
  134.                             c.Selections[i] = c.Selections[i].ToUpper();
  135.                         else
  136.                             c.Selections[i] = char.ToUpper(c.Selections[i][0]) + c.Selections[i].Substring(1).ToLower();
  137.                     })
  138.             },
  139.  
  140.             // Row 4.
  141.             {
  142.                 new ConsoleMenuValueItem("Letters, numbers, symbols, and punctuation:")
  143.             },
  144.  
  145.             // Row 5.
  146.             {
  147.                 new ConsoleMenuValueItem("Letters and numbers:", new Regex(@"^[a-zA-Z\d\s]*$"))
  148.             },
  149.  
  150.             // Row 6.
  151.             {
  152.                 new ConsoleMenuValueItem("Numbers only:", new Regex(@"^[\d]*$"))
  153.             },
  154.         });
  155.  
  156.         #endregion Options menu
  157.  
  158.         #region Prompt menu
  159.  
  160.         private static ConsoleMenu _promptMenu = new ConsoleMenu("Prompt Menu: (Highlight an entry and press Enter to append and Backspace to delete from the end)", new Func<JaggedList<ConsoleMenuItem>>(() =>
  161.         {
  162.             JaggedList<ConsoleMenuItem> items = new JaggedList<ConsoleMenuItem>();
  163.  
  164.             // Row 0.
  165.             items.Add(new ConsoleMenuItem("< Back", (c, k) =>
  166.             {
  167.                 if (k.Key == ConsoleKey.Enter)
  168.                     _mainMenu.Show();
  169.             }));
  170.  
  171.             #region Generate a square-ish arrangement of rows and columns
  172.  
  173.             Random random = new Random();
  174.  
  175.             // Generate names.
  176.             const int ITEMS_COUNT = 23;
  177.             List<string> names = new List<string>(ITEMS_COUNT);
  178.             for (int i = 1; i <= ITEMS_COUNT; i++)
  179.                 names.Add($"Item_{random.Next(1, 1000)}");
  180.  
  181.             int itemsPerRow = (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
  182.  
  183.             // Depending on the length of each item you might want to clamp the max number of items per row.
  184.             //const int MAX_ITEMS_PER_ROW = 3;
  185.             //ITEMS_PER_ROW = Math.Min(MAX_ITEMS_PER_ROW, ITEMS_PER_ROW);
  186.  
  187.             List<ConsoleMenuItem> row = new List<ConsoleMenuItem>(itemsPerRow);
  188.  
  189.             for (int i = 0, j = 0; i < names.Count; i++, j++)
  190.             {
  191.                 if (j >= itemsPerRow)
  192.                 {
  193.                     // Add filled row.
  194.                     items.Add(row);
  195.                     row = new List<ConsoleMenuItem>(itemsPerRow);
  196.                     j = 0;
  197.                 }
  198.  
  199.                 row.Add(new ConsoleMenuItem(names[i], (c, k) =>
  200.                 {
  201.                     switch (k.Key)
  202.                     {
  203.                         case ConsoleKey.Enter:
  204.                             _promptArguments.Add(c.Text);
  205.  
  206.                             UpdatePromptStatus();
  207.                             break;
  208.  
  209.                         case ConsoleKey.Backspace:
  210.                             if (_promptArguments.Count > 0)
  211.                                 _promptArguments.RemoveAt(_promptArguments.Count - 1);
  212.  
  213.                             UpdatePromptStatus();
  214.                             break;
  215.                     }
  216.                 }));
  217.             }
  218.  
  219.             // Add partially filled row.
  220.             items.Add(row);
  221.  
  222.             #endregion Generate a square-ish arrangement of rows and columns
  223.  
  224.             // Row N.
  225.             items.Add(new ConsoleMenuItem("Save", (c, k) =>
  226.             {
  227.                 switch (k.Key)
  228.                 {
  229.                     case ConsoleKey.Enter:
  230.                         ConsoleMenu.DisplayMessage($"You selected {c.Text.Trim()}.");
  231.                         break;
  232.  
  233.                     case ConsoleKey.Backspace:
  234.                         if (_promptArguments.Count > 0)
  235.                             _promptArguments.RemoveAt(_promptArguments.Count - 1);
  236.  
  237.                         UpdatePromptStatus();
  238.                         break;
  239.                 }
  240.             }));
  241.  
  242.             return items;
  243.         })())
  244.         { Status = PROMPT_TEXT };
  245.  
  246.         private static readonly string PROMPT_TEXT = $"$ {Assembly.GetExecutingAssembly().GetName().Name}.exe";
  247.  
  248.         private static List<string> _promptArguments = new List<string>();
  249.  
  250.         #endregion Prompt menu
  251.  
  252.         #endregion Fields
  253.     }
  254.  
  255.     public class ConsoleMenu
  256.     {
  257.         #region Properties
  258.  
  259.         #region Items
  260.  
  261.         private JaggedList<ConsoleMenuItem> _items;
  262.         public JaggedList<ConsoleMenuItem> Items
  263.         {
  264.             get { return _items; }
  265.             private set
  266.             {
  267.                 if (value == null)
  268.                     throw new ArgumentNullException(nameof(Items));
  269.  
  270.                 _items = value;
  271.             }
  272.         }
  273.  
  274.         #endregion Items
  275.  
  276.         #region SelectedColumn
  277.  
  278.         private int _selectedColumn = 0;
  279.         public int SelectedColumn
  280.         {
  281.             get
  282.             {
  283.                 // Clamp the selected column to allow error-free navigation across uniquely sized rows in the jagged collection.
  284.                 _selectedColumn = Math.Max(0, Math.Min(Items[SelectedRow].Count - 1, _selectedColumn));
  285.  
  286.                 return _selectedColumn;
  287.             }
  288.             set
  289.             {
  290.                 if (value < 0 ||
  291.                     value >= Items[SelectedRow].Count)
  292.                     throw new ArgumentOutOfRangeException(nameof(SelectedColumn), $"Value '{value}' must be in the range [{0}, {Items[SelectedRow].Count - 1}].");
  293.  
  294.                 _selectedColumn = value;
  295.             }
  296.         }
  297.  
  298.         #endregion SelectedColumn
  299.  
  300.         #region SelectedRow
  301.  
  302.         private int _selectedRow = 0;
  303.         public int SelectedRow
  304.         {
  305.             get
  306.             {
  307.                 // Clamp the selected row in case the collection was modified.
  308.                 _selectedRow = Math.Max(0, Math.Min(Items.Count - 1, _selectedRow));
  309.  
  310.                 return _selectedRow;
  311.             }
  312.             set
  313.             {
  314.                 if (value < 0 ||
  315.                     value >= Items.Count)
  316.                     throw new ArgumentOutOfRangeException(nameof(SelectedRow), $"Value '{value}' must be in the range [{0}, {Items.Count - 1}].");
  317.  
  318.                 _selectedRow = value;
  319.             }
  320.         }
  321.  
  322.         #endregion SelectedRow
  323.  
  324.         #region SelectedItem
  325.  
  326.         public ConsoleMenuItem SelectedItem
  327.         {
  328.             get
  329.             {
  330.                 if (Items.Count == 0)
  331.                     return null;
  332.  
  333.                 return Items[SelectedRow][SelectedColumn];
  334.             }
  335.         }
  336.  
  337.         #endregion SelectedItem
  338.  
  339.         #region Status
  340.  
  341.         public string Status { get; set; } = null;
  342.  
  343.         #endregion Status
  344.  
  345.         #region Title
  346.  
  347.         public string Title { get; set; }
  348.  
  349.         #endregion Title
  350.  
  351.         #region LeftPadding
  352.  
  353.         public string LeftPadding { get; set; } = " ";
  354.  
  355.         #endregion LeftPadding
  356.  
  357.         #region RightPadding
  358.  
  359.         public string RightPadding { get; set; } = " ";
  360.  
  361.         #endregion RightPadding
  362.  
  363.         #endregion Properties
  364.  
  365.         #region Events
  366.  
  367.         #region ConsoleKeyEvent
  368.  
  369.         public event EventHandler<ConsoleKeyEventArgs> ConsoleKeyEvent;
  370.  
  371.         protected virtual void OnConsoleKeyEvent(ConsoleKeyEventArgs e)
  372.         {
  373.             if (e == null)
  374.                 throw new ArgumentNullException(nameof(e));
  375.  
  376.             ConsoleKeyEvent?.Invoke(this, e);
  377.         }
  378.  
  379.         #endregion ConsoleKeyEvent
  380.  
  381.         #region PreConsoleKeyEvent
  382.  
  383.         public event EventHandler<PreConsoleKeyEventArgs> PreConsoleKeyEvent;
  384.  
  385.         protected virtual bool OnPreConsoleKeyEvent(PreConsoleKeyEventArgs e)
  386.         {
  387.             if (e == null)
  388.                 throw new ArgumentNullException(nameof(e));
  389.  
  390.             PreConsoleKeyEvent?.Invoke(this, e);
  391.  
  392.             return e.Cancel;
  393.         }
  394.  
  395.         #endregion PreConsoleKeyEvent
  396.  
  397.         #endregion Events
  398.  
  399.         #region Constructors
  400.  
  401.         static ConsoleMenu()
  402.         {
  403.             // Hide the cursor since its confusing to see when entering input to a field that isn't on the last line of the console output.
  404.             Console.CursorVisible = false;
  405.  
  406.             // Enable Unicode output so we can display box-drawing characters.
  407.             Console.OutputEncoding = Encoding.UTF8;
  408.         }
  409.  
  410.         public ConsoleMenu(string title)
  411.             : this(title, new JaggedList<ConsoleMenuItem>())
  412.         {
  413.         }
  414.  
  415.         public ConsoleMenu(string title, JaggedList<ConsoleMenuItem> items)
  416.         {
  417.             Title = title;
  418.             Items = items;
  419.         }
  420.  
  421.         #endregion Constructors
  422.  
  423.         #region Members
  424.  
  425.         /// <summary>
  426.         /// Display the menu.
  427.         /// </summary>
  428.         public void Show()
  429.         {
  430.             if (_currentMenu != this)
  431.             {
  432.                 bool firstRun = _currentMenu == null;
  433.                 _currentMenu = this;
  434.  
  435.                 if (firstRun == false) // If a menu-drawing loop is running.
  436.                     return;
  437.             }
  438.  
  439.             while (true)
  440.             {
  441.                 DrawMenu(_currentMenu);
  442.                 ProcessKeyboardInput(_currentMenu);
  443.             }
  444.         }
  445.  
  446.         /// <summary>
  447.         /// Display a message to the user and pause the menu loop.
  448.         /// </summary>
  449.         /// <param name="value">The <see cref="string"/> to display.</param>
  450.         /// <exception cref="ArgumentNullException"></exception>
  451.         public static void DisplayMessage(string value)
  452.         {
  453.             if (string.IsNullOrEmpty(value))
  454.                 throw new ArgumentNullException(nameof(value));
  455.  
  456.             if (_currentMenu == null)
  457.                 return;
  458.  
  459.             Console.Write($"{Environment.NewLine}{Environment.NewLine}{_currentMenu.LeftPadding}{_currentMenu.LeftPadding}{value}");
  460.             Pause();
  461.         }
  462.  
  463.         /// <summary>
  464.         /// Pause the menu loop.
  465.         /// </summary>
  466.         public static void Pause()
  467.         {
  468.             if (_currentMenu == null)
  469.                 return;
  470.  
  471.             Console.Write($"{Environment.NewLine}{Environment.NewLine}{_currentMenu.LeftPadding}{_currentMenu.LeftPadding}Press any key to continue . . . ");
  472.             Console.ReadKey();
  473.         }
  474.  
  475.         /// <summary>
  476.         /// Draw menu title and items.
  477.         /// </summary>
  478.         /// <param name="menu">The <see cref="ConsoleMenu"/> to draw.</param>
  479.         /// <exception cref="ArgumentNullException"></exception>
  480.         private static void DrawMenu(ConsoleMenu menu)
  481.         {
  482.             if (menu == null)
  483.                 throw new ArgumentNullException(nameof(menu));
  484.  
  485.             Console.Clear();
  486.  
  487.             #region Draw title
  488.  
  489.             if (string.IsNullOrEmpty(menu.Title) == false)
  490.                 Console.WriteLine($"{menu.LeftPadding}{menu.Title}{menu.LeftPadding}");
  491.  
  492.             #endregion Draw title
  493.  
  494.             #region Measure horizontal items
  495.  
  496.             int largestConsoleMenuItemTextLength = 0;
  497.             for (int row = 0; row < menu.Items.Count; row++)
  498.             {
  499.                 int columnsCount = menu.Items[row].Count;
  500.                 if (columnsCount == 1) // If only one item in this row.
  501.                     continue;
  502.  
  503.                 for (int column = 0; column < columnsCount; column++)
  504.                 {
  505.                     ConsoleMenuItem item = menu.Items[row][column];
  506.                     largestConsoleMenuItemTextLength = Math.Max(largestConsoleMenuItemTextLength, item.Text.Length);
  507.                 }
  508.             }
  509.  
  510.             #endregion Measure horizontal items
  511.  
  512.             #region Draw items
  513.  
  514.             for (int row = 0; row < menu.Items.Count; row++)
  515.             {
  516.                 int columnsCount = menu.Items[row].Count;
  517.                 for (int column = 0; column < columnsCount; column++)
  518.                 {
  519.                     ConsoleMenuItem consoleMenuItem = menu.Items[row][column];
  520.                     if (consoleMenuItem == null)
  521.                         throw new NullReferenceException($"Null {nameof(ConsoleMenuItem)} detected.");
  522.  
  523.                     string rowIndent;
  524.                     if (row == 0)
  525.                         rowIndent = menu.LeftPadding; // Has the affect of 2x left-padding indent.
  526.  
  527.                     else if (column == 0)
  528.                         rowIndent = $"{Environment.NewLine}{menu.LeftPadding}"; // Has the affect of 2x left-padding indent.
  529.  
  530.                     else // If a horizontal item.
  531.                         rowIndent = null;
  532.  
  533.                     Console.Write(rowIndent);
  534.                     Console.Write(consoleMenuItem.Indent);
  535.  
  536.                     bool isSelected = (row == menu.SelectedRow && column == menu.SelectedColumn);
  537.                     if (isSelected)
  538.                     {
  539.                         // Apply selected item colors.
  540.                         if (consoleMenuItem.IsEnabled)
  541.                         {
  542.                             if (consoleMenuItem.IsError)
  543.                             {
  544.                                 Console.ForegroundColor = ConsoleColor.Black;
  545.                                 Console.BackgroundColor = ConsoleColor.DarkRed;
  546.                             }
  547.  
  548.                             else
  549.                             {
  550.                                 Console.ForegroundColor = ConsoleColor.Black;
  551.                                 Console.BackgroundColor = ConsoleColor.White;
  552.                             }
  553.                         }
  554.                         else
  555.                         {
  556.                             Console.ForegroundColor = ConsoleColor.Black;
  557.                             Console.BackgroundColor = ConsoleColor.DarkYellow;
  558.                         }
  559.                     }
  560.  
  561.                     else
  562.                     {
  563.                         // Apply selected item colors.
  564.                         if (consoleMenuItem.IsEnabled)
  565.                         {
  566.                             if (consoleMenuItem.IsError)
  567.                             {
  568.                                 Console.ForegroundColor = ConsoleColor.DarkRed;
  569.                                 Console.BackgroundColor = ConsoleColor.Black;
  570.                             }
  571.  
  572.                             else
  573.                             {
  574.                                 Console.ForegroundColor = ConsoleColor.White;
  575.                                 Console.BackgroundColor = ConsoleColor.Black;
  576.                             }
  577.                         }
  578.                         else
  579.                         {
  580.                             Console.ForegroundColor = ConsoleColor.DarkYellow;
  581.                             Console.BackgroundColor = ConsoleColor.Black;
  582.                         }
  583.                     }
  584.  
  585.                     Console.Write($"{menu.LeftPadding}{consoleMenuItem}{menu.RightPadding}");
  586.                     Console.ResetColor();
  587.  
  588.                     if (columnsCount > 1)
  589.                     {
  590.                         string consoleMenuItemOutdent = new string(' ', largestConsoleMenuItemTextLength - consoleMenuItem.Text.Length);
  591.                         Console.Write(consoleMenuItemOutdent);
  592.                     }
  593.                 }
  594.             }
  595.  
  596.             #endregion Draw items
  597.  
  598.             #region Draw status
  599.  
  600.             if (string.IsNullOrEmpty(menu.Status) == false)
  601.                 Console.Write($"{Environment.NewLine}{Environment.NewLine}{menu.LeftPadding}{menu.LeftPadding}{menu.Status}");
  602.  
  603.             #endregion Draw status
  604.         }
  605.  
  606.         /// <summary>
  607.         /// Navigate and execute menu items based on keyboard input.
  608.         /// </summary>
  609.         /// <param name="menu"></param>
  610.         /// <exception cref="ArgumentNullException"></exception>
  611.         private static void ProcessKeyboardInput(ConsoleMenu menu)
  612.         {
  613.             if (menu == null)
  614.                 throw new ArgumentNullException(nameof(menu));
  615.  
  616.             ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(true); // True to not display the pressed key.
  617.  
  618.             bool cancel = menu.OnPreConsoleKeyEvent(new PreConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
  619.             if (cancel)
  620.                 return;
  621.  
  622.             int rows = menu.Items.Count;
  623.             if (rows == 0)
  624.                 return;
  625.  
  626.             int columns = menu.Items[menu.SelectedRow].Count;
  627.  
  628.             switch (consoleKeyInfo.Key)
  629.             {
  630.                 case ConsoleKey.UpArrow:
  631.                     // Loop around backwards.
  632.                     menu.SelectedRow = (menu.SelectedRow - 1 + rows) % rows;
  633.                     break;
  634.  
  635.                 case ConsoleKey.DownArrow:
  636.                     // Loop around forwards.
  637.                     menu.SelectedRow = (menu.SelectedRow + 1) % rows;
  638.                     break;
  639.  
  640.                 case ConsoleKey.LeftArrow:
  641.                     // Loop around backwards.
  642.                     menu.SelectedColumn = (menu.SelectedColumn - 1 + columns) % columns;
  643.                     break;
  644.  
  645.                 case ConsoleKey.RightArrow:
  646.                     // Loop around forwards.
  647.                     menu.SelectedColumn = (menu.SelectedColumn + 1) % columns;
  648.                     break;
  649.  
  650.                 default:
  651.                     menu.SelectedItem.OnExecute(consoleKeyInfo);
  652.                     break;
  653.             }
  654.  
  655.             menu.OnConsoleKeyEvent(new ConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
  656.         }
  657.  
  658.         #endregion Members
  659.  
  660.         #region Fields
  661.  
  662.         private static ConsoleMenu _currentMenu = null;
  663.  
  664.         #endregion Fields
  665.     }
  666.  
  667.     #region Types
  668.  
  669.     public class ConsoleMenuValueItem : ConsoleMenuItem
  670.     {
  671.         #region Properties
  672.  
  673.         #region Filter
  674.  
  675.         private Regex _filter = null;
  676.         public Regex Filter
  677.         {
  678.             get { return _filter; }
  679.             set
  680.             {
  681.                 _filter = value;
  682.                 UpdateIsError();
  683.             }
  684.         }
  685.  
  686.         #endregion Filter
  687.  
  688.         #region Value
  689.  
  690.         private string _value = null;
  691.         public string Value
  692.         {
  693.             get { return IsError ? null : _value; }
  694.             set
  695.             {
  696.                 _value = value;
  697.                 UpdateIsError();
  698.             }
  699.         }
  700.  
  701.         #endregion Value
  702.  
  703.         #endregion Properties
  704.  
  705.         #region Constructors
  706.  
  707.         public ConsoleMenuValueItem(string text)
  708.             : this(text, null)
  709.         {
  710.         }
  711.  
  712.         public ConsoleMenuValueItem(string text, Regex filter)
  713.             : this(text, filter, null)
  714.         {
  715.         }
  716.  
  717.         public ConsoleMenuValueItem(string text, Regex filter, string value)
  718.             : this(text, filter, value, null)
  719.         {
  720.         }
  721.  
  722.         public ConsoleMenuValueItem(string text, Regex filter, string value, Action<ConsoleMenuValueItem, ConsoleKeyInfo> code)
  723.             : base(text, WrapCode(code))
  724.         {
  725.             Filter = filter;
  726.             Value = value;
  727.         }
  728.  
  729.         #endregion Constructors
  730.  
  731.         #region Overrides
  732.  
  733.         /// <summary>
  734.         /// Execute menu item code.
  735.         /// </summary>
  736.         /// <param name="consoleKeyInfo"></param>
  737.         public override void OnExecute(ConsoleKeyInfo consoleKeyInfo)
  738.         {
  739.             switch (consoleKeyInfo.Key)
  740.             {
  741.                 case ConsoleKey.Backspace:
  742.                     if (string.IsNullOrEmpty(_value) == false)
  743.                         _value = _value.Remove(_value.Length - 1);
  744.                     break;
  745.  
  746.                 default:
  747.                     char character = consoleKeyInfo.KeyChar;
  748.                     if (char.IsControl(character) == false ||
  749.                         consoleKeyInfo.Key == ConsoleKey.Tab)
  750.                         _value += (consoleKeyInfo.Key == ConsoleKey.Tab) ? "    " : character.ToString();
  751.                     break;
  752.             }
  753.  
  754.             UpdateIsError();
  755.  
  756.             base.OnExecute(consoleKeyInfo);
  757.         }
  758.  
  759.         public override string ToString()
  760.         {
  761.             string errorMessage = IsError && Filter != null ? $"    <-- Does not match pattern: {Filter}" : null;
  762.             return $"{Text ?? base.ToString()}{(string.IsNullOrEmpty(_value) ? null : " ")}{_value}{errorMessage}";
  763.         }
  764.  
  765.         #endregion Overrides
  766.  
  767.         #region Members
  768.  
  769.         private void UpdateIsError()
  770.         {
  771.             IsError = _value != null && _filter?.IsMatch(_value) == false;
  772.         }
  773.  
  774.         #endregion Members
  775.     }
  776.  
  777.     public class ConsoleMenuSelectorItem : ConsoleMenuItem
  778.     {
  779.         #region Properties
  780.  
  781.         #region SelectedIndex
  782.  
  783.         private int _selectedIndex;
  784.         public int SelectedIndex
  785.         {
  786.             get { return _selectedIndex; }
  787.             set
  788.             {
  789.                 if (value < 0 || value >= Selections.Count)
  790.                     throw new ArgumentOutOfRangeException(nameof(SelectedIndex));
  791.  
  792.                 _selectedIndex = value;
  793.             }
  794.         }
  795.  
  796.         #endregion SelectedIndex
  797.  
  798.         #region Selections
  799.  
  800.         public List<string> Selections { get; }
  801.  
  802.         #endregion Selections
  803.  
  804.         #endregion Properties
  805.  
  806.         #region Constructors
  807.  
  808.         public ConsoleMenuSelectorItem(string[] selections)
  809.             : this(selections, 0)
  810.         {
  811.         }
  812.  
  813.         public ConsoleMenuSelectorItem(string[] selections, int selectedIndex)
  814.             : this(selections, selectedIndex, null)
  815.         {
  816.         }
  817.  
  818.         public ConsoleMenuSelectorItem(string[] selections, int selectedIndex, Action<ConsoleMenuSelectorItem, ConsoleKeyInfo> code)
  819.             : base(null, WrapCode(code))
  820.         {
  821.             if (selections == null)
  822.                 throw new ArgumentNullException(nameof(selections));
  823.  
  824.             Selections = selections.ToList();
  825.             SelectedIndex = selectedIndex;
  826.         }
  827.  
  828.         #endregion Constructors
  829.  
  830.         #region Overrides
  831.  
  832.         /// <summary>
  833.         /// Execute menu item code.
  834.         /// </summary>
  835.         /// <param name="consoleKeyInfo"></param>
  836.         public override void OnExecute(ConsoleKeyInfo consoleKeyInfo)
  837.         {
  838.             if (consoleKeyInfo.Key == ConsoleKey.Enter)
  839.                 Toggle();
  840.  
  841.             base.OnExecute(consoleKeyInfo);
  842.         }
  843.  
  844.         public override string ToString()
  845.         {
  846.             return string.Join(" ", Selections.Select((value, index) => (index == SelectedIndex) ? $"[{value}]" : value));
  847.         }
  848.  
  849.         #endregion Overrides
  850.  
  851.         #region Members
  852.  
  853.         /// <summary>
  854.         /// Toggle menu item state.
  855.         /// </summary>
  856.         public void Toggle()
  857.         {
  858.             if (IsEnabled &&
  859.                 Selections.Count > 0)
  860.                 SelectedIndex = (SelectedIndex + 1) % Selections.Count; // Loop around forwards.
  861.         }
  862.  
  863.         #endregion Members
  864.     }
  865.  
  866.     public class ConsoleMenuToggleItem : ConsoleMenuItem
  867.     {
  868.         #region Properties
  869.  
  870.         #region IsToggled
  871.  
  872.         private bool _isToggled = false;
  873.         public bool IsToggled
  874.         {
  875.             get { return _isToggled; }
  876.             set
  877.             {
  878.                 if (IsEnabled)
  879.                     _isToggled = value;
  880.             }
  881.         }
  882.  
  883.         #endregion IsToggled
  884.  
  885.         #endregion Properties
  886.  
  887.         #region Constructors
  888.  
  889.         public ConsoleMenuToggleItem(string text, bool isToggled)
  890.             : this(text, isToggled, null)
  891.         {
  892.         }
  893.  
  894.         public ConsoleMenuToggleItem(string text, bool isToggled, Action<ConsoleMenuToggleItem, ConsoleKeyInfo> code)
  895.             : base(text, WrapCode(code))
  896.         {
  897.             IsToggled = isToggled;
  898.         }
  899.  
  900.         #endregion Constructors
  901.  
  902.         #region Overrides
  903.  
  904.         /// <summary>
  905.         /// Execute menu item code.
  906.         /// </summary>
  907.         /// <param name="consoleKeyInfo"></param>
  908.         public override void OnExecute(ConsoleKeyInfo consoleKeyInfo)
  909.         {
  910.             if (consoleKeyInfo.Key == ConsoleKey.Enter)
  911.                 Toggle();
  912.  
  913.             base.OnExecute(consoleKeyInfo);
  914.         }
  915.  
  916.         public override string ToString()
  917.         {
  918.             return $"{Text ?? base.ToString()} [{(IsToggled ? 'x' : ' ')}]";
  919.         }
  920.  
  921.         #endregion Overrides
  922.  
  923.         #region Members
  924.  
  925.         /// <summary>
  926.         /// Toggle menu item state.
  927.         /// </summary>
  928.         public void Toggle()
  929.         {
  930.             IsToggled ^= true;
  931.         }
  932.  
  933.         #endregion Members
  934.     }
  935.  
  936.     public class ConsoleMenuItem
  937.     {
  938.         #region Properties
  939.  
  940.         #region Code
  941.  
  942.         public Action<ConsoleMenuItem, ConsoleKeyInfo> Code { get; set; }
  943.  
  944.         #endregion Code
  945.  
  946.         #region Indent
  947.  
  948.         public string Indent { get; set; } = null;
  949.  
  950.         #endregion Indent
  951.  
  952.         #region IsEnabled
  953.  
  954.         public bool IsEnabled { get; set; } = true;
  955.  
  956.         #endregion IsEnabled
  957.  
  958.         #region IsError
  959.  
  960.         public bool IsError { get; set; }
  961.  
  962.         #endregion IsError
  963.  
  964.         #region Tag
  965.  
  966.         public object Tag { get; set; } = null;
  967.  
  968.         #endregion Tag
  969.  
  970.         #region Text
  971.  
  972.         public string Text { get; set; }
  973.  
  974.         #endregion Text
  975.  
  976.         #endregion Properties
  977.  
  978.         #region Constructors
  979.  
  980.         public ConsoleMenuItem()
  981.         {
  982.         }
  983.  
  984.         public ConsoleMenuItem(string text)
  985.             : this(text, null)
  986.         {
  987.         }
  988.  
  989.         public ConsoleMenuItem(string text, Action<ConsoleMenuItem, ConsoleKeyInfo> code)
  990.         {
  991.             Text = text;
  992.             Code = code;
  993.         }
  994.  
  995.         #endregion Constructors
  996.  
  997.         #region Overrides
  998.  
  999.         public override string ToString()
  1000.         {
  1001.             return Text ?? base.ToString();
  1002.         }
  1003.  
  1004.         #endregion Overrides
  1005.  
  1006.         #region Members
  1007.  
  1008.         /// <summary>
  1009.         /// Execute menu item code.
  1010.         /// </summary>
  1011.         /// <param name="consoleKeyInfo"></param>
  1012.         public virtual void OnExecute(ConsoleKeyInfo consoleKeyInfo)
  1013.         {
  1014.             Code?.Invoke(this, consoleKeyInfo);
  1015.         }
  1016.  
  1017.         protected static Action<ConsoleMenuItem, ConsoleKeyInfo> WrapCode<T>(Action<T, ConsoleKeyInfo> code)
  1018.             where T : ConsoleMenuItem
  1019.         {
  1020.             if (code == null)
  1021.                 return null;
  1022.  
  1023.             return (c, k) => code((T)c, k);
  1024.         }
  1025.  
  1026.         #endregion Members
  1027.     }
  1028.  
  1029.     public class ConsoleKeyEventArgs : EventArgs
  1030.     {
  1031.         #region Properties
  1032.  
  1033.         public ConsoleMenuItem ConsoleMenuItem { get; private set; }
  1034.  
  1035.         public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
  1036.  
  1037.         #endregion Properties
  1038.  
  1039.         #region Constructors
  1040.  
  1041.         public ConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
  1042.         {
  1043.             if (consoleMenuItem == null)
  1044.                 throw new ArgumentNullException(nameof(consoleMenuItem));
  1045.  
  1046.             if (consoleKeyInfo == null)
  1047.                 throw new ArgumentNullException(nameof(consoleKeyInfo));
  1048.  
  1049.             ConsoleKeyInfo = consoleKeyInfo;
  1050.             ConsoleMenuItem = consoleMenuItem;
  1051.         }
  1052.  
  1053.         #endregion Constructors
  1054.     }
  1055.  
  1056.     public class PreConsoleKeyEventArgs : CancelEventArgs
  1057.     {
  1058.         #region Properties
  1059.  
  1060.         public ConsoleMenuItem ConsoleMenuItem { get; private set; }
  1061.  
  1062.         public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
  1063.  
  1064.         #endregion Properties
  1065.  
  1066.         #region Constructors
  1067.  
  1068.         public PreConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
  1069.         {
  1070.             if (consoleMenuItem == null)
  1071.                 throw new ArgumentNullException(nameof(consoleMenuItem));
  1072.  
  1073.             if (consoleKeyInfo == null)
  1074.                 throw new ArgumentNullException(nameof(consoleKeyInfo));
  1075.  
  1076.             ConsoleKeyInfo = consoleKeyInfo;
  1077.             ConsoleMenuItem = consoleMenuItem;
  1078.         }
  1079.  
  1080.         #endregion Constructors
  1081.     }
  1082.  
  1083.     public class JaggedList<T> : List<List<T>>
  1084.     {
  1085.         #region Members
  1086.  
  1087.         /// <summary>
  1088.         /// Add a variable-length array of <see cref="T"/> items to the end of the <see cref="JaggedList{T}"/>.
  1089.         /// </summary>
  1090.         /// <param name="items">A variable-length array of <see cref="T"/> items.</param>
  1091.         /// <example>
  1092.         /// JaggedList<int> jaggedList = new JaggedList<int> {
  1093.         ///     1,
  1094.         ///     { 2, 3 },
  1095.         ///     { 5 },
  1096.         /// };
  1097.         /// jaggedList.Add(6);
  1098.         /// jaggedList.Add(7, 8);
  1099.         /// jaggedList.Add(new int[] { 9, 10, 11 });
  1100.         /// </example>
  1101.         public void Add(params T[] items)
  1102.         {
  1103.             Add(new List<T>(items));
  1104.         }
  1105.  
  1106.         /// <summary>
  1107.         /// 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}"/>.
  1108.         /// </summary>
  1109.         /// <param name="value">The object to locate in the <see cref="JaggedList{T}"/>. The value can be null for reference types.</param>
  1110.         /// <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>
  1111.         public (int row, int column)? IndexOf(T value)
  1112.         {
  1113.             for (int row = 0, column; row < Count; row++)
  1114.             {
  1115.                 for (column = 0; column < this[row].Count; column++)
  1116.                 {
  1117.                     T item = this[row][column];
  1118.                     if ((item == null && value == null) ||
  1119.                         (item != null && item.Equals(value)))
  1120.                         return (row, column);
  1121.                 }
  1122.             }
  1123.  
  1124.             return null;
  1125.         }
  1126.  
  1127.         #endregion Members
  1128.     }
  1129.  
  1130.     #endregion Types
  1131. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement