Menús y submenús en consola C#

Iniciado por Meta, 28 Febrero 2020, 18:02 PM

0 Miembros y 1 Visitante están viendo este tema.

Meta

Hola:

Haciendo menús y submenús con la consola de C# 2019.

La verdad que he cometido fallos y no se si es posible resolverlo, al menos no se hacerlo. Todo de momento está hecho en un único archivo en Visual Studioi 2019.

He hecho un esquema boceto para que se entienda.


Ver zoom.

Se ha hecho un esquema de todas las opciones para tener una vista rápida y una posible facilidad de compresión.

Si nada más ejecutar el programa o aplicación, pulso Enter, muestra las opciones aue son A, B y C, señalo Salir, pulso Enter vuelve al reloj, como debe ser, así susecibamente. Hasta ahí todo bien.

El problema es cuando estoy en la opción indicada aquí.

Lo único que me falla, si voy a la opción C-1, le doy salir, al llegar a la opción principal que pone:

CitarOpción A.  
  Opción B.
> Opción C.


Se me pone la flecha marcadore de opciónes siempre en Opción A, no se me guarda en la última opción seleccionada. Debo corregir esto, ya sería con una variable para memorizarlo.

Dejo el código hehco hasta el momento.

Código C#:
Código (csharp) [Seleccionar]
using System;

namespace Menu_consola_18_cs
{
   class Program
   {
       static void Main(string[] args)
       {
           Console.Title = "Menú de opciones";

           // Tamaño ventana consola.
           // X anchura.
           Console.WindowWidth = 20;

           // Y altura.
           Console.WindowHeight = 5;

           // Oculto el cursor.
           Console.CursorVisible = false;

           // Fondo verde.
           Console.BackgroundColor = ConsoleColor.Green;

           // Letras negras.
           Console.ForegroundColor = ConsoleColor.Black;

           MenuPrincipal();
       }

       #region Menú principal.
       public static void MenuPrincipal()
       {
           // Almacena la tecla pulsada en la variable.
           ConsoleKey teclaInicial;

           // Limpiar pantalla.
           Console.Clear();

           // Posición del cursor del título del MENÚ PRINCIPAL.
           Console.SetCursorPosition(0, 0);

           // Título.
           Console.Write("   MENÚ PRINCIPAL   ");

           // Pocisión de la hora.
           Console.SetCursorPosition(4, 2);

           // Formato numérico dd/MM/yyyy.
           Console.Write(DateTime.Now.ToString("ddd dd MMM"));

           // Almacena en la variable una tecla pulsada.
           teclaInicial = Console.ReadKey(true).Key;

           // ¿Haz pulsado la tecla Enter?
           if (teclaInicial == ConsoleKey.Enter)
           {
               // Sí. Se ejecuta esta función.
               MenuOpciones();
           }
       }
       #endregion

