代码之家  ›  专栏  ›  技术社区  ›  IVR Avenger

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

  •  444
  • IVR Avenger  · 技术社区  · 15 年前

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

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

    15 回复  |  直到 6 年前
        1
  •  684
  •   Carra    8 年前

    如果你只打印 "\r" 到控制台,光标回到当前行的开头,然后您可以重写它。这应该可以做到:

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

    注意数字后面的几个空格,以确保删除之前存在的内容。
    同时注意使用 Write() 而不是 WriteLine() 因为您不想在行尾添加一个“\n”。

        2
  •  227
  •   Dirk Vollmar    10 年前

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

    这里是一个 example 显示一个简单的“旋转器”:

    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
  •  70
  •   Dirk Vollmar    7 年前

    到目前为止,我们有三种可供选择的方法:

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

    在我的机器上,我得到以下结果:

    • Backspaces: 25秒
    • 回车: 28.7秒
    • 设置光标位置: 49.7秒

    此外, SetCursorPosition 引起了明显的闪烁,我没有用任何一种方法观察到。所以,道德是 尽可能使用退格键或回车键 谢谢你教我 一种更快的方法,所以!


    更新 :在注释中,joel建议setCursorPosition相对于移动的距离是常量,而其他方法是线性的。进一步的测试证实了这一点, 然而 持续的时间和缓慢的速度仍然很慢。在我的测试中,在大约60个字符之前,在控制台上写一长串backspace比setcursorposition快。因此,用退格键替换短于60个字符的行的某些部分要快得多(大约如此)。 它不会闪烁,因此我将支持我最初对\b的认可,而不是\r和 设置光标位置 .

        4
  •  24
  •   Sean    15 年前

    你可以使用 \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);
    }
    

    在这里, 线 是要写入控制台的百分比行。诀窍是生成正确的 \b 上一个输出的字符。

    这比 R 方法是,即使您的百分比输出不在行首,也可以使用if。

        5
  •  16
  •   InteXX    6 年前

    \r 用于这些方案。
    R 表示回车,表示光标返回到行首。
    这就是Windows使用的原因 \n\r 作为它的新行标记。
    \n 将您下移一行,然后 R 返回行首。

        6
  •  12
  •   Joel Coehoorn    15 年前

    我只是想和女主角玩 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
  •  4
  •   ChrisF    15 年前

    在行首显式使用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();    
    }
    
        8
  •  2
  •   Christian Specht    13 年前

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

    另一种到达那里的方法。

        9
  •  2
  •   Jose    12 年前
        public void Update(string data)
        {
            Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
            Console.Write(string.Format("\r{0}", data));
        }
    
        10
  •  0
  •   cleftheris    12 年前

    这是我对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.");
    
        }
    
        11
  •  0
  •   BenMorel mehmet cinar    11 年前

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

    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");
    
        }
    }
    
        12
  •  0
  •   Zakir_SZH    8 年前

    我在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
  •  0
  •   Adam Hey    6 年前

    我在做一个搜索,看看我写的解决方案是否可以优化速度。我想要的是一个倒计时,而不仅仅是更新当前行。 这就是我想到的。可能对某人有用

                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
  •  -1
  •   Tom    12 年前

    还有一个: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]);
            }
        }
    }
    
        15
  •  -1
  •   imgen    11 年前

    这个 SetCursorPosition 方法在多线程场景中工作,其他两个方法不工作