Mover este > y barras en la misma pantalla consola

Iniciado por Meta, 13 Marzo 2020, 21:01 PM

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

Meta

Hola gente del foro:

En la consola C# 2019 quiero hacer un menú con estas dimensiones.

Código (csharp) [Seleccionar]
            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;


El diseño lo hago primero a mano. Hay que seleccionar o indicar la parte de la pantalla este símbolo >.
Con esto maneja las dos barras indicada abajo y las opciónes ATRÁS y MENÚ.

Las coordenadas del > está indicada en el dibujo de abajo.



Ver zoom.

El > lo mueves solo con flechas del teclado, arriba, abajo, derecha e izquierda.

Para dejarlo más claro, aquí una captura pero no es funcional.


La barra Brillo y Voum. se mueve del 0 al 7.

Antes de sacar un super código horrible, quiero saber cual es la mejor idea y facilidad para el dibujado y que funcione.

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

Serapis

Basta crear algunas funciones...

Una que dibuje cada línea del menú y reciba como parámetros, la línea que ocupa, el texto, si está o no seleccionado y el tipo de menú.

Otra función en base al tipo de menú dibuja lo que tenga a su derecha, de estas debe haber una función especializada por cada tipo, un parámetro en cualquier función es la fila y columna donde se comineza a dibujar. considera una columna fija para la señal de seleccionado, otra columna para el texto del menú y otra columna el punto para comenzar dibujar el valor que tenga a su derecha.

Una función que consuma la tecla pulsada, es la que deriva hacia las otras...

Exige antes de comenzar, tomar un lápiz y anotar cuantos tipos de menús distintos tendrás y en base a ello como se comporta cada uno. Cáles son y como se agrupan o el orden que tienenn entre sí.

Meta

#2
Tienes las cosas claras.

Por lo que cuentas. Lo que llamas función, otros lo llaman métodos. Solo se diferencia entre que uno no tiene parámetos y el otro si.

1. El dibujado.

2. Función o método de controlar el signo >. Arriba, abajo, izquierda y derecha. En este caso, solo tiene 4 posiciones indicadas en el dibujo hecho a mano en el primer post.
3. Función el brillo.
4. Función Volumen.

¿Falta algo?

Parecía fácil pero la cosa se complica.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Meta

Hola:

Una barra por probar, funciona.
Código (csharp) [Seleccionar]
using System;

namespace Menu_consola_22_cs
{
    class Program
    {
        // Variables.
        static readonly int MARGEN = 9;
        static int indicadorPos = 0;
        static readonly int INDICADOR_MIN = 0;
        //int indicadorMax = Console.WindowWidth - 2 * margen;
        static readonly int INDICADOR_MAX = 7;
        static int volumen = 0;
        static void Main(string[] args)
        {
            Console.Title = "Barra volumen y brillo";

            // 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;

            Console.Clear();
            Dibuja();
            Barra();
        }

        #region Barra.
        static void Barra()
        {
            // Combinación de tecla.
            Console.TreatControlCAsInput = true;

            for (ConsoleKey ck = ConsoleKey.Spacebar; ck != ConsoleKey.S; ck = Console.ReadKey(true).Key)
            {
                switch (ck)
                {
                    case ConsoleKey.Add: // Tecla de suma del teclado numérico.
                    case ConsoleKey.OemPlus:
                    case ConsoleKey.RightArrow: // Flecha derecha.
                        if (indicadorPos < INDICADOR_MAX)
                        {
                            indicadorPos++;
                            volumen++;
                            Dibuja();
                        }
                        break;

                    case ConsoleKey.Subtract: // Tecla de resta del teclado numérico.
                    case ConsoleKey.OemMinus:
                    case ConsoleKey.LeftArrow: // Flecha izquierda.
                        if (indicadorPos > INDICADOR_MIN)
                        {
                            indicadorPos--;
                            volumen--;
                            Dibuja();
                        }
                        break;

                    case ConsoleKey.UpArrow:
                        if (ConsoleKey.UpArrow == Console.ReadKey(true).Key)
                        {
                            Console.SetCursorPosition(0, 3);
                            Console.Write(" ");
                            Console.SetCursorPosition(13, 3);
                            Console.Write(">");
                        }

                        break;

                    case ConsoleKey.DownArrow:
                        if (ConsoleKey.DownArrow == Console.ReadKey(true).Key)
                        {
                            Console.SetCursorPosition(0, 3);
                            Console.Write(">");
                            Console.SetCursorPosition(13, 3);
                            Console.Write(" ");
                        }
                        break;
                }
            }
        }
        static void Dibuja()
        {
            Console.SetCursorPosition(0, 1); // X = -, Y = |.
            Console.Write("> Volum.:");
            Console.SetCursorPosition(MARGEN, 1);
            //Console.BackgroundColor = ConsoleColor.Blue;
            Console.ForegroundColor = ConsoleColor.Black;
            Console.Write(string.Empty.PadRight(INDICADOR_MAX + 1, '─'));
            Console.SetCursorPosition(MARGEN + indicadorPos, 1);
            Console.ForegroundColor = ConsoleColor.Black;
            Console.Write("█");
            Console.SetCursorPosition(18, 1);
            Console.Write(volumen);
            //Console.SetCursorPosition(0, 3);
            //Console.Write("  Atrás");
            //Console.SetCursorPosition(13, 3);
            //Console.Write("  Menú");
        }
#endregion
    }
}


El problema está hacer la otra barra y los ATRÁS y MENÚ.

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

EdePC

Saludos,

- Yo lo he implementado así:



- Bajo la premisa de guardar la coordenada de cada caracter cambiante (">" "X" "5") e ir restaurando/cambiando dicha posición +1 o -1 según corresponda.

