当 PictureBox.Visible 设置为 True 时,C# 中到底发生了什么?

What Exactly Happens in C# When PictureBox.Visible Is Set to True?

我正在创建一个带有图片框的 WinForms 应用程序,这些图片框在默认情况下被禁用且不可见。当我单击表单中的单选按钮时,我希望出现图片框,然后立即在它们上面绘制一些东西:

// the radio button CheckedChanged event handler:
table1PictureBox.Enabled = true;
table1PictureBox.Visible = true;
DrawCorrectAnswers();  // draw something over the picture box

问题是绘图在图片可见之前就完成了,所以绘图最终被图片覆盖了。

在解决问题时我读到here Visibility设置为true后,实际的图片加载在窗体的消息队列中排队。答案甚至建议一个可能的解决方案是设置一个计时器,然后异步等待它的滴答声,然后进行绘图,以便图片有时间加载。我不喜欢设置计时器的解决方案,而是我想等待图片本身加载。

有办法吗?在这种情况下,将 Visible 设置为 true 究竟如何工作?


我也尝试想出一个替代解决方案,如下所示:

// the radio button CheckedChanged event handler:
table1PictureBox.Enabled = true;
table1PictureBox.Visible = true;
this.BeginInvoke(new Action(() => { DrawCorrectAnswers(); }));  // 'this' is the form

我的想法是,这会将要绘制的消息排在要加载的消息之后,这样即使操作也会按要求的顺序执行。然而,这也没有用。

在这种情况下,如果我在表单的线程中,BeginInvoke 是否会有特殊行为?我什至尝试了普通的 Invoke,令我惊讶的是,它并没有导致死锁。这是为什么?


[编辑] 这是一个说明问题的最小示例:

public Form1()
    {
        InitializeComponent();

        pictureBox1.Visible = false;
        pictureBox1.Enabled = false;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        pictureBox1.Enabled = true;
        pictureBox1.Visible = true;

        Graphics graphics = pictureBox1.CreateGraphics();
        graphics.DrawLine(Pens.Black, 0, 0, 50, 50);
    }

这里的问题是你在图片框上绘制,而不是在图像上绘制,每当重绘控件时,你在其上绘制的所有内容都将被擦除,你需要重绘它。

更好的解决办法是手动加载图片,在图片上绘制文字,然后设置到图片框:

private void button1_Click(object sender, EventArgs e)
{

    Bitmap bmp = Bitmap.FromFile(pathToTheFile);

    using(var graphics = Graphics.FromImage(bmp))
        graphics.DrawLine(Pens.Black, 0, 0, 50, 50);

    var oldImg = pictureBox1.Image;
    pictureBox1.Image = bmp;

    if(oldImg != null)
      oldImg.Dispose();

    pictureBox1.Enabled = true;
    pictureBox1.Visible = true;

}

注意一些事情:始终处置您创建的任何 Graphics 对象,最好用 using 块包围它。此外,在不需要时处理任何未使用的图像,这就是为什么我检索旧图像并在存在时处理它的原因。

最后,如果您不想将图像作为物理文件包含在内,您可以将其作为资源嵌入,有很多关于如何做到这一点的示例。

编辑:

当您将 Visible 设置为 true 时,幕后发生的事情是窗体上的 PictureBox 区域无效,然后在下一个绘制周期中,窗体将测试哪些可见控件与该矩形(或任何其他无效区域)相交) 然后会绘制它们。

另外,关于Invoke,为什么会导致死锁?你没有使用任何锁,当你调用 Invoke 时它会检查线程,如果线程是 UI 那么它会执行函数,否则它会 post 调用 UI 线程和调用线程将被阻塞,直到 UI 线程处理完函数调用。