Paint Event called Synchronously...help me!

sn1fflez

Newcomer
Joined
Jul 30, 2010
Messages
8
I am working with OpenGl (Tao Framework) on vb.net. Now, I know that I've implemented opengl correctly but I'm not actually sure where the problem lies with my code. Here is the best explanation of the problem I can come up with.

Given: The application I'm working with works perfectly in VB6 but in vb.net I've had to make changes to the UI portion of the code. Most pertinent to the problem at hand is that calls to the paint method of a picturebox can no longer be called as easily. I've had to replace calls to the paint method such as
Code:
 frmBackground.PctAnimate_Paint(Nothing, New System.Windows.Forms.PaintEventArgs(Nothing, Nothing))
with
Code:
frmbackground.pctanimate.refresh
.
The application I'm working on has a main form called frmbackground and it has a picturebox control called pctanimate on which I have attached the opengl graphics context (sorry if my terminology is incorrect).
When I rightclick on pctanimate, a menu pops up from which I can select to open(show and focus on) a form called frmgraphics in which I can change some graphics "properties" in pctanimate. When frmgraphics is displayed on the screen, frmbackground is disabled. There are three buttons in frmgraphics: apply, done, and cancel.
One last thing you should know: The default color of pctanimate is black. But the graphics that should always be shown are a white grid on a black background.


Problem: (In frmgraphics) Whenever I hit apply I lose the graphics on the screen and when I hit done (frmgraphics.close is called after calling frmbackground.enable = true) the graphics come back but the portion of the screen on which the frmgraphics was visible has not been painted. So when I hit done there is a black rectangle where frmgraphics used to be. There is no obvious problem with my paint event because when I hit apply (in frmgraphics) I lose the graphics but if I then move frmgraphics to another position on the screen pctanimate is correctly painted with the correct graphics(ie a grid) objects (ie no black rectangles result). How can I correct this problem?

