The context:
Consider drawing a GroupBox
with a gradient as a part of it's background.
Example:
Let's perform the following actions:
class
that inherits GroupBox
.
FlatStyle
property
to FlatStyle.System
.override
it's WndProc
method.WM_ERASEBKGND
message, in which we draw the gradient.WM_PRINTCLIENT
message, where we call DefWndProc
and return
.Add a Label
as it's child Control
.
(The Label
's background must be transparent to be able to see the gradient behind it's Text
.
class
that inherits Label
.override
the WndProc
method.DrawThemeParentBackground
function to draw the GroupBox
's background on the Label
's Graphics
.The issue:
Depending on whether a temporary variable is used to hold the
Graphics
object
, the end result varies, depicted with the code sample and image below:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace MCVE
{
class GroupBox : System.Windows.Forms.GroupBox
{
const int WM_ERASEBKGND = 0x14;
const int WM_PRINTCLIENT = 0x318;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
base.WndProc(ref m);
using (var g = Graphics.FromHdc(m.WParam))//CASE 1
//using (var e = new PaintEventArgs(Graphics.FromHdc(m.WParam), ClientRectangle))//CASE 2
{
var e = new PaintEventArgs(g, ClientRectangle);//CASE 1
var r = new Rectangle(2, 12, Width - 4, Height - 2);
using (var b = new LinearGradientBrush(r, BackColor, SystemColors.Window, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(b, r);//Draw the gradient.
}
}
m.Result = new IntPtr(1);//Signal that no further drawing of the background is necessary by WM_PAINT.
return;
case WM_PRINTCLIENT:
DefWndProc(ref m);//Bypass GroupBox's internal handling so that actual painting is handled by Windows.
return;
}
base.WndProc(ref m);//Default processing of the rest of the messages.
}
};
class Label : System.Windows.Forms.Label
{
const int WM_ERASEBKGND = 0x14;
const int WM_PAINT = 0xF;
[DllImport("user32.dll")] static extern IntPtr BeginPaint(IntPtr hWnd, out PAINTSTRUCT lpPaint);
[DllImport("user32.dll")] static extern IntPtr EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
//Ask Windows to send a message to the parent to draw it's background in the current device context.
[DllImport("uxtheme.dll")] extern static int DrawThemeParentBackground(IntPtr hWnd, IntPtr hdc, ref Rectangle pRect);
[StructLayout(LayoutKind.Sequential)]
struct PAINTSTRUCT
{
public IntPtr hdc;
public bool fErase;
public Rectangle rcPaint;
public bool fRestore;
public bool fIncUpdate;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] rgcReserved;
};
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
var r = ClientRectangle;
DrawThemeParentBackground(Handle, m.WParam, ref r);
m.Result = new IntPtr(1);//Signal that no further drawing of the background is necessary by WM_PAINT.
return;
case WM_PAINT:
PAINTSTRUCT ps;
var hdc = BeginPaint(Handle, out ps);
EndPaint(Handle, ref ps);//Don't paint any text so that the gradient remains visible.
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);//Default processing of the rest of the messages.
}
};
static class Program
{
[STAThread] static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form() { BackColor = SystemColors.Highlight };
var groupbox = new GroupBox() { Anchor = (AnchorStyles)15, FlatStyle = FlatStyle.System, Location = new Point(10, 10), Text = "groupBox1" };
form.Controls.Add(groupbox);
groupbox.Controls.Add(new Label() { FlatStyle = FlatStyle.System, Location = new Point(50, 50) });
Application.Run(form);
}
};
}
Running the above MCVE (CASE 1) produces the expected ouput as shown in the example image.
On commenting out the lines remarked CASE 1 and uncommenting the line marked CASE 2 gives the following undesired output:
The question:
Why does the removal of the temporary variable produce such a vastly different output?
Posting this as an answer for the sake of completeness.
PaintEventArgs
does not call Dispose
on the Graphics
object
.WM_PRINTCLIENT
Message
, that is sent next to the WndProc
.Manually calling Dispose
on the Graphics
object
confirms this.
using (var g = Graphics.FromHdc(m.WParam))
{
using (var e = new PaintEventArgs(g, ClientRectangle))
{
var r = new Rectangle(2, 12, Width - 4, Height - 2);
using (var b = new LinearGradientBrush(r, BackColor, SystemColors.Window, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(b, r);//Draw the gradient.
}
}
}