dc.Clear(): is this a bug or am I doing something wrong?

Hello, I’m working with OGL. I have a diagram in an ogl.ShapeCanvas. When the user wants to delete a shape in the diagram, in order to redraw the canvas, the following code is executed after the shape has been deleted (self is the ShapeCanvas):

    dc = wx.ClientDC(self)
    self.PrepareDC(dc)
    self.GetDiagram().Clear(dc)
    self.Redraw(dc)

This code works fine when I have no scrollbars. However with scrollbars it doesn’t work as good. If I have a shape in the top-left of the canvas (that is, I don’t need to scroll in order to see it) when i select it and erase it everything goes fine. But if I have a shape in the right or bottom of the canvas, I scroll to see it, select it and erase it, the shape doesn’t dissapear until I scroll out of view and scroll back again.

I have looked into OGL’s code and Diagram.Clear is just:

def Clear(self, dc):
    """Clear the specified device context."""
    dc.Clear()

So, what’s wrong? Why is the DC not getting cleared?

Armando.

Armando Serrano Lombillo wrote:

Hello, I'm working with OGL. I have a diagram in an ogl.ShapeCanvas. When the user wants to delete a shape in the diagram, in order to redraw the canvas, the following code is executed after the shape has been deleted (self is the ShapeCanvas):

        dc = wx.ClientDC(self)
        self.PrepareDC(dc)
        self.GetDiagram().Clear(dc)
        self.Redraw(dc)

This code works fine when I have no scrollbars. However with scrollbars it doesn't work as good. If I have a shape in the top-left of the canvas (that is, I don't need to scroll in order to see it) when i select it and erase it everything goes fine. But if I have a shape in the right or bottom of the canvas, I scroll to see it, select it and erase it, the shape doesn't dissapear until I scroll out of view and scroll back again.

I have looked into OGL's code and Diagram.Clear is just:

    def Clear(self, dc):
        """Clear the specified device context."""
        dc.Clear()

So, what's wrong? Why is the DC not getting cleared?

It is probably not refreshing the correct location for that shape because of the scrolled offset. Look into the OGL code where it is doing the delete and double check the rectangle that it is causing to be refreshed.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

I have been looking into the OGL code and this is what I understand:
· When you delete a shape, nothing is refreshed. The only thing that happens is that the shape is deleted from the internal lists of the canvas and diagram. So it is up to you to manually force a redraw of the canvas (next two steps).

· In order to redraw the diagram you first call diagram.Clear(dc), which in turn calls dc.Clear().
· You then call canvas.Redraw(dc). This redraws each shape of the diagram, but since the deleted shape is no longer there, it’s rectangle is not redrawn. However, as I understand it, this shouldn’t be a problem because the shape should have been erased from the screen in the previous step.

I have no previous experience with DCs, so I don’t know what should be done so that dc.Clear() clears the whole of the DC (or at least the visible part). Any help?

Armando

···

On Thu, Sep 18, 2008 at 2:12 AM, Robin Dunn robin@alldunn.com wrote:

Armando Serrano Lombillo wrote:

Hello, I’m working with OGL. I have a diagram in an ogl.ShapeCanvas. When the user wants to delete a shape in the diagram, in order to redraw the canvas, the following code is executed after the shape has been deleted (self is the ShapeCanvas):

    dc = wx.ClientDC(self)

    self.PrepareDC(dc)

    self.GetDiagram().Clear(dc)

    self.Redraw(dc)

This code works fine when I have no scrollbars. However with scrollbars it doesn’t work as good. If I have a shape in the top-left of the canvas (that is, I don’t need to scroll in order to see it) when i select it and erase it everything goes fine. But if I have a shape in the right or bottom of the canvas, I scroll to see it, select it and erase it, the shape doesn’t dissapear until I scroll out of view and scroll back again.

I have looked into OGL’s code and Diagram.Clear is just:

def Clear(self, dc):

    """Clear the specified device context."""

    dc.Clear()

So, what’s wrong? Why is the DC not getting cleared?

It is probably not refreshing the correct location for that shape because of the scrolled offset. Look into the OGL code where it is doing the delete and double check the rectangle that it is causing to be refreshed.

Robin Dunn

Software Craftsman

http://wxPython.org Java give you jitters? Relax with wxPython!


