我正在尝试在面板上放置一个自定义标题,它正在显示但是当我使用滚动条滚动时标题被绘制但它并不总是被删除,因此它显示了很多标题,因为我再次向上滚动
关于我做错的任何想法?
代码如下:
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
答案 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 Tom在this 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