       #region Menú de opciones principales.
       public static void MenuOpciones()
       {
           // Contador de teclas y navegador.
           int opcion = 0;

           // Capturar tecla para luego validar.
           ConsoleKey tecla;

           while (true)
           {
               //******************************************************************
               // Dibujo el menú principal.

               // Limpiar pantalla.
               Console.Clear();

               switch (opcion)
               {
                   case 0:
                       Console.SetCursorPosition(0, 0);
                       Console.Write("> Opción A.         ");
                       Console.SetCursorPosition(0, 1);
                       Console.Write("  Opción B.         ");
                       Console.SetCursorPosition(0, 2);
                       Console.Write("  Opción C.         ");
                       Console.SetCursorPosition(0, 3);
                       Console.Write("  Salir.            ");
                       break;
                   case 1:
                       Console.SetCursorPosition(0, 0);
                       Console.Write("  Opción A.         ");
                       Console.SetCursorPosition(0, 1);
                       Console.Write("> Opción B.         ");
                       Console.SetCursorPosition(0, 2);
                       Console.Write("  Opción C.         ");
                       Console.SetCursorPosition(0, 3);
                       Console.Write("  Salir.            ");
                       break;
                   case 2:
                       Console.SetCursorPosition(0, 0);
                       Console.Write("  Opción A.         ");
                       Console.SetCursorPosition(0, 1);
                       Console.Write("  Opción B.         ");
                       Console.SetCursorPosition(0, 2);
                       Console.Write("> Opción C.         ");
                       Console.SetCursorPosition(0, 3);
                       Console.Write("  Salir.            ");
                       break;
                   case 3:
                       Console.SetCursorPosition(0, 0);
                       Console.Write("  Opción A.         ");
                       Console.SetCursorPosition(0, 1);
                       Console.Write("  Opción B.         ");
                       Console.SetCursorPosition(0, 2);
                       Console.Write("  Opción C.         ");
                       Console.SetCursorPosition(0, 3);
                       Console.Write("> Salir.            ");
                       break;
                   default:
                       Console.Write("Fuera de rango.     ");
                       break;
               }

               // Fin de pintar el menú principal.
               //******************************************************************

               // Leer tecla ingresada por el usuario.
               tecla = Console.ReadKey(true).Key;

               // Validar el tipo de tecla.
               if (tecla == ConsoleKey.Enter)
               {
                   switch (opcion)
                   {
                       case 0:
                           OpcionA();
                           break;
                       case 1:
                           OpcionB();
                           break;
                       case 2:
                           OpcionC();
                           break;
                       case 3:
                           MenuPrincipal();
                           break;
                       default:
                           break;
                   }
               }

               // Flecha abajo del teclado.
               if (tecla == ConsoleKey.DownArrow)
               {
                   opcion++;
               }

               // Flecha arriba del teclado.
               if (tecla == ConsoleKey.UpArrow)
               {
                   opcion--;
               }

               // Si está en la última opción del menú, salta a la primera.
               if (opcion > 3)
               {
                   opcion = 0;
               }

               // Si está en la primera posición del menú, salta a la última.
               if (opcion < 0)
               {
                   opcion = 3;
               }
           }
       }
       #endregion

       #region Opción A (0).
       public static void OpcionA()
       {
           ConsoleKey teclaOpcionA;
           Console.Clear();
           do
           {
               Console.SetCursorPosition(0, 0);
               Console.WriteLine("Estás en Opción A.");
               Console.SetCursorPosition(0, 2);
               Console.WriteLine("Pulse Enter para");
               Console.SetCursorPosition(0, 3);
               Console.WriteLine("Salir.");

               // Almacena el teclado pulsado en la variable teclaSubMenuA.
               teclaOpcionA = Console.ReadKey(true).Key;

           } while (teclaOpcionA != ConsoleKey.Enter);
       }
       #endregion

       #region Opción B (1).
       public static void OpcionB()
       {
           // Contador de teclas y navegador.
           int opcionB = 0;

           // Capturar tecla para luego validar.
           ConsoleKey teclaOpcionB;

           while (true)
           {
               switch (opcionB)
               {
                   case 0:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción B.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("> SubOpción B-1.    ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  SubOpción B-2     ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Salir.            ");
                       break;
                   case 1:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción B.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  SubOpción B-1.    ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("> SubOpción B-2     ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Salir.            ");
                       break;
                   case 2:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción B.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  SubOpción B-1.    ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  SubOpción B-2     ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("> Salir.            ");
                       break;
                   default:
                       Console.Write("Fuera de rango.     ");
                       break;
               }

               // Leer tecla ingresada por el usuario.
               teclaOpcionB = Console.ReadKey(true).Key;

               // Validar el tipo de tecla.
               if (teclaOpcionB == ConsoleKey.Enter)
               {
                   switch (opcionB)
                   {
                       case 0:
                           OpcionB1();
                           break;
                       case 1:
                           OpcionB2();
                           break;
                       case 2:
                           MenuOpciones();
                           break;
                       default:
                           Console.Write("Fuera de rango.     ");
                           break;
                   }
               }

               if (teclaOpcionB == ConsoleKey.DownArrow)
               {
                   opcionB++;
               }

               if (teclaOpcionB == ConsoleKey.UpArrow)
               {
                   opcionB--;
               }

               // Si está en la última opción, salta a la primera.
               if (opcionB > 2)
               {
                   opcionB = 0;
               }

               // Si está en la primera posición, salta a la última.
               if (opcionB < 0)
               {
                   opcionB = 2;
               }
           }
       }

       #endregion

