【发布时间】:2020-10-15 14:53:06
【问题描述】:
我有一个自定义按钮控件,我需要在其中进行一些绘图。 没有什么花哨或复杂的东西,但我注意到在 WndProc WM_PAINT 中绘图和在 OnPaint 事件中绘图时性能有很大差异。
当我在 OnPaint 中绘图时,我根本没有任何闪烁。 当我在 WM_PAINT 中绘制时,我确实会闪烁,但只有在进入和离开按钮时才会出现。所以当按钮接收或失去高亮显示时会发生闪烁(BN_HILITE / BN_UNHILITE 通知)。
据我所知,OnPaint 事件只是 WM_PAINT 消息的基于事件的包装器。 所以理论上 OnPaint 事件的效率应该较低,因为它为绘画过程增加了一层抽象。
我不确定是我的代码产生了闪烁还是其他原因。
这是我的自定义按钮的重写 OnPaint 事件的代码:
protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);
if (Day <= 0) return;
if (string.IsNullOrEmpty(Text)) return;
// Adjust font size so all text will fit.
AdjustFont(pevent.ClipRectangle);
// Check which ForeColor to use.
Color fc = Month == ExpectedMonth
? ForeColor
: UnexpectedMonthForeColor;
using (var brush = new SolidBrush(fc))
{
// Use StringFormat to center string in control.
StringFormat sf = new StringFormat {
LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Center
};
pevent.Graphics.DrawString(Text, Font, brush, pevent.ClipRectangle, sf);
}
}
这是产生闪烁的 WndProc 实现:
const int WM_PAINT = 0x000f;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
if (Day <= 0) return;
if (string.IsNullOrEmpty(Text)) return;
using (var gr = Graphics.FromHwnd(Handle))
{
//Adjust font size so all text will fit.
AdjustFont(ClientRectangle);
//Check which ForeColor to use.
Color fc = Month == ExpectedMonth
? ForeColor
: UnexpectedMonthForeColor;
using (var brush = new SolidBrush(fc))
{
// Use StringFormat to center string in control.
StringFormat sf = new StringFormat {
LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Center
};
gr.DrawString(Text, Font, brush, ClientRectangle, sf);
}
}
}
}
这里是AdjustFont方法:
private void AdjustFont(Rectangle rcBounds)
{
// Calculate string size and check if it fits into the current bounds.
var szText = TextRenderer.MeasureText(Text, Font);
if (szText.Width > rcBounds.Width || szText.Height > rcBounds.Height)
{
// Reduce font size by 0.25 until the text fits into the bounds.
while (Font.Size > 0.25f
&& (szText.Width > rcBounds.Width
|| szText.Height > rcBounds.Height))
{
Font = new Font(
Font.FontFamily,
Font.Size - 0.25f,
FontStyle.Regular,
Font.Unit,
Font.GdiCharSet,
Font.GdiVerticalFont);
szText = TextRenderer.MeasureText(Text, Font);
}
}
}
【问题讨论】:
-
考虑添加一个计数器来确定调用次数。两种实现的调用次数是否相同? 请不要猜测。
-
@mjwills 我已经测试过了。两种实现都产生完全相同数量的调用。
-
鉴于两个代码块并不完全相同,最简单的解释是不同位中的一个在一个版本中比另一个慢。 (例如,
gr.Dispose在一个版本中,但不在另一个版本中)。 -
AdjustFont的while循环(其中的逻辑)在两个版本中执行的次数是否相同? -
@mjwills 刚刚测量过,循环在两个版本中执行的次数完全相同。我还测量了产生了多少毫秒和滴答声,结果表明 WndProc 实现要慢得多。我猜是由于额外的 IF 子句以及图形元素的创建和处置。
标签: c# winforms performance drawing gdi