- Otra forma que vi en otro mensaje del foro era re-escribiendo todo el menú con los cambios adecuados.

- No soy desarrollador en C#, haber si me leo unos manuales sencillos para aplicar funciones, clases y esas cosas, ya tengo varios libros a la mano XD. Lo más para probar el IDE VS Express 2008 Sp1 que la verdad no me está convenciendo mucho, le falta más inteligencia. También he estado revisando métodos para compilar sin tener Visual Studio, tratar de instalar .Net Framework > 6.2 en mi Win8 ya que no soporta versiones más recientes :-/

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

namespace LCD {
  class Program {
    static void Main(string[] args) {

      Console.BackgroundColor = ConsoleColor.Green;
      Console.ForegroundColor = ConsoleColor.Black;
      Console.SetWindowSize(21, 5);
      Console.CursorVisible = false;

      ConsoleKey key;
      int selPosY = 0;
      int volPosX = 9;
      int briPosX = 9;

      Console.Clear();
      Console.WriteLine("  Brillo:--------   ");
      Console.WriteLine("  Volum.:--------   ");
      Console.WriteLine("                    ");
      Console.WriteLine("  Atrás        Menú ");

      Console.SetCursorPosition(0, selPosY);
      Console.Write(">");
      Console.SetCursorPosition(briPosX, 0);
      Console.Write("X");
      Console.SetCursorPosition(volPosX, 1);
      Console.Write("X");
      Console.SetCursorPosition(18, 0);
      Console.Write(briPosX - 9);
      Console.SetCursorPosition(18, 1);
      Console.Write(volPosX - 9);

      do {
        key = Console.ReadKey(true).Key;
        if (key == ConsoleKey.UpArrow && selPosY - 1 >= 0) {

          Console.SetCursorPosition(0, selPosY);
          Console.Write(" ");

          Console.CursorTop--;
          selPosY = Console.CursorTop;
          Console.SetCursorPosition(0, selPosY);
          Console.Write(">");
        }

        if (key == ConsoleKey.DownArrow && selPosY + 1 <= 1) {

          Console.SetCursorPosition(0, selPosY);
          Console.Write(" ");

          Console.CursorTop++;
          selPosY = Console.CursorTop;
          Console.SetCursorPosition(0, selPosY);
          Console.Write(">");
        }

        if (key == ConsoleKey.LeftArrow) {

          if (selPosY == 0 && briPosX - 1 >= 9) {
            Console.SetCursorPosition(briPosX, 0);
            Console.Write("-");

            Console.CursorLeft -= 2;
            briPosX = Console.CursorLeft;
            Console.Write("X");

            Console.SetCursorPosition(18, 0);
            Console.Write(briPosX - 9);
          }

          if (selPosY == 1 && volPosX - 1 >= 9) {
            Console.SetCursorPosition(volPosX, 1);
            Console.Write("-");

            Console.CursorLeft -= 2;
            volPosX = Console.CursorLeft;
            Console.Write("X");

            Console.SetCursorPosition(18, 1);
            Console.Write(volPosX - 9);
          }

        }

        if (key == ConsoleKey.RightArrow) {

          if (selPosY == 0 && briPosX + 1 <= 16) {
            Console.SetCursorPosition(briPosX, 0);
            Console.Write("-");

            briPosX = Console.CursorLeft;
            Console.Write("X");

            Console.SetCursorPosition(18, 0);
            Console.Write(briPosX - 9);
          }

          if (selPosY == 1 && volPosX + 1 <= 16) {
            Console.SetCursorPosition(volPosX, 1);
            Console.Write("-");

            volPosX = Console.CursorLeft;
            Console.Write("X");

            Console.SetCursorPosition(18, 1);
            Console.Write(volPosX - 9);
          }
        }

      } while (key != ConsoleKey.Escape);

    }
  }
}

Serapis

#5
bueno, todo depende de como te quieras complicar las cosas...

Una solución efectiva ty rápida es ahcer código espaguetti, resuelve el caso, pero solo el presente caso... cada vez que tengas que crear un menú distinto, tendrás que codificcar.

La solución eficaz es hacer funciones abstractas que relaicen exclusivamente la función que señale su nombre y poco más... un puñado de ellas, satisfacen el menú completo por complejo que sea, la única diferencia en cada ocasión son los datos que reciban en cada ocasión el menú...
Es algo más elaborado, pero a futuro, siempre siempre, resulta ser lo más práctico.

Si tan solo es un ejercicio de práctica, es suficiente con lo que has hecho, si lo quieres para algo más, intenta darle la vuelta a la tuerca que te señalo...

Las cosas seguirían un trazo similar a esto:


(nota los elementos entre corchetes son opcionales, el resto obligatorios)
Lineamenu = columnaleftEspacios foco prefijo textoItem [valor] [sufijo]
columnaleftEspacios = "                    " // 20  espacios por ejemplo, al gusto
foco = ("   "|" > ")          // esto es, si tiene el foco, lleva el '>' si no, su espacio. ocupa 3 espacios.
prefijo = ("   "|" x "|" v ")   // nada, item seleccionado, item seleccionado en un grupo exclusivo (donde solo puede haber 1 seleccionado a la vez 'option/radio button').
textoItem = (A-Z, a-z, 0-9, " ")* string que contiene el texto, lógicamente no vale cualquier longitud... no es preciso limitarlo, se enteiende algo entre 1 y quizás 32 caracteres.
valor = otro texto que viene a la derecha dle texto... mismas consideraciones d etamaño que textoItem
sufijo = ("   "|" > "|"...") // similar a foco, pero aparece a la derecha del todo, básicamente indican: nada, contiene un submenú, se despliega/abre otra ventana con más detalles (puede omitirse este último tratándose de consola)

