440 votos

¿Cómo puedo actualizar la línea actual en un C # para Windows App consola?

Cuando se construye una aplicación de consola de Windows en C #, es posible escribir en la consola sin tener que extender una línea actual o ir a una nueva línea? Por ejemplo, si quiero mostrar un porcentaje representa la cercanía de un proceso es hasta el final, sólo me gustaría actualizar el valor en la misma línea que el cursor, y no tener que poner a cada porcentaje en una nueva línea.

Se puede hacer esto con una aplicación de consola de C # "estándar"?

680voto

shoosh Puntos 34322

Si imprime sólo "\r" a la consola el cursor se remonta al comienzo de la línea actual y luego se puede volver a escribir. Esto debería hacer el truco:

 for(int i = 0; i < 100; ++i)
{
    Console.Write("\r{0}%   ", i);
}
 

Observe los pocos espacios después del número para asegurarse de que todo lo que había antes se borra.
Observe también el uso de Write() en lugar de WriteLine() ya que no desea agregar un "\ n" al final de la línea.

226voto

0xA3 Puntos 73439

Usted puede utilizar Console.SetCursorPosition para fijar la posición del cursor y, a continuación, escriba en la posición actual.

Aquí está un ejemplo que muestra un sencillo "ruleta":

static void Main(string[] args)
{
    var spin = new ConsoleSpinner();
    Console.Write("Working....");
    while (true) 
    {
        spin.Turn();
    }
}

public class ConsoleSpinner
{
    int counter;
    public ConsoleSpinner()
    {
        counter = 0;
    }
    public void Turn()
    {
        counter++;        
        switch (counter % 4)
        {
            case 0: Console.Write("/"); break;
            case 1: Console.Write("-"); break;
            case 2: Console.Write("\\"); break;
            case 3: Console.Write("|"); break;
        }
        Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
    }
}

Tenga en cuenta que usted tendrá que asegúrate de sobrescribir cualquier salida existente con la nueva salida o espacios en blanco.

Actualización: Como se ha criticado que el ejemplo mueve el cursor de nuevo sólo por un carácter, voy a añadir esta aclaración: el Uso de SetCursorPosition puede colocar el cursor en cualquier posición en la ventana de la consola.

Console.SetCursorPosition(0, Console.CursorTop);

le coloque el cursor al principio de la línea actual (o puede utilizar Console.CursorLeft = 0 directamente).

70voto

Kevin Puntos 3692

Hasta ahora tenemos tres alternativas que compiten para saber cómo hacerlo:

Console.Write("\r{0}   ", value); //option 1: carriage return
Console.Write("\b\b\b\b\b{0}", value); //option 2: backspace
{                                           //option 3 in two parts:
  Console.SetCursorPosition(0, Console.CursorTop); //move cursor
  Console.Write(value);                            //rewrite
}

Yo siempre he usado Console.CursorLeft = 0, una variación en la tercera opción, así que me decidí a hacer algunas pruebas. Aquí está el código que he usado:

public static void CursorTest()
{
  int testsize = 1000000;
  Console.WriteLine("Testing cursor position");
  Stopwatch sw = new Stopwatch();
  sw.Start();
  for (int i = 0; i < testsize; i++)
  {
    Console.Write("\rCounting: {0}     ", i);
  }
  sw.Stop();
  Console.WriteLine("\nTime using \\r: {0}", sw.ElapsedMilliseconds);
  sw.Reset();
  sw.Start();
  int top = Console.CursorTop;
  for (int i = 0; i < testsize; i++)
  {
    Console.SetCursorPosition(0, top);        
    Console.Write("Counting: {0}     ", i);
  }
  sw.Stop();
  Console.WriteLine("\nTime using CursorLeft: {0}", sw.ElapsedMilliseconds);
  sw.Reset();
  sw.Start();
  Console.Write("Counting:          ");
  for (int i = 0; i < testsize; i++)
  {        
    Console.Write("\b\b\b\b\b\b\b\b{0,8}", i);
  }
  sw.Stop();
  Console.WriteLine("\nTime using \\b: {0}", sw.ElapsedMilliseconds);
}

En mi máquina, me obtienen los siguientes resultados:

  • Backspaces: 25.0 segundos
  • Retornos de carro: 28.7 segundos
  • SetCursorPosition: 49.7 segundos

Además, SetCursorPosition causado notable parpadeo que no me observar con cualquiera de las alternativas. Así, la moral es para uso backspaces o retornos de carro cuando sea posible, y gracias por enseñarme una manera más rápida de hacer esto, ASÍ que!


