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

IVR*_*ger 479 c# windows console

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

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

sho*_*osh 744

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

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

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

  • 当前一次写入比新写入更长时,你如何处理?有没有办法获得控制台的宽度和填充空格线,也许? (12认同)
  • for(int i = 0; i <= 100; ++ i)将达到100% (7认同)
  • @druciferre在我的脑海中,我可以为你的问题想出两个答案.它们都涉及首先将当前输出保存为字符串,并用一定数量的字符填充它,如下所示:Console.Write("\ r {0}",strOutput.PadRight(nPaddingCount,'')); "nPaddingCount"可以是您自己设置的数字,也可以跟踪前一个输出,并将nPaddingCount设置为前一个输出和当前输出之间的长度差异加上当前输出长度.如果nPaddingCount是负数,那么你不必使用PadRight,除非你做abs(prev.len - curr.len). (5认同)
  • @JohnOdom您只需要保留先前的(未填充)输出长度,然后将其作为`PadRight`的第一个参数输入(当然,首先保存未填充的字符串或长度)。 (2认同)

Dir*_*mar 242

您可以使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,您必须确保使用新输出或空白覆盖任何现有输出.

更新:由于有人批评该示例仅将光标移回一个字符,我将添加此内容以澄清:使用SetCursorPosition您可以将光标设置为控制台窗口中的任何位置.

Console.SetCursorPosition(0, Console.CursorTop);
Run Code Online (Sandbox Code Playgroud)

将光标设置为当前行的开头(或者您可以Console.CursorLeft = 0直接使用).

  • +1是冗长的,超出了职责范围.好东西谢谢. (13认同)
  • 问题可能是使用\ r来解决的,但是使用`SetCursorPosition`(或`CursorLeft`)可以提供更大的灵活性,例如不在行的开头写入,在窗口中向上移动等等,所以这是一种更通用的方法,可用于输出自定义进度条或ASCII图形. (8认同)
  • 同时确认行长度不会导致控制台换行到下一行,或者您可能会遇到控制台窗口中运行的内容问题. (5认同)

Kev*_*vin 78

到目前为止,我们有三种竞争方案可供选择:

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
}
Run Code Online (Sandbox Code Playgroud)

我总是使用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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 退格:25.0秒
  • 回车:28.7秒
  • SetCursorPosition:49.7秒

此外,SetCursorPosition引起了明显的闪烁,我没有观察到任何一种替代方案.因此,道德是尽可能使用退格或回车,并感谢教我一个更快的方法来做到这一点,所以!


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

  • 基准测试存在根本缺陷.无论光标移动多远,SetCursorPosition()时间都可能相同,而其他选项可能因控制台必须处理的字符数而异. (6认同)
  • 有问题的操作效率确实无关紧要.它应该发生得太快,用户无法注意到.不必要的微观刺激是不好的. (4认同)

Sea*_*ean 27

您可以使用\ 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);
}
Run Code Online (Sandbox Code Playgroud)

这里,line是写入控制台的百分比行.诀窍是为前一个输出生成正确数量的\ b字符.

这种优于\ r方法的优点是,即使您的百分比输出不在行的开头,也能正常工作.


Mal*_*ist 18

\r用于这些场景.
\r 表示回车符,表示光标返回到行的开头.
这就是Windows \n\r用作新线标记的原因.
\n让你向下移动一行,然后\r返回到行的开头.

  • 除了它实际上\ r \n. (20认同)
  • 我一直想知道为什么\n\r.谢谢你的信息. (2认同)

Joe*_*orn 14

我只需要和divo的ConsoleSpinner课一起玩.我的距离很简洁,但是我觉得这个类的用户必须编写自己的while(true)循环.我正在拍摄更像这样的体验:

static void Main(string[] args)
{
    Console.Write("Working....");
    ConsoleSpinner spin = new ConsoleSpinner();
    spin.Start();

    // Do some work...

    spin.Stop(); 
}
Run Code Online (Sandbox Code Playgroud)

我用下面的代码意识到了这一点.由于我不希望我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

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