Así cualquier ítem de un menú lleva siempre una cantidad de espacios fijos a su izquierda (pudiera ser 0, si el menú se alinea completamente a la izquierda), le siguen 3 caracteres para señalar que menú tiene el foco, sabemos que esto se cambia con las flechas de cursor up/down (más tarde volvemos a este dato), luego le sigue el texto del menú, opcionalmente el texto puede tener un valor a su derecha (no es oblgatorio, depende del tipo de menú) igualmente el sufijo final es opcional y depende del tipo de menú, básicamente solo es obligatorio para un menú que qctúe como el 'padre' de un submenú'.




Ahota toca reflejar los datos de un menú en una estructura que refleje todas esas propiedades lo más fiel y claro posible:


Estructura ItemMenu
   byte Fila
   // entero Columna    // no es preciso, se marca solo porque resulta tentador que si tiene fila, deba tener columna. no es necesario.
   byte Indice
   string Texto
   buleano Seleccionado
   // buleano enfocado NO... lo pongo solo por que se note que es tentador poner esto, ese dato no precisa portarlo cada ítem es común a todo el menú.
   TipoMenu Tipo
   entero Hijos
   entero PrimerHijo
fin estructura


Explico un poco por encima que es cada campo y para que puede ser útil:
- Fila: Indica el orden en que se dibujara en el menú cuando se dibuje... típicamente el primero empezará en la línea 1 e irá creciendo, puede llegar a omitirse, si un menú rescrebie al existente actualmente, en cambio es necesario si un menú se superpone a otro existente (para consola, no suele ser éste el caso, pero tampoco estorba).
- Texto: Obvimanete cada ítem de un menú lo más llamativo que tiene es el texto que lo describe brevemente. No puede ser un parrafo, es habitual que tenga un tamaño máximo prefijado incluso que se rellene con espacios si no alcanza ese tamaño... eso es algo que ya decidirás  según veas si lo necesitas o se ve mejor, etc...
- Seleccionado: Hay menús que admiten un estado de selección individual y otros que que admiten un ítem seleccionado dentro de un grupo, otros no admiten un estado de selección. Para esos casos donde se precise señalar que está seleccionado a la izquierda del texto se pondrá una ' x ' (nota que hay 2 espacios rodeándolo, tu luego ya decidirás si basta el de su derecha o ninguno...
- Tipo: Aunque todos los items en principio parecen iguales (texto), lo cierto es que la diferenciación entre ellos favorece el tratamiento funcional  al tiempo que simplifica su operatoria y también facilita al usuario entenderlo, suele verse reflejado en como se ve en el menú, aunque no necesariamente. Después de estas explicaciones vamos a ellos.
- Hijos: Un ítem de un menú puede dar acceso a subítems, luego este actúa como padre para aquellos. Puesto que montaremos todos los ítems en un array (no en una lista enlazada ni árbol), necesitamos dos cosas para esto, saber cuantos son y el índice del primero. Obiamente si un ítem no es padre de ninguno, este valor será 0.
Este valor también sirve para saber cuando no es aceptable que el usuario pulse 'down', pués se sabe cuantos ítems tiene el menú activo, por cuanto son exactamente el número de 'hijos'.
- PrimerHijo: índice dle primer hijo, cuando este ítem actúa como padre. Se entiende que los hijos de un ítem están contiguos en el array, el primero de ellos se localiza, pués en este índice.
- Indice: Técnicamente cada ítem no precisa saber que posición ocupa, es una propiedad reflexiva, a la que se le puede sacar sustancia para simplificar determinadas tareas congorme a un diseño más elaborado... lo pongo solo por que sea una pieza a estudiar cuando termines tu menú para ver como usarlo y sacar provecho, de momento, prácticamente se puede ignorar.
+ No está reflejado pero a veces, puede intenresar tener un IndicePadre, así cada ítem apunta expresamente al al menú padre en el que se aloja. estrictamente no es necesario, porque cierto datos son comunes a todo el menú...




Igual que hay una estructura para cada ítem del menú, procede que el propio menú tenga sus propiedades la más importante y clara es precisamente Items:

No es preciso montarlos en una estructura salvo que haya varios menús bailando en una aplicación y entonces sea preciso diferenciar uno de otro.

// Estructura Menu
   Array de ItemMenu Items(0 a ....los que se precisen) // en realidad es raro que sean más de unas pocas decenas.
   entero foco  // -1 cuando ningún menú se ha desplegado.
   entero IndexMenu
   array de bytes Pila(0 a ...3-5)
// fin estructura

Básicamente eso es lo principal del menú. Ahora toca explicar los campos.
Como ya he señalado , no es preciso ponerlos en una estructura si solo hay un único menú.
- Items: Este campo es lo que he tratado de explicar más arriba..
Un menú habitualmente tiene entre 2 y unas pocas decenas de ítems, por lo que un array suele ser más suficiente para reflejar su estructura, especialmente si el menú no es dinámico (se crean y eliminan ítems a necesidad). Si precisa reflejar de alguna manera los submenús. Mas arriba señalaba qe una pción es indicar el IndexPadre para cada ítem, pero hay otra forma más  simple y que es más breve, consiste en usar una pila de llamadas, tal y como es cualquier pila.
- Pila: Cuando se abre el menú, en la pila se añade el ítem 0, que es el menú padre de todo el menú, este es padre de todos los ítems primarios, supongamos que hay 4 ítems primarios, cuando el usuario pulse en uno de los que entre ellos sean también padre de otros, se abrirá su submenú añadiendo el índice de ese pulsado a la pila, y así sucisivamente, cuando se cierra un menú, se retrocede en la pila y ahí está el padre del menú que debe dibujarse, se ve así que esta sencilla pila que apenas tendrá media docena de elementos, satisface por igual el acceso a las diferentes ramas (al regreso), como si de una lista enlazada o un árbol se tratara, pero con la premisa de que es enormemente más simple y rápido de programar y entender... ese mismo 'padre' es el que tiene el foco cuando se despliegue ese menú.
- foco: Solo es para el menú activo, señala el indice relativo (como hijo). El relativo es para contar las veces que el usuario baja o sube en el menú activo, tambien para cuando se pulsa en un ítem del menú activo, sumado al 'IndexMenu', se sabe que índice absoluto en el array es el ítem pulsado.
- IndexMenu: Es el índice del primer hijo en el menú activo (ya se ha dicho que todos los hijos de un padre son correlativos en el array), pueden por tanto ser ubicado en cualquier parte de un array. Y que será el que se pase (sumaod al foco) a una función 'DibujarSubmenu(Index)', que tendrá por efecto, borrar el menú actual y dibujar los hijos de ese índice.




Lo siguiente es considerar los distintos tipos de menú... cada tipo lleva aparejado un comportamiento más o menos igeramente distinto, conviene listar y denotar ese comportamiento para cada uno.

(ahora mismo tengo que salir, continuó más tarde o mañana si no encuentro el tiempo suficiente hoy)

**Aincrad**

#6
En la consola basicamente solo Imprime datos en la pantalla.
mas que para interactuar con los datos como por ejemplo una trackbar, en tu caso. debes basicamente manejar todo el control y sus propiedades a codigo y mostrar en la pantalla los datos.

Cita de: NEBIRE en 14 Marzo 2020, 19:59 PM

Ahota toca reflejar los datos de un menú en una estructura que refleje todas esas propiedades lo más fiel y claro posible:

...

Igual que hay una estructura para cada ítem del menú, procede que el propio menú tenga sus propiedades la más importante y clara es precisamente Items:


yo hice basicamente lo mismo en mi Proyecto de "Hack Menu D3D para juegos" la api direcx nada mas me permitia dibujar resultados, basicamente es como la consola, solo me permitia imprimir datos.

Bueno la cosa es que tuve que manejar los controles y sus propiedades desde el codigo y las teclas.
basicamente lo mismo que tu . tenia que buscar una manera. entonces hice lo mismo que el compañero @NEBIRE te sugirió.

maneje los controles y sus propiedades como estructura. (Fue un dolor en el trasero) ...

Puedes Revisar mi codigo y guiarte , es lo mismo que necesitas hacer :

https://github.com/DestroyerDarkNess/DirectX-Menu-Game/blob/master/S4Lsalsoft/Form1.vb

Ej:

Código (vbnet) [Seleccionar]
Public Structure LabelTitle
       Shared Text As String = "VB.NET | Direcx Menu"
       Shared X As Integer = Menu_Gui.X + 5
       Shared Y As Integer = Menu_Gui.X + 3
       Shared FontColor As Color = Color.White
       Shared InfoNumber As Integer = 0
   End Structure

 Public Structure Label1
       Shared IsActive As Boolean = False
       Shared Text As String = "Hack 1"
       Shared X As Integer = Controls_Size.GUI.X - (Controls_Size.GUI.X - (CalculateSizePointX(Controls_Size.GUI.X) / 2)) 'Menu_Gui.X + 25
       Shared Y As Integer = 40 'Menu_Gui.Y + 45
       Shared FontColor As Color = OnSwishedColor
       Shared InfoNumber As Integer = 0
   End Structure


Si lo haces de esta manera, facilmente cuando tengas que hacer otro menu o agregarle mas controles, solo c&p y ya. no teandras que codearlo todo de new.








Meta

Muy buen trabajo, explicaciones y ayudas.

Los códigos prefiero guardarlos dentro de un método o funciones, que me sirve para futuros proyectos. Falta seleccionar la parte de Atrás y Menú. Si se selecciona Atrás, pulsas Enter, dice un mensaje, estás en opción Atrás, si estás en Menú, dice un mensaje que estás en la opción Menú.

Al menos mover el > en esas cuatro zonas, son solo cuatro zonas. Que se dice pronto.

Una cosa lo dejo clara. Ya no uso el Do While, para evitar condiciones como el ejemlo de arriba, pulsar el escape.

En este ejemplo del menú, solo se usa cinco teclas, felchas arriba, abajo, izquierda, derecha y Enter. En este caso uso While (true). Aunque suene bucle cerrado infinito, dentro de ella si cumple una condición, por ejemplo, señalar con el > la zona Menú y pulso Enter, se va a otro método o función, que en otra ventana o subopción y ya está. Es algo parecido a esto. Por eso es bueno hacer un esquema boceto de los menús y submenús, para tener una idea rápida de lo que se quiere hacer, aquí hablando de diseño, en este tema, hablando de funcionalidades, como controlar el > y una barra de estado.

Frente a tu código, solo falta lo indicado arriba. Lo modifiqué un pelín, solo la apariencia y nada más.
Código (csharp) [Seleccionar]
using System;

namespace Consola_menu_25_cs
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "Título de la ventana.";
            Console.BackgroundColor = ConsoleColor.Green;
            Console.ForegroundColor = ConsoleColor.Black;
            Console.SetWindowSize(21, 5);
            Console.CursorVisible = false;

            ConsoleKey key;
            int selPosY = 0;
            int volPosX = 9;
            int briPosX = 9;

            Console.Clear();
            Console.WriteLine("  Brillo:────────   ");
            Console.WriteLine("  Volum.:────────   ");
            Console.WriteLine("                    ");
            Console.WriteLine("  Atrás        Menú ");

            Console.SetCursorPosition(0, selPosY);
            Console.Write(">");
            Console.SetCursorPosition(briPosX, 0);
            Console.Write("█");
            Console.SetCursorPosition(volPosX, 1);
            Console.Write("█");
            Console.SetCursorPosition(18, 0);
            Console.Write(briPosX - 9);
            Console.SetCursorPosition(18, 1);
            Console.Write(volPosX - 9);

            do
            {
                key = Console.ReadKey(true).Key;
                if ((key == ConsoleKey.UpArrow) && ((selPosY - 1) >= 0))
                {

                    Console.SetCursorPosition(0, selPosY);
                    Console.Write(" ");

                    Console.CursorTop--;
                    selPosY = Console.CursorTop;
                    Console.SetCursorPosition(0, selPosY);
                    Console.Write(">");
                }

                if ((key == ConsoleKey.DownArrow) && ((selPosY + 1) <= 1))
                {

                    Console.SetCursorPosition(0, selPosY);
                    Console.Write(" ");

                    Console.CursorTop++;
                    selPosY = Console.CursorTop;
                    Console.SetCursorPosition(0, selPosY);
                    Console.Write(">");
                }

                if (key == ConsoleKey.LeftArrow)
                {

                    if ((selPosY == 0) && ((briPosX - 1) >= 9))
                    {
                        Console.SetCursorPosition(briPosX, 0);
                        Console.Write("─");

                        Console.CursorLeft -= 2;
                        briPosX = Console.CursorLeft;
                        Console.Write("█");

                        Console.SetCursorPosition(18, 0);
                        Console.Write(briPosX - 9);
                    }

                    if ((selPosY == 1) && ((volPosX - 1) >= 9))
                    {
                        Console.SetCursorPosition(volPosX, 1);
                        Console.Write("─");

                        Console.CursorLeft -= 2;
                        volPosX = Console.CursorLeft;
                        Console.Write("█");

                        Console.SetCursorPosition(18, 1);
                        Console.Write(volPosX - 9);
                    }

                }

                if (key == ConsoleKey.RightArrow)
                {

                    if ((selPosY == 0) && ((briPosX + 1) <= 16))
                    {
                        Console.SetCursorPosition(briPosX, 0);
                        Console.Write("─");

                        briPosX = Console.CursorLeft;
                        Console.Write("█");

                        Console.SetCursorPosition(18, 0);
                        Console.Write(briPosX - 9);
                    }

                    if ((selPosY == 1) && ((volPosX + 1) <= 16))
                    {
                        Console.SetCursorPosition(volPosX, 1);
                        Console.Write("─");

                        volPosX = Console.CursorLeft;
                        Console.Write("█");

                        Console.SetCursorPosition(18, 1);
                        Console.Write(volPosX - 9);
                    }
                }

            } while (key != ConsoleKey.Escape);
        }
    }
}


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

