当 GUI 线程繁忙(绘图)时,不会触发 C# 事件.如何解决
本文关键字:解决 事件 何解决 线程 GUI 绘图 | 更新日期: 2023-09-27 18:37:16
我的程序的简短介绍(已解决)
<小时 />我的程序正在拍摄图像并显示一个又一个瓷砖(猜谜游戏)。我决定(手动)淡入每个瓷砖。由于我需要用户能够与 GUI 交互,因此我执行计算和线程阻塞操作,例如在另一个线程上Thread.Sleep
。我还向图片框添加了一个 OnClickEvent(它覆盖要显示的图像)。=>如果有人猜到了图像,用户可以单击图片框以完全显示图像。(对于淡入淡出,我正在剪切图片框的一个区域,然后用颜色清除它。颜色的 alpha 值会逐步减小,直到完全透明。然后我去下一个区域。
传入问题
<小时 />每次迭代后,我需要刷新图片框,以便它显示新的"情况"。因此,我必须在 GUI 线程上调用该操作。现在,如果每次刷新之间的时间变得太短,例如 10 毫秒,则 GUI 似乎忙于刷新/绘制图像,以至于不再触发我的 OnClickEvent。
显示功能
<小时 />public void Reveal(int step, int intervalFading, int intervalNextTile)
{
StopThread = false; // Changed by the OnClickEvent
Graphics grx = Graphics.FromImage(Overlay.Image);
step = 255 / step;
foreach (RectangleF R in AreasShuffled)
{
grx.Clip = new Region(R);
for (int i = 255; i >= 0; i-=step) //Fading out loop
{
Thread.Sleep(intervalFading); //if intervalFading < 15 GUI is too busy
if (StopThread) //Condition if someone guessed correctly
{
grx.ResetClip();
grx.Clear(Color.FromArgb(0, 0, 0, 0)); //revealing the image
ParentControl.BeginInvoke((Action)(() => Overlay.Refresh()));
grx.Dispose();
return;
}
grx.Clear(Color.FromArgb(i, 0, 0, 0)); //Clearing region
ParentControl.BeginInvoke((Action)(() => Overlay.Refresh())); //Redrawing image
}
grx.Clear(Color.FromArgb(0, 0, 0, 0));
ParentControl.BeginInvoke((Action)(() => Overlay.Refresh()));
Thread.Sleep(intervalNextTile);
}
grx.ResetClip();
grx.Clear(Color.FromArgb(0, 0, 0, 0));
ParentControl.BeginInvoke((Action)(() => Overlay.Refresh()));
grx.Dispose();
}
溶液
<小时 />按照建议,我使用了异步任务。这是更新的功能。(是的,我没有更新 grx.dispose() ^^)
public async Task Reveal(int step, int intervalFading, int intervalNextTile)
{
taskIsRunning = true;
stopTask = false; // Changed by the OnClickEvent
Graphics grx = Graphics.FromImage(Overlay.Image);
step = 255 / step;
foreach (RectangleF R in AreasShuffled)
{
grx.Clip = new Region(R);
for (int i = 255; i >= 0; i -= step)
{
await Task.Delay(intervalFading);
if (stopTask)
{
grx.ResetClip();
grx.Clear(Color.FromArgb(0, 0, 0, 0));
Overlay.Refresh();
grx.Dispose();
taskIsRunning = false;
return;
}
grx.Clear(Color.FromArgb(i, 0, 0, 0));
Overlay.Refresh();
}
grx.Clear(Color.FromArgb(0, 0, 0, 0));
Overlay.Refresh();
await Task.Delay(intervalNextTile);
}
grx.ResetClip();
grx.Clear(Color.FromArgb(0, 0, 0, 0));
Overlay.Refresh();
grx.Dispose();
taskIsRunning = false;
}
以及检查任务是否正在运行的调用函数
private void pictureBoxOverlay_Click(object sender, EventArgs e)
{
if (UCM != null && UCM.taskIsRunning) //if it is running the function is being notified
{ //and reveals the image.
UCM.stopTask = true;
}
else //makes sure that the user has to click again to start with the next image
{
if (index < Images.Count - 1)
{
PrepareNextImage();
UCM.Reveal(Properties.Settings.Default.steps, Properties.Settings.Default.fadeInterval, Properties.Settings.Default.nextTileInterval);
}
else
MessageBox.Show("End of presentation.");
}
}
感谢您的帮助;)
这里有一个简短的答案。如果希望 UI 响应,请不要在 UI 线程上调用
Thread.Sleep
更新
您似乎没有运行 UI 线程的动画代码。好东西!那么问题可能出在哪里呢?我怀疑每秒多次调用 4 次 BeginInvoke
导致应用程序消息泵充斥着调用事件,并在为它们提供服务时延迟 GUI 更新。
通过减少调用次数来解决此问题。在每个间隔的单个调用中执行所有更新。
这个简短的示例在每个间隔仅调用一次调用上下文。应从 UI 调用它。
async Task Animate(Control control, int interval)
{
while(true)
{
// this line causes the method to pause by queueing
// everything after await similarly to `BeginInvoke`
await Task.Delay(interval);
// all of this still happens on the UI thread
// increment control properties here
// check to see if the animation should end.
if (END STATE IS MET)
{
return;
}
}
}
作为旁注 - 您grx.Dispose
多次打电话。最好将整个代码块包装在 using(grx){ }
中。这仍然适用于异步!如何?最黑暗的魔法。