1

我正在为程序构建 UI,但我无法弄清楚为什么在单击转换按钮后我的进度条不会变得可见。

private void convertButton_Click(object sender, EventArgs e)
{
    toolStripProgressBar.Visible = true;

    ...

    toolStripProgressBar.Visible = false;
}

我在 Python 中遇到了与 tkinter 类似的问题,我不得不调用一个函数来更新空闲任务。有没有办法在不使用线程的情况下使用 Windows 窗体来做到这一点?

编辑:在旁注中,这是一个工具条中的进度条,它还包含一个标签,该标签使用状态栏文本进行更新。有没有办法让左侧的标签和进度条在另一侧而不是在左侧彼此相邻?

4

7 回答 7

4

好吧,有一种方法可以在不使用线程 ( Application.DoEvents) 的情况下做到这一点,但我强烈建议您不要使用它。重入是讨厌的,你真的不希望 UI 线程被捆绑。

改为使用BackgroundWorker- 这很简单,而且它几乎为进度条设计的。它消除了使用单独线程并将进度报告回 UI 线程的麻烦。不需要Control.Invoke等-它会为您处理。

很多教程BackgroundWorker- 不应该花太长时间来学习它。

于 2009-04-02T19:33:11.667 回答
2

根据您提出的在没有线程的情况下执行此操作的方法的问题,即使用 Application.DoEvents(); 进行操作。(只需在将进度条设置为可见后立即添加该调用。)

现在我同意 Jon Skeet 的观点,尽管 BackgroundWorker 是一种更好的方法,但它确实使用了单独的线程。

于 2009-04-02T19:33:42.323 回答
1

您需要在与 UI 线程分开的线程中执行您的进程,然后让它定期向 UI 线程报告其进度。如果您的转换操作在 UI 线程内工作,它只会在操作完成之前无响应。

于 2009-04-02T19:33:49.187 回答
1

我猜问题在于"..."您的代码中的 是一个长期运行的过程。UI 更新不是即时的,而是必须通过 windows 中的消息队列运行,然后绘制到屏幕上。队列被抽出,绘画发生在与您的事件相同的线程中。

因此,任何长时间运行的任务都需要移动到不同的线程。不仅如此,您的代码行需要在该线程终止后调用。否则,您设置进度条,然后立即再次将其关闭。

一种方法是使用 BackgroundWorker 控件。

于 2009-04-02T19:34:24.240 回答
1

进度条只有在消息处理过程中允许绘制时才可见。当您处于事件处理程序的中间时,消息处理通常不会发生。如果要显示进度条,则必须将可见性设置为 true,启动后台线程以完成工作并从处理程序返回。

于 2009-04-02T19:35:26.983 回答
1

这里有两个链接试图解释事情是如何工作的: (1) (2)

现在,我将尝试尽快解释它。在 Windows 窗体应用程序中发生的大部分事情都发生在单个线程中,通常 Main() 运行在同一个线程中。如果打开 Program.cs,您会看到 Main() 有一行如下所示:

Application.Run(new Form1());

如果您在任何时候调试应用程序并检查调用堆栈,您将看到它将追溯到该 Run 方法。这意味着 Windows 窗体应用程序实际上是 Run 方法的连续运行。那么,Run在做什么呢?Run 正在吃一个消息队列,Windows 通过该消息队列向它发送消息。Run 然后将这些消息发送到正确的控件,这些控件本身会执行添加与按下的键相对应的文本、重绘自身等操作。请注意,所有这些都发生在与单个线程一起运行的无限循环期间,因此您正在输入天气或者简单地移动窗口,这些消息的负载被传递到应用程序,应用程序反过来处理它们并做出相应的反应,所有这些都在单个线程中。控件还可以通过队列向自己发送消息,甚至您可以通过 Control.BeginInvoke 将消息放入泵中。这些控件所做的其中一件事是根据发生的情况引发事件。因此,如果您单击一个按钮,您为处理该单击而编写的代码将最终间接地由 Application.Run 方法运行。

现在,您的代码正在发生的事情是,即使您将进度条的可见状态更改为可见,然后更新其值,您也将其可见性更改为 false,所有这些都使用相同的方法。这意味着只有在您离开该方法后,Application.Run() 才能继续迭代和消费消息队列,有效地要求进度条更新其显示。发生这种情况时,您已经将进度条的可见性设置为 false,这是您在退出方法之前所做的最后一件事。DoEvents() 是一种快速而肮脏的解决方法,因为它读取队列中的消息并处理它们。我真的不喜欢使用它,因为它会带来重入问题。

使用线程是一个很好的解决方案,但我建议在这种情况下使用 ThreadPool 线程而不是自定义线程,因为我倾向于仅在我的长寿命线程数量有限并且我需要的情况下使用自定义线程控制它们的生命周期。使用线程的最简单和最实用的方法是使用 BackgroundWorker 组件,尽管如果您想真正了解发生了什么,我建议您通过了解如何使用委托进行 Windows 窗体多线程处理的痛苦。

于 2009-04-02T20:39:59.230 回答
0

我的解决方案是在状态条上调用刷新。
我相信这会导致 UI 线程重新绘制状态条。

toolStripStatusBar1.PerformStep();
statusStrip1.Refresh();

这适用于 .NET 4.0。尽管这个问题很老,但这是我在谷歌搜索这个问题时发现的第一个问题。

于 2017-02-12T20:37:13.913 回答