C# Graphics.CopyFromScreen “parameter is not valid

2020-03-27 10:27发布

I had made an app in C# that will perform screen capture continuously and display it in a PictureBox using timer. After running for a few seconds, there was an ArgumentException.

Below is the code and the line that has the ArgumentException

private void timer1_Tick(object sender, EventArgs e)
    {
        Rectangle bounds = Screen.GetBounds(Point.Empty);
        Graphics graphics;
        Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
        using (graphics = Graphics.FromImage(bitmap))
        {

            graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width , bounds.Height )); // ArgumentException
            pictureBox1.Image = bitmap;
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;

        }
    }

Besides that, I had notices that an alert saying low memory from Windows after running the app for a few seconds.

Any tips on resolving this problem?

3条回答
We Are One
2楼-- · 2020-03-27 11:07
   pictureBox1.Image = bitmap;

Yes, your program won't last long when you frequently update the picture box. The Bitmap class is the singular .NET class where IDisposable can't easily be ignored. It is like an iceberg, bitmaps can use massive amounts of unmanaged memory but very little managed memory. You must dispose bitmaps when you no longer use them to prevent them from consuming all available unmanaged memory for their pixel data. The garbage collector tends to hide that problem but it can't do so when it doesn't run frequently enough. And the managed portion of Bitmap is too small to trigger a collection often enough. Fix:

   if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
   pictureBox1.Image = bitmap;
查看更多
我只想做你的唯一
3楼-- · 2020-03-27 11:27

You keep setting a new bitmap to the picturebox, and the previous bitmap is never disposed. After a while, the system runs short of GDI handles and/or memory (running your code, I consumed one gig of memory in under 15 seconds).

You can simply reuse your existing bitmap:

Rectangle bounds = Screen.GetBounds(Point.Empty);

Image bitmap = pictureBox1.Image ?? new Bitmap(bounds.Width, bounds.Height);

using (Graphics graphics = Graphics.FromImage(bitmap))
{
    graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));

    if (pictureBox1.Image == null)
    {
        pictureBox1.Image = bitmap;
    }
    else
    {
        pictureBox1.Refresh();
    }

}

You also don't have to reset pictureBox1.SizeMode on each iteration.


Alternatively, you can dispose the previous bitmap manually:

Rectangle bounds = Screen.GetBounds(Point.Empty);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
    graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));

    using (Image prev_bitmap = pictureBox1.Image)
    {
        pictureBox1.Image = bitmap;
    }
}
查看更多
放我归山
4楼-- · 2020-03-27 11:31

The following KB can help understanding this issue: Bitmap and Image constructor dependencies

GDI+, and therefore the System.Drawing namespace, may defer the decoding of raw image bits until the bits are required by the image. Additionally, even after the image has been decoded, GDI+ may determine that it is more efficient to discard the memory for a large Bitmap and to re-decode later. Therefore, GDI+ must have access to the source bits for the image for the life of the Bitmap or the Image object.

To retain access to the source bits, GDI+ locks any source file, and forces the application to maintain the life of any source stream, for the life of the Bitmap or the Image object.

One has to figure out when is the object ready for disposal.

查看更多
登录 后发表回答