Groups | Blog | Home
all groups > dotnet drawing api > september 2005 >

dotnet drawing api : Weird problem when re-painting form while moving it outside of the screen!


theunissen NO[at]SPAM comcast.net
9/28/2005 3:47:53 PM
Hi,

I've been struggling with this problem for days on and off. Hopefully
somebody has some insight in this. I have a usercontrol which is placed
on a form. When the control gets the paint event, I use a memory bitmap
to draw on and then blit it back to the original DC (from the
graphics).

The problem is that when moving the form out of the screen and moving
it back into the screen view. the control receives paint events and the
clipping rectangle is correct, however nothing gets updated
appropriately. When the control receives the paint events while inside
the screen area everything is blitted fine.

I have set the styles for the control as follows:
MyBase.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
MyBase.SetStyle(ControlStyles.DoubleBuffer, False)
MyBase.SetStyle(ControlStyles.Opaque, True)
MyBase.SetStyle(ControlStyles.ResizeRedraw, True)


And the code for the paint event is as follows:

Private Sub m_Control_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles m_Control.Paint
'Get original hdc
Dim hdcWindow As Integer = e.Graphics.GetHdc().ToInt32

'Create a compatible DC with the one given to us
Dim hdcMemory As Integer = CreateCompatibleDC(hdcWindow)

'Create a compatible bitmap in memory DC with the size of the
clipping rectangle
Dim hMemoryBitmap As Integer = CreateCompatibleBitmap(hdcWindow,
e.ClipRectangle.Width, e.ClipRectangle.Height)

'Select the new bitmap into memory DC
Dim hMemoryBitmapOld As Integer = SelectObject(hdcMemory,
hMemoryBitmap)

'Clear the memory DC
BitBlt(hdcMemory, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width, e.ClipRectangle.Height, hdcMemory,
e.ClipRectangle.X, e.ClipRectangle.Y, WHITENESS)

'cOPY the memory bitmap back to the real DC.
BitBlt(hdcWindow, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width, e.ClipRectangle.Height, hdcMemory,
e.ClipRectangle.X, e.ClipRectangle.Y, SRCCOPY)

'Select the original bitmap back into the DC
SelectObject(hdcMemory, hMemoryBitmapOld)

'Delete the compatible bitmap
DeleteObject(hMemoryBitmap)

'Delete the compatible DC
DeleteDC(hdcMemory)

'Release the DC
e.Graphics.ReleaseHdc(New IntPtr(hdcWindow))
End Sub


Once again, everything works fine, as long as the form is inside the
screen area!

Thanks in advance.
theunissen NO[at]SPAM comcast.net
9/28/2005 4:32:39 PM
I have compiled a small application displaying the problem. Just copy &
paste this into a VB file and launch this form. Then move the form out
of the screen area and back in. You'll see thatr the bitblt copying the
background doesn't work anymore. However, if you resize the form all
works fine.

Public Class GdiForm
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Declare Function CreateCompatibleDC Lib "gdi32" Alias
"CreateCompatibleDC" (ByVal hdc As Integer) As Integer
Public Declare Function CreateCompatibleBitmap Lib "gdi32" Alias
"CreateCompatibleBitmap" (ByVal hdc As Integer, ByVal nWidth As
Integer, ByVal nHeight As Integer) As Integer
Public Declare Function DeleteDC Lib "gdi32" Alias "DeleteDC"
(ByVal hdc As Integer) As Integer
Public Declare Function BitBlt Lib "gdi32" Alias "BitBlt" (ByVal
hDestDC As Integer, ByVal x As Integer, ByVal y As Integer, ByVal
nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer,
ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer)
As Integer
Public Declare Function SelectObject Lib "gdi32" Alias
"SelectObject" (ByVal hdc As Integer, ByVal hObject As Integer) As
Integer
Public Declare Function DeleteObject Lib "gdi32" Alias
"DeleteObject" (ByVal hObject As Integer) As Integer

Public Const WHITENESS As Integer = &HFF0062
Public Const SRCCOPY As Integer = &HCC0020

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, False)
Me.SetStyle(ControlStyles.Opaque, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.SetStyle(ControlStyles.UserPaint, True)
End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As
Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form
Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
'
'GdiForm
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(384, 358)
Me.Name = "GdiForm"
Me.Text = "GdiForm"

End Sub

#End Region


'***************************************************************************************************************************************
' Overrides

'***************************************************************************************************************************************
Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)

'Get original hdc
Dim hdcWindow As Integer = e.Graphics.GetHdc().ToInt32

'Create a compatible DC with the one given to us
Dim hdcMemory As Integer = CreateCompatibleDC(hdcWindow)

