Derek Johnson
Derek Johnson

Reputation: 1056

C# Winform ArgumentException was unhandled

I simply want to display the mouse position on a copy of the desktop.

I have a form that is borderless, maximized and contains a PictureBox with DockStyle.Fill. In the form constructor I create a bitmap from the PrimaryScreen to set the PictureBox's image and add a handler for the PictureBox.MouseMove event. This code works if the event handler is commented out.

    public partial class Form1 : Form
    {
        // The original image.
        Bitmap ScreenCaptureBitmap = null;

        public Form1()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.WindowState = FormWindowState.Maximized;
            picDesktop.Dock = DockStyle.Fill;
            Graphics graphics = null;
            picDesktop.MouseMove += new System.Windows.Forms.MouseEventHandler(this.PictureBox_MouseMove);

            try
            {
                ScreenCaptureBitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
                graphics = Graphics.FromImage(ScreenCaptureBitmap);
                graphics.CopyFromScreen(0, 0, 0, 0, ScreenCaptureBitmap.Size);
                picDesktop.Image = ScreenCaptureBitmap;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                graphics.Dispose();
            }
        }

Here is the code for the MouseMove event handler:

private void PictureBox_MouseMove(object sender, MouseEventArgs e)
{
    try
    {
        // Make a Bitmap to display the location text.
        using (Bitmap bmTemp = new Bitmap(ScreenCaptureBitmap))
        {
            // Draw the location text on the bitmap.
            using (Graphics gr = Graphics.FromImage(bmTemp))
            {
                Brush theBrush =
                     SystemBrushes.FromSystemColor(SystemColors.WindowText);
                gr.DrawString(String.Format("({0}, {1})", e.X, e.Y),
                    this.Font, theBrush, e.X + 10, e.Y - 30);
                // Display the temporary bitmap.
                picDesktop.Image = bmTemp;
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

When I compile and debug, the Application.Run(new Form1()); line gets "ArgumentException was unhandled". The detail is "{System.ArgumentException: Parameter is not valid. at System.Drawing.Image.get_Width()}".

Upvotes: 0

Views: 124

Answers (1)

Jimi
Jimi

Reputation: 32248

The main problem with the code presented here is that the Bitmap object generated in the MouseMove event handler is declared with a using statement.
When the using block exits, the Bitmap is disposed. This is the game-stopper. Different kind of exceptions can be generated at this point, which one depends on what other code is trying to do after.

Anyway, generating a new Bitmap - presented in full screen - on every mouse move event, just to paint some text, is a very expensive procedure, bound to generate quite visible stuttering (and, possibly, other forms of exceptions).

My suggestion is to copy the content of the Screen, set the generated Bitmap to the BackgroundImage property (the Image property would also do), then draw the text in the Paint event handler of your PictureBox.
The text is rendered onto the surface of the Control, it doesn't affect the Bitmap.

As a note, the BackgroundImage, the Image and the Control's surface (its Device Context) constitute three different, independent, layers. You can modify one without affecting the others.

The Screen object that Screen.PrimaryScreen returns can be null.

Modified code based on these suggestions:

The GetTextBoundingBox() method is used to clip the rendering of the text inside the bounds of the visible screen capture, so it doesn't move outside the screen when the mouse pointer (eventually) is moved outside of these bounds.

In case the text is not visible enough, TextRenderer.DrawText() allows to specify a background color (it cannot use the alpha channel, in this context, though)

public partial class Form1 : Form {
    Bitmap screenCaptureBitmap = null;
    Point mousePosition = Point.Empty;

    public Form1() {
        InitializeComponent();

        FormBorderStyle = FormBorderStyle.None;
        WindowState = FormWindowState.Maximized;
        picDesktop.Dock = DockStyle.Fill;

        try {
            if (Screen.PrimaryScreen != null) {
                Size imageSize = Screen.PrimaryScreen.Bounds.Size;
                screenCaptureBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                using (var g = Graphics.FromImage(screenCaptureBitmap)) {
                    g.CopyFromScreen(0, 0, 0, 0, imageSize);
                    picDesktop.BackgroundImage = screenCaptureBitmap;
                }
            }
        }
        catch (Exception ex) {
            Debug.WriteLine(ex.Message);
        }

        picDesktop.MouseMove += PicDesktop_MouseMove;
        picDesktop.Paint += PicDesktop_Paint;
    }

    private void PicDesktop_MouseMove(object? sender, MouseEventArgs e) {
        mousePosition = e.Location;
        picDesktop.Invalidate();
    }

    private void PicDesktop_Paint(object sender, PaintEventArgs e) {
        string positionText = $"(x: {mousePosition.X}, y: {mousePosition.Y})";
        var textBounds = GetTextBoundingBox(
            e.Graphics, Font, positionText, new Point(mousePosition.X + 10, mousePosition.Y - 10));

        TextRenderer.DrawText(e.Graphics, positionText, Font, textBounds, SystemColors.WindowText);
    }

    private Rectangle GetTextBoundingBox(Graphics g, Font font, string text, Point location) {
        var textBox = new Rectangle(location, TextRenderer.MeasureText(g, text, font));
        var screenBox = g.VisibleClipBounds;

        textBox.Location = new Point(
            Math.Max(Math.Min((int)screenBox.Width - textBox.Width, location.X), 0),
            Math.Max(Math.Min((int)screenBox.Height - textBox.Height, location.Y), 0));
        return textBox;
    }
}

Upvotes: 2

Related Questions