wxpython-users mailing list

wxpython-users@lists.wxwidgets.org

http://lists.wxwidgets.org/mailman/listinfo/wxpython-users

Yes, these days doing a refresh as needed and waiting for the paint event to do the actual drawing is a better approach. If you wanted to optimize it somewhat you could do a RefreshRect with a rectangle covering just the shape's old position and another RefreshRect covering the new position. Using the shape's OnMovePre and OnMovePost handlers would be a good place to do it. Off the top of my head I'm not sure if that rectangle should be in device coordinates or logical coordinates for the scrolled window, but my guess would be that it is device coords. By only refreshing subsets of the window the rest of it should be clipped and not updated in the paint event.

For the rest of the flicker problem probably the best thing to do would be to override the canvas's OnPaint and for platforms not already doing double buffering use a wx.BufferedPaintDC instead of the wx.PaintDC.

···

On 7/25/12 6:07 AM, tcab wrote:

So the answer I found was not to do this:

         shape.Move(dc, random.randint(40,400), shape.GetY(), display=False)
         canvas.GetDiagram().Clear(dc)
         canvas.GetDiagram().Redraw(dc)

but to do this instead

         shape.Move(dc, random.randint(40,400), shape.GetY())
         canvas.Refresh()

which successfully draws and refreshes the entire virtual area
correctly, but unfortunately its not as smooth and flicker free.

Am I on the right track with this solution - is there a better way that
doesn't introduce the performance penalty/slight flicker?

--
Robin Dunn
Software Craftsman

Thanks Robin for the confirmation that using .Refresh() is
the way to go these days, when trying to programmatically move shapes on a
scrolled ogl shapecanvas.

It seems therefore that the following OGL drawing and
clearing operations

  • shape.Move(…, display=True)
  • diagram.Redraw(…)
  • diagram.Clear(…)

are not useful when dealing with a ogl shapecanvas with
scrollbars.

diagram.Clear(dc) - Only clears the top, physical screen area

  • pointless in a situation where you want to clear the contents of a larger virtual
    sized ogl shapecanvas with scrollbars.

diagram.Redraw(dc) – Operates correctly and immediately draws
all shapes in the correct position on the virtual sized ogl canvas. However because we can’t diagram.Clear()
away ALL the old stuff, we need to call a subsequent .Refresh() which WILL
clear and cause a redraw/paint of everything – thus there is no point calling .Redraw()
in the first place- you will just get redundant calls to Draw.

shape.Move(dc, x, y, display=True) – Same issue as .Redraw()
in that the subsequent needed call to .Refresh() will simply redraw everything again
a second time anyway. The setting of x and y and a few other things done by
this method might be useful for some people, but they can at least set display
to false which skips the draw done by this method viz. shape.Move(dc, x, y,
display=False) works just as well. For others a shape.SetX() and shape.SetY() may be all they need.

So just to re-iterate – when trying to programmatically move
shapes on a scrolled ogl shapecanvas - there is no way of erasing the entire
virtual canvas, except to rely on calling a shapecanvas.Refresh() - which by
default will erase the background before sending the paint event, which
presumably then triggers calls to various .Draw() methods - so any initial
calls to draw and clear methods are all redundant and unnecessary and
potentially misleading to developers hoping for a correct result.

P.S. These methods (Move, Redraw, Clear as listed above) are
OK to use in a NON scrolling shapecanvas situation, and will give you a
slightly less flicker - .Refresh() need not be used at all.

I hope my above understanding is correct – I find it easy to
be confused by this whole paint / refresh / draw / clear / size/ scroll aspect
of wxpython!

-Andy

···

On Thursday, July 26, 2012 4:48:28 AM UTC+10, Robin Dunn wrote:

On 7/25/12 6:07 AM, tcab wrote:

So the answer I found was not to do this:

     shape.Move(dc, random.randint(40,400), shape.GetY(), display=False)
     canvas.GetDiagram().Clear(dc)
     canvas.GetDiagram().Redraw(dc)

but to do this instead

     shape.Move(dc, random.randint(40,400), shape.GetY())
     canvas.Refresh()

which successfully draws and refreshes the entire virtual area

correctly, but unfortunately its not as smooth and flicker free.

Am I on the right track with this solution - is there a better way that

