不要使用DoEvents()。使用线程!
这个口头禅在互联网上漫游,包括SO。 Okey所以我创建了一个简短的概念证明,我试图只使用Threads。所以基本上按钮应该触发向下移动蓝框。
它在一个单独的线程中运行YET窗体没有响应(我无法移动它或再次单击该按钮),直到它完成向下移动。
问题是,我搞砸了什么?如果我不应该使用DoEvents(),那么呢? (如果您取消注释Application.DoEvents()
行,它就会响应)。
CODE
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace doevents
{
public partial class Main : Form
{
// Use static so that I don't have to pass them over and over
public static TableLayoutPanel movingBox;
public static Form mainForm;
// Initialize
public Main()
{
InitializeComponent();
Main.mainForm = this;
Main.movingBox = tableLayoutPanel1;
}
// Button that runs thread which will move the blue box down
private void button1_Click(object sender, EventArgs e)
{
new Thread(
new ThreadStart(
() =>
{
SlideBoxDown();
}
)
).Start();
}
// Delegate
delegate void SlideBoxDownCallback();
// Slide box down
private static void SlideBoxDown()
{
if (Main.movingBox.InvokeRequired)
{
SlideBoxDownCallback d = new SlideBoxDownCallback(SlideBoxDown);
Main.mainForm.Invoke(d, new object[] { });
}
else
{
for (int i = 0; i < 20; i++)
{
Main.movingBox.Location = new Point(Main.movingBox.Location.X, Main.movingBox.Location.Y + 2);
Thread.Sleep(100);
//Application.DoEvents();
}
}
}
}
}
应用程序布局
答案 0 :(得分:3)
您仍然在主线程上调用Thread.Sleep(100)
。因为SlideBoxDown
正在将自己调用回GUI线程。由于这是Thread中唯一发生的事情,因此您的程序基本上是单线程的。
for (int i = 0; i < 20; i++)
根本不是控制动画的好方法
使用计时器。
关于(如果取消注释Application.DoEvents()行,它会响应) - 正确,但请尝试在该动画中间关闭Form。你不会喜欢它。
答案 1 :(得分:1)
您创建一个新线程,然后在您从新线程调用的方法中,您将编组回UI线程,然后在UI线程中运行所有其余代码。这很好地打破了首先开始新线程的目的。
要每隔X个时间间隔执行一次操作,在这种情况下每100毫秒移动一个对象,请使用Timer
。
答案 2 :(得分:-1)
对应用程序UI的更改需要UI线程。这就是它的全部内容。您的代码正在创建后台线程,其唯一目的是将消息传递给应用程序的消息泵,该消息由UI线程使用。因此,“else”子句中发生的所有事情,包括Thread.Sleep()调用,都在主线程上执行。
我会重构这段代码,用Timer替换Sleep。 Timer将在后台线程上运行,并且可以在其“Tick”事件(间隔100ms)上触发处理程序,该事件可以将矩形移动一个增量。这样,绘图基于在UI线程上消费消息而发生,但是等待是由后台线程完成的,从而保持UI响应。