Programming

C # Windows 콘솔 앱에서 현재 줄을 어떻게 업데이트합니까?

procodes 2020. 2. 14. 23:49
반응형

C # Windows 콘솔 앱에서 현재 줄을 어떻게 업데이트합니까?


C #에서 Windows 콘솔 앱을 빌드 할 때 현재 줄을 연장하거나 새 줄로 갈 필요없이 콘솔에 쓸 수 있습니까? 예를 들어, 프로세스가 완료되는 정도를 나타내는 백분율을 표시하려면 커서와 동일한 행에서 값을 업데이트하고 각 백분율을 새 행에 넣지 않아도됩니다.

"표준"C # 콘솔 앱으로이 작업을 수행 할 수 있습니까?


"\r"콘솔 에만 인쇄 하면 커서가 현재 줄의 시작 부분으로 돌아가서 다시 쓸 수 있습니다. 트릭을 수행해야합니다.

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

숫자 뒤에 약간의 공백이있어 이전에 있던 것이 지워 졌는지 확인하십시오.
또한 줄 끝에 "\ n"을 추가하고 싶지 않기 때문에 Write()대신 사용 WriteLine()하십시오.


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직접 사용할 수 있습니다 .


지금까지이 작업을 수행하는 방법에 대한 세 가지 경쟁 대안이 있습니다.

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대안 중 하나에서 관찰하지 않은 눈에 띄는 깜박임이 발생했습니다. 그래서, 도덕적이다 사용 백 스페이스 또는 캐리지 리턴이 가능 하고, 나에게 가르쳐 주셔서 감사 SO,이 작업을 수행하는 빠른 방법!


업데이트 : 의견에서 Joel은 SetCursorPosition은 이동 거리에 대해 일정하지만 다른 방법은 선형이라고 제안합니다. 추가 테스트에서 이것이 사실임을 확인 했지만, 일정한 시간과 느린 속도는 여전히 느립니다. 필자의 테스트에서 긴 백 스페이스 문자열을 콘솔에 쓰는 것이 60 자 정도가 될 때까지 SetCursorPosition보다 빠릅니다. 백 스페이스 빠르게 라인의보다 짧은 60 자 (정도) 부분을 교체하고, 그래서 그리고 그것은 내가 \ 연구를 통해 \ 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의 시작 부분으로 돌아갑니다.


나는 단지 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();
    }
}

끝에 새 줄 (\ n)을 사용하는 대신 (암시 적 또는 명시 적으로) 줄의 시작 부분에 Carrage Return (\ r)을 명시 적으로 사용하면 원하는 것을 얻을 수 있습니다. 예를 들면 다음과 같습니다.

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

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

MSDN의 콘솔 문서에서 :

Out 또는 Error 속성의 TextWriter.NewLine 속성을 다른 줄 종료 문자열로 설정하여이 문제를 해결할 수 있습니다. 예를 들어 C # 문인 Console.Error.NewLine = "\ r \ n \ r \ n";은 표준 오류 출력 스트림에 대한 줄 종결 문자열을 두 개의 캐리지 리턴 및 줄 바꿈 시퀀스로 설정합니다. 그런 다음 C # 문에서와 같이 오류 출력 스트림 개체의 WriteLine 메서드를 명시 적으로 호출 할 수 있습니다. Console.Error.WriteLine ();

그래서-나는 이것을했다 :

Console.Out.Newline = String.Empty;

그런 다음 출력을 직접 제어 할 수 있습니다.

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

거기에 도착하는 또 다른 방법.


다음은 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();
    }
}

사용법은 이와 같습니다. 수업 프로그램 {

    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

필자가 작성한 솔루션이 속도에 맞게 최적화 될 수 있는지 확인하기 위해 이것을 검색하고있었습니다. 내가 원하는 것은 현재 줄을 업데이트하는 것이 아니라 카운트 다운 타이머였습니다. 여기에 내가 생각해 낸 것이 있습니다. 누군가에게 유용 할 수 있습니다

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

또 다른 하나가 있습니다 : 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]);
        }
    }
}

SetCursorPosition방법은 멀티 스레딩 시나리오에서 작동하며 다른 두 방법은 그렇지 않습니다.

참고 URL : https://stackoverflow.com/questions/888533/how-can-i-update-the-current-line-in-ac-sharp-windows-console-app



반응형