如何更新C#Windows控制台应用程序中的当前行?

在C#中构buildWindows控制台应用程序时,是否可以写入控制台而不必扩展当前行或转到新行? 例如,如果我想显示一个百分比表示一个进程完成的距离,我只想更新与游标在同一行上的值,而不必将每个百分比放在一个新行上。

这可以通过“标准”C#控制台应用程序来完成吗?

如果您仅将"\r"打印到控制台,则光标会返回到当前行的开头,然后您可以重写它。 这应该做的伎俩:

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

注意数字之后的几个空格,以确保之前的内容被删除。
另外请注意使用Write()而不是WriteLine()因为您不想在行尾添加“\ n”。

您可以使用Console.SetCursorPosition来设置光标的位置,然后写入当前位置。

下面是一个简单的“微调”的例子 :

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

请注意,您将必须确保用新的输出或空白覆盖任何现有的输出。

更新:因为有人批评这个例子只将光标移回一个字符,所以我将添加这个来澄清:使用SetCursorPosition你可以将光标设置到控制台窗口中的任何位置。

 Console.SetCursorPosition(0, Console.CursorTop); 

将光标设置到当前行的开头(或者可以直接使用Console.CursorLeft = 0 )。

到目前为止,我们有三个竞争的替代方法来做到这一点:

 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 } 

我一直使用Console.CursorLeft = 0 ,这是第三个选项的变体,所以我决定做一些testing。 这是我使用的代码:

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

在我的机器上,我得到以下结果:

  • 退格: 25.0秒
  • 运输返回: 28.7秒
  • SetCursorPosition: 49.7秒

此外, SetCursorPosition引起了明显的闪烁,我没有观察到任何一种select。 所以道德是尽可能使用退后或回车谢谢教我更快的方法来做到这一点。


更新 :在注释中,Joelbuild议SetCursorPosition在移动距离方面是恒定的,而其他方法是线性的。 进一步的testing证实了这种情况, 但是持续时间和速度仍然很慢。 在我的testing中,在控制台上写一长串退格比SetCursorPosition快,直到60个字符左右。 所以backspace的replace速度比60个字符(大约60个字符左右)要快, 而且不会闪烁,所以我会坚持\ b和\ SetCursorPosition \ b的最初认可。

您可以使用\ b (退格)转义序列来备份当前行上的特定数量的字符。 这只是移动当前的位置,它不会删除字符。

例如:

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

在这里, 是写入控制台的百分比行。 诀窍是为以前的输出生成正确数量的\ b字符。

这种方法的优点在于,即使您的百分比输出不在行首,也是如此。

\r用于这种情况。
\r 表示一个回车符,表示光标返回到行首。
这就是为什么窗口使用\n\r因为它是新的线标记。
\n将您移动到一条线上, \r将您返回到该行的开头。

我只是玩了divo的ConsoleSpinner类。 我远没有那么简洁,但是对于我来说,这个类的用户必须自己编写自己的while(true)循环。 我正在拍摄更像这样的经历:

 static void Main(string[] args) { Console.Write("Working...."); ConsoleSpinner spin = new ConsoleSpinner(); spin.Start(); // Do some work... spin.Stop(); } 

我用下面的代码意识到了这一点。 由于我不希望我的Start()方法被阻塞,所以我不希望用户不得不担心编写一段while(spinFlag)的循环,而且我希望允许多个spinners在同一时间产生一个单独的线程来处理旋转。 这意味着代码必须更复杂。

另外,我没有做太多的multithreading,所以有可能(甚至有可能)我在这里留下了一个微妙的bug。 但是目前看起来效果还不错:

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

在行的开头显式使用Carrage Return(\ r),而不是(隐式或显式)在最后使用New Line(\ n)应该得到你想要的。 例如:

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

从MSDN中的控制台文档:

您可以通过将Out或Error属性的TextWriter.NewLine属性设置为另一个行终止string来解决此问题。 例如,C#语句Console.Error.NewLine =“\ r \ n \ r \ n”;将标准错误输出stream的行终止string设置为两个回车和换行序列。 然后,您可以显式调用错误输出stream对象的WriteLine方法,如C#语句Console.Error.WriteLine();

所以 – 我做到了这一点:

 Console.Out.Newline = String.Empty; 

那我自己就可以控制输出了。

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

另一种方式到达那里。

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

这里是我拿起soosh和0xA3的答案。 它可以在更新微调器的同时用用户消息更新控制台,并且还有一个已用时间指示器。

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

用法是这样的。 课程{

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

SetCursorPosition方法适用于multithreading场景,其他两种方法不适用

如果你想更新一行,但信息太长,不能显示在一行上,可能需要一些新的行。 我遇到过这个问题,下面是解决这个问题的方法之一。

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

我在vb.net寻找相同的解决scheme,我发现这一个,这是伟大的。

然而@JohnOdom提出了一个更好的方法来处理空白空间,如果前一个大于当前空间

我在vb.net中创build一个函数,并认为有人可以得到帮助..

这里是我的代码:

 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 

这是另一个: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]); } } }