FocusedWolf

C#: Simple Console Menu

Oct 19th, 2022 (edited)
1,173
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 22.99 KB | None | 0 0
  1. // Online post: https://pastebin.com/K4KcHS5u
  2.  
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7.  
  8. // An advanced multi-menu alternative to my simple console menu code: https://pastebin.com/9NmGvV4y
  9.  
  10. namespace ConsoleMenuTest
  11. {
  12.     internal class Program
  13.     {
  14.         #region Constructors
  15.  
  16.         private static void Main(string[] args)
  17.         {
  18.             GenerateMenuItems();
  19.             GeneratePromptItems();
  20.  
  21.             _mainMenu.Show();
  22.         }
  23.  
  24.         #endregion Constructors
  25.  
  26.         #region Members
  27.  
  28.         private static void GenerateMenuItems()
  29.         {
  30.             _itemsMenu.Items.Add(new ConsoleMenuItem(" < Back ", (c, k) => {
  31.                 if (k.Key == ConsoleKey.Enter)
  32.                 {
  33.                     _mainMenu.Show();
  34.                     _itemsMenu.Status = null;
  35.                 }
  36.             }));
  37.  
  38.             _itemsMenu.Items.Add(new ConsoleMenuItem(" Add Item ", (c, k) => {
  39.                 if (k.Key == ConsoleKey.Enter)
  40.                 {
  41.                     _itemsMenu.Items.Add(new ConsoleMenuItem($" Option {_itemsMenuAddedItemsCount} ", (cc, kk) => {
  42.                         if (kk.Key == ConsoleKey.Enter)
  43.                             _itemsMenu.Status = $"\n\n  You selected {cc.Text.Trim()}.";
  44.                     }));
  45.                 }
  46.                 _itemsMenuAddedItemsCount++;
  47.             }));
  48.         }
  49.  
  50.         private static void GeneratePromptItems()
  51.         {
  52.             // Row 0.
  53.             _promptMenu.Items.Add(new ConsoleMenuItem(" < Back ", (c, k) => {
  54.                 if (k.Key == ConsoleKey.Enter)
  55.                     _mainMenu.Show();
  56.             }));
  57.  
  58.             #region Generate a square-ish arrangement of rows and columns
  59.  
  60.             var random = new Random();
  61.  
  62.             // Generate names.
  63.             const int ITEMS_COUNT = 23;
  64.             var names = new List<string>(ITEMS_COUNT);
  65.             for (var i = 1; i <= ITEMS_COUNT; i++)
  66.                 names.Add($" Item_{random.Next(1, 1000)} ");
  67.  
  68.             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
  69.  
  70.             // Depending on the length of each item you might want to clamp the max number of items per row.
  71.             //const int MAX_ITEMS_PER_ROW = 3;
  72.             //ITEMS_PER_ROW = Math.Min(MAX_ITEMS_PER_ROW, ITEMS_PER_ROW);
  73.  
  74.             var row = new List<ConsoleMenuItem>(ITEMS_PER_ROW);
  75.  
  76.             for (int i = 0, j = 0; i < names.Count; i++, j++)
  77.             {
  78.                 if (j >= ITEMS_PER_ROW)
  79.                 {
  80.                     // Add filled row.
  81.                     _promptMenu.Items.Add(row);
  82.                     row = new List<ConsoleMenuItem>(ITEMS_PER_ROW);
  83.                     j = 0;
  84.                 }
  85.  
  86.                 row.Add(new ConsoleMenuItem(names[i], (c, k) => {
  87.                     ProcessPromptKeyboardInput(c, k);
  88.                 }));
  89.             }
  90.  
  91.             // Add partially filled row.
  92.             _promptMenu.Items.Add(row);
  93.  
  94.             #endregion Generate a square-ish arrangement of rows and columns
  95.  
  96.             // Row N.
  97.             _promptMenu.Items.Add(new ConsoleMenuItem(" Save ", (c, k) => {
  98.                 switch (k.Key)
  99.                 {
  100.                     case ConsoleKey.Enter:
  101.                         Console.WriteLine($"\n\n  You selected {c.Text.Trim()}.");
  102.                         Pause();
  103.                         break;
  104.  
  105.                     case ConsoleKey.Backspace:
  106.                         if (_promptArguments.Count > 0)
  107.                             _promptArguments.RemoveAt(_promptArguments.Count - 1);
  108.  
  109.                         UpdatePromptStatus();
  110.                         break;
  111.                 }
  112.             }));
  113.         }
  114.  
  115.         private static void ProcessPromptKeyboardInput(ConsoleMenuItem c, ConsoleKeyInfo k)
  116.         {
  117.             switch (k.Key)
  118.             {
  119.                 case ConsoleKey.Enter:
  120.                     _promptArguments.Add(c.Text);
  121.  
  122.                     UpdatePromptStatus();
  123.                     break;
  124.  
  125.                 case ConsoleKey.Backspace:
  126.                     if (_promptArguments.Count > 0)
  127.                         _promptArguments.RemoveAt(_promptArguments.Count - 1);
  128.  
  129.                     UpdatePromptStatus();
  130.                     break;
  131.             }
  132.         }
  133.  
  134.         private static void UpdatePromptStatus()
  135.         {
  136.             _promptMenu.Status = PROMPT_TEXT;
  137.  
  138.             foreach (var argument in _promptArguments)
  139.                 _promptMenu.Status += argument.TrimEnd();
  140.         }
  141.  
  142.         private static void Pause()
  143.         {
  144.             // Press any key to continue.
  145.             if (Debugger.IsAttached == false)
  146.             {
  147.                 Console.WriteLine(); // Add a blank line after programs main output.
  148.                 Console.Write("Press any key to continue . . . ");
  149.                 Console.ReadKey();
  150.             }
  151.         }
  152.  
  153.         #endregion Members
  154.  
  155.         #region Fields
  156.  
  157.         #region Main menu
  158.  
  159.         private static ConsoleMenu _mainMenu = new ConsoleMenu(" Main Menu:", new JaggedList<ConsoleMenuItem> {
  160.             // Row 0.
  161.             new ConsoleMenuItem(" Items ", (c, k) => {
  162.                 if(k.Key == ConsoleKey.Enter)
  163.                     _itemsMenu.Show();
  164.             }),
  165.  
  166.             // Row 1.
  167.             new ConsoleMenuItem(" Options ", (c, k) => {
  168.                 if(k.Key == ConsoleKey.Enter)
  169.                     _optionsMenu.Show();
  170.             }),
  171.  
  172.             // Row 2.
  173.             new ConsoleMenuItem(" Prompt ", (c, k) => {
  174.                 if(k.Key == ConsoleKey.Enter)
  175.                     _promptMenu.Show();
  176.             }),
  177.  
  178.             // Row 3.
  179.             new ConsoleMenuItem(" Exit ", (c, k) => {
  180.                 if(k.Key == ConsoleKey.Enter)
  181.                 {
  182.                     Console.WriteLine();
  183.                     Environment.Exit(0);
  184.                 }
  185.             }),
  186.         });
  187.  
  188.         #endregion Main menu
  189.  
  190.         #region Items menu
  191.  
  192.         private static ConsoleMenu _itemsMenu = new ConsoleMenu("Items Menu:");
  193.  
  194.         private static int _itemsMenuAddedItemsCount = 0;
  195.  
  196.         #endregion Items menu
  197.  
  198.         #region Options menu
  199.  
  200.         private static ConsoleMenu _optionsMenu = new ConsoleMenu("Options Menu:", new JaggedList<ConsoleMenuItem> {
  201.             // Row 0.
  202.             new ConsoleMenuItem(" < Back ", (c, k) => {
  203.                 if (k.Key == ConsoleKey.Enter)
  204.                     _mainMenu.Show();
  205.             }),
  206.  
  207.             // Row 1.
  208.             {
  209.                 new ConsoleMenuItem(" Option 1 ", (c, k) => {
  210.                     if (k.Key == ConsoleKey.Enter)
  211.                         c.Toggle();
  212.                 }) { IsToggled = false },
  213.  
  214.                 new ConsoleMenuItem(" Option 2 ", (c, k) => {
  215.                     if (k.Key == ConsoleKey.Enter)
  216.                         c.Toggle();
  217.                 }) { IsToggled = false },
  218.  
  219.                 new ConsoleMenuItem(" Option 3 ", (c, k) => {
  220.                     if (k.Key == ConsoleKey.Enter)
  221.                         c.Toggle();
  222.                 }) { IsToggled = false }
  223.             },
  224.         });
  225.  
  226.         #endregion Options menu
  227.  
  228.         #region Prompt menu
  229.  
  230.         private static readonly string PROMPT_TEXT = $"\n\n  $ {System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.exe";
  231.  
  232.         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 };
  233.  
  234.         private static List<String> _promptArguments = new List<string>();
  235.  
  236.         #endregion Prompt menu
  237.  
  238.         #endregion Fields
  239.     }
  240.  
  241.     public class ConsoleMenu
  242.     {
  243.         #region Properties
  244.  
  245.         #region Items
  246.  
  247.         private JaggedList<ConsoleMenuItem> _items;
  248.         public JaggedList<ConsoleMenuItem> Items
  249.         {
  250.             get { return _items; }
  251.             private set
  252.             {
  253.                 if (value == null)
  254.                     throw new NullReferenceException(nameof(Items));
  255.  
  256.                 _items = value;
  257.             }
  258.         }
  259.  
  260.         #endregion Items
  261.  
  262.         #region SelectedColumn
  263.  
  264.         private int _selectedColumn = 0;
  265.         public int SelectedColumn
  266.         {
  267.             get
  268.             {
  269.                 // Clamp the selected column to allow error-free navigation across uniquely sized rows in the jagged collection.
  270.                 _selectedColumn = Math.Max(0, Math.Min(Items[SelectedRow].Count - 1, _selectedColumn));
  271.  
  272.                 return _selectedColumn;
  273.             }
  274.             set
  275.             {
  276.                 if (value < 0 ||
  277.                     value >= Items[SelectedRow].Count)
  278.                     throw new ArgumentOutOfRangeException(nameof(SelectedColumn), $"Value '{value}' must be in the range [{0}, {Items[SelectedRow].Count - 1}].");
  279.  
  280.                 _selectedColumn = value;
  281.             }
  282.         }
  283.  
  284.         #endregion SelectedColumn
  285.  
  286.         #region SelectedRow
  287.  
  288.         private int _selectedRow = 0;
  289.         public int SelectedRow
  290.         {
  291.             get
  292.             {
  293.                 // Clamp the selected row in case the collection was modified.
  294.                 _selectedRow = Math.Max(0, Math.Min(Items.Count - 1, _selectedRow));
  295.  
  296.                 return _selectedRow;
  297.             }
  298.             set
  299.             {
  300.                 if (value < 0 ||
  301.                     value >= Items.Count)
  302.                     throw new ArgumentOutOfRangeException(nameof(SelectedRow), $"Value '{value}' must be in the range [{0}, {Items.Count - 1}].");
  303.  
  304.                 _selectedRow = value;
  305.             }
  306.         }
  307.  
  308.         #endregion SelectedRow
  309.  
  310.         #region SelectedItem
  311.  
  312.         public ConsoleMenuItem SelectedItem
  313.         {
  314.             get
  315.             {
  316.                 if (Items.Count == 0)
  317.                     return null;
  318.  
  319.                 return Items[SelectedRow][SelectedColumn];
  320.             }
  321.         }
  322.  
  323.         #endregion SelectedItem
  324.  
  325.         #region Status
  326.  
  327.         public string Status { get; set; } = null;
  328.  
  329.         #endregion Status
  330.  
  331.         #region Title
  332.  
  333.         public string Title { get; set; }
  334.  
  335.         #endregion Title
  336.  
  337.         #endregion Properties
  338.  
  339.         #region Events
  340.  
  341.         #region ConsoleKeyEvent
  342.  
  343.         public event EventHandler<ConsoleKeyEventArgs> ConsoleKeyEvent;
  344.  
  345.         protected virtual void OnConsoleKeyEvent(ConsoleKeyEventArgs e)
  346.         {
  347.             if (e == null)
  348.                 throw new ArgumentNullException(nameof(e));
  349.  
  350.             ConsoleKeyEvent?.Invoke(this, e);
  351.         }
  352.  
  353.         #endregion ConsoleKeyEvent
  354.  
  355.         #region PreConsoleKeyEvent
  356.  
  357.         public event EventHandler<PreConsoleKeyEventArgs> PreConsoleKeyEvent;
  358.  
  359.         protected virtual bool OnPreConsoleKeyEvent(PreConsoleKeyEventArgs e)
  360.         {
  361.             if (e == null)
  362.                 throw new ArgumentNullException(nameof(e));
  363.  
  364.             PreConsoleKeyEvent?.Invoke(this, e);
  365.  
  366.             return e.Cancel;
  367.         }
  368.  
  369.         #endregion PreConsoleKeyEvent
  370.  
  371.         #endregion Events
  372.  
  373.         #region Constructors
  374.  
  375.         public ConsoleMenu(string title)
  376.             : this(title, new JaggedList<ConsoleMenuItem>())
  377.         {
  378.         }
  379.  
  380.         public ConsoleMenu(string title, JaggedList<ConsoleMenuItem> items)
  381.         {
  382.             Title = title;
  383.             Items = items;
  384.         }
  385.  
  386.         #endregion Constructors
  387.  
  388.         #region Members
  389.  
  390.         /// <summary>
  391.         /// Display menu.
  392.         /// </summary>
  393.         public void Show()
  394.         {
  395.             if (_currentMenu != this)
  396.             {
  397.                 var firstRun = _currentMenu == null;
  398.                 _currentMenu = this;
  399.  
  400.                 if (firstRun == false) // If a menu-drawing loop is running.
  401.                     return;
  402.             }
  403.  
  404.             while (true)
  405.             {
  406.                 DrawMenu(_currentMenu);
  407.  
  408.                 ProcessKeyboardInput(_currentMenu);
  409.             }
  410.         }
  411.  
  412.         /// <summary>
  413.         /// Draw menu title and items.
  414.         /// </summary>
  415.         /// <param name="menu">The <see cref="ConsoleMenu"/> to draw.</param>
  416.         /// <exception cref="ArgumentNullException"></exception>
  417.         private static void DrawMenu(ConsoleMenu menu)
  418.         {
  419.             if (menu == null)
  420.                 throw new ArgumentNullException(nameof(menu));
  421.  
  422.             Console.Clear();
  423.  
  424.             #region Draw title
  425.  
  426.             if (string.IsNullOrEmpty(menu.Title) == false)
  427.                 Console.WriteLine(menu.Title);
  428.  
  429.             #endregion Draw title
  430.  
  431.             #region Measure horizontal items
  432.  
  433.             var LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH = 0;
  434.             for (var row = 0; row < menu.Items.Count; row++)
  435.             {
  436.                 var columnsCount = menu.Items[row].Count;
  437.                 if (columnsCount == 1) // If only one item in this row.
  438.                     continue;
  439.  
  440.                 for (var column = 0; column < columnsCount; column++)
  441.                 {
  442.                     var item = menu.Items[row][column];
  443.                     LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH = Math.Max(LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH, item.Text.Length);
  444.                 }
  445.             }
  446.  
  447.             #endregion Measure horizontal items
  448.  
  449.             #region Draw items
  450.  
  451.             for (var row = 0; row < menu.Items.Count; row++)
  452.             {
  453.                 var columnsCount = menu.Items[row].Count;
  454.                 for (var column = 0; column < columnsCount; column++)
  455.                 {
  456.                     var menuItem = menu.Items[row][column];
  457.                     if (menuItem == null)
  458.                         throw new NullReferenceException($"Null {nameof(ConsoleMenuItem)} detected.");
  459.  
  460.                     string rowIndent;
  461.                     if (row == 0) // If first item.
  462.                         rowIndent = " ";
  463.  
  464.                     else if (column == 0) // If a vertical item.
  465.                         rowIndent = "\n ";
  466.  
  467.                     else // If a horizontal item.
  468.                         rowIndent = null;
  469.  
  470.                     Console.Write(rowIndent);
  471.  
  472.                     var isSelected = (row == menu.SelectedRow && column == menu.SelectedColumn);
  473.                     if (isSelected)
  474.                     {
  475.                         // Apply selected item colors.
  476.                         Console.ForegroundColor = ConsoleColor.Black;
  477.                         Console.BackgroundColor = ConsoleColor.White;
  478.                     }
  479.  
  480.                     if (menuItem.IsToggled.HasValue)
  481.                     {
  482.                         if (menuItem.IsToggled.Value)
  483.                             Console.Write(" [x] ");
  484.  
  485.                         else
  486.                             Console.Write(" [ ] ");
  487.                     }
  488.  
  489.                     Console.Write(menuItem.Text);
  490.  
  491.                     if (isSelected)
  492.                     {
  493.                         // Restore normal item colors.
  494.                         Console.ForegroundColor = ConsoleColor.White;
  495.                         Console.BackgroundColor = ConsoleColor.Black;
  496.                     }
  497.  
  498.                     if (columnsCount > 1)
  499.                     {
  500.                         var alignmentOutdent = new string(' ', LARGEST_HORIZONTAL_ITEM_TEXT_LENGTH - menuItem.Text.Length);
  501.                         Console.Write(alignmentOutdent);
  502.                     }
  503.                 }
  504.             }
  505.  
  506.             #endregion Draw items
  507.  
  508.             #region Draw status
  509.  
  510.             if (string.IsNullOrEmpty(menu.Status) == false)
  511.                 Console.Write(menu.Status);
  512.  
  513.             #endregion Draw status
  514.         }
  515.  
  516.         /// <summary>
  517.         /// Navigate and execute menu items based on keyboard input.
  518.         /// </summary>
  519.         /// <param name="menu"></param>
  520.         /// <exception cref="ArgumentNullException"></exception>
  521.         private static void ProcessKeyboardInput(ConsoleMenu menu)
  522.         {
  523.             if (menu == null)
  524.                 throw new ArgumentNullException(nameof(menu));
  525.  
  526.             var consoleKeyInfo = Console.ReadKey();
  527.  
  528.             var cancel = menu.OnPreConsoleKeyEvent(new PreConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
  529.             if (cancel)
  530.                 return;
  531.  
  532.             var rows = menu.Items.Count;
  533.             if (rows == 0)
  534.                 return;
  535.  
  536.             var columns = menu.Items[menu.SelectedRow].Count;
  537.  
  538.             switch (consoleKeyInfo.Key)
  539.             {
  540.                 case ConsoleKey.UpArrow:
  541.                     // Loop around backwards.
  542.                     menu.SelectedRow = (menu.SelectedRow - 1 + rows) % rows;
  543.                     break;
  544.  
  545.                 case ConsoleKey.DownArrow:
  546.                     // Loop around forwards.
  547.                     menu.SelectedRow = (menu.SelectedRow + 1) % rows;
  548.                     break;
  549.  
  550.                 case ConsoleKey.LeftArrow:
  551.                     // Loop around backwards.
  552.                     menu.SelectedColumn = (menu.SelectedColumn - 1 + columns) % columns;
  553.                     break;
  554.  
  555.                 case ConsoleKey.RightArrow:
  556.                     // Loop around forwards.
  557.                     menu.SelectedColumn = (menu.SelectedColumn + 1) % columns;
  558.                     break;
  559.  
  560.                 default:
  561.                     menu.SelectedItem.Execute(consoleKeyInfo);
  562.                     break;
  563.             }
  564.  
  565.             menu.OnConsoleKeyEvent(new ConsoleKeyEventArgs(menu.SelectedItem, consoleKeyInfo));
  566.         }
  567.  
  568.         #endregion Members
  569.  
  570.         #region Fields
  571.  
  572.         private static ConsoleMenu _currentMenu = null;
  573.  
  574.         #endregion Fields
  575.     }
  576.  
  577.     #region Types
  578.  
  579.     public class ConsoleMenuItem
  580.     {
  581.         #region Properties
  582.  
  583.         #region Code
  584.  
  585.         public Action<ConsoleMenuItem, ConsoleKeyInfo> Code { get; set; }
  586.  
  587.         #endregion Code
  588.  
  589.         #region IsToggled
  590.  
  591.         public bool? IsToggled { get; set; } = null;
  592.  
  593.         #endregion IsToggled
  594.  
  595.         #region Tag
  596.  
  597.         public object Tag { get; set; } = null;
  598.  
  599.         #endregion Tag
  600.  
  601.         #region Text
  602.  
  603.         private string _text;
  604.         public string Text
  605.         {
  606.             get { return _text; }
  607.             set
  608.             {
  609.                 if (string.IsNullOrEmpty(value))
  610.                     throw new ArgumentNullException(nameof(Text));
  611.  
  612.                 _text = value;
  613.             }
  614.         }
  615.  
  616.         #endregion Text
  617.  
  618.         #endregion Properties
  619.  
  620.         #region Constructors
  621.  
  622.         public ConsoleMenuItem(string text)
  623.             : this(text, null)
  624.         {
  625.         }
  626.  
  627.         public ConsoleMenuItem(string text, Action<ConsoleMenuItem, ConsoleKeyInfo> code)
  628.         {
  629.             Text = text;
  630.             Code = code;
  631.         }
  632.  
  633.         #endregion Constructors
  634.  
  635.         #region Members
  636.  
  637.         /// <summary>
  638.         /// Execute menu item code.
  639.         /// </summary>
  640.         /// <param name="consoleKeyInfo"></param>
  641.         public void Execute(ConsoleKeyInfo consoleKeyInfo)
  642.         {
  643.             var code = Code;
  644.  
  645.             if (code == null)
  646.                 return;
  647.  
  648.             code(this, consoleKeyInfo);
  649.         }
  650.  
  651.         /// <summary>
  652.         /// Toggle menu item state.
  653.         /// </summary>
  654.         public void Toggle()
  655.         {
  656.             if (IsToggled.HasValue == false)
  657.                 IsToggled = true;
  658.  
  659.             else
  660.                 IsToggled ^= true;
  661.         }
  662.  
  663.         #endregion Members
  664.     }
  665.  
  666.     public class ConsoleKeyEventArgs : EventArgs
  667.     {
  668.         #region Properties
  669.  
  670.         public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
  671.  
  672.         public ConsoleMenuItem ConsoleMenuItem { get; private set; }
  673.  
  674.         #endregion Properties
  675.  
  676.         #region Constructors
  677.  
  678.         public ConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
  679.         {
  680.             if (consoleMenuItem == null)
  681.                 throw new ArgumentNullException(nameof(consoleMenuItem));
  682.  
  683.             if (consoleKeyInfo == null)
  684.                 throw new ArgumentNullException(nameof(consoleKeyInfo));
  685.  
  686.             ConsoleKeyInfo = consoleKeyInfo;
  687.             ConsoleMenuItem = consoleMenuItem;
  688.         }
  689.  
  690.         #endregion Constructors
  691.     }
  692.  
  693.     public class PreConsoleKeyEventArgs : CancelEventArgs
  694.     {
  695.         #region Properties
  696.  
  697.         public ConsoleKeyInfo ConsoleKeyInfo { get; private set; }
  698.  
  699.         public ConsoleMenuItem ConsoleMenuItem { get; private set; }
  700.  
  701.         #endregion Properties
  702.  
  703.         #region Constructors
  704.  
  705.         public PreConsoleKeyEventArgs(ConsoleMenuItem consoleMenuItem, ConsoleKeyInfo consoleKeyInfo)
  706.         {
  707.             if (consoleMenuItem == null)
  708.                 throw new ArgumentNullException(nameof(consoleMenuItem));
  709.  
  710.             if (consoleKeyInfo == null)
  711.                 throw new ArgumentNullException(nameof(consoleKeyInfo));
  712.  
  713.             ConsoleKeyInfo = consoleKeyInfo;
  714.             ConsoleMenuItem = consoleMenuItem;
  715.         }
  716.  
  717.         #endregion Constructors
  718.     }
  719.  
  720.     public class JaggedList<T> : List<List<T>>
  721.     {
  722.         #region Members
  723.  
  724.         /// <summary>
  725.         /// Add a variable-length array of <see cref="T"/> items to the end of the <see cref="JaggedList{T}"/>.
  726.         /// </summary>
  727.         /// <param name="items">A variable-length array of <see cref="T"/> items.</param>
  728.         /// <example>
  729.         /// var jaggedList = new JaggedList<int> {
  730.         ///     1,
  731.         ///     { 2, 3 },
  732.         ///     { 5 },
  733.         /// };
  734.         /// jaggedList.Add(6);
  735.         /// jaggedList.Add(7, 8);
  736.         /// jaggedList.Add(new int[] { 9, 10, 11 });
  737.         /// </example>
  738.         public void Add(params T[] items)
  739.         {
  740.             Add(new List<T>(items));
  741.         }
  742.  
  743.         /// <summary>
  744.         /// 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}"/>.
  745.         /// </summary>
  746.         /// <param name="value">The object to locate in the <see cref="JaggedList{T}"/>. The value can be null for reference types.</param>
  747.         /// <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>
  748.         public Tuple<int, int> IndexOf(T value)
  749.         {
  750.             for (int row = 0, column; row < Count; row++)
  751.             {
  752.                 for (column = 0; column < this[row].Count; column++)
  753.                 {
  754.                     var item = this[row][column];
  755.                     if ((item == null && value == null) ||
  756.                         (item != null && item.Equals(value)))
  757.                         return Tuple.Create(row, column);
  758.                 }
  759.             }
  760.  
  761.             return null;
  762.         }
  763.  
  764.         #endregion Members
  765.     }
  766.  
  767.     #endregion Types
  768. }
Add Comment
Please, Sign In to add comment