当PictureBox.Visible设置为True时,C#中究竟会发生什么?

时间:2021-03-16 22:50:35

I'm creating a WinForms app with picture boxes which are disabled and not visible by default. When I click on a radio button in my form, I want the picture boxes to appear and immediately after that I want something to be drawn over them:

我正在创建一个带有图片框的WinForms应用程序,这些图片框已禁用且默认情况下不可见。当我点击表单中的单选按钮时,我想要显示图片框,之后我想要在它们上面绘制一些东西:

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

The problem is that the drawing finishes before the picture is made visible, so the drawing is ultimately covered by the picture.

问题是绘图在图片可见之前完成,因此绘图最终会被图片覆盖。

While solving the problem I read here that after Visibility is set to true, the actual image load is queued in the message queue of the form. The answer even suggests that a possible solution is to set a timer and then asynchronously wait for its tick and then do the drawing, so that the pictures have time to load. I don't like the solution with setting a timer, instead I would like to wait for the pictures themselves to be loaded.

解决这个问题的时候我在这里看到,在Visibility设置为true之后,实际的图像加载在表单的消息队列中排队。答案甚至建议一个可能的解决方案是设置一个计时器,然后异步等待其刻度,然后进行绘图,以便图片有时间加载。我不喜欢设置计时器的解决方案,而是我想等待图片本身加载。

Is there a way to do this? How exactly does setting Visible to true work in this case?

有没有办法做到这一点?在这种情况下,如何将Visible设置为真正的工作?


I also tried to come up with an alternative solution which looked like this:

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

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

My idea was that this would enqueue the message for drawing after the message for loading, so even the operations would be performed in the required order. This, however, didn't work either.

我的想法是,这将在加载消息之后将消息排入队列,因此即使操作也将按所需顺序执行。然而,这也没有用。

In this case, might there be a special behavior of BeginInvoke if I'm in the thread of the form? I even tried normal Invoke and to my surprise, it didn't cause a deadlock. Why is that?

在这种情况下,如果我在表单的线程中,可能会有BeginInvoke的特殊行为吗?我甚至尝试了普通的Invoke,令我惊讶的是,它没有造成僵局。这是为什么?


[EDIT] Here is a minimal example that illustrates the problem:

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

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);
    }

1 个解决方案

#1


2  

The problem here is you are drawing on the picturebox, not on the image, whenever the control gets redrawn everything you have draw on it will be erased and you need to redraw it.

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

The better solution is to load manually the image, draw the text on the image and then set it to the picturebox:

更好的解决方案是手动加载图像,在图像上绘制文本,然后将其设置为图片框:

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;

}

Note some things: dispose always any Graphics object you have created, and better surround it with a using block. Also, dispose any unused image when it's not needed, that's why I retrieve the old image and dispose it if exists.

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

Finally, if you don't want to include the image as a physical file you can embedd it as a resource, there are plenty of examples on how to do it.

最后,如果您不想将图像作为物理文件包含在内,可以将其作为资源嵌入,那么有很多关于如何实现它的示例。

EDIT:

编辑:

What happens under the hood when you set Visible to true is that the PictureBox area is invalidated on the form, then on the next Draw cycle the form will test which visible controls intersect with that rectangle (or any other invalidated area) and then will draw them.

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

Also, about the Invoke, why it should cause a deadlock? you aren't using any lock, when you call Invoke it will check the thread, if the thread is the UI one then it will execute the function, else it would post the call to the UI thread and the calling one would be blocked until the UI one has processed the function call.

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

#1


2  

The problem here is you are drawing on the picturebox, not on the image, whenever the control gets redrawn everything you have draw on it will be erased and you need to redraw it.

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

The better solution is to load manually the image, draw the text on the image and then set it to the picturebox:

更好的解决方案是手动加载图像,在图像上绘制文本,然后将其设置为图片框:

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;

}

Note some things: dispose always any Graphics object you have created, and better surround it with a using block. Also, dispose any unused image when it's not needed, that's why I retrieve the old image and dispose it if exists.

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

Finally, if you don't want to include the image as a physical file you can embedd it as a resource, there are plenty of examples on how to do it.

最后,如果您不想将图像作为物理文件包含在内,可以将其作为资源嵌入,那么有很多关于如何实现它的示例。

EDIT:

编辑:

What happens under the hood when you set Visible to true is that the PictureBox area is invalidated on the form, then on the next Draw cycle the form will test which visible controls intersect with that rectangle (or any other invalidated area) and then will draw them.

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

Also, about the Invoke, why it should cause a deadlock? you aren't using any lock, when you call Invoke it will check the thread, if the thread is the UI one then it will execute the function, else it would post the call to the UI thread and the calling one would be blocked until the UI one has processed the function call.

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