Drawing on top of a RichTextBox control has always been a pain due to the Paint event, and related OnPaint() method being suppressed. Here is a bit of code that gets around this issue.
The following derived RichTextBox will use a seperate NativeWindow control to hook into the message stream of the RichTextBox, listen for the WM_PAINT message, and then raise the Paint event on the RichTextBox for you.
In this way, it is possible to use the Paint event handler of the RichTextBox control to paint on top of the control after it has performed its default rendering of the text.
The following code is an initial implementation but appears to be pretty solid so far.
'Based in part on code located at: 'http://www.codedblog.com/2007/09/17/owner-drawing-a-windowsforms-textbox/ ''' <summary> ''' A RichTextBox object which raises, and utilizes, its own Paint event. ''' </summary> ''' <remarks></remarks> Public Class PaintableRichTextBox Inherits RichTextBox 'Re-exposes the hidden event in the designer Public Shadows Event Paint As EventHandler(Of PaintEventArgs) 'Holds the native window which will provide paint messages Private mWindowExtender As WindowExtender Protected Overrides Sub Dispose(ByVal disposing As Boolean) mWindowExtender.ReleaseHandle() mWindowExtender.TearDown() MyBase.Dispose(disposing) End Sub 'Used to raise the shadowed paint event Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs) RaiseEvent Paint(Me, e) End Sub 'Ensures underlaying graphic resources match control size Protected Overrides Sub OnResize(ByVal e As System.EventArgs) MyBase.OnResize(e) mWindowExtender.ReinitializeCanvas() End Sub Public Sub New() mWindowExtender = New WindowExtender(Me) mWindowExtender.AssignHandle(Handle) End Sub 'Object which handles normally suppressed messages Public Class WindowExtender Inherits NativeWindow 'The Win32 WM_PAINT message id Private Const WM_PAINT As Integer = 15 'The underlying RichTextBox Private mBaseControl As PaintableRichTextBox 'An off-screen image to draw to Private mCanvas As Bitmap 'The graphics object associated with the canvas bitmap Private mBufferGraphics As Graphics 'The clip region of the canvas graphics (visible control bounds) Private mBufferClip As Rectangle 'The graphics object associated with the RichTextBox Private mControlGraphics As Graphics 'Flag ensuring that the control can be drawn to Private mCanRender As Boolean = False 'Handles the custom painting after a Win32 paint message Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Select Case m.Msg Case WM_PAINT 'Refresh the control's display mBaseControl.Invalidate() 'Perform default drawing MyBase.WndProc(m) 'Perform custom drawing OnPerformPaint() Case Else MyBase.WndProc(m) End Select End Sub Public Sub New(ByVal baseControl As PaintableRichTextBox) mBaseControl = baseControl ReinitializeCanvas() End Sub 'Draws the result of the event handler to the textbox Protected Sub OnPerformPaint() If mCanRender Then 'clear the canvas mBufferGraphics.Clear(Color.Transparent) 'give the event handler a chance to draw to the buffer mBaseControl.OnPaint(New PaintEventArgs(mBufferGraphics, mBufferClip)) 'render the buffer contents to the RichTextBox mControlGraphics.DrawImageUnscaled(mCanvas, 0, 0) End If End Sub 'Builds up graphics resources Protected Friend Sub ReinitializeCanvas() SyncLock Me TearDown() If mBaseControl.Width > 0 AndAlso mBaseControl.Height > 0 Then mCanRender = True mCanvas = New Bitmap(mBaseControl.Width, mBaseControl.Height) mBufferGraphics = Graphics.FromImage(mCanvas) mBufferClip = mBaseControl.ClientRectangle mBufferGraphics.Clip = New Region(mBufferClip) mControlGraphics = Graphics.FromHwnd(mBaseControl.Handle) Else mCanRender = False End If End SyncLock End Sub 'Cleans up resources Protected Friend Sub TearDown() If mControlGraphics IsNot Nothing Then mControlGraphics.Dispose() End If If mBufferGraphics IsNot Nothing Then mBufferGraphics.Dispose() End If If mCanvas IsNot Nothing Then mCanvas.Dispose() End If End Sub End Class End Class
I may continue to expand upon this idea, but I wanted to get the initial code out here as this seems to be the minimum to get the painting functionality to work.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"Friday, October 16, 2009 10:43 PM