doesn’t introduce the performance penalty/slight flicker?

Yes, these days doing a refresh as needed and waiting for the paint
event to do the actual drawing is a better approach. If you wanted to
optimize it somewhat you could do a RefreshRect with a rectangle
covering just the shape’s old position and another RefreshRect covering
the new position. Using the shape’s OnMovePre and OnMovePost handlers
would be a good place to do it. Off the top of my head I’m not sure if
that rectangle should be in device coordinates or logical coordinates
for the scrolled window, but my guess would be that it is device coords.
By only refreshing subsets of the window the rest of it should be
clipped and not updated in the paint event.

For the rest of the flicker problem probably the best thing to do would
be to override the canvas’s OnPaint and for platforms not already doing
double buffering use a wx.BufferedPaintDC instead of the wx.PaintDC.


Robin Dunn

Software Craftsman

http://wxPython.org

Thanks Robin for the confirmation that using .Refresh() is the way to go
these days, when trying to programmatically move shapes on a scrolled
ogl shapecanvas.

It seems therefore that the following OGL drawing and clearing operations

  * shape.Move(..., display=True)
  * diagram.Redraw(...)
  * diagram.Clear(...)

are not useful when dealing with a ogl shapecanvas with scrollbars.

*diagram.Clear(dc)* - Only clears the top, physical screen area -
pointless in a situation where you want to clear the contents of a
larger virtual sized ogl shapecanvas with scrollbars.

What dc are you passing it? Do you do canvas.PrepareDC(dc) on it?

When those portions are scrolled into view there should be paint events with the newly exposed regions in the update region, and so those regions should be repainted at that time. Unless there is more going on there than just dc.Clear that I'm not remembering at the moment, the unseen areas really should not need to be explicitly cleared. Unless, of course, there is a buffer bitmap that is being used and maintained by OGL. In that case it should be doing a full clear, but also in that case you should probably be passing it a buffered dc that has been set to use that buffer...

*diagram.Redraw(dc)* � Operates correctly and immediately draws all
shapes in the correct position on the virtual sized ogl canvas. However
because we can�t diagram.Clear() away ALL the old stuff, we need to call
a subsequent .Refresh() which WILL clear and cause a redraw/paint of
everything � thus there is no point calling .Redraw() in the first
place- you will just get redundant calls to Draw.

*shape.Move(dc, x, y, display=True)* � Same issue as .Redraw() in that
the subsequent needed call to .Refresh() will simply redraw everything
again a second time anyway. The setting of x and y and a few other
things done by this method might be useful for some people, but they can
at least set display to false which skips the draw done by this method
viz. shape.Move(dc, x, y, display=False) works just as well. For others
a shape.SetX() and shape.SetY() may be all they need.

If you (or anybody else) want to try to come up with a better implementation for these OGL methods in a scrolled window I'll review any patches that you come up with.

An additional step that would be nice to take would be to reduce or eliminate the use of wx.ClientDCs all over the place. An alternative that would probably fit in to OGL's current design fairly well would be to maintain a buffer bitmap and do all drawing to it via a wx.MemoryDC or wx.BufferedDC, and then when each place in the current code that creates and uses a wx.ClientDC is finished with that DC you could add one or more canvas.RefreshRect's if you know the rectangle(s) that need to be updated, or you can do a canvas.Refresh(False) if you don't. Then the paint event handler just becomes a few lines to draw that buffer bitmap to the window.

···

On 7/26/12 10:22 PM, Andy Bulka wrote:

--
Robin Dunn
Software Craftsman

Ironically this discussion seems to be leading back to the topic of the original poster’s subject line “dc.Clear(): is this a bug or am I doing something wrong?”. I think there may be a bug in .Clear(dc) not taking into account of the scroll, even though canvas.PrepareDC(dc) has been called.

diagram.Clear(dc) - Only clears the top, physical screen area -

pointless in a situation where you want to clear the contents of a

larger virtual sized ogl shapecanvas with scrollbars.

What dc are you passing it? Do you do canvas.PrepareDC(dc) on it?

Yes I am calling canvas.PrepareDC(dc)

Run the following code and when you press the “c” key you will see how .Clear(dc) always clears the top, physical screen area regardless of the scroll situation.

import wx

import wx.lib.ogl as ogl

