首页 文章

如何更新C#Windows控制台应用程序中的当前行?

提问于
浏览
430

在C#中构建Windows控制台应用程序时,是否可以写入控制台而无需扩展当前行或转到新行?例如,如果我想显示一个百分比表示一个进程完成的接近程度,我只想更新与光标在同一行的值,而不必将每个百分比放在一个新行上 .

这可以通过“标准”C#控制台应用程序完成吗?

15 回答

  • 15

    在行的开头明确地使用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();    
    }
    
  • -1
    public void Update(string data)
        {
            Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
            Console.Write(string.Format("\r{0}", data));
        }
    
  • 661

    如果仅将 "\r" 打印到控制台,则光标会返回到当前行的开头,然后您可以重写它 . 这应该是诀窍:

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

    注意数字后面的几个空格,以确保删除之前的任何内容 .
    另请注意使用 Write() 而不是 WriteLine() ,因为您不想在行尾添加"\n" .

  • 0

    从MSDN中的控制台文档:

    您可以通过将Out或Error属性的TextWriter.NewLine属性设置为另一个行终止字符串来解决此问题 . 例如,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:");
    

    到达那里的另一种方式 .

  • 24

    我正在搜索这个,看看我写的解决方案是否可以针对速度进行优化 . 我想要的是倒计时器,而不仅仅是更新当前线路 . 这就是我想出的 . 可能对某人有用

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

    \r 用于此方案 .
    \r represents a carriage return which means the cursor returns to the start of the line.
    这就是为什么Windows使用 \n\r 作为它的新行标记 .
    \n 向下移动一行, \r 返回到行的开头 .

  • 0

    SetCursorPosition 方法适用于多线程场景,而其他两种方法则不适用

  • 224

    我在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
    
  • 70

    我只需要玩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();
        }
    }
    
  • 4

    如果您想要更新一行,但信息太长而无法在一行显示,则可能需要一些新行 . 我遇到过这个问题,下面是解决这个问题的一种方法 .

    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

    您可以使用 \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 方法,这样做的好处是即使您的百分比输出不在行的开头也能正常工作 .

  • 2

    您可以使用 Console.SetCursorPosition 设置光标的位置,然后在当前位置写入 .

    这是example显示一个简单的"spinner":

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

  • 2

    这是我对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.");
    
        }
    
  • 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]);
            }
        }
    }
    
  • -1

    到目前为止,我们有三个竞争对手如何做到这一点的替代方案:

    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 seconds

    • 回车: 28.7 seconds

    • SetCursorPosition: 49.7 seconds

    此外, SetCursorPosition 引起了明显的闪烁,我没有观察到任何一种替代方案 . 所以,道德是 use backspaces or carriage returns when possible ,而 thanks for teaching me 这是一个更快的方法,所以!


    Update :在评论中,Joel建议SetCursorPosition相对于移动的距离是恒定的,而其他方法是线性的 . 进一步的测试证实了这种情况, however 恒定时间和慢速仍然很慢 . 在我的测试中,将一长串退格写入控制台比SetCursorPosition更快,直到大约60个字符 . 因此,退格会更快地替换短于60个字符(或左右)的部分行, and 它不会支持我最初代表\ b over \ r和 SetCursorPosition .

相关问题