       #region Opcion B-1.
       public static void OpcionB1()
       {
           ConsoleKey teclaOpcionB1;
           Console.Clear();
           do
           {
               Console.SetCursorPosition(0, 0);
               Console.WriteLine("Estás en Opción B-1.");
               Console.SetCursorPosition(0, 2);
               Console.WriteLine("Pulse Enter para    ");
               Console.SetCursorPosition(0, 3);
               Console.WriteLine("volver atrás.       ");

               // Almacena el teclado pulsado en la variable teclaSubMenuA.
               teclaOpcionB1 = Console.ReadKey(true).Key;

           } while (teclaOpcionB1 != ConsoleKey.Enter);
       }
       #endregion

       #region Opcion B-2.
       public static void OpcionB2()
       {
           ConsoleKey teclaOpcionB2;
           Console.Clear();
           do
           {
               Console.SetCursorPosition(0, 0);
               Console.WriteLine("Estás en Opción B-2.");
               Console.SetCursorPosition(0, 2);
               Console.WriteLine("Pulse Enter para    ");
               Console.SetCursorPosition(0, 3);
               Console.WriteLine("volver atrás.       ");

               // Almacena el teclado pulsado en la variable teclaSubMenuA.
               teclaOpcionB2 = Console.ReadKey(true).Key;

           } while (teclaOpcionB2 != ConsoleKey.Enter);
       }
       #endregion