import random

import time

class MyShapeCanvas(ogl.ShapeCanvas):

def __init__(self, parent, frame):

    ogl.ShapeCanvas.__init__(self, parent)

    self.frame = frame

    self.SetBackgroundColour("LIGHT BLUE")

    self.SetDiagram(ogl.Diagram())

    self.GetDiagram().SetCanvas(self)

WINDOW_SIZE = (600, 400)

VIRTUAL_SIZE_X = 1200

VIRTUAL_SIZE_Y = 800

class MainApp(wx.App):

def OnInit(self):

    self.frame = wx.Frame(None, -1, "test2 scroll drawing", pos=(450,450), size=WINDOW_SIZE,

                    style=wx.NO_FULL_REPAINT_ON_RESIZE|wx.DEFAULT_FRAME_STYLE)

    self.shapecanvas = MyShapeCanvas(self.frame, self.frame)

    self.shapecanvas.frame = self.frame

    

    self.shapecanvas.SetScrollbars(1, 1, VIRTUAL_SIZE_X, VIRTUAL_SIZE_Y)

    self.Bind(wx.EVT_CHAR, self.onKeyChar)

    ogl.OGLInitialize()  # creates some pens and brushes that the OGL library uses.

    self.frame.Show(True)

    wx.CallAfter(self.BootStrap)

    return True

def BootStrap(self):

    # Add some shapes

    y = 30

    for x in range(30, 1200, 70):

        shape = ogl.RectangleShape( 60, 60 )

        shape.SetX(x)

        shape.SetY(y)

        self.shapecanvas.AddShape( shape )

        y += 70

    self.shapecanvas.GetDiagram().ShowAll( 1 )

def onKeyChar(self, event):

    canvas = self.shapecanvas

    keycode = chr(event.GetKeyCode())

    if keycode == 'c':

        # The .Clear(dc) below always clears the top, physical size window

        # area - never the scrolled physical size window visible at the

        # moment (even though I have called prepareDC !!). This I think may

        # be a bug? 

        dc = wx.ClientDC(canvas)

        canvas.PrepareDC(dc)

        canvas.GetDiagram().Clear(dc)

def main():

application = MainApp(0)

application.MainLoop()

if name == ‘main’:

main()

In the above demo, as you scroll up and down, the newly exposed regions are repainted nicely. But when you hit “c” you will see that .Clear(dc) doesn’t clear the visible physical screen area taking into account the scroll. It always clears the topmost visible physical screen area.

When those portions are scrolled into view there should be paint events
with the newly exposed regions in the update region, and so those
regions should be repainted at that time. Unless there is more going on
there than just dc.Clear that I’m not remembering at the moment, the
unseen areas really should not need to be explicitly cleared.

Ok yes I agree. Originally I thought that shapecanvas.Clear(dc) should perhaps clear the whole virtualsized canvas - but now I see that that it should only be clearing the visible area, taking into account of the scroll. As you say, the newly exposed regions will look after themselves with the relevant paint events being triggered.

Trouble is, shapecanvas.Clear(dc) doesn’t take into account the scroll - as my demo shows.

P.S. Thanks for your comments re buffers and possible enhancements to OGL. My understanding of wxpython hasn’t really progressed into buffering yet, so I can’t comment. I am running on windows 7, wx-2.8-msw-unicode, python 2.7 and am assuming there is some sort of double buffering I am already getting for free :slight_smile: though I don’t know whether mac os x and linux will perform the same - I do plan to port my app to those platforms so will soon discover this.

-Andy

In the above demo, as you scroll up and down, the newly exposed regions
are repainted nicely. But when you hit "c" you will see that .Clear(dc)
doesn't clear the visible physical screen area taking into account the
scroll. It always clears the topmost visible physical screen area.

Ok, I see it too. It's not quite expected but I'm not sure it's a bug as from some perspectives it is probably the correct thing to do... Anyway, a simple work around is to simply not call PrepareDC in that case. Then it will always be the physical window area that is cleared.

P.S. Thanks for your comments re buffers and possible enhancements to
OGL. My understanding of wxpython hasn't really progressed into
buffering yet, so I can't comment.

The best way to learn it is to do it.

