【问题标题】:Console application: How to update the display without flicker?控制台应用程序:如何更新显示而不闪烁?
【发布时间】:2011-03-25 16:44:39
【问题描述】:

在持续报告进度的 Windows 控制台应用程序中使用 C# 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")));

这会导致很多闪烁。

【问题讨论】:

    标签: c#-4.0 console


    【解决方案1】:

    试试Console.SetCursorPosition。更多细节在这里:How can I update the current line in a C# Windows Console App?

    【讨论】:

      【解决方案2】:
      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();
      }
      

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 对我的目的没有帮助。仍然有闪烁。 :(
        • 不,如果您通过调用Clear 然后多次调用WriteLines 快速(每250 毫秒或更短时间)刷新控制台,则不会
        【解决方案4】:

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

            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”的内容。出于这个原因,我们使用 whiteSpace 变量来确保前一行中的任何延迟字符都被空格覆盖。调整 whiteSpace 变量的长度以满足您的需要。对于更改的行,您只需要 whiteSpace 变量。

        个人笔记: 出于我的目的,我想显示应用程序的当前状态(每秒一次)以及一堆其他状态信息,并且我想避免使用 Console.Clear() 时可能发生的任何烦人的闪烁。在我的应用程序中,我在一个单独的线程后面运行我的状态更新,因此即使我有许多其他线程和长时间运行的任务同时进行,它也会不断提供更新。

        致谢: 感谢之前的海报和演示中使用的随机字符串生成器的 dtb。 How can I generate random alphanumeric strings in C#?

        【讨论】:

          【解决方案5】:

          您可以尝试使用核心库一起破解一些东西。

          与其把时间浪费在不符合标准的结果上,我会检查 ncurses 库的这个 C# 端口(这是一个用于格式化控制台输出的库):

          Curses Sharp

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

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

              问题:以下演示使用标准 StringBuilder 进行演示,其中更新比之前编写的行短的行会变得混乱。它通过写一个短字符串,然后在一个循环上写一个长字符串来做到这一点:

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

              结果:

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

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

              结果:

              扩展方法:

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

              【讨论】:

              • 这是一个很好的解决方案,但是您需要补偿表格 ('\t') 字符,因为它们会搞砸一切。 c.Append(' ', Console.BufferWidth - msg.Length -1 - msg.Count(x =&gt; x == '\t') * 5);
              • 另外你需要补偿高度(在 UpdateConsole 的最后一行):while (Console.CursorTop != Console.WindowHeight -1) Console.Write(" ");
              【解决方案8】:

              我确实遇到了这个问题,所以我做了一个快速简单的方法来尝试消除这个问题。

                  static void Clear(string text, int x, int y)
                  {
                      char[] textChars = text.ToCharArray();
                      string newText = "";
                      //Converts the string you just wrote into a blank string
                      foreach(char c in textChars)
                      {
                          text = text.Replace(c, ' ');
                      }
                      
                      newText = text;
              
                      //Sets the cursor position
                      Console.SetCursorPosition(x, y);
              
                      //Writes the blank string over the old string
                      Console.WriteLine(newText);
              
                      //Resets cursor position
                      Console.SetCursorPosition(0, 0);
                  }
              

              它实际上工作得非常好,我希望它对你有用!

              【讨论】:

              • 为什么不newText = new String(' ', text.Length); 来避免循环和替换操作?
              【解决方案9】:

              朴素的方法,但对于简单的应用程序是有效的:

                  protected string clearBuffer = null; // Clear this if window size changes
                  protected void ClearConsole()
                  {
                      if (clearBuffer == null)
                      {
                          var line = "".PadLeft(Console.WindowWidth, ' ');
                          var lines = new StringBuilder();
              
                          for (var i = 0; i < Console.WindowHeight; i++)
                          {
                              lines.AppendLine(line);
                          }
              
                          clearBuffer = lines.ToString();
                      }
              
                      Console.SetCursorPosition(0, 0);
                      Console.Write(clearBuffer);
                      Console.SetCursorPosition(0, 0);
                  }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-03-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-21
                • 2017-01-29
                • 1970-01-01
                相关资源
                最近更新 更多