       #region Opción C (2).
       public static void OpcionC()
       {
           // Contador de teclas y navegador.
           int opcionC = 0;

           // Capturar tecla para luego validar.
           ConsoleKey teclaOpcionC;
           Console.Clear();
           
           while(true)
           {
               switch (opcionC)
               {
                   case 0:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción C.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("> Color 1.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  Color 2.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Opción C-1.       ");
                       break;
                   case 1:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción C.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  Color 1.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("> Color 2.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Opción C-1.       ");
                       break;
                   case 2:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción C.  ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  Color 1.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  Color 2.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("> Opción C-1.       ");
                       break;
                   case 3:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("> Color 3.          ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  Color 4.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  Color 5.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Salir.            ");
                       break;
                   case 4:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("  Color 3.          ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("> Color 4.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  Color 5.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Salir.            ");
                       break;
                   case 5:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("  Color 3.          ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  Color 4.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("> Color 5.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("  Salir.            ");
                       break;
                   case 6:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("  Color 3.          ");
                       Console.SetCursorPosition(0, 1);
                       Console.WriteLine("  Color 4.          ");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  Color 5.          ");
                       Console.SetCursorPosition(0, 3);
                       Console.WriteLine("> Salir.            ");
                       break;

                   default:
                       Console.Write("Fuera de rango.     ");
                       break;
               }

               // Leer tecla ingresada por el usuario.
               teclaOpcionC = Console.ReadKey(true).Key;

               // Validar el tipo de tecla.
               if (teclaOpcionC == ConsoleKey.Enter)
               {
                   switch (opcionC)
                   {
                       case 0:
                           // Fondo azul.
                           Console.BackgroundColor = ConsoleColor.Blue;

                           // Letras blancas.
                           Console.ForegroundColor = ConsoleColor.White;
                           break;
                       case 1:
                           // Fondo verde.
                           Console.BackgroundColor = ConsoleColor.Green;

                           // Letras negras.
                           Console.ForegroundColor = ConsoleColor.Black;
                           break;
                       case 2:
                           OpcionC1();
                           break;
                       case 3:
                           // Fondo negro.
                           Console.BackgroundColor = ConsoleColor.Black;

                           // Letras rojo.
                           Console.ForegroundColor = ConsoleColor.Red;
                           break;
                       case 4:
                           // Fondo negro.
                           Console.BackgroundColor = ConsoleColor.Black;

                           // Letras rojo.
                           Console.ForegroundColor = ConsoleColor.Yellow;
                           break;
                       case 5:
                           // Fondo negro.
                           Console.BackgroundColor = ConsoleColor.Red;

                           // Letras rojo.
                           Console.ForegroundColor = ConsoleColor.DarkRed;
                           break;
                       case 6:
                           MenuOpciones();
                           break;
                       default:
                           Console.Write("Fuera de rango.     ");
                           break;
                   }
               }

               if (teclaOpcionC == ConsoleKey.DownArrow)
               {
                   opcionC++;
               }

               if (teclaOpcionC == ConsoleKey.UpArrow)
               {
                   opcionC--;
               }

               // Si está en la última opción, salta a la primera.
               if (opcionC > 6)
               {
                   opcionC = 0;
               }

               // Si está en la primera posición, salta a la última.
               if (opcionC < 0)
               {
                   opcionC = 6;
               }
           }
       }
       #endregion

       #region OpcionC-1.
       public static void OpcionC1()
       {
           // Contador de teclas y navegador.
           int opcionC1 = 0;

           // Capturar tecla para luego validar.
           ConsoleKey teclaOpcionC1;
           Console.Clear();
           
           while(true)
           {
               Console.Clear();

               switch (opcionC1)
               {
                   case 0:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción C-1.");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("  SI");
                       Console.SetCursorPosition(16, 2);
                       Console.WriteLine("> NO");
                       break;
                   case 1:
                       Console.SetCursorPosition(0, 0);
                       Console.WriteLine("Estás en Opción C-1.");
                       Console.SetCursorPosition(0, 2);
                       Console.WriteLine("> SI");
                       Console.SetCursorPosition(16, 2);
                       Console.WriteLine("  NO");
                       break;
                   default:
                       Console.Write("Fuera de rango.     ");
                       break;
               }

               // Leer tecla ingresada por el usuario.
               teclaOpcionC1 = Console.ReadKey(true).Key;

               // Validar el tipo de tecla.
               if (teclaOpcionC1 == ConsoleKey.Enter)
               {
                   switch (opcionC1)
                   {
                       case 0:
                           MenuPrincipal();
                           Console.Clear();
                           break;
                       case 1:
                           OpcionSI();
                           break;
                       default:
                           Console.Write("Fuera de rango.     ");
                           break;
                   }
               }

               // Flecha derecha.
               if (teclaOpcionC1 == ConsoleKey.RightArrow)
               {
                   opcionC1++;
               }

               // Flecha izquierda.
               if (teclaOpcionC1 == ConsoleKey.LeftArrow)
               {
                   opcionC1--;
               }

               // Si está en la última opción, salta a la primera.
               if (opcionC1 > 1)
               {
                   opcionC1 = 0;
               }

               // Si está en la primera posición, salta a la última.
               if (opcionC1 < 0)
               {
                   opcionC1 = 1;
               }
           }
       }
       #endregion

       #region opcionSI del sub menú C-1.
       public static void OpcionSI()
       {
           ConsoleKey teclaOpcionB1;
           Console.Clear();
           do
           {
               Console.SetCursorPosition(0, 0);
               Console.WriteLine("Estás en Opción SÍ.");
               Console.SetCursorPosition(0, 2);
               Console.WriteLine("Pulse Enter para    ");
               Console.SetCursorPosition(0, 3);
               Console.WriteLine("volver atrás.       ");

               // Almacena el teclado pulsado en la variable teclaOpciónB1.
               teclaOpcionB1 = Console.ReadKey(true).Key;

           } while (teclaOpcionB1 != ConsoleKey.Enter);
       }
       #endregion
   }
}


Se que se podrá hacer mucho mejor y sin fallos como estos. No se quedará así, sino que mi idea principal, es coger el truco que se pueda crear menús y submenús que se pueda hacer de forma muy sencilla.

Dejo clarqo eu solo hacerlo que funcione con botones flecha arriba, abajo, derecha, izquierda y Enter (Escape solo para salir si estás en la ventana principal).

Saludos.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

ThunderCls

Porque no lo implementas usando algo similar a un arbol?. Creo que seria mucho mas eficiente, dinamico y sobretodo sencillo
Saludos
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/


ThunderCls

#3
Cita de: Meta en  1 Marzo 2020, 08:04 AM
¿Tu crees que es más fácil?
http://www.udb.edu.sv/udb/archivo/guia/informatica-ingenieria/programacion-iv/2013/ii/guia-6.pdf
https://docs.microsoft.com/es-es/dotnet/csharp/programming-guide/concepts/expression-trees/

No creo que era necesario referenciarme una conferencia de Arboles  :laugh:  se muy bien lo que son. En fin, en mi opinion una implementacion en este caso usando una ED tipo Arbol es la solucion ideal y vuelvo a repetirme, es una implementacion mas eficiente, dinamica y si, muchisimo mas sencilla aunque no lo parezca.
Como me parecio divertida la idea aqui dejo un sencillo framework (poc) que hice para crear menus en consola de forma dinamica usando un Arbol como abstraccion de datos. Aclaro que con esto no quiero decir que esta es la unica forma de hacerlo, simplemente que a mi parecer es la mas natural


Código (csharp) [Seleccionar]
/// <summary>
/// Menu.cs
/// Abstract menu class
/// </summary>
public abstract class Menu
{
    /// <summary>
    /// Menu header UI details
    /// </summary>
    public class Position
    {
        public int TopSpacing { get; set; }
        public int TopPadding { get; set; }
        public int LeftPadding { get; set; }
        public int LeftSpacing { get; set; }
        public bool LeftCentered { get; set; }
        public bool TopCentered { get; set; }
    }

