【问题标题】:Progress bar in console application控制台应用程序中的进度条
【发布时间】:2014-09-15 03:02:05
【问题描述】:

我正在编写一个简单的 c# 控制台应用程序,用于将文件上传到 sftp 服务器。但是,文件量很大。我想显示上传文件的百分比,或者仅显示要上传的文件总数中已上传​​的文件数。

首先,我得到所有文件和文件总数。

string[] filePath = Directory.GetFiles(path, "*");
totalCount = filePath.Length;

然后我遍历文件并在foreach循环中一一上传。

foreach(string file in filePath)
{
    string FileName = Path.GetFileName(file);
    //copy the files
    oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
    //Console.WriteLine("Uploading file..." + FileName);
    drawTextProgressBar(0, totalCount);
}

在 foreach 循环中,我有一个进度条,但我遇到了问题。无法正常显示。

private static void drawTextProgressBar(int progress, int total)
{
    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = 32;
    Console.Write("]"); //end
    Console.CursorLeft = 1;
    float onechunk = 30.0f / total;

    //draw filled part
    int position = 1;
    for (int i = 0; i < onechunk * progress; i++)
    {
        Console.BackgroundColor = ConsoleColor.Gray;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw unfilled part
    for (int i = position; i <= 31 ; i++)
    {
        Console.BackgroundColor = ConsoleColor.Green;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw totals
    Console.CursorLeft = 35;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Write(progress.ToString() + " of " + total.ToString() + "    "); //blanks at the end remove any excess
}

输出只是 [ ] 0 out of 1943

我在这里做错了什么?

编辑:

我试图在加载和导出 XML 文件时显示进度条。然而,它正在经历一个循环。完成第一轮后进入第二轮,以此类推。

string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
Console.WriteLine("Loading XML files...");
foreach (string file in xmlFilePath)
{
     for (int i = 0; i < xmlFilePath.Length; i++)
     {
          //ExportXml(file, styleSheet);
          drawTextProgressBar(i, xmlCount);
          count++;
     }
 }

它永远不会离开 for 循环...有什么建议吗?

【问题讨论】:

  • 什么是 xmlCount 和 count?
  • 计数只是递增。 xmlCount 只是指定文件夹 DirectoryInfo 中 XML 文件的总数 xmlDir = new DirectoryInfo(xmlFullpath); xmlCount = xmlDir.GetFiles().Length;
  • 另外,为什么 for 循环在 foreach 循环中?它似乎在迭代同一件事。可能没有必要保留 foreach 循环。
  • 您是否删除了外部 foreach 循环?将注释位更改为ExportXml(xmlFilePath[i])
  • 就是这样。我只有 for 循环,它可以工作。

标签: c# console progress


【解决方案1】:

我也在寻找控制台进度条。我没有找到一个能满足我需要的,所以我决定自己动手。 Click here for the source code(MIT 许可证)。

特点:

  • 适用于重定向输出

    如果您重定向控制台应用程序的输出(例如,Program.exe &gt; myfile.txt),大多数实现将崩溃并出现异常。那是因为Console.CursorLeftConsole.SetCursorPosition() 不支持重定向输出。

  • 实现IProgress&lt;double&gt;

    这允许您将进度条与异步操作一起使用,以报告 [0..1] 范围内的进度。

  • 线程安全

  • 快速

    Console 类因其糟糕的性能而臭名昭著。对它的调用过多,您的应用程序就会变慢。这个类每秒只执行 8 次调用,无论您多久报告一次进度更新。

像这样使用它:

Console.Write("Performing some task... ");
using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
        progress.Report((double) i / 100);
        Thread.Sleep(20);
    }
}
Console.WriteLine("Done.");

【讨论】:

  • 这看起来很整洁!你会考虑给它添加一个 OSS 许可证,比如 MIT 吗? choosealicense.com
  • 好主意。做到了。
  • @DanielWolf 您是如何通过更改 CursorPosition 获得 Console.Write 的?
  • @knocte:在生产代码中,我当然会。这里的目标是使示例尽可能简洁,而不是分散相关部分的注意力。
  • 动图很吸引人。
【解决方案2】:

我知道这是一个旧线程,并为自我宣传道歉,但是我最近在 nuget Goblinfactory.Konsole 上编写了一个开源控制台库,支持线程安全的多进度条,这可能会帮助任何刚接触此页面的人需要一个不会阻塞主线程的。

这与上面的答案有些不同,因为它允许您并行启动下载和任务并继续执行其他任务;

干杯,希望对你有帮助

一个

var t1 = Task.Run(()=> {
   var p = new ProgressBar("downloading music",10);
   ... do stuff
});

