I'm building a UI for a program, and I can't figure out why my progress bar won't become visible after the convert button is clicked.
我正在为一个程序构建一个UI,我无法弄清楚为什么我的进度条在单击转换按钮后不会显示。
private void convertButton_Click(object sender, EventArgs e)
{
toolStripProgressBar.Visible = true;
...
toolStripProgressBar.Visible = false;
}
I ran into a similar problem with tkinter in Python, and I had to call a function to update the idle tasks. Is there a way to do this with windows forms without using threads?
我在Python中遇到了与tkinter类似的问题,我不得不调用一个函数来更新空闲任务。有没有办法在不使用线程的情况下使用Windows窗体执行此操作?
Edit: On a side note, this is a progress bar in a toolStrip that also contains a label that gets updated with status bar text. Is there any way to get the label on the left side and the progress bar on the other instead of right next to each other on the left?
编辑:在旁注中,这是toolStrip中的进度条,其中还包含一个使用状态栏文本更新的标签。有没有办法让左边的标签和另一边的进度条而不是左边的另一个?
7 个解决方案
#1
Well, there is a way to do this without using threads (Application.DoEvents
) but I strongly recommend against you using it. Re-entrancy is nasty, and you really don't want the UI thread tied up at all.
好吧,有一种方法可以在不使用线程的情况下执行此操作(Application.DoEvents),但我强烈建议您不要使用它。 Re-entrancy是令人讨厌的,你真的不希望UI线程被束缚。
Use BackgroundWorker
instead - it's easy, and it's pretty much designed for progress bars. It takes the hassle out of using a separate thread and reporting progress back to the UI thread. No need for Control.Invoke
etc - it takes care of that for you.
使用BackgroundWorker - 它很简单,而且它几乎是为进度条而设计的。无需使用单独的线程并将进度报告回UI线程。不需要Control.Invoke等 - 它会照顾你。
There are lots of tutorials for BackgroundWorker
- it shouldn't take you too long to get going with it.
有很多关于BackgroundWorker的教程 - 它不应该花太长时间来使用它。
#2
You need to execute your process in a thread separate from the UI thread, and then have it periodically report back to the UI thread with it's progress. If your convert operation is working inside the UI thread, it will simply go unresponsive until the operation is complete.
您需要在与UI线程分开的线程中执行您的进程,然后让它定期向UI线程报告其进度。如果您的转换操作在UI线程内部工作,它将无法响应,直到操作完成。
#3
The progress bar can only become visible when it is allowed to paint which occurs during the processing of messages. Message processing cannot normally happen while you are in the middle of an event handler. If you want the progress bar to show up you will have to set the visiblitity to true, start a background thread to complete the work and return from the handler.
只有在允许绘制在处理消息期间发生的进度条时,才能看到进度条。当您处于事件处理程序的中间时,通常不会发生消息处理。如果要显示进度条,则必须将visiblitity设置为true,启动后台线程以完成工作并从处理程序返回。
#4
Per the question you asked for the way to do this WITHOUT threads, that is to do it with Application.DoEvents();. (Just add that call right after setting the progress bar as visible.)
根据你要求的方法来解决没有线程的问题,就是用Application.DoEvents();来做。 (只需在将进度条设置为可见后立即添加该调用。)
Now I do agree with Jon Skeet though that BackgroundWorker is a better way of doing this, but it does use a separate thread.
现在我同意Jon Skeet,虽然BackgroundWorker是一种更好的方法,但它确实使用了一个单独的线程。
#5
I'm guessing the problem is that the "..."
in your code is a long-running process. UI updates are not instantaneous, but must run through the message queue in windows and then be painted to the screen. The queue is pumped and painting takes place in the same thread as your events.
我猜测问题是你的代码中的“...”是一个长期运行的过程。 UI更新不是即时的,但必须在Windows中通过消息队列运行,然后绘制到屏幕上。抽取队列并在与事件相同的线程中进行绘制。
As a result, any long-running tasks need to be moved to a different thread. More than that, your line line of code needs to called after that thread terminates. Otherwise you set the progress bar and then immediately turn it off again.
因此,任何长时间运行的任务都需要移动到不同的线程。更重要的是,您的行代码需要在该线程终止后调用。否则,您设置进度条,然后立即再次将其关闭。
One way to do that is with a BackgroundWorker control.
一种方法是使用BackgroundWorker控件。
#6
Here go two links trying to explain you how things work: (1) (2)
这里有两个链接试图解释你的工作原理:(1)(2)
Now, I will try to explain it as shortly as I can. Most of what happens inside a windows forms application happens in a single thread, usually the same thread Main() runs in. If you open Program.cs, you will see that Main() has a line that looks like the following:
现在,我会尽快解释它。 Windows窗体应用程序中发生的大多数事情都发生在一个线程中,通常是同一个线程Main()运行。如果打开Program.cs,您将看到Main()有一行如下所示:
Application.Run(new Form1());
If you debug the application at any moment and examine the call stack, you will see it will trace back to that Run method. This means that a Windows Forms application is in fact a continuous run of the Run method. So, what is Run doing? Run is eating a message queue through which Windows sends messages to it. Run then dispatches those messages to the correct controls, which themselves do things like add text which corresponds to the key being pressed, redraw themselves, etc. Notice that all this happens during and endless loop running alongside a single thread, so weather you are typing or simply moving the window around, loads of those messages are being passed onto the application, which in turn is processing them and reacting accordingly, all in that single thread. Controls can also send messages to themselves through the queue and even you can place messages in the pump via Control.BeginInvoke. One of the things those controls do is to raise events according to what happens. So, if you click a button, the code you've written to handle that click will ultimately and indirectly be run by the Application.Run method.
如果您随时调试应用程序并检查调用堆栈,您将看到它将追溯到该Run方法。这意味着Windows窗体应用程序实际上是Run方法的连续运行。那么,Run在做什么?运行正在进行消息队列,Windows通过该队列向其发送消息。运行然后将这些消息发送到正确的控件,这些控件本身会执行诸如添加与按下的键相对应的文本,重绘自身等等的操作。请注意,所有这些都发生在无限循环和单个线程一起运行时,所以你要输入的天气或者只是移动窗口,这些消息的负载被传递到应用程序,而应用程序又处理它们并相应地做出反应,所有这些都在该单个线程中。控件还可以通过队列向自己发送消息,甚至可以通过Control.BeginInvoke将消息放入泵中。这些控制措施之一就是根据发生的事情来提升事件。因此,如果单击某个按钮,则您编写的用于处理该单击的代码最终将间接由Application.Run方法运行。
Now, what is happening with your code is that even though you are changing the visible status of your progress bar to visible and then updating its Value, you are then changing its visibility to false, all in the same method. This means that only after you leave the method, will Application.Run() be able to continue iterating and consuming the message queue, effectively asking the progress bar to update its display. When that happens, you've already left the progress bar's visibility to false, the last thing you did before exiting the method. DoEvents() is a quick and dirty workaround to your problem as it reads the messages in the queue and processes them. I don't really feel comfortable using it as it can bring reentrancy problems.
现在,您的代码发生的情况是,即使您将进度条的可见状态更改为可见,然后更新其值,您也可以在同一方法中将其可见性更改为false。这意味着只有在您离开方法之后,Application.Run()才能继续迭代并使用消息队列,从而有效地要求进度条更新其显示。当发生这种情况时,您已经将进度条的可见性保留为false,这是您在退出方法之前所做的最后一件事。 DoEvents()是一个快速而又脏的解决方法,因为它读取队列中的消息并处理它们。我觉得使用它并不是很舒服,因为它会带来重入问题。
Using threads is a good solution, but I would recommend using a ThreadPool thread instead of a custom thread in this kind of situation, as I tend to use custom threads only in cases where I have a limited number of long lived threads and I need to control their life cycles. The easiest and most practical way to use threads is to use the BackgroundWorker component, even though I would recommend going through the pains of understanding how to do Windows Forms multithreading with delegates if you want to really understand what is going on.
使用线程是一个很好的解决方案,但我建议在这种情况下使用ThreadPool线程而不是自定义线程,因为我倾向于仅在我拥有有限数量的长寿命线程且我需要使用自定义线程的情况下控制他们的生命周期。使用线程的最简单和最实用的方法是使用BackgroundWorker组件,即使我想建议如果你想真正了解正在发生的事情,也要了解如何使用委托进行Windows窗体多线程处理。
#7
My solution is to call refresh on the status strip.
I believe this causes the UI thread to repaint the status strip.
我的解决方案是在状态条上调用refresh。我相信这会导致UI线程重新绘制状态条。
toolStripStatusBar1.PerformStep();
statusStrip1.Refresh();
This is for .NET 4.0. Even though this question is old it was the first I found on googling this issue.
这适用于.NET 4.0。虽然这个问题很老,但这是我在谷歌搜索这个问题时发现的第一个问题。
#1
Well, there is a way to do this without using threads (Application.DoEvents
) but I strongly recommend against you using it. Re-entrancy is nasty, and you really don't want the UI thread tied up at all.
好吧,有一种方法可以在不使用线程的情况下执行此操作(Application.DoEvents),但我强烈建议您不要使用它。 Re-entrancy是令人讨厌的,你真的不希望UI线程被束缚。
Use BackgroundWorker
instead - it's easy, and it's pretty much designed for progress bars. It takes the hassle out of using a separate thread and reporting progress back to the UI thread. No need for Control.Invoke
etc - it takes care of that for you.
使用BackgroundWorker - 它很简单,而且它几乎是为进度条而设计的。无需使用单独的线程并将进度报告回UI线程。不需要Control.Invoke等 - 它会照顾你。
There are lots of tutorials for BackgroundWorker
- it shouldn't take you too long to get going with it.
有很多关于BackgroundWorker的教程 - 它不应该花太长时间来使用它。
#2
You need to execute your process in a thread separate from the UI thread, and then have it periodically report back to the UI thread with it's progress. If your convert operation is working inside the UI thread, it will simply go unresponsive until the operation is complete.
您需要在与UI线程分开的线程中执行您的进程,然后让它定期向UI线程报告其进度。如果您的转换操作在UI线程内部工作,它将无法响应,直到操作完成。
#3
The progress bar can only become visible when it is allowed to paint which occurs during the processing of messages. Message processing cannot normally happen while you are in the middle of an event handler. If you want the progress bar to show up you will have to set the visiblitity to true, start a background thread to complete the work and return from the handler.
只有在允许绘制在处理消息期间发生的进度条时,才能看到进度条。当您处于事件处理程序的中间时,通常不会发生消息处理。如果要显示进度条,则必须将visiblitity设置为true,启动后台线程以完成工作并从处理程序返回。
#4
Per the question you asked for the way to do this WITHOUT threads, that is to do it with Application.DoEvents();. (Just add that call right after setting the progress bar as visible.)
根据你要求的方法来解决没有线程的问题,就是用Application.DoEvents();来做。 (只需在将进度条设置为可见后立即添加该调用。)
Now I do agree with Jon Skeet though that BackgroundWorker is a better way of doing this, but it does use a separate thread.
现在我同意Jon Skeet,虽然BackgroundWorker是一种更好的方法,但它确实使用了一个单独的线程。
#5
I'm guessing the problem is that the "..."
in your code is a long-running process. UI updates are not instantaneous, but must run through the message queue in windows and then be painted to the screen. The queue is pumped and painting takes place in the same thread as your events.
我猜测问题是你的代码中的“...”是一个长期运行的过程。 UI更新不是即时的,但必须在Windows中通过消息队列运行,然后绘制到屏幕上。抽取队列并在与事件相同的线程中进行绘制。
As a result, any long-running tasks need to be moved to a different thread. More than that, your line line of code needs to called after that thread terminates. Otherwise you set the progress bar and then immediately turn it off again.
因此,任何长时间运行的任务都需要移动到不同的线程。更重要的是,您的行代码需要在该线程终止后调用。否则,您设置进度条,然后立即再次将其关闭。
One way to do that is with a BackgroundWorker control.
一种方法是使用BackgroundWorker控件。
#6
Here go two links trying to explain you how things work: (1) (2)
这里有两个链接试图解释你的工作原理:(1)(2)
Now, I will try to explain it as shortly as I can. Most of what happens inside a windows forms application happens in a single thread, usually the same thread Main() runs in. If you open Program.cs, you will see that Main() has a line that looks like the following:
现在,我会尽快解释它。 Windows窗体应用程序中发生的大多数事情都发生在一个线程中,通常是同一个线程Main()运行。如果打开Program.cs,您将看到Main()有一行如下所示:
Application.Run(new Form1());
If you debug the application at any moment and examine the call stack, you will see it will trace back to that Run method. This means that a Windows Forms application is in fact a continuous run of the Run method. So, what is Run doing? Run is eating a message queue through which Windows sends messages to it. Run then dispatches those messages to the correct controls, which themselves do things like add text which corresponds to the key being pressed, redraw themselves, etc. Notice that all this happens during and endless loop running alongside a single thread, so weather you are typing or simply moving the window around, loads of those messages are being passed onto the application, which in turn is processing them and reacting accordingly, all in that single thread. Controls can also send messages to themselves through the queue and even you can place messages in the pump via Control.BeginInvoke. One of the things those controls do is to raise events according to what happens. So, if you click a button, the code you've written to handle that click will ultimately and indirectly be run by the Application.Run method.
如果您随时调试应用程序并检查调用堆栈,您将看到它将追溯到该Run方法。这意味着Windows窗体应用程序实际上是Run方法的连续运行。那么,Run在做什么?运行正在进行消息队列,Windows通过该队列向其发送消息。运行然后将这些消息发送到正确的控件,这些控件本身会执行诸如添加与按下的键相对应的文本,重绘自身等等的操作。请注意,所有这些都发生在无限循环和单个线程一起运行时,所以你要输入的天气或者只是移动窗口,这些消息的负载被传递到应用程序,而应用程序又处理它们并相应地做出反应,所有这些都在该单个线程中。控件还可以通过队列向自己发送消息,甚至可以通过Control.BeginInvoke将消息放入泵中。这些控制措施之一就是根据发生的事情来提升事件。因此,如果单击某个按钮,则您编写的用于处理该单击的代码最终将间接由Application.Run方法运行。
Now, what is happening with your code is that even though you are changing the visible status of your progress bar to visible and then updating its Value, you are then changing its visibility to false, all in the same method. This means that only after you leave the method, will Application.Run() be able to continue iterating and consuming the message queue, effectively asking the progress bar to update its display. When that happens, you've already left the progress bar's visibility to false, the last thing you did before exiting the method. DoEvents() is a quick and dirty workaround to your problem as it reads the messages in the queue and processes them. I don't really feel comfortable using it as it can bring reentrancy problems.
现在,您的代码发生的情况是,即使您将进度条的可见状态更改为可见,然后更新其值,您也可以在同一方法中将其可见性更改为false。这意味着只有在您离开方法之后,Application.Run()才能继续迭代并使用消息队列,从而有效地要求进度条更新其显示。当发生这种情况时,您已经将进度条的可见性保留为false,这是您在退出方法之前所做的最后一件事。 DoEvents()是一个快速而又脏的解决方法,因为它读取队列中的消息并处理它们。我觉得使用它并不是很舒服,因为它会带来重入问题。
Using threads is a good solution, but I would recommend using a ThreadPool thread instead of a custom thread in this kind of situation, as I tend to use custom threads only in cases where I have a limited number of long lived threads and I need to control their life cycles. The easiest and most practical way to use threads is to use the BackgroundWorker component, even though I would recommend going through the pains of understanding how to do Windows Forms multithreading with delegates if you want to really understand what is going on.
使用线程是一个很好的解决方案,但我建议在这种情况下使用ThreadPool线程而不是自定义线程,因为我倾向于仅在我拥有有限数量的长寿命线程且我需要使用自定义线程的情况下控制他们的生命周期。使用线程的最简单和最实用的方法是使用BackgroundWorker组件,即使我想建议如果你想真正了解正在发生的事情,也要了解如何使用委托进行Windows窗体多线程处理。
#7
My solution is to call refresh on the status strip.
I believe this causes the UI thread to repaint the status strip.
我的解决方案是在状态条上调用refresh。我相信这会导致UI线程重新绘制状态条。
toolStripStatusBar1.PerformStep();
statusStrip1.Refresh();
This is for .NET 4.0. Even though this question is old it was the first I found on googling this issue.
这适用于.NET 4.0。虽然这个问题很老,但这是我在谷歌搜索这个问题时发现的第一个问题。