当 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.");
        }
    }

感谢您的帮助;)

当 GUI 线程繁忙(绘图)时,不会触发 C# 事件.如何解决

这里有一个简短的答案。如果希望 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){ } 中。这仍然适用于异步!如何?最黑暗的魔法。