Meta

Mover el cursos con las flechas.

También pulsar Enter para ver las coordenadas.

Código (csharp) [Seleccionar]

using System;

namespace Menu_consola_26_cs
{
    class Program
    {
        static void Main(string[] args)
        {
            int xMax = 80;
            int yMax = 25;
            int x = 1;
            int y = 1;
            Console.Title = "Moviendo cursor con las flechas del teclado.";
            Console.SetWindowSize(xMax + 1, yMax + 2);
            DrawMarco(0, 0, xMax, yMax);
            Console.CursorVisible = false;
            do
            {
                gotoxy(x, y, "█"); // ♠
                ConsoleKey opc = Console.ReadKey(true).Key;
                if (opc == ConsoleKey.Enter)
                {
                    gotoxy(5, yMax, $" posicion del cursor es x:{x} y:{y} ══════");
                    continue;
                }
                gotoxy(x, y, " ");
                if (opc == ConsoleKey.DownArrow)
                {
                    if (y < yMax - 1)
                        y++;
                }
                if (opc == ConsoleKey.UpArrow)
                {
                    if (y > 1)
                        y--;
                }
                if (opc == ConsoleKey.RightArrow)
                {
                    if (x < xMax - 1)
                        x++;
                }
                if (opc == ConsoleKey.LeftArrow)
                {
                    if (x > 1)
                        x--;
                }
                if (opc == ConsoleKey.Escape)
                    break;
            } while (true);
            Console.CursorVisible = true;
        }

