如何更新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]); } } }