临时变量对擦除 GroupBox 背景的影响
Effect of temporary variable on erasing the background of a GroupBox
上下文:
考虑绘制一个 GroupBox
并将渐变作为其背景的一部分。
示例:
让我们执行以下操作:
- 创建继承
GroupBox
的class
。
- 将
FlatStyle
property
设置为 FlatStyle.System
。
override
这是 WndProc
方法。
- 处理
WM_ERASEBKGND
消息,我们在其中绘制渐变。
- 处理
WM_PRINTCLIENT
消息,这里调用DefWndProc
和return
。
(后面会用到。 )
添加一个Label
,因为它是child Control
。
(The Label
的背景必须是透明的才能看到它背后的渐变 Text
.
- 创建继承
Label
的class
。
override
WndProc
方法。
- "模拟透明度" 通过调用
DrawThemeParentBackground
函数在 Label
的 [=] 上绘制 GroupBox
的背景34=].
问题:
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);
}
};
}
运行 上面的 MCVE (CASE 1) 产生了示例图像中所示的预期输出。
注释掉标记为 CASE 1 的行并取消注释标记为 CASE 2 的行会给出以下不需要的输出:
问题:
为什么删除临时变量会产生如此大不相同的输出?
所有这些都可以归结为一个没有 PInvoking 的简单派生 class 吗?
此class源自GroupBox
,使用CreateParams CreateParams.ExStyle proeprty, enables support for Transparent colors with Control.SetStyle() and ControlStyles.SupportsTransparentBackColor
style (this way you can better control the effect of the LinearGradientBrush) and overrides the OnPaintBackground()方法将其设置为透明进行绘画。
这只是一个基本示例,但可以通过任何其他方式对其进行调整,并且仍然更加便携。
您可以在其上放置任何控件。
class GradientGroupBox : GroupBox
{
private const int WS_EX_TRANSPARENT = 0x20;
public GradientGroupBox() => this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Color gradFillTo = Color.FromArgb(200, SystemColors.Window);
Color gradFillFrom = Color.FromArgb(128, this.Parent.BackColor);
using (LinearGradientBrush gradientBrush = new LinearGradientBrush(this.ClientRectangle, gradFillFrom, gradFillTo, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(gradientBrush, this.ClientRectangle);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams parameters = base.CreateParams;
parameters.ExStyle |= WS_EX_TRANSPARENT;
return parameters;
}
}
}
为了完整起见,将此作为答案发布。
- 正如 Ivan Stoev 所指出的,非所有者
PaintEventArgs
不会 调用 Dispose
在 Graphics
object
.
- 这有可见的副作用,因为 DC 在
WM_PRINTCLIENT
中被 Windows 重用 Message
,即在WndProc
. 旁边发送
在 Graphics
object
上手动调用 Dispose
确认了这一点。
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.
}
}
}
上下文:
考虑绘制一个 GroupBox
并将渐变作为其背景的一部分。
示例:
让我们执行以下操作:
- 创建继承
GroupBox
的class
。- 将
FlatStyle
property
设置为FlatStyle.System
。 override
这是WndProc
方法。- 处理
WM_ERASEBKGND
消息,我们在其中绘制渐变。 - 处理
WM_PRINTCLIENT
消息,这里调用DefWndProc
和return
。
(后面会用到。 )
- 将
添加一个
Label
,因为它是childControl
。
(TheLabel
的背景必须是透明的才能看到它背后的渐变Text
.- 创建继承
Label
的class
。 override
WndProc
方法。- "模拟透明度" 通过调用
DrawThemeParentBackground
函数在Label
的 [=] 上绘制GroupBox
的背景34=].
- 创建继承
问题:
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);
}
};
}
运行 上面的 MCVE (CASE 1) 产生了示例图像中所示的预期输出。
注释掉标记为 CASE 1 的行并取消注释标记为 CASE 2 的行会给出以下不需要的输出:
问题:
为什么删除临时变量会产生如此大不相同的输出?
所有这些都可以归结为一个没有 PInvoking 的简单派生 class 吗?
此class源自GroupBox
,使用CreateParams CreateParams.ExStyle proeprty, enables support for Transparent colors with Control.SetStyle() and ControlStyles.SupportsTransparentBackColor
style (this way you can better control the effect of the LinearGradientBrush) and overrides the OnPaintBackground()方法将其设置为透明进行绘画。
这只是一个基本示例,但可以通过任何其他方式对其进行调整,并且仍然更加便携。
您可以在其上放置任何控件。
class GradientGroupBox : GroupBox
{
private const int WS_EX_TRANSPARENT = 0x20;
public GradientGroupBox() => this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Color gradFillTo = Color.FromArgb(200, SystemColors.Window);
Color gradFillFrom = Color.FromArgb(128, this.Parent.BackColor);
using (LinearGradientBrush gradientBrush = new LinearGradientBrush(this.ClientRectangle, gradFillFrom, gradFillTo, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(gradientBrush, this.ClientRectangle);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams parameters = base.CreateParams;
parameters.ExStyle |= WS_EX_TRANSPARENT;
return parameters;
}
}
}
为了完整起见,将此作为答案发布。
- 正如 Ivan Stoev 所指出的,非所有者
PaintEventArgs
不会 调用Dispose
在Graphics
object
. - 这有可见的副作用,因为 DC 在
WM_PRINTCLIENT
中被 Windows 重用Message
,即在WndProc
. 旁边发送
在 Graphics
object
上手动调用 Dispose
确认了这一点。
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.
}
}
}