【问题标题】:Performance difference WndProc WM_PAINT vs OnPaint性能差异 WndProc WM_PAINT 与 OnPaint
【发布时间】: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 在一个版本中,但不在另一个版本中)。
  • AdjustFontwhile 循环(其中的逻辑)在两个版本中执行的次数是否相同?
  • @mjwills 刚刚测量过,循环在两个版本中执行的次数完全相同。我还测量了产生了多少毫秒和滴答声,结果表明 WndProc 实现要慢得多。我猜是由于额外的 IF 子句以及图形元素的创建和处置。

标签: c# winforms performance drawing gdi


【解决方案1】:

我选择使用 OnPaint() 版本,因为它的性能更好,我不需要在设计器中立即更新。

我将按照@TnTinMn 的建议深入研究 WM_PAINT 实现。 任何新结果都将添加到此答案中。

【讨论】:

    猜你喜欢
    • 2021-02-03
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-25
    • 2010-11-30
    • 2019-11-15
    相关资源
    最近更新 更多