    public const string MarkerSymbol = "=>";
    public List<string> HeaderText { get; set; }
    public string EntryTitle { get; set; }
    public bool Selected { get; set; }
    public Position HeaderPos { get; set; }
    public Position EntriesPos { get; set; }

    protected internal Menu Parent;
    protected internal List<Menu> Children;
    protected internal int ChildIndex;

    protected internal Menu()
    {
        Children = new List<Menu>();
        HeaderPos = new Position();
        EntriesPos = new Position();
    }

    protected internal void Select()
    {
        Selected = true;
    }

    protected internal void Deselect()
    {
        Selected = false;
    }

    protected internal string GetMarker()
    {
        return Selected ? MarkerSymbol : new string(' ', MarkerSymbol.Length);
    }

    public void AddChild(Menu child)
    {
        child.Parent = this;
        Children.Add(child);
    }

    protected internal string GetEntryString()
    {
        return $"{this.GetMarker()} {this.EntryTitle}";
    }

    public void ControlLoop()
    {
        Console.CursorVisible = false;
        do
        {
            ShowMenu();
            ProcessKeyPress();
        } while (true);
    }

    protected internal abstract void Execute();
    protected internal abstract void ShowMenu();
    protected internal abstract void ProcessKeyPress();
}


/// <summary>
/// VerticalMenu.cs
/// Vertical menu class
/// </summary>
public class VerticalMenu : Menu
{
    protected internal override void Execute()
    {
        ControlLoop();
    }

    protected internal override void ShowMenu()
    {
        Console.Clear();

        // calculating left positioning for menu header
        int leftPosition = (HeaderPos.LeftCentered)
                ? (Console.WindowWidth / 2) - (HeaderText.Max(x => x.Length) / 2)
                : HeaderPos.LeftSpacing;

        int topPosition = (HeaderPos.TopCentered)
            ? (Console.WindowHeight / 2) -
              (((HeaderPos.TopPadding > 0) ? HeaderText.Count * 2 * HeaderPos.TopPadding : HeaderText.Count +
              ((EntriesPos.TopPadding > 0) ? Children.Count * 2 * EntriesPos.TopPadding : Children.Count)) / 2)
            : Console.CursorTop + HeaderPos.TopSpacing;

        Console.SetCursorPosition(leftPosition, topPosition);
        foreach (string line in HeaderText)
        {
            Console.WriteLine(line);
            Console.SetCursorPosition(leftPosition, Console.CursorTop + HeaderPos.TopPadding);
        }

        // calculating left positioning for menu entries
        int totalLeft = Children.Select(x => x.GetEntryString().Length).ToList().Max(x => x);
        leftPosition = (EntriesPos.LeftCentered)
            ? (Console.WindowWidth / 2) -
              (totalLeft / 2)
            : EntriesPos.LeftSpacing;

        Console.SetCursorPosition(leftPosition, Console.CursorTop + EntriesPos.TopSpacing);
        foreach (var child in Children)
        {
            Console.WriteLine(child.GetEntryString());
            Console.SetCursorPosition(leftPosition, Console.CursorTop + EntriesPos.TopPadding);
        }
    }