'Create a compatible bitmap in memory DC with the size of the
clipping rectangle
Dim hMemoryBitmap As Integer =
CreateCompatibleBitmap(hdcWindow, e.ClipRectangle.Width,
e.ClipRectangle.Height)

'Select the new bitmap into memory DC
Dim hMemoryBitmapOld As Integer = SelectObject(hdcMemory,
hMemoryBitmap)

'Clear the memory DC
BitBlt(hdcMemory, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width, e.ClipRectangle.Height, hdcMemory,
e.ClipRectangle.X, e.ClipRectangle.Y, WHITENESS)

'Blit the memory bitmap back to the real DC.
BitBlt(hdcWindow, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width, e.ClipRectangle.Height, hdcMemory,
e.ClipRectangle.X, e.ClipRectangle.Y, SRCCOPY)

'Select the original bitmap back into the DC
SelectObject(hdcMemory, hMemoryBitmapOld)

'Delete the compatible bitmap
DeleteObject(hMemoryBitmap)

'Delete the compatible DC
DeleteDC(hdcMemory)

'Release the DC
e.Graphics.ReleaseHdc(New IntPtr(hdcWindow))
End Sub

End Class
theunissen NO[at]SPAM comcast.net
9/28/2005 5:20:27 PM
I understand I can only paint what's visible. I'm using the
ClipRectangle" which is even "smarter" than "RectVisible " by only
telling me what needs to be updated, instead what's visible. However,
the onpaint still doesn't update anything when scrolling out of the
screen and back..You'll see traces of the window when scrolling back,
but when resizing you'll see everything gets repainted. In this case
I'm only painting the background, but when I draw lines or anything
else, the behavior stays the same. all gets repainted as long as I'm
not moving the entire form outside of the screen area.
theunissen NO[at]SPAM comcast.net
9/28/2005 5:33:21 PM
I checked the "RectVisible" function, and you're right, you can only
draw what's visible. So, I used RectVisible to determine what's inside
the clipping area, and when passing the "ClipRectangle" from the
graphics object I noticed that while resizing the window, the clipping
rectangle is visible ("RectVisible" returns 1), but when moving the
window out-of-screen and back-in-screen, the clipping rectangle from
the paint event is NOT visible ("RectVisible" returns 0).

So, I'm stuck here. What's going on. Why is the ClipRect not inside the
clipping area of the DC here?
Michael Phillips, Jr.
9/28/2005 7:41:07 PM
You can only paint what is actually visible on the screen.

If you want to determine what portion of your control is visible,
use RectVisible to determine if any part of the control is in the
screen's clipping region.


[quoted text, click to view]

theunissen NO[at]SPAM comcast.net
9/29/2005 8:51:54 AM
Interesting article. It's a little different in my case: I DO receive
the onpaint events when necessary, only the cliprectangle is not
visible according to the RectVisible function. I could ofcourse call to
"Control.Invalidate", which will bypass the problem, but it seems to me
there is a bug inside the framework or something else is going on that
I'm missing. I prefer not to do "Invalidate" for the client area but
find out why the RectVisible returns 0.
theunissen NO[at]SPAM comcast.net
9/29/2005 9:01:06 AM
I thought Invalidate would solve the problem, but it doesn't. Still,
nothing gets painted. Even when I blit on the entire client area,
without using the ClipRectangle.
Michael Phillips, Jr.
9/29/2005 9:55:43 AM
Raymond Chen's "The Old New Thing" had an interesting
article concerning the painting of windows when they are
actually visible. It may be of some help in understanding your issue.

Here is the link:
http://blogs.msdn.com/oldnewthing/archive/2003/08/29/54728.aspx


[quoted text, click to view]

theunissen NO[at]SPAM comcast.net
9/29/2005 10:33:46 AM
Thanks for responding again,

If I use the e.Graphics.Clear method, it DOES clear the entire client
area, and repaints it, even when moving back into the screen area.
However, since I'm in the example I gave you only clearing the
background, in real life I'm drawing between the clear of the
background and the bitblt back to the windowHDC, and my painting still
has the same problem when moving the form back into the screen area.

Unfortunately, if I use the e.Graphics.Clear method, then I'm using
GDI+ again, causing a blinking (since I turned the GDI+ double
buffering off) and slowing down the painting dramatically, the main
reason I want to use GDI and my own double buffering method.
theunissen NO[at]SPAM comcast.net
9/29/2005 11:02:09 AM
I figured it out! Thanks to your help pointing me about the
RectVisible, otherwise I would have never found it this fast.

I made a critical mistake, when creating the compatible DC, I am using
the clipping rectangle, which is correct. Hoever, all drawing and
blitting should occur with a 0,0 offset inside this memory DC, and I
was using the wrong offset, I was using the clipRectangle, which is
offset against the client area of the control! I'll post a working
example when I'm done with it!