        // Generar marco.
        private static void DrawMarco(int v1, int v2, int v3, int v4)
        {
            gotoxy(v1, v2, "╔");
            gotoxy(v1, v4, "╚");
            gotoxy(v3, v2, "╗");
            gotoxy(v3, v4, "╝");

            for (int i = v1 + 1; i < v3; i++)
            {
                gotoxy(i, v2, "═");
                gotoxy(i, v4, "═");
            }
            for (int i = v2 + 1; i < v4; i++)
            {
                gotoxy(v1, i, "║");
                gotoxy(v3, i, "║");
            }
        }

        public static void gotoxy(int x, int y, string cad)
        {
            Console.SetCursorPosition(x, y);
            Console.WriteLine(cad);
        }
    }
}

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

Serapis

Sigo... más o menos desde donde lo dejé...

Cada ítem de un menú por su utilidad puede tener una funcionalidad más acentuada que otro, respecto de alguna característica, lo que viene a indicarnos que hay que considerar diferentes tipos de ítems de menú... Voy a considerar una lista que suele ser habitual, y en casos concretos puede haber otros o descartarse algunos de aquí... es normal. Mi texto debe servir de base a las necesidades de cada uno.., no hay porqué seguirlo a rajatabla.

Básicamente hay 2 categorías de tipos de menús: 'Título' y 'Acción', el primero es el 'conductor', sirve para mantener orden y conducir al usuario... la categoría 'acción' sirve para realizar la acción cuya funcionalidad es la que espera y se ofrece al usuario...
Todos los tipos los encajamos en uno u otra categoría:

