How can I update the current line in a C# Windows Console App?
在C_中构建Windows控制台应用程序时,是否可以在不必扩展当前行或转到新行的情况下写入控制台?例如,如果我想显示一个百分比来表示一个进程接近完成的程度,我只想更新光标所在行上的值,而不必将每个百分比都放到新行上。
这可以用"标准"的C控制台应用程序完成吗?
如果只在控制台上打印
"
1 2 3 4 5 | for(int i = 0; i < 100; ++i) { Console.Write(" {0}% ", i); } |
注意数字后面的几个空格,以确保删除之前存在的内容。另外,请注意使用
您可以使用
下面是一个显示简单"微调器"的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); } } |
请注意,必须确保用新输出或空白覆盖任何现有输出。
更新:由于有人批评该示例只将光标向后移动一个字符,因此我将添加此内容以作澄清:使用
1 | Console.SetCursorPosition(0, Console.CursorTop); |
将光标设置在当前行的开始处(或者您可以直接使用
到目前为止,我们有三种可供选择的方法:
1 2 3 4 5 6 7 | Console.Write(" {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 } |
我一直使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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(" Counting: {0} ", i); } sw.Stop(); Console.WriteLine(" Time using \ : {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(" Time 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(" Time using \\b: {0}", sw.ElapsedMilliseconds); } |
在我的机器上,我得到以下结果:
- 退格:25.0秒
- 回车:28.7秒
- 设置光标位置:49.7秒
另外,
更新:在注释中,joel建议setCursorPosition相对于移动的距离是常量,而其他方法是线性的。进一步的测试证实了这一点,但是恒定的时间和缓慢的速度仍然很慢。在我的测试中,在大约60个字符之前,在控制台上写一长串backspace比setcursorposition快。所以退格键在替换短于60个字符的行部分时速度更快,而且不会闪烁,所以我将支持最初对 over
和
您可以使用(退格)转义序列备份当前行上的特定字符数。这只会移动当前位置,而不会删除字符。
例如:
1 2 3 4 5 6 7 8 9 | 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); } |
这里,line是要写入控制台的百分比行。技巧是为以前的输出生成正确的字符数。
与
方法相比,此方法的优势在于,即使百分比输出不在行的开头,它仍然有效。
这些场景使用
作为其新行标记的原因。
我只是和迪沃的
1 2 3 4 5 6 7 8 9 10 | static void Main(string[] args) { Console.Write("Working...."); ConsoleSpinner spin = new ConsoleSpinner(); spin.Start(); // Do some work... spin.Stop(); } |
我用下面的代码实现了它。由于我不希望我的
而且,我没有做过太多的多线程,所以有可能(甚至可能)我在其中留下了一个或三个小错误。但到目前为止,它似乎工作得很好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 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返回(
),而不是(隐式或显式)在行尾使用新行(),应该得到您想要的。例如:
1 2 3 4 5 6 7 8 | void demoPercentDone() { for(int i = 0; i < 100; i++) { System.Console.Write(" Processing {0}%...", i ); System.Threading.Thread.Sleep( 1000 ); } System.Console.WriteLine(); } |
1 2 3 4 5 6 7 | public void Update(string data) { Console.Write(string.Format(" {0}","".PadLeft(Console.CursorLeft, ' '))); Console.Write(string.Format(" {0}", data)); } |
从控制台在MSDN文档:
You can solve this problem by setting
the TextWriter.NewLine property of the
Out or Error property to another line
termination string. For example, the
C# statement, Console.Error.NewLine =
"";, sets the line termination
string for the standard error output
stream to two carriage return and line
feed sequences. Then you can
explicitly call the WriteLine method
of the error output stream object, as
in the C# statement,
Console.Error.WriteLine();
所以,我这样做:
1 | Console.Out.Newline = String.Empty; |
我在自己能够控制输出;
1 2 3 4 | Console.WriteLine("Starting item 1:"); Item1(); Console.WriteLine("OK. Starting Item2:"); |
另一个方式去那里。
我做一个搜索的解决方案,这看看我写的可能是除了飞车。我想要的是一个倒计时的计时器,不只是更新电流线。这是我来的。可能是一个有用的人
1 2 3 4 5 6 7 8 9 10 11 12 | 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($" Process will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)}"); Thread.Sleep(1000); } Console.WriteLine(""); |
我正在寻找相同的解决方案在VB.NET和我发现这一个和它是伟大的。
然而,作为一个更好的方式来"johnodom手柄内的空白空间,如果以前是一个装修一电流。
i make a函数思想在VB.NET和人可以得到帮助。
这里是我的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
我在这里是个soosh’s和0xa3的答案。它可以在控制台与用户消息的更新而更新的怪人和安切洛蒂在elapsed自然时间指示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public class ConsoleSpiner : IDisposable { private static readonly string INDICATOR ="/-\\|"; private static readonly string MASK =" {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(); } } |
使用的是这样的东西。类程序{
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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!!!!! Press any key to exit."); } |
如果你想更新一行,但太长的信息显示在一个线,它可能需要一些新的行。我遇到的这个问题,是一个与下面的方式解决这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | 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"); } } |
作品《
这里是另一个:D
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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]); } } } |