控制台应用程序中的进度栏


82

我正在编写一个简单的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
}

输出仅为1943年的[] 0

我在这里做错了什么?

编辑:

在加载和导出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和计数?
eddie_cat 2014年

计数只是增加。xmlCount只是指定文件夹中XML文件的总数DirectoryInfo xmlDir = new DirectoryInfo(xmlFullpath); xmlCount = xmlDir.GetFiles()。Length;
smr5

1
另外,为什么for循环在foreach循环内?似乎在重复同一件事。可能不必保留foreach循环。
eddie_cat 2014年

1
您是否删除了外部foreach循环?更改后的注释位为ExportXml(xmlFilePath[i])
eddie_cat

1
就是这样 我只是有for循环,它的工作原理。
smr5

Answers:


10

这行是你的问题:

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

它是第一次工作,而我在另一个地方以相同的方式在做同样的事情,这导致了循环。它永远不会停止。我更新了我的帖子。你可以看看吗?谢谢。
smr5

您更新了什么?在我看来,我想念的是什么?
eddie_cat 2014年

195

我也在寻找控制台进度栏。我找不到能满足我需求的产品,所以我决定自己动手做。单击此处获取源代码(MIT许可证)。

动画进度条

特征:

  • 与重定向输出一起使用

    如果您重定向控制台应用程序(例如Program.exe > myfile.txt)的输出,则大多数实现都会崩溃,并发生异常。这是因为,Console.CursorLeft并且Console.SetCursorPosition()不支持重定向输出。

  • 机具 IProgress<double>

    这使您可以将进度条与异步操作一起使用,这些异步操作报告进度为[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.");

3
这看起来很整洁!您是否考虑向其添加诸如MIT之类的OSS许可证?choicealicense.com
丹尼尔(Daniel Plaisted)

2
好主意。做到了。
Daniel Wolf

@DanielWolf您如何通过更改CursorPosition获得Console.Write的?
JJS

1
@knocte:在生产代码中,我当然会的。这里的目标是使示例尽可能简洁,而不会分散相关部分的注意力。
丹尼尔·沃尔夫,

8
gif很吸引人。
雷阳

16

我知道这是一个旧的线程,对自我推广表示歉意,但是我最近在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软件包,是因为每当我编写build和ops控制台脚本和实用程序时,我都会不断编写许多常见的控制台例程。

如果要下载多个文件,我过去通常会慢慢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

产生这个

在此处输入图片说明

您还可以在窗口内创建进度条,就像在窗口上书写一样容易。(连连看)。


这是最好的
Pratik '18


6

我已复制粘贴您的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


5

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

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
}

除了删除之前的控制台输出(如之前的任何文本)而且不会在其后移到新行之外,此方法大体上是....
David Shnayder

5

我创建了这个与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();
    }
}

0

我只是偶然发现了这个线程在寻找其他东西,我以为我会放下我在一起的代码,该代码使用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)
    {
    }

这是输出示例: 在此处输入图片说明

希望它能对某人有所帮助!


2
@regisbsb那些不是进度条,好像他检查了一部分文件名:)我知道,我一开始也被自己弄糊涂了。
Silkfire '16

-1

我还是一个新手,C#但我相信以下内容可能会有所帮助。

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++;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.