Thanks for all your time and effort, I owe you big time!
theunissen NO[at]SPAM comcast.net
9/29/2005 12:42:41 PM
I promised a working version of the specified problem, here it is (note
that when you wants to really draw on it, you should set the viewport
and window extents and window origin, In this example, I'm only
clearing the area that needs to be repainted according to the clipping
rectangle using a double buffer technique.


Public Class GdiForm
Inherits System.Windows.Forms.Form

Public Declare Function CreateCompatibleDC Lib "gdi32" Alias
"CreateCompatibleDC" (ByVal hdc As Integer) As Integer
Public Declare Function CreateCompatibleBitmap Lib "gdi32" Alias
"CreateCompatibleBitmap" (ByVal hdc As Integer, ByVal nWidth As
Integer, ByVal nHeight As Integer) As Integer
Public Declare Function DeleteDC Lib "gdi32" Alias "DeleteDC"
(ByVal hdc As Integer) As Integer
Public Declare Function BitBlt Lib "gdi32" Alias "BitBlt" (ByVal
hDestDC As Integer, ByVal x As Integer, ByVal y As Integer, ByVal
nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer,
ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer)
As Integer
Public Declare Function SelectObject Lib "gdi32" Alias
"SelectObject" (ByVal hdc As Integer, ByVal hObject As Integer) As
Integer
Public Declare Function DeleteObject Lib "gdi32" Alias
"DeleteObject" (ByVal hObject As Integer) As Integer
Public Declare Function RectVisible Lib "gdi32" Alias "RectVisible"
(ByVal hdc As Integer, ByRef lpRect As Rectangle) As Integer

Public Const WHITENESS As Integer = &HFF0062
Public Const SRCCOPY As Integer = &HCC0020

Public Sub New()
MyBase.New()

'Add any initialization after the InitializeComponent() call
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, False)
Me.SetStyle(ControlStyles.Opaque, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
Me.SetStyle(ControlStyles.UserPaint, True)
End Sub

Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
'Call to base so event is raised!
MyBase.OnPaint(e)

'Get window hdc
Dim hdcWindow As Integer = e.Graphics.GetHdc().ToInt32

'Create compatible DC
Dim hdcMemory As Integer = CreateCompatibleDC(hdcWindow)

'Create compatible bitmap with the size of the clipping
rectangle
Dim hMemoryBitmap As Integer =
CreateCompatibleBitmap(hdcWindow, e.ClipRectangle.Width,
e.ClipRectangle.Height)

'Select the bitmap into memory DC
Dim hMemoryBitmapOld As Integer = SelectObject(hdcMemory,
hMemoryBitmap)

'Fill memory DC with white
BitBlt(hdcMemory, 0, 0, e.ClipRectangle.Width,
e.ClipRectangle.Height, hdcMemory, 0, 0, WHITENESS)

'Blit memory DC to real DC
BitBlt(hdcWindow, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width, e.ClipRectangle.Height, hdcMemory, 0, 0,
SRCCOPY)

'Select the original bitmap back into the memory DC
SelectObject(hdcMemory, hMemoryBitmapOld)

'Delete the bitmap
DeleteObject(hMemoryBitmap)

If RectVisible(hdcWindow, e.ClipRectangle) = 0 Then
Debug.WriteLine(e.ClipRectangle.ToString + ": Not Visible")
Else
Debug.WriteLine(e.ClipRectangle.ToString + ": Visible")
End If

'Release the window hdc
e.Graphics.ReleaseHdc(New IntPtr(hdcWindow))
End Sub

End Class
Michael Phillips, Jr.
9/29/2005 1:18:13 PM
Did you try clearing the graphics context
before using your double buffering technique?

For example, adding
e.Graphics.Clear(Color.White) ' clear screen to form's background color
to your OnPaint handler before setting up the BitBlt

[quoted text, click to view]

Michael Phillips, Jr.
9/29/2005 3:43:42 PM
Here is my solution:

Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

<System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)>
Public Structure Rect
<System.Runtime.InteropServices.FieldOffset(0)> Public left As
Integer
<System.Runtime.InteropServices.FieldOffset(4)> Public top As
Integer
<System.Runtime.InteropServices.FieldOffset(8)> Public right As
Integer
<System.Runtime.InteropServices.FieldOffset(12)> Public bottom As
Integer
End Structure 'Rect

<System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)>
Public Structure Point
<System.Runtime.InteropServices.FieldOffset(0)> Public x As Long
<System.Runtime.InteropServices.FieldOffset(4)> Public y As Long
End Structure

Class LibWrapper