* Titulo: Es un tipo que actúa de padre de otros, los ítems que tiene bajo sí son sus hijos. Hay 3 formas de este tipo de menús a la hora de implementarlos y según donde se precise usarlo.
---- Título: Describe sus ítems debajo, cuando se pulsa despliega u oculta los ítems bajo él. En consola es algo más complejo y laborioso de usar... Suele llevar algún indicador tipo flecha arriba/abajo a su derecha para indicar que que al pulsarse despliega/repliega su lista.
---- Submenú: Como el anterior, pero a la hora de desplegar, a sus hijos, los ubica en una nueva ventana. Tratándose de consola, implica dibujar esos ítems que pasan a ser el menú activo. Suele llevar algún indicador tipo flecha derecha, a su derecha para indicar que que al pulsarse despliega su lista. en este tipo de menú el repliege suele hacerse cuando la ventana pierde el foco, al tratarse de consola exige un ítem específico.
---- Delimitador: Es un tipo de ítem que delimita donde terminan los ítems de un tipo dado, por ejemplo, los hijos de un submenú o los ítems de un tipo específico. Como tal puede ser visible o invisible.
A veces es visible y simplemente contiene una línea que aclara al usuario el límite 'físico' de los ítems previso.
Este ítem si se acepta en un menú podría hacer innecesario el campo 'cantidad de hijos', se entendería que un menú empieza en el índice que señale el padre y acaba cuando encuentre un ítem de este tipo. No importa tnto si se usa este método o el otro, siempre que se sea consecuente y se elija uno y solo un método para el caso.
En el caso de consola este mismo puede ser el que una vez pulsado cierre el menú actual y retorno al previo, en este caso el texto debe ser claro 'Volver <' y en caso del menú raíz 'Salir <', nótese la flechita (el sufijo) a la derecha del texto.

