Як я можу оновити поточний рядок у додатку консолі C # Windows?


507

Створюючи додаток консолі Windows на C #, чи можна писати на консоль, не поширюючи поточний рядок або переходити до нового рядка? Наприклад, якщо я хочу показати відсоток, який відображає, наскільки близький процес до завершення, я просто хотів би оновити значення в тому ж рядку, що і курсор, і не потрібно ставити кожен відсоток у новий рядок.

Це можна зробити за допомогою "стандартного" додатка консолі C #?


Якщо ви дійсно зацікавлені в прохолодних інтерфейсах командного рядка, ви повинні перевірити curses / ncurses.
Чарльз Аддіс

@CharlesAddis, але чи не прокльони / ncurses працюють лише в C ++?
Xam

Відповіді:


783

Якщо ви друкуєте лише "\r"на консолі, курсор повертається до початку поточного рядка, а потім ви можете переписати його. Для цього слід зробити фокус:

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

Помітьте кілька пробілів після номера, щоб переконатися, що все, що там було раніше, буде видалено.
Також помічайте використання Write()замість цього, WriteLine()оскільки ви не хочете додавати "\ n" в кінці рядка.


7
для (int i = 0; i <= 100; ++ i) піде на 100%
Ніколя Тайлер

13
Як вам впоратися, коли попереднє записування було довше, ніж нове? Чи є якийсь спосіб отримати ширину консолі та прокладати лінію пробілами, можливо?
Дрю Шапін

6
@druciferre Вгорі голови я можу придумати дві відповіді на ваше запитання. Вони обидва передбачають збереження поточного виводу в якості рядка спочатку і доповнення його заданим числом символів, як це: Console.Write ("\ r {0}", strOutput.PadRight (nPaddingCount, '')); "NPaddingCount" може бути числом, яке ви встановили самостійно, або ви можете відслідковувати попередній вихід і встановити nPaddingCount як різницю довжини між попереднім та поточним висновком плюс поточну довжину виводу. Якщо nPaddingCount негативний, вам не доведеться використовувати PadRight, якщо ви не зробите abs (prev.len - curr.len).
Джон Одом

1
@malgm Добре організований код. Якщо будь-яка з десятків потоків зможе записати на консоль у будь-який час, це створить проблеми, незалежно від того, пишете ви нові рядки чи ні.
Марк

2
@JohnOdom вам потрібно лише зберегти попередню (нерозкладену) вихідну довжину, а потім подати її в якості першого аргументу PadRight(збереження нерозкладеної рядка або довжини, спочатку, звичайно).
Jesper Matthiesen

254

Ви можете 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безпосередньо).


8
Проблема може бути вирішена за допомогою \ r, але використання SetCursorPosition(або CursorLeft) забезпечує більшу гнучкість, наприклад, не написання на початку рядка, переміщення у вікно тощо, тому це є більш загальним підходом, який можна використовувати, наприклад, вихід спеціальні панелі прогресу або графіку ASCII.
Дірк Волмар

14
+1 за те, що є багатослівним та виходить за межі обов'язку. Гарні речі дякую.
Copas

1
+1 за показ іншого способу. Всі інші показали, і якщо ОП просто оновлює відсоток, за допомогою цього він може просто оновити значення без необхідності переписувати весь рядок. ОП ніколи насправді не казав, що хоче перейти до початку рядка, лише що він хоче щось оновити на тій же лінії, що й курсор.
Енді

1
Додаткова гнучкість SetCursorPosition поставляється ціною трохи швидкості та помітного мерехтіння курсору, якщо цикл достатньо довгий, щоб користувач міг помітити. Дивіться мій коментар до тесту нижче.
Кевін

5
Також переконайтеся, що довжина рядка не призводить до переключення консолі на наступний рядок, або у вас все-таки можуть виникнути проблеми із вмістом, що біжить у вікно консолі.
Мандраке

84

Наразі у нас є три конкуруючих альтернативи, як це зробити:

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

На своїй машині я отримую такі результати:

  • Резервна кількість: 25,0 секунди
  • Повернення каретки: 28,7 секунди
  • SetCursorPosition: 49,7 секунди

Крім того, SetCursorPositionвикликав помітний мерехтіння, якого я не спостерігав ні з однією з альтернатив. Отже, мораль - використовувати зворотні простори або повернення до перевезення, коли це можливо , і дякую за те, що ви навчили мене більш швидкому способу зробити це, ТАК!


Оновлення : У коментарях Джоел припускає, що SetCursorPosition є постійним щодо відстані, яку переміщують, а інші методи лінійні. Подальше тестування підтверджує, що це так, однак постійний час і повільний все ще повільний. У моїх тестах запис довгих рядків зворотних просторів на консоль швидше, ніж на SetCursorPosition, поки десь близько 60 символів. Таким чином, зворотний простір швидше замінює частини рядка, менші за 60 символів (або близько того), і він не мерехтить, тому я буду стояти біля свого початкового схвалення \ b над \ r і SetCursorPosition.


4
Ефективність операції, про яку йдеться, насправді не має значення. Це повинно відбуватися занадто швидко, щоб користувач помітив. Непотрібна мікроптимізація - це погано.
Мальфіст

@Malfist: Залежно від довжини циклу, користувач може помітити або не помітити. Як я додав у редакції вище (перш ніж я побачив ваш коментар), SetCursorPosition представив мерехтіння і займає майже вдвічі більше, ніж інші параметри.
Кевін

