How can I get rid of "Out of memory" exception thrown from Visual Studio designer?

Consider the following code:

Main form template model:

public partial class CustomForm : Form
{
    public CustomForm()
    {
        InitializeComponent();
        DoubleBuffered = true;
    }
}

Specific form template model:

public partial class PopupForm : CustomForm
{
    public PopupForm()
    {
        InitializeComponent();
        BTN_Maximize.Hide();
    }
}

Derived forms:

public partial class SiteAccess : CustomForm
{
    public SiteAccess()
    {
        InitializeComponent();
        this.SizeGripStyle = SizeGripStyle.Show;
    }
}

(main form)

+

public partial class Edition : PopupForm
{
    public Edition()
    {
        InitializeComponent();
    }
}

+

public partial class Preferences : PopupForm
{
    public Preferences()
    {
        InitializeComponent();
    }
}

+

public partial class About : PopupForm
{
    public About()
    {
        InitializeComponent();
    }
}

In my form template model PopupForm, I have defined a Paint event handler for an empty panel, like so:

private void PNL_TitleBar_Paint(object sender, PaintEventArgs e)
{
    Brush gradientBrush = new LinearGradientBrush(new Point(0, 0), 
                          new Point((int)e.ClipRectangle.Right, 0), 
                          Color.Gray, 
                          Color.FromArgb(255, 50, 50, 50));

    Pen gradientPen = new Pen(gradientBrush);

    e.Graphics.DrawLine(gradientPen, 
                        new Point((int)e.ClipRectangle.Left,  (int)e.ClipRectangle.Bottom - 1),
                        new Point((int)e.ClipRectangle.Right, (int)e.ClipRectangle.Bottom - 1));

    gradientPen.Dispose();
    gradientBrush.Dispose();
}

To short it out, it is simply drawing a gradient line on the bottom of the panel.

The problem comes from the fact that whenever I try to insert a new control from the Toolbox in one of the derived forms of PopupForm (Edition, Preferences or About), I get this message:

OutOfMemoryException

The reason why I have shown you the Paint event handler above is because I found that it was the source of the problem, but I still don't know how to fix it. In fact, if I delete the code of the Paint event handler, no exception will be thrown at all.

By the way, I have to specify a few things:

  1. The exception is being thrown from the Visual Studio Designer view only (after compilation).
  2. The program itself is running perfectly fine (no exception at all).
  3. If I execute the program, close it and come back to the Designer, no exception will be thrown when inserting controls in the form.
  4. However, if I rebuild the solution (without executing the program) and come back to the Designer, the exception will show off when inserting controls in the form.

    • I tried using the 'using' statement instead of calling the Dispose() function directly, but still nothing. Also, keep in mind that I have a really similar code in another Paint event handler in my SiteAccess form, so I really doubt this code is the real issue.

Any idea about this strange behavior?

Upvotes: 3

Views: 2304

Answers (2)

Hans Passant
Hans Passant

Reputation: 941407

This is caused by a bug in your code. The underlying exception is a GDI+ exception thrown by the LinearGradientBrush constructor. Notorious for being hard to interpret, it does get a lot worse when it happens at design time. Technically this can go wrong at runtime as well, just less likely. You can repro it consistently by making a small change in your code:

        Brush gradientBrush = new LinearGradientBrush(new Point(0, 0),
                  new Point((int)0, 0),            // <=== here
                  Color.Gray,
                  Color.FromArgb(255, 50, 50, 50));

Kaboom. It is never correct to use e.ClipRectangle to initialize the brush, the gradient you'll get is highly unpredictable. The e.ClipRectangle value changes depending on what part of the window needs to be repainted, right now you completely depend on it being the entire window. That will not be the case when you, say, drag the window off an edge of the screen. The visual artifact should be quite noticeable.

Fix:

        Brush gradientBrush = new LinearGradientBrush(new Point(0, 0),
                  new Point(this.ClientSize.Width, 0),
                  Color.Gray,
                  Color.FromArgb(255, 50, 50, 50));

The DrawLine() call must be fixed as well to get the line to appear in a consistent position. Don't fret about clipping, Windows already takes care of it automatically.

The takeaway is to be very careful using the e.ClipRectangle property, it works too often by accident but is almost never what you really want to use. Only ever use it to check if you can skip drawing parts because they are outside of the clipping area. Which is code that's quite hard to make pay off, it just doesn't happen very often on modern Windows versions that have Aero enabled. Don't bother.

Upvotes: 7

dbc
dbc

Reputation: 116670

In your Paint handler, you can check the DesignMode property and skip drawing the titlebar when in the Forms Designer:

    void PNL_TitleBar_Paint(object sender, PaintEventArgs e)
    {
        if (DesignMode)
            return;
        using (Brush gradientBrush = new LinearGradientBrush(new Point(0, 0),
                              new Point((int)e.ClipRectangle.Right, 0),
                              Color.Gray,
                              Color.FromArgb(255, 50, 50, 50)))
        using (Pen gradientPen = new Pen(gradientBrush))
        {

            e.Graphics.DrawLine(gradientPen,
                                new Point((int)e.ClipRectangle.Left, (int)e.ClipRectangle.Bottom - 1),
                                new Point((int)e.ClipRectangle.Right, (int)e.ClipRectangle.Bottom - 1));
        }
    }

This will work in your Paint method and most other situations. For a discussion of unusual cases, e.g. what to do inside a constructor, see here.

(Suggest also replacing explicit calls to Dispose with a using statement.)

Upvotes: 3

Related Questions