* Acción: En los menús típo Pull-Down, un menú se cierra siempre que se pulse un ítem de 'accion', el código de inmediato deriva a la acción pulsada...
---- Botón: Hace lo que dice. Cuando se pulsa ejecuta una acción y ya. Ejemplo: 'Copiar' (copiaría el textou objeto seleccionado, al objeto al que da soporte.
---- Activación: (Check) Permite que se active o desactive con cada pulsación, alternando entre uno y otro estado. Habitualmente es de tipo buleano y por tanto mantiene solo dos estados. Ejemplo: 'Guardar opciones al salir'. Que viene a indicar que: cuando se cierre la aplicación se consulta el estado de este ítem, y si está activado, se guarde a fichero las modificaciones que se hubieren realizado en la configuración de un programa.
---- Opcion (option/radio button): Cuando se pulsa, se activa previamente desactiva el ítem del mismo grupo que estuviere activo. Requiere pués o bien tener  un indicador de cual estaba activo, o bien ahorrarse el indicador y recorrer sus 'hermanos' para determinar cual estaba activo. Un grupo para consdierarse como tal, debe estar contiguo, es decir ser del mismo tipo, si un menú es demasiado complejo, y hay listas seguidas de estos: podría interponerse algún separador (un menú titulo) o bien un campo grupo y que cada menú del mismo grupo tengan el mismo valor. Ejemplo: una lista de Lunes, Martes .. *Viernes ... donde solo uno a la vez está seleccionado.
---- Vínculo: Es básicamente un botón, pero tiene como sufijo '...' y viene a indicar que es complejo y que se abre una ventana donde hay más opciones. Como botón, su acción será pués abrir dicha ventana... En consola, este tipo no suele tener cabida, pués puede complicar demasiado el tratamiento gráfico...
---- Elección/Scroll: Es un tipo que es idéntico a Activación, excepto que admite múltiples estados, no solo 2. El valor elegido en cada ocasión se hará constar a la derecha. por ejemplo: "Dia de la Semana": 5 - Viernes'. Es fácil comprender que este modelo es un modelo resumido de 'opcion' cuando es posible (sus valores son contiguos y elegibles secuencialmente, y solo uno entre todos puede estar elegido a la vez).
---- Texto: es un tipo de menú que básicamente es un botón, su funcionalidad es la misma, tiene la particularidad extra de que el contenido textual del mismo puede cambiar (el texto, no lo que llamamos su valor) Ejemplo: cuando se abre un fichero, se guarda en una pequeña lista el nombre de los x últimos ficheros distintos abiertos, el contenuido textual así es el nombre dle fichero, durante diseño, puede pués no conocerse siguiqera el texto que tendrá.

Una vez decidido que tipos de menú son los que encajan en nuestro diseño y modificando los que fueren de un tipo pero que se haya elegido otros en su remplazao, hay que ser cogerentes con la decisión para hacer la funcionalidad correcta tanto en diseño como en ejecución.

Finalmente solo faltan las funciones que hacen el trabajo.
La más simple es borrar el menú previo, pero lo normal es que partamos de 0, así que la primera acción sería mostrar el menú base, como usamos un array, el índice 0 del array es la raíz. Luego si abirmos el menú completo, se invocará justo ese índice al empezar...


funcion Main
    CargarItems  // los datos del array se cargan aquí.

    DibujarSubmenu( 0)
fin funcion


Al reusar el menú en otras aplicaciones si somos coherentes con las decisiones tomadas y por tanto el diseño final, lo único que encesitaremos cambiar en futuros proyectos, será precisamente los datos de la función 'CargarItems'
Para no hablar en hueco, vamos a simular un menú ficticio, pero muy real, que no sea demasiado extendo, pero que tenga ejemplos de cada cosa... resulta al caso muy útil el menú de la calculadora: Pongo los ítems en árbol, primeramente solo el texto, para reunir todos y considerarlos en conjunto, cuantos son y cuales, etc... pero véase en cada paso como vamos completando los datos...
(pulsa en la interfaz en 'hex', para cambiar el menú que se muestra en view, pués hay menús ocultos que cambian según la opción elegida, no es necesario complicarlo con grados, radianes...)

----------------------------------
Edit
    Copy
    Paste
View
    Standard
    Scientific
    -
    Hex
    Decimal
    Octal
    Binary
    -
    Qword
    Dword
    Word
    Byte
    -
    Digit Grouping
Help
    Help topics
    -
    About calculator
----------------------------------

Lo primero que se ve es que no haun menú raíz... y faltan menús títulos, porque es algo intuitivo, así mismo, los separadores, no tienen aclaración se deja una vez más a la intuición del usuario entender cada grupo.
Podemos canonizarlos mejor así (señalo a la derecha los ítems añadidos o modificados notoriamente):

----------------------------------
Menu Raiz   <---------
    Edit
        Copy
        Paste
        Volver  <---------
    View
        Tipo     <---------
            Standard
            Scientific
            Volver  <---------
        Base Decimal     <---------
            Hex
            Decimal
            Octal
            Binary
            Volver  <---------
        Tipo de dato     <---------
            Qword
            Dword
            Word
            Byte
            Volver  <---------
        Extra     <---------
            Digit Grouping
            Volver  <---------
        Volver  <---------
    Help
        Help topics
        About     <---------
            About calculator
            Volver  <---------
        Volver  <---------
    Exit  <---------
----------------------------------
1 - Como se ve, de entrada hemos desplazado a la derecha todos los ítems, para añadir el menú raíz.
   El menú raíz, podría ser el índice -1, pero determinados lenguajes no permiten índices negativos, luego el índice 0, también es acorde y lo consideraremos así...
2 - Lo otro que vemos, es que donde habúa un separador lo hemos remplazado por un título, para recoger más debidamente los ítems que tiene debajo, después de todo en consola no es práctico mostrar más de una docena de ítems. De este modo los subítems de cada 'padre' se desplazan a su derecha, y así el menú más grande no tendrá más de 4 ó 5 ítems.
3 - Algo que también se es que al 'canonizar' así el menú, queda suficientemente claro qué ítems son hijos de cual y qué items son padres de otros. Entonces ahora vamos a ir señalando el tipo que es de cada menú, primero los ponemos en una enumeración, y luego indicaremos en la lista de qué tipo es cada uno.
4 - Otra notariedad es que se ha añadido los ítems 'volver'
5 - Finalmente se ve que he añadido un ítem más al menú raíz 'Exit'... que es en el mismo 'volver que en raíz, conviene que tenga ese texto diferente.


Enumeracion TiposDeMenu
    MENU_TIPO_BOTON = 0
    MENU_TIPO_ACTIVACION = 1
    MENU_TIPO_OPCION = 2
    MENU_TIPO_VINCULO = 3
   
    MENU_TIPO_TITULO = 8
    MENU_TIPO_LIMITE = 15
fin enumeracion

Como se ve, no he  hecho constar todos los que decíamos más arriba, cámbiese según uno elija.
Los menús de tipo acción están arriba y los menús de tipo organizador, debajo... estos actúan como separadores, si hubiera un menú más complejo que contuviera demasiados hijos y más de una lista de opciones podría verse útil añadir un MENU_TIPO_SEPARADOR = 9 (por ejemplo), también si uno elige remplazar la funcionalidad del campo 'Hijos', por la cuenta que haya entre 'primerhijo' y un menú tipo separador... (éste sería hijo también de aquel, pero no cuenta sería oculto, no se dibujaría, o bien se dibujaría como una línea '-----------------------'.
Para las explicaciones he elegido que la separación sea con menús tipo título.

Ahora pongamos a la izquierda el tipo de cada menú.
----------------------------------
08 Menu Raiz   
08    Edit
00        Copy
00        Paste
15        Volver 
08    View
08        Tipo     
02            Standard
02            Scientific
15            Volver 
08        Base Decimal     
02            Hex
02            Decimal
02            Octal
02            Binary
15            Volver 
08        Tipo de dato     
02            Qword
02            Dword
02            Word
02            Byte
15            Volver 
08        Extra     
01            Digit Grouping
15            Volver 
15        Volver 
08    Help
00        Help topics
08        About     
03            About calculator
15            Volver 
15         Volver
15    Exit 
----------------------------------

Hay al menos un ejemplo de cada tipo de menú en la enumeración...
Ahora que queda también claro quien es padre y quien hijo de qien, puede indicarse el índice de cada uno. También a la izquierda dle todo, de cada menú. Recordar que cada hijo va contiguo. Se puede, escribir directamente como arriba,  o se pueden ya desmontar en grupos, con su padre encima:
-------------------------------
00 08 Menu Raiz
01 08    Edit
02 08    View
03 08    Help
04 15    Exit
-------------------------------
01 08    Edit
05 00        Copy
06 00        Paste
07 15        Volver 
-------------------------------
02 08    View
08 08        Tipo 
09 08        Base Decimal   
10 08        Tipo de dato   
11 08        Extra
12 15        Volver   
-------------------------------
08 08        Tipo
13 02            Standard
14 02            Scientific
15 15            Volver
-------------------------------
09 08        Base Decimal
16 02            Hex
17 02            Decimal
18 02            Octal
19 02            Binary
20 15            Volver
-------------------------------
10 08        Tipo de dato
21 02            Qword
22 02            Dword
23 02            Word
24 02            Byte
25 15            Volver
-------------------------------
11 08        Extra 
26 01            Digit Grouping
27 15            Volver
-------------------------------
03 08    Help
28 00        Help topics
29 08        About     
30 15        Volver
-------------------------------
29 08        About
31 03            About calculator
32 15            Volver
-------------------------------

Es fácil ver si hay errores, comprobando algunas cosas:
Cada menú de tipo titulo '08', encabeza siempre un submenú, contiene ítems y debe acabar en un tipo limite '15'
Níngún índice puede estar repetido, es decir cada índice aloja solo a un ítem, no equivocarse.
No es imprescindible que vayan seguidos, puede haber índices sin usar entre un submenú y otro, especialmente si uno considera que a futuro pudiera añadir más ítems.
Falta indicar algunas cosas, para cada tipo 'titulo' por ser padre, cual es el índice de su primer hijo así como la cantidad de hijos que tiene. Pondré solo 2 submenús como ejemplo..., dichos valores, los pondremos a la derecha del tipo:, así de momento van:
item = indice tipo numhijos indexprimerhijo texto

-------------------------------
00 08 04 01 Menu Raiz
01 08 03 05    Edit
02 08 05 08    View
03 08 03 28    Help
04 15 00 00    Exit
-------------------------------

Como se ve si no tiene hijos, porque el menú no e spadre el valor será 0, y por lo mismo indexprimerhijo, no importa, pero se pone 0. pogo dos cifras, solo para mantener la verticalidad y con ello una alineación visual adecuada, en el código no precisaos 0 a la derecha.
Otro ejemplo (que a diferencia del previo que casi todos sun subitems son submenús), cuyos hijos no son submenús:

-------------------------------
10 08 05 21        Tipo de dato
21 02 00 00            Qword
22 02 00 00            Dword
23 02 00 00            Word
24 02 00 00            Byte
25 15 00 00            Volver
-------------------------------

Podemos ver la línea BNF de interés:
    item = indice tipo numhijos indexprimerhijo texto
Todavía nos falta fijar algunas cosas, como el estado inicial de algunos ítems si están seleccionados.
Es tan simple como reservar un espacio más a la derecha de esos valores e indicar con '*' el ítem que esté seleccionado y con '-' el ítem que no tenga su estado seleccionado (incluso aunque el tipo no admita un estado de selección)

-------------------------------
10 08 05 21 -        Tipo de dato
21 02 00 00 -            Qword
22 02 00 00 -            Dword
23 02 00 00 *            Word
24 02 00 00 -            Byte
25 15 00 00 -            Volver
-------------------------------
Como se ve, en este submenú, el ítem seleccionado cuan do se dibuje por vez primera el menú, será el 23 'Word'....
Actualizamos la línea que explica cada ítem:
    item = indice tipo numhijos indexprimerhijo seleccionado texto

Y con esto básicamente podemos definir en texto todoel menú. Ahora la función 'CargarItems', podrá entenderse fácilmente:
Devuelve ´la cantidad de ítems que tiene el menú.

entero = funcion CargarItems 
    entero numItems = 33

    Array de ItemMenu Items(0 a numItems -1)

    Items(0 ) = GetItem("0 8 4 1 -:Menu Raiz")
    Items(1 ) = GetItem("1 8 3 5 -:Edit")
    Items(2 ) = GetItem("2 8 5 8 -:View")
    Items(3 ) = GetItem("3 8 3 28 -:Help")
    Items(4 ) = GetItem("4 15 0 0 -:Exit")
        .
        .
        .
    Items(23) = GetItem("23 02 0 0 *:Word")
    Items(24) = GetItem("24 02 0 0 -:Byte")
    Items(25) = GetItem("25 15 0 0 -:Volver <")
    Items(26) = GetItem("26 01 0 0 *:Digit Grouping")
        .
        .
        .
    Items(32) = GetItem("32 15 0 0 -:Volver <")

    devolver numItems
fin funcion



La función 'getitem' toma un string en el formato 'item' y lo convierte en un elemento de la estructura ItemMenu:
item = indice tipo numhijos indexprimerhijo seleccionado texto


ItemMenu = funcion GetItem(string txtItem)
    ItemMenu im
    array partes(0 a 1) = split(txtItem, ":")  // es necesario porque el texto puede tener más de una palabra.
    array string datos()
   

    datos = Split(parte(0), " ")  // crea un array cortando el texto entrado en tantos elementos como espacios haya entre medias...
   
    im.Indice = datos (0).ParseInteger
    im.Tipo =  datos (1).ParseInteger
    si im.Tipo = MENU_TIPO_TITULO
         im.Hijos = datos (2).ParseInteger
         im.PrimerHijo = datos (3).ParseInteger
    sino
        im.Hijos = 0   
        im.PrimerHijo = 0
    fin si
    Si (datos (4) = "*") im.Seleccionado = TRUE
   
    si im.Tipo = MENU_TIPO_LIMITE
        im.Texto = partes(1)  + " < "
    Sino
        im.Texto = partes(1)       
    fin si

    devolver im
fin funcion


Yendo un poquito más lejos, la función 'CargarItems', podría ser más abstracta, y en vez de reterner ahí el texto del menú, leer un fichero que contiene el texto, se lle una línea de cada vez, esa línea es el índice para el array que se va incrementando, si la línea está vacía se omite y se lee la siguiente (por claridad sería adecuado dejar en un fichero líneas en blanco).
Así dicha función recibe como entrada la ruta del fichero a leer, y cada línea será el contenido de 1 ítem. con esto el códig de un menú prácticamente se puede reusar en otros proyectos sin más cambios que señalar la ruta del fichero que contiene el menú deseado que se debe cargar... Si no se quiere que el fichero esté suelto ante la posibilidad de que un usuario lo elimine, se adjunta como recurso al programa...

Como ya queda un mensaje largo, corto, y a ver si mañana saco otro tiempito para explicar por encima las funciones que manejan el menú.