<System.Runtime.InteropServices.DllImport("gdi32.dll",
CallingConvention:=System.Runtime.InteropServices.CallingConvention.StdCall)>
_
Public Shared Function RectVisible(ByVal hDC As IntPtr, ByRef rect
As Rect) As Integer
End Function
<System.Runtime.InteropServices.DllImport("gdi32.dll",
CallingConvention:=System.Runtime.InteropServices.CallingConvention.StdCall)>
_
Public Shared Function SetViewportOrgEx(ByVal hDC As IntPtr, ByVal x
As Integer, ByVal y As Integer, ByRef pt As Point) As Integer

End Function
End Class 'LibWrapper
Public Declare Function CreateCompatibleDC Lib "gdi32" Alias _
"CreateCompatibleDC" (ByVal hdc As IntPtr) As IntPtr

Public Declare Function CreateCompatibleBitmap Lib "gdi32" Alias _
"CreateCompatibleBitmap" (ByVal hdc As IntPtr, ByVal nWidth As Integer,
ByVal nHeight As Integer) As Integer

Public Declare Function DeleteDC Lib "gdi32" Alias "DeleteDC" (ByVal hdc
As IntPtr) As Integer

Public Declare Function BitBlt Lib "gdi32" Alias "BitBlt" (ByVal _
hDestDC As IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal _
nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As IntPtr, _
ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer) _
As Integer

Public Declare Function PatBlt Lib "gdi32" Alias "PatBlt" (ByVal _
hDestDC As IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal _
nWidth As Integer, ByVal nHeight As Integer, ByVal dwRop As Integer) _
As Integer

Public Declare Function SelectObject Lib "gdi32" Alias _
"SelectObject" (ByVal hdc As IntPtr, ByVal hObject As Integer) As
Integer

Public Declare Function DeleteObject Lib "gdi32" Alias _
"DeleteObject" (ByVal hObject As Integer) As Integer

Public Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long


Public Const WHITENESS As Integer = &HFF0062
Public Const SRCCOPY As Integer = &HCC0020


Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, False)
Me.SetStyle(ControlStyles.Opaque, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.SetStyle(ControlStyles.UserPaint, True)



End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
components = New System.ComponentModel.Container

Me.Text = "Form1"
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(384, 358)

End Sub
#End Region
Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)

Dim rc As Rect = New Rect
rc.left = e.ClipRectangle.Left
rc.right = e.ClipRectangle.Right
rc.top = e.ClipRectangle.Top
rc.bottom = e.ClipRectangle.Bottom

'Get original hdc
Dim hdcWindow As IntPtr = e.Graphics.GetHdc()

'Create a compatible DC with the one given to us
Dim hdcMemory As IntPtr = CreateCompatibleDC(hdcWindow)

'Create a compatible bitmap in memory DC with the size of the
clipping(Rectangle)
Dim hMemoryBitmap As Integer = CreateCompatibleBitmap(hdcWindow,
Me.Width, Me.Height)

'Select the new bitmap into memory DC
Dim hMemoryBitmapOld As Integer = SelectObject(hdcMemory,
hMemoryBitmap)

'Clear the memory DC
PatBlt(hdcMemory, 0, 0, Me.Width, Me.Height, WHITENESS)

' Just use gdiplus to draw for testing purposes--- I am to lazy to
use GDI
Dim newGraphics As Graphics = Graphics.FromHdc(hdcMemory)
' Draw rectangle to screen.
newGraphics.DrawRectangle(New Pen(Color.Red, 3), 50, 50, 200, 100)
newGraphics.Dispose()

'Blit the memory bitmap back to the real DC.
Dim pt As Point = New Point
pt.x = 0
pt.y = 0

If LibWrapper.RectVisible(hdcWindow, rc) Then
LibWrapper.SetViewportOrgEx(hdcWindow, e.ClipRectangle.X,
e.ClipRectangle.Y, pt)
BitBlt(hdcWindow, 0, 0, _
Me.Width, Me.Height, hdcMemory, _
e.ClipRectangle.X, e.ClipRectangle.Y, SRCCOPY)
Else
BitBlt(hdcWindow, 0, 0, _
Me.Width, Me.Height, hdcMemory, _
0, 0, SRCCOPY)
End If
'Select the original bitmap back into the DC
SelectObject(hdcMemory, hMemoryBitmapOld)

'Delete the compatible bitmap
DeleteObject(hMemoryBitmap)

'Delete the compatible DC
DeleteDC(hdcMemory)

'Release the DC
e.Graphics.ReleaseHdc(hdcWindow)

End Sub

End Class

[quoted text, click to view]
theunissen NO[at]SPAM comcast.net
9/30/2005 9:14:30 AM
Thank you so much for your help, I'm going through your solution now!

Thanks
AddThis Social Bookmark Button