WM_ERASEBKGND problem

G

Guest

I've encountered a strange problem with GDI/GDI+ and windows forms/win32. To
explain it shortly: I've a user control with a NC area. When painting the NC
area I ask for a window DC using GetDCEx with parameters DCX_WINDOW |
DCX_CACHE. I also use the wParam of the WM_NCPAINT message which contains the
update region for the NC area (or 1 to update the whole NC area). After
creating the graphics object and painting the NC area (if necessary) the hDC
is released. Next, the WM_ERASEBKGND message arrives which contains an OS
supplied hDC in the wParam. Using this hDC, which is used automatically by
the WmEraseBkgnd method in the System.Windows.Forms.Control class if
UserPaint is true and AllPaintingInWmPaint is false, causes the problem: the
window background is NOT erased due to the fact that the system region of the
supplied hDC is NULL. This is even more strange while the update region of
the window is NOT null. The erase problem occurs when an external window
obscured the NC area of the form hosting my user control. To solve this
problems I've several workarounds: (1) set ControlStyles.AllPaintingInWmPaint
is true (forcing the control the handle background painting in the WmPaint
handler not using the OS supplied hDC for the WM_ERASEBKGND message, (2) not
using the hClipRgn parameter of GetDCEx (and neglecting the OS supplied
update region for the NC area) and (3) reinvalidate the window after handling
of the WM_NCPAINT event (the latter solution is not nice of course). Please
let someone me know if there is a better solution and if this is a bug in
win32 GDI. Thanks in advance.
 
S

Stoitcho Goutsev \(100\)

jh,

Without reproducing the problem and playing with some sample application I
don't think anyone will know the answer, unless struggle with the same
problem.

I'd suggest to post simple compilable sample that demonstrates your problem.
 
G

Guest

Stoitcho Goutsev,

See below a small sample application which can be used to reproduce the
problem. Compile and show the form and move another form over the sample to
see the painting problem. Switching on the AllPaintingInWmPaint flag will do
the correct painting. Please let me know your results.

Thanks in advance.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Test
{
public class FormTest : Form
{
private NCControl ncControl;
private CheckBox checkBox1;

public FormTest()
{
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "FormTest";

ncControl = new NCControl();
ncControl.Dock = DockStyle.Fill;

checkBox1 = new CheckBox();
checkBox1.Text = "AllPaintingInWmPaint";
checkBox1.Size = new Size(80, 17);
checkBox1.AutoSize = true;
checkBox1.Location = new Point(10, 10);
checkBox1.Anchor = AnchorStyles.Left | AnchorStyles.Top;

checkBox1.CheckedChanged += new EventHandler(checkBox1_CheckedChanged);
ncControl.Controls.Add(checkBox1);

this.Controls.Add(ncControl);
}

void checkBox1_CheckedChanged(object sender, EventArgs e)
{
this.ncControl.SetStyleAllPaintingInWmPaint(checkBox1.Checked);
}
}

public class NCControl : ContainerControl
{
public NCControl() : base()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
}

public void SetStyleAllPaintingInWmPaint(bool value)
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint, value);
}

protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCCALCSIZE:
this.WmNcCalcSize(ref m);
break;
case WM_NCPAINT:
this.WmNcPaint(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}

private void WmNcCalcSize(ref Message m)
{
base.WndProc(ref m);

if (m.WParam == IntPtr.Zero)
{
RECT clientRect = (RECT) Marshal.PtrToStructure(m.LParam, typeof(RECT));
Marshal.StructureToPtr(this.GetClientRectangleAsRECT(clientRect),
m.LParam, false);
}
else
{
NCCALCSIZE_PARAMS p = (NCCALCSIZE_PARAMS)
Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
p.rgrc0 = this.GetClientRectangleAsRECT(p.rgrc0);
Marshal.StructureToPtr(p, m.LParam, false);
}

m.Result = IntPtr.Zero;
}

private RECT GetClientRectangleAsRECT(RECT clientRect)
{
clientRect.left += Math.Min(10, clientRect.right - clientRect.left);
clientRect.top += Math.Min(10, clientRect.bottom - clientRect.top);
clientRect.bottom -= Math.Min(10, clientRect.bottom - clientRect.top);
clientRect.right -= Math.Min(10, clientRect.right - clientRect.left);

return clientRect;
}

private void WmNcPaint(ref Message m)
{
base.WndProc(ref m);

IntPtr hRgnClip = IntPtr.Zero;

int flags = DCX_WINDOW | DCX_CACHE | DCX_CLIPSIBLINGS |
DCX_LOCKWINDOWUPDATE;

if ((int) m.WParam != 1)
{
hRgnClip = m.WParam;
flags |= DCX_INTERSECTRGN;
}

IntPtr hDC = GetDCEx(this.Handle, hRgnClip, flags);

using (Graphics g = Graphics.FromHdc(hDC))
{
Rectangle frameRectangle = new Rectangle(0,0,this.Width, this.Height);
Rectangle innerRectangle = frameRectangle;

innerRectangle.Inflate(-10, -10);

Region frameRegion = new Region(frameRectangle);
frameRegion.Exclude(innerRectangle);

using (SolidBrush brush = new SolidBrush(Color.Red))
g.FillRegion(brush, frameRegion);

frameRegion.Dispose();
}

ReleaseDC(this.Handle, hDC);

m.Result = IntPtr.Zero;
}

public const int WM_NCCALCSIZE = 0x00000083;
public const int WM_NCPAINT = 0x00000085;

public const int DCX_WINDOW = 0x0000001;
public const int DCX_CACHE = 0x00000002;
public const int DCX_CLIPSIBLINGS = 0x00000010;
public const int DCX_INTERSECTRGN = 0x00000080;
public const int DCX_LOCKWINDOWUPDATE = 0x00000400;

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}

[StructLayout(LayoutKind.Sequential)]
public struct NCCALCSIZE_PARAMS
{
public RECT rgrc0;
public RECT rgrc1;
public RECT rgrc2;
public IntPtr lppos;
}

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true,
SetLastError = true)]
public static extern IntPtr GetDCEx(IntPtr hWnd, IntPtr hRgnClip, int
flags);

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true,
SetLastError = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top