2009-05-20 11 views
387

Quando si crea un'app Console di Windows in C#, è possibile scrivere sulla console senza dover estendere una linea corrente o passare a una nuova riga? Ad esempio, se voglio mostrare una percentuale che rappresenta quanto è vicino al completamento di un processo, vorrei semplicemente aggiornare il valore sulla stessa riga del cursore e non dover inserire ogni percentuale su una nuova riga.Come posso aggiornare la linea corrente in un'app Console Windows C#?

Questo può essere fatto con una app per console C# "standard"?

+0

Se siete VERAMENTE interessati a interessanti interfacce a linea di comando, dovreste dare un'occhiata a curses/ncurses. –

risposta

597

Se si stampa solo "\r" alla console il cursore risale all'inizio della riga corrente e poi si può riscrivere. Questo dovrebbe fare il trucco:

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

Notare i pochi spazi dopo il numero per assicurarsi che qualsiasi cosa fosse prima venga cancellata.
Si noti anche l'uso di Write() invece di WriteLine() dal momento che non si vuole aggiungere un "\ n" alla fine della linea.

+5

per (int i = 0; i <= 100; ++ i) andrà al 100% –

+9

Come gestisci quando la scrittura precedente era più lunga della nuova scrittura? C'è un modo per ottenere la larghezza della console e riempire la linea con spazi, forse? –

+5

@druciferre In cima alla mia testa posso pensare a due risposte per la tua domanda. Entrambi prevedono il salvataggio dell'output corrente come prima stringa e il riempimento con una quantità predefinita di caratteri come questo: Console.Write ("\ r {0}", strOutput.PadRight (nPaddingCount, '')); "NPaddingCount" può essere un numero impostato dall'utente o è possibile tenere traccia dell'output precedente e impostare nPaddingCount come differenza di lunghezza tra l'output precedente e corrente più la lunghezza di output corrente. Se nPaddingCount è negativo, non sarà necessario utilizzare PadRight a meno che non si esegua abs (prev.len - curr.len). –

205

È possibile utilizzare Console.SetCursorPosition per impostare la posizione del cursore e poi scrivere nella posizione corrente.

Ecco un example che mostra un semplice "filatrice":

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); 
    } 
} 

Nota che si dovrà fare in modo di sovrascrivere qualsiasi uscita esistente con nuova uscita o spazi vuoti.

Aggiornamento: Poiché è stato criticato che l'esempio sposta il cursore indietro di un carattere, lo aggiungo per chiarimenti: utilizzando SetCursorPosition è possibile impostare il cursore su qualsiasi posizione nella finestra della console.

Console.SetCursorPosition(0, Console.CursorTop); 

imposterà il cursore all'inizio della riga corrente (oppure si può usare direttamente Console.CursorLeft = 0).

+7

Il problema potrebbe essere risolto usando \ r, ma l'utilizzo di 'SetCursorPosition' (o' CursorLeft') consente una maggiore flessibilità, ad es. non scrivere all'inizio della riga, spostandosi verso l'alto nella finestra, ecc. quindi è un approccio più generale che può essere usato per es.mostra barre di avanzamento personalizzate o grafica ASCII. –

+0

eccessivamente complicato, sì. Maggiore flessibilità, sì. Rispondi alla domanda, non esattamente. Grande risorsa però per come dici tu, barra di avanzamento personalizzata, ecc ... – Nate

+11

+1 per essere prolissi e andare oltre il call of duty. Buone cose, grazie. – Copas

15

\r è usato per questo scenario.
\rrappresenta un ritorno che significa che il cursore ritorna all'inizio della linea.
Ecco perché Windows utilizza \n\r come se fosse un nuovo indicatore di riga.
\n voi azione corale una linea, e \r si torna alla partenza della linea.

+2

Mi sono sempre chiesto perché il \ n \ r. Grazie per queste informazioni –

+5

Tranne che in realtà \ r \ n. –

4

L'uso esplicito di un Carrage Return (\ r) all'inizio della riga anziché (implicitamente o esplicitamente) utilizzando una New Line (\ n) alla fine dovrebbe ottenere ciò che si desidera. Per esempio:

void demoPercentDone() { 
    for(int i = 0; i < 100; i++) { 
     System.Console.Write("\rProcessing {0}%...", i); 
     System.Threading.Thread.Sleep(1000); 
    } 
    System.Console.WriteLine();  
} 
+0

-1, Domanda richiede C#, lo riscrivo in C# e lo cambi in F # – Malfist

+0

Sembra un conflitto di modifica piuttosto che lui che riporta il tuo C# a F #. Il suo cambiamento fu un minuto dopo il tuo e si concentrò sullo sprint. – Andy

+0

ah, rimosso il downvote quindi. – Malfist

24

È possibile utilizzare il (backspace) sequenza \ b fuga verso il backup di un determinato numero di caratteri sulla riga corrente. Questo sposta solo la posizione corrente, non rimuove i personaggi.

Ad esempio:

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); 
} 

Qui, linea è la linea percentuale di scrivere alla console.Il trucco consiste nel generare il numero corretto di \ b caratteri per l'output precedente.

Il vantaggio di questo rispetto all'approccio \ r è che se funziona anche se l'output percentuale non è all'inizio della riga.

+1

+1, questo risulta essere il metodo più veloce presentato (vedere il mio commento di prova di seguito) – Kevin

63

Finora abbiamo tre alternative in competizione per come fare questo:

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 
} 

ho sempre usato Console.CursorLeft = 0, una variazione sul terza opzione, così ho deciso di fare alcuni test. Ecco il codice che ho usato:

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); 
} 

