当覆盖OnPaint时,Winforms面板上的“Glitch”

时间:2014-01-24 11:06:18

标签: .net vb.net winforms controls

我正在尝试在面板上放置一个自定义标题,它正在显示但是当我使用滚动条滚动时标题被绘制但它并不总是被删除,因此它显示了很多标题,因为我再次向上滚动

关于我做错的任何想法?

代码如下:

Public Class MyPanel
    Inherits Windows.Forms.Panel
Protected Overrides Sub OnPaint(ByVal myPEV As PaintEventArgs)
        Dim myRectF As RectangleF = New RectangleF(0, 0, Me.Width, Me.Height)
        Dim mySF As New StringFormat
        MyBase.OnPaint(myPEV)
        Dim myhDC As IntPtr = GetWindowDC(Me.Handle) 'from user32.dll
        Dim myGraphs As Graphics = Graphics.FromHdc(myhDC)
        If Me.Text IsNot Nothing Then
            mySF.Alignment = StringAlignment.Center
            mySF.LineAlignment = StringAlignment.Near
            myGraphs.DrawString(Me.Text, Me.Font, New SolidBrush(Me.ForeColor), myRectF, mySF)
            myGraphs.Dispose()
            ReleaseDC(Handle, myhDC) 'from user32.dll
        End If
    End Sub
End Class

4 个答案:

答案 0 :(得分:2)

我会做鱼并解释你为什么遇到这个问题。您正在与为所有现代Windows版本打开的系统选项进行战斗,名为“拖动时显示窗口内容”。这样就可以进行优化,最大限度地减少用户滚动窗口时需要完成的绘画量。

它的基本工作方式是Windows本身通过复制窗口中的像素按滚动量滚动大部分窗口内容。快速bitblt,避免重绘那些像素。底层的winapi函数是ScrollWindowEx()。然后使窗口中没有要复制的像素的部分无效,如果向下滚动则只是窗口的底部,如果向上滚动则是顶部的条子。因此,应用程序必须在其绘制事件处理程序中做更少的工作,它只需要绘制滚动显示的窗口部分。

这会对您在OnPaint()方法中绘制的文本造成严重破坏。它也会被bitblt滚动。但是你不要自己滚动它,无论滚动条位置如何,你都将它绘制在相同的位置。净效果是文本“涂抹”,文本的像素被移动,但它们没有重新绘制。

应该做的是滚动文字。容易做到的,你的OnPaint()方法覆盖应如下所示:

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    MyBase.OnPaint(e)
    If Me.Text IsNot Nothing Then
        e.Graphics.TranslateTransform(Me.AutoScrollPosition.X, Me.AutoScrollPosition.Y)
        // etc, be sure to use e.Graphics
        //...
    End If
End Sub

Graphics.TranslateTransform()调用确保文本正确偏移并与窗口中的其余像素一起滚动。并且优化的bitblt将不再导致拖尾。

这是所做的,但可能不是您想要做的事情。 Lars向您展示了一种解决方法,您必须故意击败优化并强制重新绘制整个窗口,而不仅仅是滚动显示的优化条子。

这样可行,但肯定会产生可见的神器。片刻之后,您会看到滚动的文本像素,就在它们被OnPaintBackground()覆盖之前。效果是,嗯,有趣,你看它在做“pogo”,在滚动时上下跳跃。

真正的修复需要禁用bitblt优化。 User Tomthis answer中展示了如何做到这一点。在Panel控件上运行良好,我将用VB.NET语法重现他的代码。将此代码粘贴到您的班级中:

Protected Overrides Sub WndProc(ByRef m As Message)
    '' Trap scroll notifications, send WM_SETREDRAW
    If m.Msg = 276 Or m.Msg = 277 Then
        SendMessageW(Me.Handle, 11, IntPtr.Zero, IntPtr.Zero)
        MyBase.WndProc(m)
        SendMessageW(Me.Handle, 11, New IntPtr(1), IntPtr.Zero)
        Me.Invalidate()
        Return
    End If
    MyBase.WndProc(m)
End Sub

Private Declare Function SendMessageW Lib "User32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal WParam As IntPtr, ByVal LParam As IntPtr) As IntPtr

答案 1 :(得分:1)

当面板滚动时将调用OnPaint,因此您将多次绘制它。我已经做了很长时间了,但你需要检查客户区域并确保你的Rect不是面板的可见矩形,但是面板上的绝对位置。

答案 2 :(得分:1)

我会这样试试:

Public Class MyPanel
  Inherits Windows.Forms.Panel

  Public Sub New()
    Me.Text = "Test Header"
    Me.DoubleBuffered = True
    Me.ResizeRedraw = True
  End Sub

  Protected Overrides Sub OnPaint(ByVal myPEV As PaintEventArgs)
    myPEV.Graphics.Clear(Me.BackColor)
    MyBase.OnPaint(myPEV)
    TextRenderer.DrawText(myPEV.Graphics, Me.Text, Me.Font, Me.ClientRectangle, _
                          Me.ForeColor, Color.Empty, _
                          TextFormatFlags.HorizontalCenter)
  End Sub

  Protected Overrides Sub OnScroll(se As ScrollEventArgs)
    Me.Invalidate()
    MyBase.OnScroll(se)
  End Sub    
End Class

它仍然会经历一些撕裂,这可以通过实际上不这样做并将标签置于面板上方来解决。

答案 3 :(得分:1)

问题是由于您的文本是单独绘制的 - 它与组件无关,因此Panel无法对文本占用的区域执行任何智能操作。一个组件并不总是重绘所有内容OnPaint,它通常会翻译它所能做的事情,并且只会重绘所需的内容。一个hack就是在滚动时强制重画整个控件:

Protected Overrides Sub OnScroll(se As System.Windows.Forms.ScrollEventArgs)
    Me.Invalidate(true)  'set to "true" to also repaint child controls
    MyBase.OnScroll(se)
End Sub

或许更优雅的是利用现有控件的内置布局和绘图功能。当您的工具箱中有手术刀时,无需使用斧头进行手术:

Public Class MyPanel
    Inherits Windows.Forms.Panel
    Private headLbl As New Label

    <Browsable(True)> _
    <Description("The text to appear in the panel header.")> _
    Public Overrides Property Text As String
        Get
            Return headLbl.Text
        End Get
        Set(value As String)
            headLbl.Text = value
        End Set
    End Property

    Protected Overrides Sub OnSizeChanged(e As System.EventArgs)
        MyBase.OnSizeChanged(e)
        headLbl.Width = Me.Width
    End Sub

    Public Sub New()
        headLbl.TextAlign = ContentAlignment.MiddleCenter
        headLbl.AutoSize = False
        headLbl.Location = New Point(0, 0)
        headLbl.Width = Me.Width
        Me.Controls.Add(headLbl)
    End Sub
End Class

编辑:

如果您希望标签保持在顶部中心(而不是滚动内容),您可以添加:

Protected Overrides Sub OnScroll(se As System.Windows.Forms.ScrollEventArgs)
    MyBase.OnScroll(se)
    headLbl.Location = New Point(0, 0)
End Sub