Как обновить текущую строку в приложении консоли Windows С#?
При создании приложения Windows Console на С# можно ли писать на консоль без необходимости продления текущей строки или перехода к новой строке? Например, если я хочу показать процент, показывающий, насколько завершен процесс, я бы просто хотел обновить значение в той же строке, что и курсор, и не должен накладывать каждый процент на новую строку.
Можно ли это сделать с помощью стандартного приложения консоли С#?
Ответы
Ответ 1
Если вы печатаете только "\r"
на консоли, курсор возвращается к началу текущей строки, а затем вы можете переписать его. Это должно сделать трюк:
for(int i = 0; i < 100; ++i)
{
Console.Write("\r{0}% ", i);
}
Обратите внимание на несколько пробелов после номера, чтобы убедиться, что все, что было до этого, будет стерто.
Также обратите внимание на использование Write()
вместо WriteLine()
, так как вы не хотите добавлять "\n" в конце строки.
Ответ 2
Вы можете использовать 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
).
Ответ 3
Пока у нас есть три конкурирующих альтернативы, как это сделать:
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);
}
На моей машине я получаю следующие результаты:
- Backspace: 25,0 секунд
- Возврат каретки: 28,7 секунд
- SetCursorPosition: 49,7 секунд
Кроме того, SetCursorPosition
вызвал заметное мерцание, которое я не наблюдал ни с одной из альтернатив. Итак, мораль заключается в том, чтобы использовать возвраты или возврат каретки, когда это возможно, и спасибо, что научили меня более быстрому способу сделать это, ТАК!
Обновление: в комментариях Джоэл предлагает, чтобы SetCursorPosition было постоянным относительно расстояния, в то время как другие методы являются линейными. Дальнейшее тестирование подтверждает, что это так, однако постоянное время и медленный процесс все еще медленный. В моих тестах запись длинной строки возврата на консоль выполнялась быстрее, чем SetCursorPosition, до примерно 60 символов. Так что backspace быстрее для замены частей строки короче 60 символов (или около того), и он не мерцает, поэтому я буду SetCursorPosition
моего первоначального одобрения \b over\r и SetCursorPosition
.
Ответ 4
Вы можете использовать escape-последовательность \b (backspace) для резервного копирования определенного количества символов в текущей строке. Это просто перемещает текущее местоположение, оно не удаляет символы.
Например:
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 заключается в том, что если работает, даже если ваш процентный вывод не находится в начале строки.
Ответ 5
\r
используется для этих сценариев.
\r
представляет возврат каретки, что означает, что курсор возвращается к началу строки.
Вот почему Windows использует \n\r
качестве маркера новой строки.
\n
перемещает вас вниз по строке, а \r
возвращает вас в начало строки.
Ответ 6
Мне просто пришлось играть с классом 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)
, и я хочу разрешить несколько проигрывателей в то же время, отдельный поток для обработки спиннинга. И это означает, что код должен быть намного сложнее.
Кроме того, я не делал много многопоточности, поэтому (возможно, даже), что я оставил тонкую ошибку или три там. Но, похоже, он работает очень хорошо:
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();
}
}
Ответ 7
Явно использую Return (Carr 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();
}
Ответ 8
public void Update(string data)
{
Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
Console.Write(string.Format("\r{0}", data));
}
Ответ 9
Из документов консоли в MSDN:
Вы можете решить эту проблему, установив свойство TextWriter.NewLine Out или Error в другую строку строка завершения. Например, С#, Console.Error.NewLine = "\ r\n\r\n"; устанавливает прекращение строки строка для стандартного вывода ошибки поток на два возврата каретки и линию последовательности подачи. Тогда ты можешь явно вызвать метод WriteLine объекта потока выходных данных ошибки, так как в заявлении С# Console.Error.WriteLine();
Итак - я сделал это:
Console.Out.Newline = String.Empty;
Затем я могу сам управлять выходом;
Console.WriteLine("Starting item 1:");
Item1();
Console.WriteLine("OK.\nStarting Item2:");
Другой способ добраться туда.
Ответ 10
Если вы хотите обновить одну строку, но слишком длинная информация для отображения в одной строке, ей могут потребоваться некоторые новые строки. Я столкнулся с этой проблемой, и ниже это один из способов решить эту проблему.
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");
}
}
Ответ 11
Вот мои ответы на sosh и 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.");
}
Ответ 12
Я искал такое же решение в 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
Ответ 13
Я искал это, чтобы понять, может ли написанное мной решение оптимизироваться по скорости. Я хотел таймер обратного отсчета, а не просто обновление текущей строки. Вот что я придумала. Может быть полезным для кого-то
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($"\rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
Thread.Sleep(1000);
}
Console.WriteLine("");
Ответ 14
Это работает, если вы хотите, чтобы генерирующие файлы выглядели круто.
int num = 1;
var spin = new ConsoleSpinner();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("");
while (true)
{
spin.Turn();
Console.Write("\r{0} Generating Files ", num);
num++;
}
И это метод, который я получил из ответа ниже и изменил его
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;
case 4: Console.Write("\r"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(23, Console.CursorTop);
}
}
Ответ 15
Здесь еще один: D
class Program
{
static void Main(string[] args)
{
Console.Write("Working... ");
int spinIndex = 0;
while (true)
{
// obfuscate FTW! Let hope overflow is disabled or testers are impatient
Console.Write("\b" + @"/-\|"[(spinIndex++) & 3]);
}
}
}
Ответ 16
Метод SetCursorPosition
работает в многопоточном сценарии, где другие два метода не