1
Я погоджуюсь, що це мікрооптимізація (запустити її мільйон разів і зайняти 50 секунд - це ще дуже мала кількість часу), +1 для результатів, і це, безумовно, може бути дуже корисно знати.
Енді

6
Тест є принципово хибним. Можливо, час SetCursorPosition () є однаковим незалежно від того, наскільки курсор рухається, а інші параметри залежать від кількості символів, які консоль повинна обробити.
Джоел Куехорн

1
Це дуже хороший підсумок різних варіантів. Однак я також бачу мерехтіння під час використання \ r. З \ b очевидно не мерехтіння, оскільки текст виправлення ("Підрахунок:") не переписаний. Ви також отримаєте мерехтіння, якщо додасте додаткові \ b і перепишете текст виправлення, як це відбувається з \ b та SetCursorPosition. Щодо зауваження Джоеля: Джоел в основному правий, однак все-таки перевершить SetCursorPosition на дуже довгих лініях, але різниця стає меншою.
Дірк Волмар

27

Ви можете використовувати \ 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 полягає в тому, що якщо він працює, навіть якщо ваш відсотковий вихід не знаходиться на початку рядка.


1
+1, це виявляється найшвидший спосіб представлення (див. Мій тестовий коментар нижче)
Кевін

19

\rвикористовується для цих сценаріїв.
\r являє собою повернення каретки, що означає, що курсор повертається до початку рядка.
Ось чому Windows використовує \n\rновий маркер рядка.
\nпереміщує вас вниз по лінії і \rповертає вас до початку рядка.


22
За винятком того, що насправді \ r \ n
Джоель Мюллер

14

Мені просто довелося пограти з класом диво 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();
    }
}

Хороша модифікація, хоча зразок коду не мій. Це взято з блогу Бреда Абрамса (див. Посилання у моїй відповіді). Я думаю, що це було просто написано як простий зразок, що демонструє SetCursorPosition. До речі, я, безумовно, здивований (позитивно) з приводу дискусії, розпочатої про те, що я вважав просто простим зразком. Ось чому я люблю цей сайт :-)
Дірк Волмар

4

Явне використання повернення Carrage (\ 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();    
}

-1, Питання просить C #, я переписую його на C #, а ви повертаєте його назад до F #
Malfist

Це схоже на конфлікт із редагуванням, а не на зміну вашого C # назад на F #. Його зміна відбулася через хвилину після вашого і зосередилась на спринті.
Енді

Дякуємо за редагування Я схильний використовувати інтерактивний режим F # для перевірки речей, і я зрозумів, що важливими частинами були дзвінки BCL, які однакові у C #.
Джеймс Х'югард

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

1

З Документів Консолі в MSDN:

Вирішити цю проблему можна, встановивши властивість TextWriter.NewLine властивості Out або Error в інший рядок завершення рядка. Наприклад, оператор C #, Console.Error.NewLine = "\ r \ n \ r \ n";, встановлює рядок завершення рядка для стандартного потоку виходу помилки на два послідовності повернення каретки та подачі рядків. Тоді ви можете явно викликати метод WriteLine об’єкта потоку помилок виводу помилок, як у твердженні C #, Console.Error.WriteLine ();

Отже - я зробив це:

Console.Out.Newline = String.Empty;

Тоді я в змозі контролювати вихід сам;

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

Ще один спосіб потрапити туди.


Ви можете просто використовувати Console.Write () з тією ж метою, не переробляючи властивість NewLine ...
Radosław Gers

1

Це працює, якщо ви хочете, щоб генеруючі файли виглядали круто.

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

0

Ось ще один: 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]);
        }
    }
}

0

Якщо ви хочете оновити один рядок, але інформація занадто довга для відображення в одному рядку, можливо, знадобляться нові рядки. Я стикався з цією проблемою, і нижче - один із способів вирішити цю проблему.

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

    }
}

0

Я шукав таке ж рішення в 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

Тут ви можете використовувати VB функцію локальної статичної змінної: Static intLastLength As Integer.
Марк Херд

0

Я робив пошук цього, щоб дізнатися, чи можна оптимізувати розроблене рішення для швидкості. Мені потрібно було таймер зворотного відліку, а не лише оновлення поточного рядка. Ось що я придумав. Можливо, комусь буде корисно

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

0

Натхненний рішенням @ E.Lahu, реалізація прогресу бару з відсотком.

public class ConsoleSpinner
{
    private int _counter;

    public void Turn(Color color, int max, string prefix = "Completed", string symbol = "■",int position = 0)
    {
        Console.SetCursorPosition(0, position);
        Console.Write($"{prefix} {ComputeSpinner(_counter, max, symbol)}", color);
        _counter = _counter == max ? 0 : _counter + 1;
    }

    public string ComputeSpinner(int nmb, int max, string symbol)
    {
        var spinner = new StringBuilder();
        if (nmb == 0)
            return "\r ";

        spinner.Append($"[{nmb}%] [");
        for (var i = 0; i < max; i++)
        {
            spinner.Append(i < nmb ? symbol : ".");
        }

        spinner.Append("]");
        return spinner.ToString();
    }
}


public static void Main(string[] args)
    {
        var progressBar= new ConsoleSpinner();
        for (int i = 0; i < 1000; i++)
        {
            progressBar.Turn(Color.Aqua,100);
            Thread.Sleep(1000);
        }
    }

0

Ось моя відповідь на відповіді s 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();
    }
}

використання - це щось подібне:

class Program
{
    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.");

    }
}

-1

SetCursorPositionМетод працює в багатопотоковому сценарії, де дві інші методи не

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.