Actualización: En los comentarios, Joel sugiere que SetCursorPosition es constante con respecto a la distancia recorrida, mientras que los otros métodos son lineales. Más pruebas confirman que este es el caso, sin embargo la constante de tiempo lenta y es todavía lenta. En mis pruebas, la escritura de una larga cadena de backspaces a la consola es más rápido que SetCursorPosition hasta alrededor de 60 caracteres. Así retroceso es más rápido para reemplazar partes de la línea más corta de 60 caracteres (o menos), y no parpadeo, así que voy a estar de pie por mi aprobación inicial de \b \r y SetCursorPosition.

24voto

Sean Puntos 22088

Usted puede utilizar el \b (retroceso) de la secuencia de escape de copia de seguridad de un determinado número de caracteres en la línea actual. Este apenas se mueve de la posición actual, no quita los personajes.

Por ejemplo:

string line="";

for(int i=0; i<100; i++)
{
    string backup=new string('\b',line.Length);
    Console.Write(backup);
    line=string.Format("{0}%",i);
    Console.Write(line);
}

Aquí, la línea es el porcentaje de la línea a escribir en la consola. El truco está para generar el número correcto de \b los personajes de la anterior salida.

La ventaja de este sobre la \r enfoque es que si funciona incluso si el porcentaje de salida no está al principio de la línea.

12voto

Joel Coehoorn Puntos 190579

Yo sólo tenía que jugar con el divo del ConsoleSpinner de la clase. La mina está en ninguna parte cerca tan conciso, pero simplemente no se siente bien conmigo que los usuarios de que la clase tiene que escribir sus propios while(true) de bucle. Estoy de disparo para una experiencia más parecida a esto:

static void Main(string[] args)
{
    Console.Write("Working....");
    ConsoleSpinner spin = new ConsoleSpinner();
    spin.Start();

    // Do some work...

    spin.Stop(); 
}

Y me di cuenta de que con el código de abajo. Ya no quiero que mis Start() método para bloquear, no quiero que el usuario tenga que preocuparse de escribir un while(spinFlag) -como bucle, y quiero permitir múltiples hilanderos, al mismo tiempo, tenía que generar un subproceso independiente para controlar el giro. Y eso significa que el código ha de ser mucho más complicado.

Además, no he hecho mucho multi-threading, así que es posible (probable) que me ha dejado un error sutil o tres de allí. Pero parece funcionar bastante bien hasta ahora:

public class ConsoleSpinner : IDisposable
{       
    public ConsoleSpinner()
    {
        CursorLeft = Console.CursorLeft;
        CursorTop = Console.CursorTop;  
    }

    public ConsoleSpinner(bool start)
        : this()
    {
        if (start) Start();
    }

    public void Start()
    {
        // prevent two conflicting Start() calls ot the same instance
        lock (instanceLocker) 
        {
            if (!running )
            {
                running = true;
                turner = new Thread(Turn);
                turner.Start();
            }
        }
    }

    public void StartHere()
    {
        SetPosition();
        Start();
    }

    public void Stop()
    {
        lock (instanceLocker)
        {
            if (!running) return;

            running = false;
            if (! turner.Join(250))
                turner.Abort();
        }
    }

    public void SetPosition()
    {
        SetPosition(Console.CursorLeft, Console.CursorTop);
    }

    public void SetPosition(int left, int top)
    {
        bool wasRunning;
        //prevent other start/stops during move
        lock (instanceLocker)
        {
            wasRunning = running;
            Stop();

            CursorLeft = left;
            CursorTop = top;

            if (wasRunning) Start();
        } 
    }

    public bool IsSpinning { get { return running;} }

    /* ---  PRIVATE --- */

    private int counter=-1;
    private Thread turner; 
    private bool running = false;
    private int rate = 100;
    private int CursorLeft;
    private int CursorTop;
    private Object instanceLocker = new Object();
    private static Object console = new Object();

    private void Turn()
    {
        while (running)
        {
            counter++;

            // prevent two instances from overlapping cursor position updates
            // weird things can still happen if the main ui thread moves the cursor during an update and context switch
            lock (console)
            {                  
                int OldLeft = Console.CursorLeft;
                int OldTop = Console.CursorTop;
                Console.SetCursorPosition(CursorLeft, CursorTop);

                switch (counter)
                {
                    case 0: Console.Write("/"); break;
                    case 1: Console.Write("-"); break;
                    case 2: Console.Write("\\"); break;
                    case 3: Console.Write("|"); counter = -1; break;
                }
                Console.SetCursorPosition(OldLeft, OldTop);
            }

            Thread.Sleep(rate);
        }
        lock (console)
        {   // clean up
            int OldLeft = Console.CursorLeft;
            int OldTop = Console.CursorTop;
            Console.SetCursorPosition(CursorLeft, CursorTop);
            Console.Write(' ');
            Console.SetCursorPosition(OldLeft, OldTop);
        }
    }

    public void Dispose()
    {
        Stop();
    }
}

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X