Как обновить текущую строку в консольном приложении C# Windows?
при создании консольного приложения Windows В C# можно ли писать на консоль без необходимости расширять текущую строку или переходить к новой строке? Например, если я хочу показать процент, представляющий, насколько близок процесс к завершению, я просто хотел бы обновить значение в той же строке, что и курсор, и не нужно помещать каждый процент в новую строку.
можно ли это сделать с помощью "стандартного" консольного приложения C#?
14 ответов:
если вы печатаете только
"\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); } }обратите внимание, что вам нужно будет обязательно перезаписать все существующие выходные данные новыми выходными данными или пробелами.
Update: поскольку критиковалось, что пример перемещает курсор только на один символ, я добавлю это для уточнения: Using
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, вариация на третий вариант, поэтому я решил сделать некоторые тесты. Вот код, который я использовал: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); }на моей машине, я получаю следующие результаты:
- Backspaces: 25.0 секунд
- Возвраты Каретки: 28.7 секунд
- SetCursorPosition: 49.7 секунды
кроме того,
SetCursorPositionвызвало заметное мерцание, которое я не наблюдал ни с одной из альтернатив. Итак, мораль заключается в используйте обратные пространства или возврат каретки, когда это возможно и спасибо, что научил меня более быстрый способ сделать это, так!
обновление: в комментариях Джоэл предполагает, что SetCursorPosition является постоянным по отношению к перемещенному расстоянию, в то время как другие методы линейный. Дальнейшее тестирование подтверждает, что это так, постоянное время и медленно все еще медленно. В моих тестах запись длинной строки обратных пространств на консоль выполняется быстрее, чем SetCursorPosition, пока где-то около 60 символов. Таким образом, backspace быстрее для замены частей строки короче 60 символов (или около того), и он не мерцает, поэтому я буду стоять на моем первоначальном одобрении \b over \r и
SetCursorPosition.
можно использовать \b (backspace) escape-последовательность для резервного копирования определенного количества символов в текущей строке. Это просто перемещает текущее местоположение, оно не удаляет символы.
например:
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используется для этого сценария.\rобозначает возврат каретки, который означает, что курсор возвращается в начало строки.
Вот почему windows использует\n\rкак новый маркер линии.\nперемещает вас вниз по линии, и\rвозвращает вас к началу строки.
Я просто должен был играть с диво-х
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)-как петля, и я хочу, чтобы разрешить несколько блесен в то же время я должен был создайте отдельную нить для обработки спиннинга. А это значит, что код должен быть намного сложнее.кроме того, я не сделал так много многопоточности, поэтому возможно (вероятно, даже), что я оставил там тонкую ошибку или три. Но, кажется, работает довольно хорошо до сих пор:
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) в начале строки, а не (неявно или явно) с использованием новой строки (\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:
Вы можете решить эту проблему путем установки Автор текста.Свойство новой строки Или недвижимости ошибка в другой строке строка завершения. Например, С заявлением о#, консоли.Ошибка.новая строка= "\r\n\r\n";, задает окончание строки строка для вывода стандартной ошибки поток на два возврата каретки и перевода строки последовательности подачи. Тогда вы можете явный вызов метода WriteLine объекта потока вывода ошибок, как В С# , Приставка.Ошибка.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)); }
вот мой взгляд на "СООШ" Ы и ответы по 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."); }
Если вы хотите обновить одну строку, но информация слишком длинна, чтобы показать на одной строке, может потребоваться несколько новых строк. Я столкнулся с этой проблемой, и ниже приведен один из способов ее решения.
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 и я нашел это, и это здорово.
однако, как @JohnOdom предложил лучший способ обработки пробелов пространства, если предыдущий больше, чем текущий..
Я делаю функцию в vb.net и подумал, что кому-то можно помочь ..
вот мой код:
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]); } } }
Comments