Reputation: 317
I'm trying to put a customized title on a Panel, it is displaying but when I scroll using scroll bar the title is painted but it's not always erased, so it displays a lot of titles as I'm scrolling up again.
Any ideas on what I'm doing wrong?
The code is something like:
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
Upvotes: 0
Views: 1788
Reputation: 31403
The problem is caused by the fact that your text is being drawn in isolation - it is not associated with a component and so the Panel can't do anything intelligent with the region occupied by the text. A component does not always redraw everything OnPaint
, it will often translate what it can and will only redraw what is necessary. One hack would be to force a repaint of the entire control when scrolling :
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
More elegant, perhaps, would be to leverage the already built-in layout and drawing power of the existing controls. There's no need to perform surgery with an axe when you have a scalpel in your toolbox :
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
Edit :
If you want the label to stay top center (rather than scrolling with the contents) you can add :
Protected Overrides Sub OnScroll(se As System.Windows.Forms.ScrollEventArgs)
MyBase.OnScroll(se)
headLbl.Location = New Point(0, 0)
End Sub
Upvotes: 1
Reputation: 941545
I'll do the fish and explain why you are having this problem. You are doing battle with a system option that's turned on for all modern Windows versions, named "Show window contents while dragging". That enables an optimization that minimizes the amount of painting that needs to be done when the user scrolls a window.
The basic way it works is that Windows itself scrolls the majority of the window content by the scroll amount by copying the pixels in the window. A fast bitblt that avoids having to repaint those pixels. The underlying winapi function is ScrollWindowEx(). And then invalidates the part of the window for which it doesn't have pixels to copy, just the bottom of the window if you scroll down, the top sliver if you scroll up. Accordingly, the app has to do much less work in its paint event handler, it only needs to draw the part of the window that was revealed by the scroll.
This plays havoc on the text that you draw in your OnPaint() method. It gets scrolled as well by the bitblt. But you don't scroll it yourself, you draw it in the same position regardless of the scrollbar position. The net effect is that the text "smears", the pixels of the text got moved but they are not being redrawn.
What you should do is scroll the text as well. Easy to do, your OnPaint() method override should look like this:
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
The Graphics.TranslateTransform() call ensures that the text is offset properly and scrolls along with the rest of the pixels in the window. And the optimized bitblt will no longer cause the smearing.
This is what you should do but probably not what you want to do. Lars showed you a workaround, you have to intentionally defeat the optimization and force the entire window to be repainted, not just the optimized sliver that was revealed by the scroll.
That works, but it certainly does produce a visible artifact. For a brief moment, you'll see the scrolled text pixels, just before they get overpainted by OnPaintBackground(). The effect is, erm, interesting, you see it doing the "pogo", jumping up and down as you scroll.
A real fix requires disabling the bitblt optimization. User Tom showed how to do that in this answer. Works well on a Panel control as well, I'll reproduce his code in VB.NET syntax. Paste this code into your class:
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
Upvotes: 2
Reputation: 81620
I would try it this way:
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
It will still experience some tearing, which would be solved by actually not doing this and putting a Label centered above the panel.
Upvotes: 1
Reputation: 7344
OnPaint will be called when the panel scrolls, so you're painting it multiple times. It's a long time since I've done any of this, but you need to check out the Client area and make sure your Rect is not the visible rect of the panel, but but an absolute position on the panel.
Upvotes: 1