Sulla mia macchina, ottengo i seguenti risultati:

  • Ritorna indietro: 25,0 secondi
  • ritorni a capo: 28,7 secondi
  • setCursorPosition: 49,7 secondi

Inoltre, SetCursorPosition ha causato uno sfarfallio notevole che non ho osservato con nessuna delle due alternative. Quindi, la morale è di utilizzare i backspaces o i ritorni a capo quando possibile e grazie per avermi insegnato un modo più veloce per farlo, COSÌ!


Aggiornamento: Nei commenti, Joel suggerisce che setCursorPosition è costante rispetto alla distanza spostato mentre gli altri metodi sono lineari. Ulteriori test confermano che questo è il caso, tuttavia il tempo costante e lento è ancora lento. Nei miei test, scrivere una lunga stringa di backspaces sulla console è più veloce di SetCursorPosition fino a circa 60 caratteri. Quindi backspace è più veloce per la sostituzione di porzioni della linea più brevi di 60 caratteri (o così), e non sfarfallio, quindi vado a sopportare la mia approvazione iniziale di \ b su \ r e SetCursorPosition.

+0

L'efficienza dell'operazione in questione non dovrebbe davvero avere importanza. Dovrebbe accadere tutto troppo velocemente perché l'utente se ne accorga. La microptimizzazione non necessaria è cattiva. – Malfist

+0

@Malfist: a seconda della lunghezza del loop, l'utente può o non può notare. Come ho aggiunto nella modifica sopra (prima di vedere il tuo commento), SetCursorPosition ha introdotto lo sfarfallio e richiede quasi il doppio delle altre opzioni. – Kevin

+1

Sono d'accordo che si tratta di un micro-ottimizzazione (eseguirlo un milione di volte e impiegare 50 secondi è ancora un tempo molto limitato), +1 per i risultati, e potrebbe essere sicuramente molto utile sapere. – Andy

11

Ho dovuto solo giocare con la classe ConsoleSpinner del divo. Il mio non è per niente conciso, ma semplicemente non mi è piaciuto che gli utenti di quella classe debbano scrivere il loro ciclo while(true). Sto girando per un'esperienza più simile a questa:

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

    // Do some work... 

    spin.Stop(); 
} 

E l'ho realizzato con il codice qui sotto. Dal momento che non voglio che il mio metodo Start() blocchi, non voglio che l'utente debba preoccuparsi di scrivere un ciclo while(spinFlag) -like e voglio consentire a più filatori allo stesso tempo di generare un thread separato per gestire la rotazione. E questo significa che il codice deve essere molto più complicato.

Inoltre, non ho fatto molto multi-threading, quindi è possibile (probabilmente anche) che abbia lasciato un piccolo bug o tre in là.Ma sembra funzionare abbastanza bene finora:

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(); 
    } 
} 
+0

Bella modifica, anche se il codice di esempio non è mio. È preso dal blog di Brad Abrams (vedi il link nella mia risposta). Penso che sia stato appena scritto come un semplice esempio che dimostra SetCursorPosition. A proposito, sono decisamente sorpreso (in modo positivo) sulla discussione iniziata su quello che pensavo fosse solo un semplice esempio. Ecco perché amo questo sito :-) –

2

Dalla documentazione Console in MSDN:

È possibile risolvere questo problema impostando la proprietà TextWriter.NewLine del Out o proprietà Error a un'altra stringa di terminazione . Ad esempio, l'istruzione # C, Console.Error.NewLine = "\ r \ n \ r \ n" ;, imposta la terminazione spezzata per il ritorno standard error flusso a due trasporto e la linea sequenze mangimi. Quindi è possibile chiamare in modo esplicito il metodo WriteLine dell'oggetto flusso di output di errore, come nell'istruzione C#, Console.Error.WriteLine();

Così - ho fatto questo:

Console.Out.Newline = String.Empty; 

Allora io sono in grado di controllare l'output me stesso;

Console.WriteLine("Starting item 1:"); 
    Item1(); 
Console.WriteLine("OK.\nStarting Item2:"); 

Un altro modo per arrivarci.

0

Se si desidera aggiornare una riga, ma le informazioni sono troppo lunghe per essere visualizzate su una riga, potrebbero essere necessarie alcune nuove linee. Ho riscontrato questo problema, e qui di seguito è un modo per risolvere questo problema.

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"); 

    } 
} 
2
public void Update(string data) 
    { 
     Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' '))); 
     Console.Write(string.Format("\r{0}", data)); 
    } 
0

Ecco il mio prendere su risposte s di soosh e 0xA3 di. Può aggiornare la console con i messaggi dell'utente mentre aggiorna lo spinner e ha anche un indicatore del tempo trascorso.

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(); 
    } 
} 

l'utilizzo è qualcosa del genere. class Programma {

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."); 

    } 
-1

Ecco un altro: 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]); 
     } 
    } 
} 
-1

Il metodo SetCursorPosition lavora nello scenario multi-threading, dove gli altri due metodi non

0

che cercavo stessa soluzione in vb.net e ho trovato questo ed è fantastico.

tuttavia come @JohnOdom suggerito un modo migliore per gestire lo spazio vuoto se quella precedente è più grande di quella attuale ..

faccio una funzione in vb.net e pensato che qualcuno potrebbe ottenere aiutato ..

ecco il mio codice:

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 
+0

Qui puoi usare la funzione VB di una variabile statica locale: 'Statica intLastLength As Integer'. –

Problemi correlati