var t2 = Task.Run(()=> {
   var p = new ProgressBar("downloading video",10);
   ... do stuff
});

var t3 = Task.Run(()=> {
   var p = new ProgressBar("starting server",10);
   ... do stuff .. calling p.Refresh(n);
});

Task.WaitAll(new [] { t1,t2,t3 }, 20000);
Console.WriteLine("all done.");

给你这种类型的输出

nuget 包还包括用于写入控制台窗口部分的实用程序,具有完整的裁剪和包装支持,以及 PrintAt 和各种其他有用的类。

我编写了 nuget 包,因为每当我编写构建和操作控制台脚本和实用程序时,我都会不断地编写许多常见的控制台例程。

如果我正在下载多个文件,我习惯于在每个线程上慢慢地将Console.Write 放到屏幕上,并尝试各种技巧以使读取屏幕上的交错输出更容易阅读,例如不同的颜色或数字。我最终编写了窗口库,以便可以简单地将来自不同线程的输出打印到不同的窗口,并且它减少了我的实用程序脚本中的大量样板代码。

比如这段代码,

        var con = new Window(200,50);
        con.WriteLine("starting client server demo");
        var client = new Window(1, 4, 20, 20, ConsoleColor.Gray, ConsoleColor.DarkBlue, con);
        var server = new Window(25, 4, 20, 20, con);
        client.WriteLine("CLIENT");
        client.WriteLine("------");
        server.WriteLine("SERVER");
        server.WriteLine("------");
        client.WriteLine("<-- PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.DarkYellow, "--> PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.Red, "<-- 404|Not Found|some long text to show wrapping|");
        client.WriteLine(ConsoleColor.Red, "--> 404|Not Found|some long text to show wrapping|");

        con.WriteLine("starting names demo");
        // let's open a window with a box around it by using Window.Open
        var names = Window.Open(50, 4, 40, 10, "names");
        TestData.MakeNames(40).OrderByDescending(n => n).ToList()
             .ForEach(n => names.WriteLine(n));

        con.WriteLine("starting numbers demo");
        var numbers = Window.Open(50, 15, 40, 10, "numbers", 
              LineThickNess.Double,ConsoleColor.White,ConsoleColor.Blue);
        Enumerable.Range(1,200).ToList()
             .ForEach(i => numbers.WriteLine(i.ToString())); // shows scrolling

产生这个

您还可以在窗口内创建进度条,就像在窗口中写入一样容易。 (混搭)。

【讨论】:

  • 这是最好的
【解决方案3】:

你可能想试试https://www.nuget.org/packages/ShellProgressBar/

我刚刚偶然发现了这个进度条实现 - 它是跨平台的,真的易于使用,可配置性强,开箱即用。

只是分享,因为我非常喜欢它。