    protected internal override void ProcessKeyPress()
    {
        var key = Console.ReadKey();
        switch (key.Key)
        {
            case ConsoleKey.UpArrow:
                MoveMarkerUp();
                break;

            case ConsoleKey.DownArrow:
                MoveMarkerDown();
                break;

            case ConsoleKey.Enter:
                ExecuteEntry();
                break;

            case ConsoleKey.Escape:
                TryExitConsole();
                break;
        }
    }

    private void MoveMarkerUp()
    {
        ChildIndex = (ChildIndex - 1 >= 0) ? ChildIndex -= 1 : Children.Count - 1;
        Children.ForEach(x => x.Deselect());
        Children[ChildIndex].Select();
    }

    private void MoveMarkerDown()
    {
        ChildIndex = (ChildIndex + 1 < Children.Count) ? ChildIndex += 1 : 0;
        Children.ForEach(x => x.Deselect());
        Children[ChildIndex].Select();
    }

    private void ExecuteEntry()
    {
        if (ChildIndex >= 0 && ChildIndex < Children.Count)
        {
            Menu menu = Children[ChildIndex];
            if (menu?.GetType() != typeof(Entry) && menu?.Children.Count == 0)
            {
                menu = this.Parent;
            }

            menu?.Execute();
        }
    }

    private void TryExitConsole()
    {
        if (this.Parent == null)
            Environment.Exit(0);
    }
}


/// <summary>
/// HorizontalMenu.cs
/// Horizontal menu class
/// </summary>
public class HorizontalMenu : Menu
{
    protected internal override void Execute()
    {
        ControlLoop();
    }

    protected internal override void ShowMenu()
    {
        Console.Clear();

        // calculating left positioning for menu header
        int leftPosition = (HeaderPos.LeftCentered)
                ? (Console.WindowWidth / 2) - (HeaderText.Max(x => x.Length) / 2)
                : HeaderPos.LeftSpacing;

        int topPosition = (HeaderPos.TopCentered)
            ? (Console.WindowHeight / 2) -
              (((HeaderPos.TopPadding > 0) ? HeaderText.Count * 2 * HeaderPos.TopPadding : HeaderText.Count +
              ((EntriesPos.TopSpacing > 0) ? EntriesPos.TopSpacing + 1 : 1)) / 2)
            : Console.CursorTop + HeaderPos.TopSpacing;

        Console.SetCursorPosition(leftPosition, topPosition);
        foreach (string line in HeaderText)
        {
            Console.WriteLine(line);
            Console.SetCursorPosition(leftPosition, Console.CursorTop + HeaderPos.TopPadding);
        }

        // calculating left positioning for menu entries
        int totalEntriesLeftSpace = Children.Select(x => x.GetEntryString().Length + EntriesPos.LeftPadding)
                                            .ToList().Sum(x => x) - EntriesPos.LeftPadding;
        topPosition = Console.CursorTop + EntriesPos.TopSpacing;
        leftPosition = (EntriesPos.LeftCentered)
            ? (Console.WindowWidth / 2) - (totalEntriesLeftSpace / 2)
            : EntriesPos.LeftSpacing;
        foreach (var entry in Children)
        {
            Console.SetCursorPosition(leftPosition, topPosition);
            Console.WriteLine(entry.GetEntryString());
            leftPosition += MarkerSymbol.Length + entry.EntryTitle.Length + EntriesPos.LeftPadding;
        }
    }

    protected internal override void ProcessKeyPress()
    {
        var key = Console.ReadKey();
        switch (key.Key)
        {
            case ConsoleKey.LeftArrow:
                MoveMarkerLeft();
                break;

            case ConsoleKey.RightArrow:
                MoveMarkerRight();
                break;

            case ConsoleKey.Enter:
                ExecuteEntry();
                break;

            case ConsoleKey.Escape:
                TryExitConsole();
                break;
        }
    }

    private void MoveMarkerLeft()
    {
        ChildIndex = (ChildIndex - 1 >= 0) ? ChildIndex -= 1 : Children.Count - 1;
        Children.ForEach(x => x.Deselect());
        Children[ChildIndex].Select();
    }

    private void MoveMarkerRight()
    {
        ChildIndex = (ChildIndex + 1 < Children.Count) ? ChildIndex += 1 : 0;
        Children.ForEach(x => x.Deselect());
        Children[ChildIndex].Select();
    }

