关于c#4.0:控制台应用程序:如何在没有闪烁的情况下更新显示器?

Console application: How to update the display without flicker?

在不断报告进度的Windows控制台应用程序中使用C 4,如何使屏幕的"重画"更流畅?

我想做以下一项:-让它只"重画"屏幕上正在更改的部分(进度部分),其余部分保持原样。-"重新绘制"整个屏幕,但不闪烁。

目前我重新编写所有文本(应用程序名等)。这样地:

1
2
3
4
Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0"), total.ToString("N0"), (count / (decimal)total).ToString("P2")));

这会引起很多闪烁。


试试Console.SetCursorPosition。这里有更多的细节:如何更新C Windows控制台应用程序中的当前行?


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
static void Main(string[] args)
{
    Console.SetCursorPosition(0, 0);
    Console.Write("################################");
    for (int row = 1; row < 10; row++)
    {
        Console.SetCursorPosition(0, row);
        Console.Write("#                              #");
    }
    Console.SetCursorPosition(0, 10);
    Console.Write("################################");

    int data = 1;
    System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
    clock.Start();
    while (true)
    {
        data++;
        Console.SetCursorPosition(1, 2);
        Console.Write("Current Value:" + data.ToString());
        Console.SetCursorPosition(1, 3);
        Console.Write("Running Time:" + clock.Elapsed.TotalSeconds.ToString());
        Thread.Sleep(1000);
    }

    Console.ReadKey();
}


我知道这个问题有点老,但我发现如果你设置了Console.CursorVisible = false,那么闪烁也会停止。


我建议使用以下扩展方法。它们允许您使用StringBuilder刷新控制台视图而不闪烁,还可以整理每行上的任何残留字符。

问题:下面的演示演示演示了如何使用标准的StringBuilder,其中更新的行比以前编写的行短,会造成混乱。它通过编写一个短字符串,然后在循环上编写一个长字符串来实现这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void Main(string[] args)
{            
   var switchTextLength = false;

   while(true)
   {
       var sb = new StringBuilder();      

       if (switchTextLength)
            sb.AppendLine("Short msg");
       else
            sb.AppendLine("Longer message");

       sb.UpdateConsole();

       switchTextLength = !switchTextLength;
       Thread.Sleep(500);
   }
}

结果:

The remainder of the longer string still exists because the shorter message is not long enough to overwrite it

解决方案:通过使用下面提供的扩展方法,解决了问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void Main(string[] args)
{            
   var switchTextLength = false;

   while(true)
   {
       var sb = new StringBuilder();      

       if (switchTextLength)
            sb.AppendLineEx("Short msg");
       else
            sb.AppendLineEx("Longer message");

       sb.UpdateConsole();

       switchTextLength = !switchTextLength;
       Thread.Sleep(500);
   }
}

结果:

氧化镁

扩展方法:

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
public static class StringBuilderExtensions
{
    /// <summary>
    /// Allows StrinbBuilder callers to append a line and blank out the remaining characters for the length of the console buffer width
    /// </summary>
    public static void AppendLineEx(this StringBuilder c, string msg)
    {
        // Append the actual line
        c.Append(msg);
        // Add blanking chars for the rest of the buffer
        c.Append(' ', Console.BufferWidth - msg.Length - 1);
        // Finish the line
        c.Append(Environment.NewLine);
    }

    /// <summary>
    /// Combines two StringBuilders using AppendLineEx
    /// </summary>
    public static void AppendEx(this StringBuilder c, StringBuilder toAdd)
    {
        foreach (var line in toAdd.ReadLines())
        {
            c.AppendLineEx(line);
        }
    }

    /// <summary>
    /// Hides the console cursor, resets its position and writes out the string builder
    /// </summary>
    public static void UpdateConsole(this StringBuilder c)
    {
        // Ensure the cursor is hidden
        if (Console.CursorVisible) Console.CursorVisible = false;

        // Reset the cursor position to the top of the console and write out the string builder
        Console.SetCursorPosition(0, 0);
        Console.WriteLine(c);
    }
}

这是一个简单的工作演示,它显示了多行使用而不闪烁。它每秒显示当前时间和一个随机字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    private static void StatusUpdate()
    {
        var whiteSpace = new StringBuilder();
        whiteSpace.Append(' ', 10);
        var random = new Random();
        const string chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var randomWord = new string(Enumerable.Repeat(chars, random.Next(10)).Select(s => s[random.Next(s.Length)]).ToArray());
        while (true)
        {
            Console.SetCursorPosition(0, 0);
            var sb = new StringBuilder();
            sb.AppendLine($"Program Status:{whiteSpace}");
            sb.AppendLine("-------------------------------");
            sb.AppendLine($"Last Updated: {DateTime.Now}{whiteSpace}");
            sb.AppendLine($"Random Word: {randomWord}{whiteSpace}");
            sb.AppendLine("-------------------------------");
            Console.Write(sb);
            Thread.Sleep(1000);
        }
    }

上面的例子假设您的控制台窗口是空白的。如果没有,请确保首先使用console.clear()。

技术说明:setCursorPosition(0,0)将光标放回顶部(0,0),以便下次调用console.write将从第0行、第0个字符开始。注意,它不会在写入之前删除以前的内容。例如,如果您在前一行(如"0123456")上写"asdf",那么您将在该行上得到类似"asdf456"的结果。因此,我们使用一个空白变量来确保前一行中的任何延迟字符都被空格覆盖。调整空白变量的长度以满足您的需要。对于更改的行,只需要空白变量。

个人说明:出于我的目的,我希望显示应用程序的当前状态(每秒一次)以及一系列其他状态信息,并且希望避免在使用console.clear()时出现任何令人讨厌的闪烁。在我的应用程序中,我在一个单独的线程后面运行我的状态更新,这样即使我有许多其他线程和长时间运行的任务同时进行,它也会不断地提供更新。

信用:感谢之前的海报和演示中使用的随机字符串生成器的DTB。如何在C_中生成随机字母数字字符串?


我认为您可以在Windows控制台中使用
返回行首。也可以使用setCursorPosition。


您可以尝试使用核心库将一些东西拼凑在一起。

我将检查ncurses库(用于格式化控制台输出的库)的这个C端口,而不是将您的时间浪费在低于标准的结果上:

尖刻的诅咒