【讨论】:

    【解决方案4】:

    这一行是你的问题:

    drawTextProgressBar(0, totalCount);
    

    您是说每次迭代的进度都为零,这应该增加。也许改用 for 循环。

    for (int i = 0; i < filePath.length; i++)
    {
        string FileName = Path.GetFileName(filePath[i]);
        //copy the files
        oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
        //Console.WriteLine("Uploading file..." + FileName);
        drawTextProgressBar(i, totalCount);
    }
    

    【讨论】:

    • 它第一次工作,我在另一个地方做同样的事情,它导致了一个循环。它永远不会停止。我更新了我的帖子。你能看看吗?谢谢。
    • 你更新了什么?对我来说看起来一样,我错过了什么?
    【解决方案5】:

    我已经复制粘贴了您的ProgressBar 方法。因为您的错误在循环中,正如所提到的已接受答案。但是ProgressBar 方法也有一些语法错误。这是工作版本。稍作修改。

    private static void ProgressBar(int progress, int tot)
    {
        //draw empty progress bar
        Console.CursorLeft = 0;
        Console.Write("["); //start
        Console.CursorLeft = 32;
        Console.Write("]"); //end
        Console.CursorLeft = 1;
        float onechunk = 30.0f / tot;
    
        //draw filled part
        int position = 1;
        for (int i = 0; i < onechunk * progress; i++)
        {
            Console.BackgroundColor = ConsoleColor.Green;
            Console.CursorLeft = position++;
            Console.Write(" ");
        }
    
        //draw unfilled part
        for (int i = position; i <= 31; i++)
        {
            Console.BackgroundColor = ConsoleColor.Gray;
            Console.CursorLeft = position++;
            Console.Write(" ");
        }
    
        //draw totals
        Console.CursorLeft = 35;
        Console.BackgroundColor = ConsoleColor.Black;
        Console.Write(progress.ToString() + " of " + tot.ToString() + "    "); //blanks at the end remove any excess
    }
    

    请注意@Daniel-wolf 有更好的方法:https://stackoverflow.com/a/31193455/169714

    【讨论】:

      【解决方案6】:

      我很喜欢原始海报的进度条,但发现它在某些进度/总项目组合下无法正确显示进度。例如,以下内容无法正确绘制,在进度条末尾留下一个额外的灰色块:

      drawTextProgressBar(4114, 4114)
      

      我重新编写了一些绘图代码以消除不必要的循环,从而解决了上述问题并加快了速度:

      public static void drawTextProgressBar(string stepDescription, int progress, int total)
      {
          int totalChunks = 30;
      
          //draw empty progress bar
          Console.CursorLeft = 0;
          Console.Write("["); //start
          Console.CursorLeft = totalChunks + 1;
          Console.Write("]"); //end
          Console.CursorLeft = 1;
      
          double pctComplete = Convert.ToDouble(progress) / total;
          int numChunksComplete = Convert.ToInt16(totalChunks * pctComplete);
      
          //draw completed chunks
          Console.BackgroundColor = ConsoleColor.Green;
          Console.Write("".PadRight(numChunksComplete));
      
          //draw incomplete chunks
          Console.BackgroundColor = ConsoleColor.Gray;
          Console.Write("".PadRight(totalChunks - numChunksComplete));
      
          //draw totals
          Console.CursorLeft = totalChunks + 5;
          Console.BackgroundColor = ConsoleColor.Black;
      
          string output = progress.ToString() + " of " + total.ToString();
          Console.Write(output.PadRight(15) + stepDescription); //pad the output so when changing from 3 to 4 digits we avoid text shifting
      }
      

      【讨论】:

      • 这通常有效,只是它会像之前的任何文本一样删除以前的控制台输出,并且在之后不会换行......
      【解决方案7】:

      我创建了这个与 System.Reactive 一起使用的方便的类。我希望你觉得它足够可爱。

      public class ConsoleDisplayUpdater : IDisposable
      {
          private readonly IDisposable progressUpdater;
      
          public ConsoleDisplayUpdater(IObservable<double> progress)
          {
              progressUpdater = progress.Subscribe(DisplayProgress);
          }
      
          public int Width { get; set; } = 50;
      
          private void DisplayProgress(double progress)
          {
              if (double.IsNaN(progress))
              {
                  return;
              }
      
              var progressBarLenght = progress * Width;
              System.Console.CursorLeft = 0;
              System.Console.Write("[");
              var bar = new string(Enumerable.Range(1, (int) progressBarLenght).Select(_ => '=').ToArray());
      
              System.Console.Write(bar);
      
              var label = $@"{progress:P0}";
              System.Console.CursorLeft = (Width -label.Length) / 2;
              System.Console.Write(label);
              System.Console.CursorLeft = Width;
              System.Console.Write("]");
          }
      
          public void Dispose()
          {
              progressUpdater?.Dispose();
          }
      }
      

      【讨论】:

        【解决方案8】:

        C# 中的控制台进度条(完整代码 - 只需复制并粘贴到您的 IDE)

        class ConsoleUtility
        {
            const char _block = '■';
            const string _back = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
            const string _twirl = "-\\|/";
        
            public static void WriteProgressBar(int percent, bool update = false)
            {
                if (update)
                    Console.Write(_back);
                Console.Write("[");
                var p = (int)((percent / 10f) + .5f);
                for (var i = 0; i < 10; ++i)
                {
                    if (i >= p)
                        Console.Write(' ');
                    else
                        Console.Write(_block);
                }
                Console.Write("] {0,3:##0}%", percent);
            }
        
            public static void WriteProgress(int progress, bool update = false)
            {
                if (update)
                    Console.Write("\b");
                Console.Write(_twirl[progress % _twirl.Length]);
            }
        }
        
        
        //This is how to call in your main method
        static void Main(string[] args)
        {
            ConsoleUtility.WriteProgressBar(0);
            for (var i = 0; i <= 100; ++i)
            {
               ConsoleUtility.WriteProgressBar(i, true);
               Thread.Sleep(50);
            }
        
            Console.WriteLine();
            ConsoleUtility.WriteProgress(0);
            for (var i = 0; i <= 100; ++i)
            {
                ConsoleUtility.WriteProgress(i, true);
                Thread.Sleep(50);
            }
        }
        

        【讨论】:

          【解决方案9】:

          我只是偶然发现这个线程正在寻找其他东西,我想我会放弃我放在一起的代码,它使用 DownloadProgressChanged 下载文件列表。我发现这非常有用,因此我不仅可以看到进度,还可以看到文件通过时的实际大小。希望它可以帮助某人!

          public static bool DownloadFile(List<string> files, string host, string username, string password, string savePath)
              {
                  try
                  {
                      //setup FTP client
          
                      foreach (string f in files)
                      {
                          FILENAME = f.Split('\\').Last();
                          wc.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                          wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
                          wc.DownloadFileAsync(new Uri(host + f), savePath + f);
                          while (wc.IsBusy)
                              System.Threading.Thread.Sleep(1000);
                          Console.Write("  COMPLETED!");
                          Console.WriteLine();
                      }
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine(ex.ToString());
                      return false;
                  }
                  return true;
              }
          
              private static void ProgressChanged(object obj, System.Net.DownloadProgressChangedEventArgs e)
              {
                  Console.Write("\r --> Downloading " + FILENAME +": " + string.Format("{0:n0}", e.BytesReceived / 1000) + " kb");
              }
          
              private static void Completed(object obj, AsyncCompletedEventArgs e)
              {
              }
          

          以下是输出示例:

          希望它对某人有所帮助!

          【讨论】:

          • @regisbsb 那些不是进度条,看起来他审查了部分文件名:) 我知道,一开始我也被骗了。
          【解决方案10】:

          根据以上所有帖子,我做了一个改进版。

          1. 没有跳跃光标。现在看不见了。

          2. 改进的性能(成本是原来的 1/5~1/10 倍)。

          3. 基于接口。很容易转移到其他地方。

             public class ConsoleProgressBar : IProgressBar
             {
                 private const ConsoleColor ForeColor = ConsoleColor.Green;
                 private const ConsoleColor BkColor = ConsoleColor.Gray;
                 private const int DefaultWidthOfBar = 32;
                 private const int TextMarginLeft = 3;
            
                 private readonly int _total;
                 private readonly int _widthOfBar;
            
                 public ConsoleProgressBar(int total, int widthOfBar = DefaultWidthOfBar)
                 {
                     _total = total;
                     _widthOfBar = widthOfBar;
                 }
            
                 private bool _intited;
                 public void Init()
                 {
                     _lastPosition = 0;
            
                     //Draw empty progress bar
                     Console.CursorVisible = false;
                     Console.CursorLeft = 0;
                     Console.Write("["); //start
                     Console.CursorLeft = _widthOfBar;
                     Console.Write("]"); //end
                     Console.CursorLeft = 1;
            
                     //Draw background bar
                     for (var position = 1; position < _widthOfBar; position++) //Skip the first position which is "[".
                     {
                         Console.BackgroundColor = BkColor;
                         Console.CursorLeft = position;
                         Console.Write(" ");
                     }
                 }
            
                 public void ShowProgress(int currentCount)
                 {
                     if (!_intited)
                     {
                         Init();
                         _intited = true;
                     }
                     DrawTextProgressBar(currentCount);
                 }
            
                 private int _lastPosition;
            
                 public void DrawTextProgressBar(int currentCount)
                 {
                     //Draw current chunk.
                     var position = currentCount * _widthOfBar / _total;
                     if (position != _lastPosition)
                     {
                         _lastPosition = position;
                         Console.BackgroundColor = ForeColor;
                         Console.CursorLeft = position >= _widthOfBar ? _widthOfBar - 1 : position;
                         Console.Write(" ");
                     }
            
                     //Draw totals
                     Console.CursorLeft = _widthOfBar + TextMarginLeft;
                     Console.BackgroundColor = ConsoleColor.Black;
                     Console.Write(currentCount + " of " + _total + "    "); //blanks at the end remove any excess
                 }
             }
            
             public interface IProgressBar
             {
                 public void ShowProgress(int currentCount);
             }
            

          还有一些测试代码:

                  var total = 100;
                  IProgressBar progressBar = new ConsoleProgressBar(total);
                  for (var i = 0; i <= total; i++)
                  {
                      progressBar.ShowProgress(i);
                      Thread.Sleep(50);
                  }
          
                  Thread.Sleep(500);
                  Console.Clear();
          
                  total = 9999;
                  progressBar = new ConsoleProgressBar(total);
                  for (var i = 0; i <= total; i++)
                  {
                      progressBar.ShowProgress(i);
                  }
          

          【讨论】:

            【解决方案11】:

            我对@9​​87654321@ 还有些陌生,但我相信以下内容可能会有所帮助。

            string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
            Console.WriteLine("Loading XML files...");
            int count = 0;
            foreach (string file in xmlFilePath)
            {
                //ExportXml(file, styleSheet);
                drawTextProgressBar(count, xmlCount);
                count++;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-30
              • 1970-01-01
              相关资源
              最近更新 更多