I am running on windows 7,
wx-2.8-msw-unicode, python 2.7 and am assuming there is some sort of
double buffering I am already getting for free :slight_smile: though I don't know
whether mac os x and linux will perform the same - I do plan to port my
app to those platforms so will soon discover this.

Windows still doesn't double buffer by default, although it can be turned on now. However it doesn't always work the way one would expect, especially when wx.ClientDCs are involved. The other platforms do double-buffer by default.

···

On 7/28/12 9:14 PM, Andy Bulka wrote:

--
Robin Dunn
Software Craftsman

Thanks - a most fascinating workaround, which opens up all sorts of possibilities again. Now I have a way to Clear the visible physical window area regardless of scroll. This now means that I can rework my old code and it now works e.g.

dc = wx.ClientDC(canvas)

canvas.GetDiagram().Clear(dc) # do not prepare the dc !!

canvas.PrepareDC(dc)

shape.Move(dc, x, y)

canvas.GetDiagram().Redraw(dc)

which is not quite as pithy as the newer .Refresh based solution

shape.Move(dc, x, y, display=False)

canvas.Refresh()

but nevertheless is now a viable option.

Yeah its curious that you must call PrepareDC before drawing operations in order to take into account scrolling offsets, but must not call PrepareDC before clearing operations. It does seem inconsistent.

But now that I think about it, it sort of makes sense. I mean, to clear the current physical screen is a no brainer - its just sitting there in the frame all the time - from a hardware point of view. On the other hand to clear just the topmost window area that has possibly scrolled out of view - that’s the thing that requires calculation, and thus which requires a call to PrepareDC - and thus you do get the clever ability to only clear that top windowful of area and if e.g. there is a slight scroll in place, the Clear will only clear a portion of the visible screen. As to why anybody would ever want to just clear the top window-ful of a scrolled canvas - I don’t know. But I think I’m at peace with how this all works now. Thanks!

-Andy Bulka

www.andypatterns.com

···

On Tue, Jul 31, 2012 at 2:40 AM, Robin Dunn robin@alldunn.com wrote:

Ok, I see it too. It’s not quite expected but I’m not sure it’s a bug as from some perspectives it is probably the correct thing to do… Anyway, a simple work around is to simply not call PrepareDC in that case. Then it will always be the physical window area that is cleared.

At last! Just read this thread and, 4 years later, I’ve been able to fix this bug in my code. Thanks to both.

Armando.

···

On Tue, Jul 31, 2012 at 10:42 AM, Andy Bulka abulka@gmail.com wrote:

On Tue, Jul 31, 2012 at 2:40 AM, Robin Dunn robin@alldunn.com wrote:

Ok, I see it too. It’s not quite expected but I’m not sure it’s a bug as from some perspectives it is probably the correct thing to do… Anyway, a simple work around is to simply not call PrepareDC in that case. Then it will always be the physical window area that is cleared.

Thanks - a most fascinating workaround, which opens up all sorts of possibilities again. Now I have a way to Clear the visible physical window area regardless of scroll. This now means that I can rework my old code and it now works e.g.

dc = wx.ClientDC(canvas)

canvas.GetDiagram().Clear(dc) # do not prepare the dc !!

canvas.PrepareDC(dc)

shape.Move(dc, x, y)

canvas.GetDiagram().Redraw(dc)

which is not quite as pithy as the newer .Refresh based solution

shape.Move(dc, x, y, display=False)

canvas.Refresh()

but nevertheless is now a viable option.

Yeah its curious that you must call PrepareDC before drawing operations in order to take into account scrolling offsets, but must not call PrepareDC before clearing operations. It does seem inconsistent.

But now that I think about it, it sort of makes sense. I mean, to clear the current physical screen is a no brainer - its just sitting there in the frame all the time - from a hardware point of view. On the other hand to clear just the topmost window area that has possibly scrolled out of view - that’s the thing that requires calculation, and thus which requires a call to PrepareDC - and thus you do get the clever ability to only clear that top windowful of area and if e.g. there is a slight scroll in place, the Clear will only clear a portion of the visible screen. As to why anybody would ever want to just clear the top window-ful of a scrolled canvas - I don’t know. But I think I’m at peace with how this all works now. Thanks!

-Andy Bulka

www.andypatterns.com

To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com

or visit http://groups.google.com/group/wxPython-users?hl=en