allocconsole - ¿Cómo puedo actualizar la línea actual en una aplicación de consola de Windows C#?
allocconsole c# (15)
Al crear 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? Por ejemplo, si quiero mostrar un porcentaje que represente qué tan cerca está de un proceso, me gustaría actualizar el valor en la misma línea que el cursor y no tener que poner cada porcentaje en una nueva línea.
¿Se puede hacer esto con una aplicación de consola C # "estándar"?
Aquí está mi opinión sobre las respuestas de s soosh y 0xA3. Puede actualizar la consola con mensajes de usuario mientras actualiza el control de giro y también tiene un indicador de tiempo transcurrido.
public class ConsoleSpiner : IDisposable
{
private static readonly string INDICATOR = "/-//|";
private static readonly string MASK = "/r{0} {1:c} {2}";
int counter;
Timer timer;
string message;
public ConsoleSpiner() {
counter = 0;
timer = new Timer(200);
timer.Elapsed += TimerTick;
}
public void Start() {
timer.Start();
}
public void Stop() {
timer.Stop();
counter = 0;
}
public string Message {
get { return message; }
set { message = value; }
}
private void TimerTick(object sender, ElapsedEventArgs e) {
Turn();
}
private void Turn() {
counter++;
var elapsed = TimeSpan.FromMilliseconds(counter * 200);
Console.Write(MASK, INDICATOR[counter % 4], elapsed, this.Message);
}
public void Dispose() {
Stop();
timer.Elapsed -= TimerTick;
this.timer.Dispose();
}
}
El uso es algo como esto. programa de clase {
static void Main(string[] args) {
using (var spinner = new ConsoleSpiner()) {
spinner.Start();
spinner.Message = "About to do some heavy staff :-)"
DoWork();
spinner.Message = "Now processing other staff".
OtherWork();
spinner.Stop();
}
Console.WriteLine("COMPLETED!!!!!/nPress any key to exit.");
}
Aquí hay otro: D
class Program
{
static void Main(string[] args)
{
Console.Write("Working... ");
int spinIndex = 0;
while (true)
{
// obfuscate FTW! Let''s hope overflow is disabled or testers are impatient
Console.Write("/b" + @"/-/|"[(spinIndex++) & 3]);
}
}
}
Desde los documentos de la consola en MSDN:
Puede resolver este problema configurando la propiedad TextWriter.NewLine de la propiedad Out o Error en otra cadena de terminación de línea. Por ejemplo, la declaración C #, Console.Error.NewLine = "/ r / n / r / n" ;, establece la cadena de terminación de línea para el flujo de salida de error estándar en dos secuencias de retorno de carro y avance de línea. Luego puede llamar explícitamente al método WriteLine del objeto de flujo de salida de error, como en la declaración C #, Console.Error.WriteLine ();
Entonces, hice esto:
Console.Out.Newline = String.Empty;
Entonces puedo controlar la salida yo mismo;
Console.WriteLine("Starting item 1:");
Item1();
Console.WriteLine("OK./nStarting Item2:");
Otra forma de llegar allí.
El método SetCursorPosition
funciona en un escenario de subprocesos múltiples, donde los otros dos métodos no lo hacen
Estaba buscando la misma solución en vb.net y encontré esta y es genial.
sin embargo, como @JohnOdom sugirió una mejor manera de manejar el espacio en blanco si el anterior es más grande que el actual ...
Hago una función en vb.net y pensé que alguien podría obtener ayuda ...
Aquí está mi código:
Private Sub sPrintStatus(strTextToPrint As String, Optional boolIsNewLine As Boolean = False)
REM intLastLength is declared as public variable on global scope like below
REM intLastLength As Integer
If boolIsNewLine = True Then
intLastLength = 0
End If
If intLastLength > strTextToPrint.Length Then
Console.Write(Convert.ToChar(13) & strTextToPrint.PadRight(strTextToPrint.Length + (intLastLength - strTextToPrint.Length), Convert.ToChar(" ")))
Else
Console.Write(Convert.ToChar(13) & strTextToPrint)
End If
intLastLength = strTextToPrint.Length
End Sub
Estaba haciendo una búsqueda de esto para ver si la solución que escribí podría optimizarse para la velocidad. Lo que quería era un temporizador de cuenta regresiva, no solo actualizar la línea actual. Esto es lo que se me ocurrió. Podría ser útil para alguien
int sleepTime = 5 * 60; // 5 minutes
for (int secondsRemaining = sleepTime; secondsRemaining > 0; secondsRemaining --)
{
double minutesPrecise = secondsRemaining / 60;
double minutesRounded = Math.Round(minutesPrecise, 0);
int seconds = Convert.ToInt32((minutesRounded * 60) - secondsRemaining);
Console.Write($"/rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
Thread.Sleep(1000);
}
Console.WriteLine("");
Hasta ahora tenemos tres alternativas en competencia para hacer esto:
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
}
Siempre he usado Console.CursorLeft = 0
, una variación de la tercera opción, así que decidí hacer algunas pruebas. Aquí está el código que utilicé:
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, obtengo los siguientes resultados:
- Backspaces: 25.0 segundos
- Retornos de carro: 28.7 segundos.
- SetCursorPosition: 49.7 segundos
Además, SetCursorPosition
causó un parpadeo notable que no observé con ninguna de las alternativas. Por lo tanto, la moraleja es usar backspaces o retornos de carro cuando sea posible , y gracias por enseñarme una manera más rápida de hacerlo, ¡SO!
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. Pruebas adicionales confirman que este es el caso, sin embargo , el tiempo constante y lento sigue siendo lento. En mis pruebas, escribir una larga cadena de espacios en la consola es más rápido que SetCursorPosition hasta alrededor de 60 caracteres. Por lo tanto, el retroceso es más rápido para reemplazar partes de la línea de menos de 60 caracteres (o así), y no parpadea, así que voy a respaldar mi aprobación inicial de / b over / r y SetCursorPosition
.
Puede usar Console.SetCursorPosition
para establecer la posición del cursor y luego escribir en la posición actual.
Aquí hay un example muestra un simple "spinner":
static void Main(string[] args)
{
var spin = new ConsoleSpinner();
Console.Write("Working....");
while (true)
{
spin.Turn();
}
}
public class ConsoleSpinner
{
int counter;
public void Turn()
{
counter++;
switch (counter % 4)
{
case 0: Console.Write("/"); counter = 0; break;
case 1: Console.Write("-"); break;
case 2: Console.Write("//"); break;
case 3: Console.Write("|"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
}
}
Tenga en cuenta que deberá asegurarse de sobrescribir cualquier salida existente con una nueva salida o espacios en blanco.
Actualización: Como se ha criticado que el ejemplo mueve el cursor solo hacia atrás en un carácter, SetCursorPosition
esto para aclarar: usando SetCursorPosition
puede colocar el cursor en cualquier posición en la ventana de la consola.
Console.SetCursorPosition(0, Console.CursorTop);
colocará el cursor al principio de la línea actual (o puede usar Console.CursorLeft = 0
directamente).
Puede usar la secuencia de escape / b (retroceso) para hacer una copia de seguridad de un número particular de caracteres en la línea actual. Esto solo mueve la ubicación actual, no elimina los caracteres.
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 la línea de porcentaje para escribir en la consola. El truco consiste en generar el número correcto de caracteres / b para la salida anterior.
La ventaja de esto sobre el enfoque es que funciona incluso si su porcentaje de salida no está al principio de la línea.
Si desea actualizar una línea, pero la información es demasiado larga para mostrarla, es posible que necesite algunas líneas nuevas. Me he encontrado con este problema, y a continuación es una forma de resolver esto.
public class DumpOutPutInforInSameLine
{
//content show in how many lines
int TotalLine = 0;
//start cursor line
int cursorTop = 0;
// use to set character number show in one line
int OneLineCharNum = 75;
public void DumpInformation(string content)
{
OutPutInSameLine(content);
SetBackSpace();
}
static void backspace(int n)
{
for (var i = 0; i < n; ++i)
Console.Write("/b /b");
}
public void SetBackSpace()
{
if (TotalLine == 0)
{
backspace(OneLineCharNum);
}
else
{
TotalLine--;
while (TotalLine >= 0)
{
backspace(OneLineCharNum);
TotalLine--;
if (TotalLine >= 0)
{
Console.SetCursorPosition(OneLineCharNum, cursorTop + TotalLine);
}
}
}
}
private void OutPutInSameLine(string content)
{
//Console.WriteLine(TotalNum);
cursorTop = Console.CursorTop;
TotalLine = content.Length / OneLineCharNum;
if (content.Length % OneLineCharNum > 0)
{
TotalLine++;
}
if (TotalLine == 0)
{
Console.Write("{0}", content);
return;
}
int i = 0;
while (i < TotalLine)
{
int cNum = i * OneLineCharNum;
if (i < TotalLine - 1)
{
Console.WriteLine("{0}", content.Substring(cNum, OneLineCharNum));
}
else
{
Console.Write("{0}", content.Substring(cNum, content.Length - cNum));
}
i++;
}
}
}
class Program
{
static void Main(string[] args)
{
DumpOutPutInforInSameLine outPutInSameLine = new DumpOutPutInforInSameLine();
outPutInSameLine.DumpInformation("");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
//need several lines
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbb");
}
}
Solo tuve que jugar con la clase ConsoleSpinner
de divo. El mío no es tan conciso, pero simplemente no me pareció bien que los usuarios de esa clase tengan que escribir su propio bucle while(true)
. Estoy disparando para una experiencia más como esta:
static void Main(string[] args)
{
Console.Write("Working....");
ConsoleSpinner spin = new ConsoleSpinner();
spin.Start();
// Do some work...
spin.Stop();
}
Y me di cuenta con el código de abajo. Ya que no quiero que mi método Start()
se bloquee, no quiero que el usuario tenga que preocuparse por escribir un bucle similar a while(spinFlag)
, y quiero permitir que haya múltiples giradores al mismo tiempo que tuve que Engendra un hilo separado para manejar el hilado. Y eso significa que el código tiene que ser mucho más complicado.
Además, no he hecho muchos subprocesos múltiples, así que es posible (incluso probable) que haya dejado un error sutil o tres 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();
}
}
Usar explícitamente un Retorno de Carrage (/ r) al principio de la línea en lugar de (implícita o explícitamente) usar una Nueva Línea (/ n) al final debería obtener lo que desea. Por ejemplo:
void demoPercentDone() {
for(int i = 0; i < 100; i++) {
System.Console.Write( "/rProcessing {0}%...", i );
System.Threading.Thread.Sleep( 1000 );
}
System.Console.WriteLine();
}
/r
se utiliza para estos escenarios.
/r
representa un retorno de carro, lo que significa que el cursor vuelve al inicio de la línea.
Es por eso que Windows usa /n/r
como su nuevo marcador de línea.
/n
te mueve por una línea y te devuelve al inicio de la línea.
Si imprime solo "/r"
en la consola, el cursor vuelve al principio de la línea actual y luego puede reescribirlo. Esto debería funcionar:
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 borre.
También note el uso de Write()
lugar de WriteLine()
ya que no desea agregar un "/ n" al final de la línea.
public void Update(string data)
{
Console.Write(string.Format("/r{0}", "".PadLeft(Console.CursorLeft, '' '')));
Console.Write(string.Format("/r{0}", data));
}