    private void ExecuteEntry()
    {
        if (ChildIndex >= 0 && ChildIndex < Children.Count)
        {
            Menu menu = Children[ChildIndex];
            if (menu?.GetType() != typeof(Entry) && menu?.Children.Count == 0)
            {
                menu = this.Parent;
            }

            menu?.Execute();
        }
    }

    private void TryExitConsole()
    {
        if (this.Parent == null)
            Environment.Exit(0);
    }
}


/// <summary>
/// Entry.cs
/// Menu entry class
/// </summary>
public class Entry : Menu
{
    public Action Event { get; set; }

    protected internal override void Execute()
    {
        Event?.Invoke();
    }

    protected internal override void ShowMenu() { }

    protected internal override void ProcessKeyPress() { }
}



Su uso para crear menus seria muy basico


Código (csharp) [Seleccionar]
static void Main(string[] args)
{
    Menu mainMenu = new VerticalMenu
    {
        HeaderText = new List<string>()
        {
            "============",
            " Main Menu  ",
            "============",
            "Options:"
        }
    };
    Entry blueEntry = new Entry
    {
        Selected = true,
        EntryTitle = "Blue",
        Event = () => { Console.BackgroundColor = ConsoleColor.DarkBlue; }
    };
    Entry redEntry = new Entry
    {
        Selected = false,
        EntryTitle = "Red",
        Event = () => { Console.BackgroundColor = ConsoleColor.DarkRed; }
    };

    mainMenu.AddChild(blueEntry);
    mainMenu.AddChild(redEntry);
    mainMenu.ControlLoop();
}



Igual he añadido un par de opciones para jugar con el posicionamiento de los elementos en la ventana de la consola, como el centrado horizontal y vertical, padding, etc



Resumiendo todo lo anterior, como decia antes el codigo base es mucho mas sencillo usando un Arbol para almacenar cada menu y si le quitamos todo el codigo extra para lo ultimo que comente y demas mierdas que puse son mucho menos lineas de codigo y logica de funcionamiento en general, ademas tienes la posibilidad de crear todos los menus, submenus y entradas que quieras de forma dinamica simplemente usando dichas clases en cualquier proyecto.
Pues nada, estuvo divertido  ;-)
Saludos
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

Meta

Pedazo de código. Eso si, tengo que entenderlo todo paso por paso. Si lo hiciste tu, buen trabajo.

LAs campturas te quedaron de lujo. Voy a investigar.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

ThunderCls

Cita de: Meta en  3 Marzo 2020, 00:20 AM
Pedazo de código. Eso si, tengo que entenderlo todo paso por paso. Si lo hiciste tu, buen trabajo.

LAs campturas te quedaron de lujo. Voy a investigar.

Si si lo hice yo  :xD de igual manera si no entiendes algo por parte del codigo o el algoritmo en si puedes preguntar, en realidad no es tan complicado. Como dije en un mensaje anterior, una vez que te abstraes lo suficiente puedes ver perfectamente como la relacion de un menu con un Arbol es casi perfecta, haciendo este tipo de estructura de datos las mas apropiada para implementarlo. El codigo se puede simplificar muchisimo si solo se deja el funcionamiento basico. La clase abstracta Menu es nuestra abstraccion del arbol y los atributos principales son:

Código (csharp) [Seleccionar]
protected internal Menu Parent;
protected internal List<Menu> Children;
protected internal int ChildIndex;


Parent sera el nodo padre o el menu padre si se quiere ver, Children los nodos hijos de dicho nodo padre, el cual es una lista de nodos pues evidentemente no estamos tratando con arbol binario en este caso pues un menu puede tener mas de dos entradas y finalmente ChildIndex sera nuestro selector.
Luego una de las funciones mas importantes es public void ControlLoop(), la encargada de mostrar el Nodo (menu) y procesar el input del usuario. Finalmente las clases HorizontalMenu/VerticalMenu y Entry son clases especializadas que implementan la clase abstracta Menu de acuerdo a su especificacion para lograr el objetivo de cada una y sus nombres son bien descriptivos del tipo de Nodo que serian.
Saludos

-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

Meta

Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/