性能差异 WndProc WM_PAINT 与 OnPaint

Posted

技术标签:

【中文标题】性能差异 WndProc WM_PAINT 与 OnPaint【英文标题】:Performance difference WndProc WM_PAINT vs 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 子句以及图形元素的创建和处置。 【参考方案1】:

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

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

【讨论】:

以上是关于性能差异 WndProc WM_PAINT 与 OnPaint的主要内容,如果未能解决你的问题,请参考以下文章

WM_size在WM_PAINT之后执行

VC SDK中关于WM_PAINT的新手问题

冒泡与快速排序的算法原理与性能对比

TWinControl的消息覆盖函数大全(41个WM_函数和31个CM_函数,它的WndProc就处理鼠标(转发)键盘(取消拖动)焦点和WM_NCHITTEST一共4类消息)

并发编程理论1.并发问题的由来

C 和 C++ 样式文件 IO 之间的性能差异