programing

C# Windows Console App의 현재 행을 업데이트하려면 어떻게 해야 합니까?

powerit 2023. 4. 13. 21:15
반응형

C# Windows Console App의 현재 행을 업데이트하려면 어떻게 해야 합니까?

C#에서 Windows Console App을 빌드할 때 현재 회선을 연장하거나 새 회선으로 이동하지 않고 콘솔에 쓸 수 있습니까?예를 들어 프로세스가 완료되기까지의 거리를 백분율로 표시하는 경우 커서와 같은 줄에 값을 업데이트하고 각 백분율을 새 줄에 표시할 필요가 없습니다.

이 작업은 "표준" C# 콘솔 앱에서 수행할 수 있습니까?

"\r"커서가 현재 행의 선두로 돌아가 다시 쓸 수 있습니다.이렇게 하면 효과가 있습니다.

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

숫자 뒤에 공백이 몇 개 있으면 이전에 있던 것이 지워집니다.
, 「」, 「」의 사용법에 .Write()WriteLine()'\n'은 '\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★★★★★★★★★★★★★★★★★★」

지금까지 NAT은 이 방법에 대해 세 가지 경쟁사 대안을 제시했습니다.

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초
  • Set Cursor Position : 49.7초

★★★★★SetCursorPosition눈에 띄는 깜박임을 일으켰지만 다른 방법으로는 관찰하지 못했습니다.가능한 백스페이스나 캐리지 리턴을 이용하는 것이 교훈입니다.이렇게 빨리 할 수 있는 방법을 가르쳐 주셔서 감사합니다.


업데이트: 코멘트에서 Joel은 SetCursorPosition이 이동 거리에 대해 일정하고 다른 메서드는 선형이라고 제안합니다.추가 테스트 결과, 이것이 사실로 확인되었지만, 일정한 시간과 느린 속도는 여전히 느립니다.테스트에서는 콘솔에 긴 백스페이스 스트링을 쓰는 것이 SetCursorPosition보다 60자 정도 빠릅니다.즉, 백스페이스는 60자 미만의 행의 일부를 교체하는 것이 빠르고(또는 그 정도), 깜박거리지 않기 때문에 \r에 대한 \b의 초기 보증은 그대로 두겠습니다.SetCursorPosition.

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

여기서 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(); 
}

그리고 아래 코드로 깨달았습니다.는 내 가원 since my my my since since since since since since since since since since since since since since since 를 원하지 않기 때문에Start()차단 방법, 사용자가 파일 작성에 대해 걱정할 필요가 없습니다.while(spinFlag) 루프, 개의 할 수 스피너로 을 처리할 수 있는 를 따로 스레드를 생성해야 합니다.는말는는는는는는는는는는는는는는는는는는는는는는는는

또, 멀티 스레드는 그다지 실시하지 않았기 때문에, 거기에 미묘한 버그를 3개 남겼을 가능성이 있습니다.하지만 아직까지는 꽤 효과가 있는 것 같습니다.

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();
    }
}
    public void Update(string data)
    {
        Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("\r{0}", data));
    }

(암시적 또는 명시적으로) 새로운 회선(\n)을 사용하는 것이 아니라 회선의 선두에 캐러지 리턴(\r)을 명시적으로 사용하면 원하는 것을 얻을 수 있습니다.예를 들어 다음과 같습니다.

void demoPercentDone() {
    for(int i = 0; i < 100; i++) {
        System.Console.Write( "\rProcessing {0}%...", i );
        System.Threading.Thread.Sleep( 1000 );
    }
    System.Console.WriteLine();    
}

MSDN의 콘솔 문서에서 다음을 수행합니다.

TextWriter를 설정하면 이 문제를 해결할 수 있습니다.[ Out ]속성 또는 [Error]속성의 [NewLine]속성을 다른 줄 끝 문자열로 지정합니다.예를 들어 C# 문, Console 등이 있습니다.Error.NewLine = "\r\n\r\n"; 표준 오류 출력 스트림의 회선 종단 문자열을 2개의 캐리지 리턴 시퀀스와 회선 피드 시퀀스로 설정합니다.그런 다음 C# 문과 같이 오류 출력 스트림 객체의 WriteLine 메서드를 Console로 명시적으로 호출할 수 있습니다.Error.WriteLine();

그래서 이렇게 했어요.

Console.Out.Newline = String.Empty;

그러면 출력을 직접 제어할 수 있습니다.

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

거기 가는 또 다른 방법이지

이것은, 파일의 생성을 쿨하게 하고 싶은 경우에 유효합니다.

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

여기 또 있습니다.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]);
        }
    }
}

한 줄을 업데이트하고 싶은데 정보가 너무 길어서 한 줄에 표시할 수 없는 경우 새 줄이 필요할 수 있습니다.저는 이 문제를 겪었습니다.이 문제를 해결할 수 있는 한 가지 방법은 다음과 같습니다.

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

제가 작성한 솔루션이 속도에 맞게 최적화될 수 있는지 알아보기 위해 이것을 검색했습니다.제가 원했던 건 카운트다운 타이머였어요.그냥 현재의 회선을 갱신하는 게 아니라.내가 생각해낸 건 이거야누군가에게 도움이 될 수도 있다

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

@E에서 영감을 얻었다.라후 솔루션, 바의 진행률을 퍼센티지와 함께 구현합니다.

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

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

    }
}

SetCursorPosition 두 방법이시나리오에서합니다.

언급URL : https://stackoverflow.com/questions/888533/how-can-i-update-the-current-line-in-a-c-sharp-windows-console-app

반응형