Sample code:
This is called first as initiated by pressing the apply button on the frmgraphics form.
Code:
   Private Sub buttonApply_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles ApplyButton.Click
        Dim k, i, j, KTemp As Integer
        Dim IAnimateModeT As Integer
        Dim DD As Double
        Dim Str1 As Double
        Dim Str2 As String
        Dim DDC(3) As Double
        frmBackground.PctAnimate.BackColor = Picture1.BackColor
        'frmBackground.PctAnimate_Paint(Nothing, New System.Windows.Forms.PaintEventArgs(Nothing, Nothing))
        frmBackground.PctAnimate.Refresh()
        ScreenColor = SelectedColor
        GridColor = GridColor1
        For i = 1 To 3
            AXColor(i) = AxisColor(i)
        Next i
        If Check1.CheckState = 1 Then
            IGridShow = 1
        Else
            IGridShow = 0
        End If
        If Check2.CheckState = 1 Then
            IAxesShow = 1
        Else
            IAxesShow = 0
        End If
        GridSpace = Combo1.SelectedIndex + 1
        GridThickness = Combo2.SelectedIndex + 1
        AxesLength = Combo3.SelectedIndex + 1
        AxesThickness = Combo4.SelectedIndex + 1
        'Camera
        DDC(1) = CDbl(Text1.Text) - CDbl(Text4.Text)
        DDC(2) = CDbl(Text2.Text) - CDbl(Text5.Text)
        DDC(3) = CDbl(Text3.Text) - CDbl(Text6.Text)
        k = 0
        For i = 1 To 3
            If System.Math.Abs(DDC(i)) < 0.0001 Then k = k + 1
            If k = 2 Then
                Mss(1) = "Wrong selection of the camera parameters."
                Mss(2) = CStr(MsgBoxStyle.OkOnly)
                Mss(3) = "Sams: Data Error"
                Call ErrMess()
                Exit Sub
            End If
        Next i
        CamLx = CDbl(Text1.Text)
        CamLy = CDbl(Text2.Text)
        CamLz = CDbl(Text3.Text)
        CamPx = CDbl(Text4.Text)
        CamPy = CDbl(Text5.Text)
        CamPz = CDbl(Text6.Text)
        CamUpx = CDbl(Text7.Text)
        CamUpy = CDbl(Text8.Text)
        CamUpz = CDbl(Text9.Text)
        DD = (CamUpx ^ 2 + CamUpy ^ 2 + CamUpz ^ 2) ^ 0.5
        CamUpx = CamUpx / DD
        CamUpy = CamUpy / DD
        CamUpz = CamUpz / DD
        CameraFovy = CDbl(Text11.Text)
        ICameraFollow = Combo5.SelectedIndex
        IBodyLook = Combo6.SelectedIndex
        ICameraTranslate = Check3.CheckState
        ICameraRotate = Check4.CheckState
        If ICameraFollow = 0 Then
            ICameraTranslate = 0
            ICameraRotate = 0
        End If

       

        IPreviousCameraGraham = ICameraGraham



        'Animation
        AnimationSpeed = (10 - Slider1.Value) * 5
        FrameSteps = Int(CDbl(Text10.Text)
    End Sub
The next function to be called is frmbackground_paint
Code:
Private Sub frmBackground_Paint(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        If Err.Number <> 0 Then MessageBox.Show("Error Number: " & Err.Number.ToString & ". Error Source: " & Err.Source.ToString & ". This is error in paint 1")


        If VB6.PixelsToTwipsX(Me.Width) <= 0.6 * VB6.PixelsToTwipsX(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width) Then _
            Me.Width = VB6.TwipsToPixelsX(0.6 * VB6.PixelsToTwipsX(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width))
        If VB6.PixelsToTwipsY(Me.Height) <= 0.6 * VB6.PixelsToTwipsY(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height) Then _
            Me.Height = VB6.TwipsToPixelsY(0.6 * VB6.PixelsToTwipsY(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height))
        If IUTL = 0 And NOC(6) = 0 Then
            With PctAnimate
                .Width = Me.Width
                .Height = VB6.TwipsToPixelsY(VB6.PixelsToTwipsY(StatusBar1.Top) - VB6.PixelsToTwipsY(Toolbar1.Height))
                .Left = 0
                .Top = Toolbar1.Height
            End With
        Else
            With SamsTree1
                .Width = VB6.TwipsToPixelsX(0.177 * VB6.PixelsToTwipsX(Me.Width))
                .Height = VB6.TwipsToPixelsY(VB6.PixelsToTwipsY(StatusBar1.Top) - VB6.PixelsToTwipsY(Toolbar1.Height))
                .Left = VB6.TwipsToPixelsX(0.82 * VB6.PixelsToTwipsX(Me.Width))
                .Top = Toolbar1.Height
                '.Size = New System.Drawing.Size(100, 100)
                '.Location = New System.Drawing.Point(200, 20)
                '.Anchor = AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom
            End With
            With PctAnimate
                '.Width = 0.81 * Me.Width
                .Width = VB6.TwipsToPixelsX(VB6.PixelsToTwipsX(Me.Width) - VB6.PixelsToTwipsX(SamsTree1.Width))
                .Height = VB6.TwipsToPixelsY(VB6.PixelsToTwipsY(StatusBar1.Top) - VB6.PixelsToTwipsY(Toolbar1.Height))
                .Left = 0
                .Top = Toolbar1.Height
            End With
        End If

        If Err.Number <> 0 Then MessageBox.Show("Error Number: " & Err.Number.ToString & ". Error Source: " & Err.Source.ToString & ". This is error in paint 2")
    End Sub
The debugger loops in frmbackground_paint (i'm guessing because I'm trying to step through every time visual studio tries to display the form as it debugs). After stepping out of debugging pctanimate now is all black and I have lost the graphics (frmgraphics is not closed by clicking apply). If I where to move the form frmgraphics, however, the graphics (ie the grid) re-appears.
The sequence of subroutines that fire when I step through the program after clicking done on frmgraphics is:
First:
Code:
 Private Sub ButtonDone_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles ButtonDone.Click

        frmBackground.Enabled = True
        Me.Close()
    End Sub
then
Code:
    Private Sub frmGraphics_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        Call IBACKShow()
    End Sub
then
Code:
Sub IBACKShow()
        If IUTL = 5 Then
            frmBackground.Enabled = False
            Exit Sub
        End If
        frmBackground.Enabled = True
    End Sub
I then end up back at frmbackground_paint and looping there until I step out of debug mode. After exiting debug mode the form frmgraphics is no longer visible but there is a black rectangle in the graphics grid where the form used to be. Opening any other form and moving it slightly will cause the pctanimate to repaint itself and as a result draw the complete grid without any problems.
I believe the problem occurs because I have not understood fully the sequence of paint events that are initiated by windows and how vb.net handles them. Any ideas as to where the problem may lie would be greatly appreciated.

Thank you for your time.
 
Last edited by a moderator:
I'm having a little bit of trouble understanding exactly what your code is doing. How you deal with the paint-related events depends on how you are drawing. Are you rendering a static image every time you need to redraw the control? Or are you continuously rendering (i.e. on a loop or timer). It sounds like the former.

Unless you are running a rendering loop, you should be redrawing your pctanimate control every time its paint event is raised. You haven't posted the code for the handler, but it sounds like the problem may be there.

It looks like you are resizing controls in your frmBackground's Paint event. Generally, the only thing you should be doing in the Paint event handler is drawing to the form. Resizing controls every time the form is painted sounds like a bad idea to me.
 
Hey snarfblam,
Thanks for taking the time out to reply. I'm sorry I haven't responded sooner.

So the actual paint handler used in the program is this:
Code:
Public Sub PctAnimate_Paint( ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.PaintEventArgs) Handles PctAnimate.Paint
        Dim IB, ISSS As Integer
        Dim IKK As Integer
        Dim m(15) As Single
        If IUTL <> 0 Then
            PctAnimate.Visible = False
            Exit Sub
        End If

        If IC(1) = 2 Then ISSS = 12
        Gl.glLoadIdentity()
        Gl.glClearColor( CInt(&HFF And ScreenColor) / 255, ((&HFF00 And ScreenColor) \ 256) / 255, ((&HFF0000 And ScreenColor) \ 65536) / 255, 0)
        Gl.glClear(Gl.GL_COLOR_BUFFER_BIT Or Gl.GL_DEPTH_BUFFER_BIT)
        If IAnimate = 0 Then
            Call MoveCamera(0, 0)
        End If
        If NOC(6) > 0 And IUTL = 0 Then
            'Initial Configuration
            For IB = 1 To NOC(6)
                If IFLR(IB, 8) = 1 Then
                    Call InitialDisplay(IB)
                    IFLR(IB, 8) = 0
                End If
            Next

            If IAnimate = 0 Then
                Timer1.Enabled = False
                For IB = 1 To NOC(6)
                    'If AI(IB, ISSS) > 0 And AI(IB, IGR6) = 0 Then Call DrawObject(IB, 0)
                    If AI(IB, ISSS) > 0 And AI(IB, IGR6) = 0 Then
                        IKK = IFLR(IB, 7) + NOC(6)
                        Gl.glNewList(IKK, Gl.GL_COMPILE)
                        Gl.glPushMatrix()
                        Call DrawObjectF(IB, 0)
                        Gl.glPopMatrix()

                        Gl.glEndList()

                        Gl.glPushMatrix()

                        Gl.glCallList((IKK))

                        Gl.glPopMatrix()
                    End If
                Next IB
                IFrameL = 0
            End If
            'Animation of the system
            If IAnimate = 1 Then
                Call MoveCamera(ICameraFollow, IBodyLook)
                For IB = 1 To NOC(6)

                    If AI(IB, ISSS) > 0 And AI(IB, IGR6) = 0 Then Call DrawObjectF(IB, IFrameA)
                Next IB

                IFrameL = IFrameA
            End If
            'Pause
            If IAnimate = 2 Then
                Timer1.Enabled = False
                Call MoveCamera(ICameraFollow, IBodyLook)
                For IB = 1 To NOC(6)
                    'If AI(IB, ISSS) > 0 And AI(IB, IGR6) = 0 Then Call DrawObject(IB, IFrameL)                   
 If AI(IB, ISSS) > 0 And AI(IB, IGR6) = 0 Then Call DrawObjectF(IB, IFrameL)
                Next IB
            End If
                        If IAnimate > 0 And IFrameL > 0 Then
                If NFrame > 0 And IAnimateMode = 0 Then StatusBar1.Panels(1).Text = "Time = " & CStr (AnA(IFrameA, 1))
                If NFrame > 0 And IAnimateMode = 1 Then StatusBar1.Panels(1).Text = "Time = " & CStr (ANF(IModePrint, 1))
                StatusBar1.Panels(2).Text = "Frame = " & CStr (IFrameA)
            Else
                StatusBar1.Panels(1).Text = "Time = 0"
                StatusBar1.Panels(2).Text = "Frame = 0"
            End If
        End If
        Gl.glFlush()
        Gdi.SwapBuffers(hDC)
        '200:
    End Sub
So the program is either rendering a static image or continually rendering on a timer depending on the value of IAnimate. The timer code is as follows:
Code:
Private Sub Timer1_Tick( ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Timer1.Tick

        If IAnimate = 1 Then
            IFrameA = IFrameA + FrameSteps
            If IFrameA > NFrame Then IFrameA = 1
            'Call PctAnimate_Paint(PctAnimate, New System.Windows.Forms.PaintEventArgs(Nothing,          Nothing))
            PctAnimate.Refresh()
        End If
    End Sub

I feel as though the paint handler may be called one too many times or the buffering isn't done correctly or perhaps I should move the above code into the onpaint(or onpaintbackground) method. Should I be setting the controlstyle of pctanimate to .AllPaintingInWmPaint? I've been reading around the net to find out if maybe there is some sort of doublebuffering set into the forms by default. What do you feel is the most likely problem? If you have any other questions about the code (I know the variable and method names aren't that helpful - sorry about that) I would be more than happy to answer.

Thanks for your help so far : )

sn1fflez
 
Sorry, I can't really make a lot of sense of your PctAnimate_Paint code. I'm not going to pick on your coding style, but I can't really do much with that. I know plenty about GDI+ painting, but not so much about OpenGL or what's going on inside your app, so if the code's not clear it might as well be Chinese.
 
That's okay. Thank you for trying. I really do need to work on making my code more friendly to other people. Perhaps you could answer some questions for me: in your opinion would it be better for me to create a control that inherits picturebox, override the onpaint method with the above code, and change the style to wnAllPaintingInWmPaint?
 
AllPaintingInWmPaint is a good plan if you really are doing all painting in the Paint events/methods. It gives WinForms a better idea of how you are painting (i.e. that you are adhering strictly to the standard invalidation/paint cycle) so it can handle its own side of the painting a little better. However, the biggest issue this seems to address are flickering and double-buffering.

From MSDN,
AllPaintingInWmPaint: If true, the control ignores the window message WM_ERASEBKGND to reduce flicker. This style should only be applied if the UserPaint bit is set to true.

As for what to inherit, I used to always inherit PictureBox, Panel, or UserControl for all my controls. I guess it gave me a sense that since I'm starting with a complete control, there will be a smaller chance of gaps or omissions on my end. That, and PictureBox was the class to use when you needed a control with it's own window and handle in VB6.

Since, I've discovered that if you are implementing 100% of the painting yourself and you don't need any functionality that another control provides, there is no real reason not to simply inherit the Control class. Even so, while inheriting PictureBox doesn't buy you anything, it doesn't cost you much either.


I still think the problem lies in your paint event. The fact that it is a bit convoluted and unclear only supports the idea. I think that if you break the logic down (i.e. use more methods or even classes, and use enumerations instead of magic numbers), the problem just might present itself.
 
Thank you so much for the response Snarfblam. I will take your advice and rewrite the code for the paint handler, It seems to be a better plan of attack than guess and check :).
 
Hey SnarfBlam,
I finally finished rewriting my problem code. I've also found out that it is most likely a problem arising from the switch from vb6 to vb.net. Apparently the the autoredraw property was key to the permanence of my graphics. I cannot find the autoredraw property in vb.net and am searching for a viable alternative. I've found some people who say that the best alternative to autoredraw is to save the contents of the picturebox and then set it as the image of the picturebox every time the control is painted. There may be two problems with this approach: I don't know if saving a picture each time the control is painted will slow down animations too much, and I don't know what would be the fastest way to save an image (or how to save an image). I was hoping you would be able to give me some advice. I know you're knowledgeable in GDI and you might have encountered this problem before.

I have included a link to the download of the sample project I've come up with. The rar file also includes the tao framework install that is needed to run the project. The code is basically composed of the calls necessary to draw something with opengl in vb.net. These calls are all fine but as soon as you run the project the problem will become apparent. Clicking on the button at the upper left of the background form brings up another form. Try moving this form and you'll see the graphics come in and then leave if you click any of the buttons in the second form.
One last thing to remember in deciding how to advise me is that I cannot move away from using either opengl or the picturebox control (actually I just need to be able to use the image property of the picturebox).

Thank you so much for your time and I really appreciate your advice from before.


http://rapidshare.com/files/413337094/GraphicsFlicker3.rar
 
First, on AutoRedraw:
Apparently the the autoredraw property was key to the permanence of my graphics.

All AutoRedraw really amounts to is double-buffering. The real issue is simply that your control isn't being redrawn when it should. AutoRedraw is a very easy way of making this happen, but the standard painting cycle, if properly implemented, should also correctly paint your form.

Now, I ran your code, tinkered with it, and the problem seems a bit unusual. What is actually happening is the invalid region (i.e. the area that needs to be drawn) is not being redrawn with your image, but everything else is. That seems completely backwards, and the result is that every time the control is painted, the area that should have been updated is empty, everything else looks fine. If the entire control should be updated, the entire control will be left blank. If a small area should be updated, you will notice an artifact of blankness there.

This is either happening because something is clearing the invalid region after you update it, or because the invalid region is somehow being clipped (the wrong way) when you update. One possibility that occurs to me is that Windows is trying to optimize the painting in a way that is working against you. I don't think this is likely.

One solution that worked for me is to render in an idle loop. This is a pretty standard thing when a Windows app that mixes hardware accelerated rendering with an otherwise standard UI. Basically, you run the render code once each time the Applicition.Idle event is raised. It's easy and sidesteps the whole problem. I tried that and it worked very well.
 
First, on AutoRedraw:
Apparently the the autoredraw property was key to the permanence of my graphics.

All AutoRedraw really amounts to is double-buffering. The real issue is simply that your control isn't being redrawn when it should. AutoRedraw is a very easy way of making this happen, but the standard painting cycle, if properly implemented, should also correctly paint your form.

Now, I ran your code, tinkered with it, and the problem seems a bit unusual. What is actually happening is the invalid region (i.e. the area that needs to be drawn) is not being redrawn with your image, but everything else is. That seems completely backwards, and the result is that every time the control is painted, the area that should have been updated is empty, everything else looks fine. If the entire control should be updated, the entire control will be left blank. If a small area should be updated, you will notice an artifact of blankness there.

This is either happening because something is clearing the invalid region after you update it, or because the invalid region is somehow being clipped (the wrong way) when you update. One possibility that occurs to me is that Windows is trying to optimize the painting in a way that is working against you. I don't think this is likely.



TL;DR: I'm at a bit of a loss.

One solution that worked for me is to render in an idle loop. This is a pretty standard thing when a Windows app that mixes hardware accelerated rendering with an otherwise standard UI. Basically, you run the render code once each time the Applicition.Idle event is raised. It's easy and sidesteps the whole problem. I tried that and it worked very well.

Code:
    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        frmSelectGraphics.Hide()

        ' Add any initialization after the InitializeComponent() call.
        Call OpenGL00()

[COLOR="Blue"]        AddHandler Application.Idle, AddressOf DoDrawStuff[/COLOR]
[COLOR="Green"]        'This handler must be removed when the form is closed!
[/COLOR]    End Sub

[COLOR="Blue"]
    Private Sub DoDrawStuff(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BttnGraphics.Click
        DrawStuff()
    End Sub


    Sub DrawStuff()[/Color]
        Gl.glLoadIdentity()
        Gl.glClearColor(CInt(&HFF And screencolor) / 255, ((&HFF00 And screencolor) \ 256) / 255, ((&HFF0000 And screencolor) \ 65536) / 255, 0)
        'Gl.glClear(Gl.GL_COLOR_BUFFER_BIT Or Gl.GL_DEPTH_BUFFER_BIT)

        Call CameraPrespective()
        Glu.gluLookAt(-4, -2, 2.5, 0, 0, 0, 0, 0, 1)

        Gl.glNewList(1, Gl.GL_COMPILE)
        Gl.glPushMatrix()


        Glu.gluDisk(QuadObj, 2, 3, 32, 64)

        Gl.glPopMatrix()
        Gl.glEndList()
        Gl.glPushMatrix()
        Gl.glCallList(1)
        Gl.glPopMatrix()
        Gl.glFlush()
        Gdi.SwapBuffersFast(hDC)
[COLOR="Blue"]
    End Sub[/COLOR]
 
Thanks for the help!!! from the looks of it, this seems to be the perfect solution(I'm more than okay with sidestepping an issue whenever possible). Again thank you so much, I'll let you know how this works out.
-Sn1fflez
 
Last edited:
Back
Top