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

How can I update the current line in a C# Windows Console App?

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

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


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

1
2
3
4
5
for(int i = 0; i < 100; ++i)
{
    Console.Write("
{0}%  "
, i);
}

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


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

下面是一个显示简单"微调器"的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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可以将光标设置为控制台窗口中的任何位置。

1
Console.SetCursorPosition(0, Console.CursorTop);

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


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

1
2
3
4
5
6
7
Console.Write("
{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,这是第三个选项的变体,所以我决定做一些测试。这是我使用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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("
Counting: {0}    "
, i);
    }
    sw.Stop();
    Console.WriteLine("
Time using \
: {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("
Time 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("
Time using \\b: {0}"
, sw.ElapsedMilliseconds);
}

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

  • 退格:25.0秒
  • 回车:28.7秒
  • 设置光标位置:49.7秒

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

更新:在注释中,joel建议setCursorPosition相对于移动的距离是常量,而其他方法是线性的。进一步的测试证实了这一点,但是恒定的时间和缓慢的速度仍然很慢。在我的测试中,在大约60个字符之前,在控制台上写一长串backspace比setcursorposition快。所以退格键在替换短于60个字符的行部分时速度更快,而且不会闪烁,所以我将支持最初对 over
SetCursorPosition的认可。


您可以使用(退格)转义序列备份当前行上的特定字符数。这只会移动当前位置,而不会删除字符。

例如:

1
2
3
4
5
6
7
8
9
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是要写入控制台的百分比行。技巧是为以前的输出生成正确的字符数。


方法相比,此方法的优势在于,即使百分比输出不在行的开头,它仍然有效。


这些场景使用

表示回车,即光标返回到行首。这就是Windows使用

作为其新行标记的原因。
将您向下移动一行,
将您返回到行首。


我只是和迪沃的ConsoleSpinner班一起玩。我的没有一个地方能做到简洁,但我觉得这个类的用户必须编写自己的while(true)循环并不合适。我在拍摄这样的经历:

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
    Console.Write("Working....");
    ConsoleSpinner spin = new ConsoleSpinner();
    spin.Start();

    // Do some work...

    spin.Stop();
}

我用下面的代码实现了它。由于我不希望我的Start()方法阻塞,我不希望用户担心编写一个类似while(spinFlag)的循环,我希望允许多个spinners同时生成一个单独的线程来处理旋转。这意味着代码要复杂得多。

而且,我没有做过太多的多线程,所以有可能(甚至可能)我在其中留下了一个或三个小错误。但到目前为止,它似乎工作得很好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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();
    }
}


在行首显式使用carrage返回(
),而不是(隐式或显式)在行尾使用新行(),应该得到您想要的。例如:

1
2
3
4
5
6
7
8
void demoPercentDone() {
    for(int i = 0; i < 100; i++) {
        System.Console.Write("
Processing {0}%..."
, i );
        System.Threading.Thread.Sleep( 1000 );
    }
    System.Console.WriteLine();    
}


1
2
3
4
5
6
7
    public void Update(string data)
    {
        Console.Write(string.Format("
{0}"
,"".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("
{0}"
, data));
    }

从控制台在MSDN文档:

You can solve this problem by setting
the TextWriter.NewLine property of the
Out or Error property to another line
termination string. For example, the
C# statement, Console.Error.NewLine =
"

";, sets the line termination
string for the standard error output
stream to two carriage return and line
feed sequences. Then you can
explicitly call the WriteLine method
of the error output stream object, as
in the C# statement,
Console.Error.WriteLine();

所以,我这样做:

1
Console.Out.Newline = String.Empty;

我在自己能够控制输出;

1
2
3
4
Console.WriteLine("Starting item 1:");
    Item1();
Console.WriteLine("OK.
Starting Item2:"
);

另一个方式去那里。


我做一个搜索的解决方案,这看看我写的可能是除了飞车。我想要的是一个倒计时的计时器,不只是更新电流线。这是我来的。可能是一个有用的人

1
2
3
4
5
6
7
8
9
10
11
12
            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($"
Process will resume in {minutesRounded}:{String.Format("
{0:D2}", -seconds)}");
                Thread.Sleep(1000);
            }
            Console.WriteLine("");

我正在寻找相同的解决方案在VB.NET和我发现这一个和它是伟大的。

然而,作为一个更好的方式来"johnodom手柄内的空白空间,如果以前是一个装修一电流。

i make a函数思想在VB.NET和人可以得到帮助。

这里是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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


我在这里是个soosh’s和0xa3的答案。它可以在控制台与用户消息的更新而更新的怪人和安切洛蒂在elapsed自然时间指示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ConsoleSpiner : IDisposable
{
    private static readonly string INDICATOR ="/-\\|";
    private static readonly string MASK ="
{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();
    }
}

使用的是这样的东西。类程序{

1
2
3
4
5
6
7
8
9
10
11
12
13
    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!!!!!
Press any key to exit."
);

    }

如果你想更新一行,但太长的信息显示在一个线,它可能需要一些新的行。我遇到的这个问题,是一个与下面的方式解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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");

    }
}

作品《SetCursorPosition法在多线程的情况下,在其他两个方法不


这里是另一个:D

1
2
3
4
5
6
7
8
9
10
11
12
13
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]);
        }
    }
}