Search code examples
pythonwxpythondrawingwxwidgets

wxPython Paint Damaged, Clipped area


I have the following simple code (click the pink box and you can move it around with your mouse while holding down the left mouse button).

import wx


class AppPanel(wx.Panel):

    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id)
        p = MovablePanel(self, -1)
        self.i = 0
        self.Bind(wx.EVT_PAINT, self.OnPaint, self)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        self.i = self.i+10
        c = self.i % 255
        c = (0, 0, c)
        dc.SetPen(wx.Pen(c))
        dc.SetBrush(wx.Brush(c))
        dc.DrawRectangle(0, 0, 10000,10000)





class MovablePanel(wx.Panel):

    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id)
        self.SetMinSize((500,500))
        self.SetSize((500,500))
        self.SetBackgroundColour("PINK")
        self.LEFT_DOWN = False
        self.Bind(wx.EVT_MOTION, self.OnMove, self)

        self.Bind(wx.EVT_LEFT_DOWN,
                         self.OnClickDown,
                         self)


        self.Bind(wx.EVT_LEFT_UP,
                         self.OnClickUp,
                         self)

    def OnClickUp(self, event):
        self.LEFT_DOWN = False
        self.Refresh()

    def OnClickDown(self, event):    
        self.LEFT_DOWN = True
        self.Refresh()

    def OnMove(self, event):
        if self.LEFT_DOWN:
            p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
            self.SetPosition(p)


if __name__ == "__main__":
    app = wx.App(False)
    f   = wx.Frame(None, -1, size = (700, 700))
    p   = AppPanel(f, -1)
    f.Show()
    f.Maximize()
    app.MainLoop()

and it is suppose to look like the following (simply resize the frame)

Normal background

However after moving the pink box around you will see it really looks like this

Bad Background with ghosting

I have tried the following

dc.Clear()

dc.DestroyClippingRegion() 

wx.FULL_REPAINT_ON_RESIZE

wx.EVT_ERASE_BACKGROUND

I'm pretty sure it has to do with it being a panel, and therefore the PaintEvent only marking it partially damaged. This part is colored differently making the 'ghosting' or 'smearing' obvious. Perhaps I'm using the wrong words because I was unable to find a solution (and I this seems to be a non complex issue simply having to do with the 'damaged' region).


Solution

  • Ok I found the problem, but I'll try to post more details later.

    Basically the goal of this code is to move a panel around and then update the parent panel. SetPosition calls Move which going through the wxWidget code calls DoMoveWindow, all of this leads to a change in position and a repaint call (not sure what calls the repaint yet). Great. However the repaint only marks a certain 'area' as it tries to be efficient. That is why some of the issue can be solved by having the panel go over the 'ghosted' area. What you have to do is after the SetPosition, call GetParent().Refresh(), which will send a 'full' paint without any excluded area.

    Another thing to note is there are TWO terms for this 'damaged' or 'clipped' area. One is 'damage' however there is another, 'dirty'. Damage is used in the wx PaintDC information

    Using wx.PaintDC within EVT_PAINT handlers is important because it automatically sets the clipping area to the damaged area of the window. Attempts to draw outside this area do not appear.

    Trusting the documentation you will be mostly lost. However in one of the wxPython DoubleBuffer how to's the lingo changes (but it is the same thing as 'damage')

    Now the OnPaint() method. It's called whenever ther is a pain event sent by the system: i.e. whenever part of the window gets dirty.

    Knowing this if you Google wx Window dirty you will get the following

    Mark the specified rectangle (or the whole window) as "dirty" so it will be repainted. Causes an EVT_PAINT event to be generated and sent to the window.

    Take the following three print cycles where an EVT_PAINT was fired after a SetPosition call (this is WITHOUT the GetParent().Refresh() call)

    # first EVT_PAINT
    Drawing
    Panel Size (1440, 851)
    Clipping Rect (0, 0, 1440, 851)
    Client Update Rect (x=0, y=6, w=500, h=501) # the only place getting update is 
                                                # directly below the panel 
                                                # (that is (500, 500) )
    
    # second
    Drawing
    Panel Size (1440, 851)
    Clipping Rect (0, 0, 1440, 851)
    Client Update Rect (x=0, y=6, w=910, h=845) # however this time the update area is                                                                                                                            
                                                # bigger, this is also right before
                                                # the move
                                                # i believe what it is doing is
                                                # drawing from (0,6) to (910, 851)
                                                # why? because the panel is moving to
                                                # (410, 390) and the bottom right
                                                # corner of the panel (after moved)
                                                # is (410+500, 390+461) = (910, 851)
                                                # or about where the edge of the panel
                                                # will be
    
    # third
    Drawing
    Panel Size (1440, 851)
    Clipping Rect (0, 0, 1440, 851)
    Client Update Rect (x=410, y=390, w=500, h=461)
    

    Here is the update code to play around with, hopefully this will help others.

    import wx
    
    instructions = """
    How to use.
    1) Hover your mouse over the pink panel.
    2) Click down (left click)
    3) While holding down drag mouse around
    4) Release mouse button to stop.
    
    """
    
    class AppPanel(wx.Panel):
    
        def __init__(self, parent, id):
            wx.Panel.__init__(self, parent, id)
            self.sizer = wx.BoxSizer(wx.VERTICAL)
            self.settings_sizer = wx.BoxSizer(wx.HORIZONTAL)
            p = MovablePanel(self, -1)
    
            self.c = wx.CheckBox(self, -1, label = "Ghosting On?")
            self.p = p
            self.i = 0
    
            self.settings_sizer.Add(self.c)
    
            self.sizer.Add(self.settings_sizer)
            self.sizer.Add(self.p)
            self.SetSizer(self.sizer)
            self.Layout()
    
            self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.c)
            self.Bind(wx.EVT_PAINT, self.OnPaint, self)
            self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase, self)
    
    
        def OnCheck(self, event):
            print "CHECK\n\n\n\n\n"
            v = self.c.GetValue()
            self.p.r = v
            print v
    
    
        def OnErase(self, event):
            pass
    
        def OnPaint(self, event):
            print "Drawing"
            dc = wx.PaintDC(self)
            print "Panel Rect, ", self.p.GetPosition(),
            print self.p.GetSize()
            print "Clipping Rect", dc.GetClippingBox()
            print "Client Update Rect", self.GetUpdateClientRect()
            print "----------------------------"
            self.i = self.i+10
            c = self.i % 255
            c = (0, 0, c)
            dc.SetPen(wx.Pen(c))
            dc.SetBrush(wx.Brush(c))
    
            dc.DrawRectangle(0, 0, 10000,10000)
            self.SetBackgroundColour(c)
    
            dc.SetPen(wx.Pen("WHITE"))
            dc.SetBrush(wx.Brush("WHITE"))
    
    
            dc.DrawRectangle(0, 0, self.GetSize()[0], self.c.GetSize()[1])
    
    
    
    
    
    class MovablePanel(wx.Panel):
    
        def __init__(self, parent, id):
            wx.Panel.__init__(self, parent, id)
            self.SetMinSize((300,300))
            self.SetSize((300,300))
    
            txt = wx.StaticText(self, -1, label = "CLICK AND DRAG ME!")
            inst = wx.StaticText(self, -1, label = instructions)
    
            font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)
            txt.SetFont(font)
            inst.SetFont(font)
    
            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.Add(txt, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
            sizer.Add(inst, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
            self.SetSizer(sizer)
    
            self.SetBackgroundColour("PINK")
            self.LEFT_DOWN = False
            self.r = False
            self.Bind(wx.EVT_MOTION, self.OnMove, self)
    
            self.Bind(wx.EVT_LEFT_DOWN,
                             self.OnClickDown,
                             self)
    
    
            self.Bind(wx.EVT_LEFT_UP,
                             self.OnClickUp,
                             self)
    
        def OnClickUp(self, event):
            self.LEFT_DOWN = False
            self.Refresh()
    
        def OnClickDown(self, event):    
            self.LEFT_DOWN = True
            self.Refresh()
    
        def OnMove(self, event):
            if self.LEFT_DOWN:
                p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
                self.SetPosition(p)
                if not self.r:
                    self.GetParent().Refresh()
    
    
    if __name__ == "__main__":
        app = wx.App(False)
        f   = wx.Frame(None, -1, size = (700, 700))
        p   = AppPanel(f, -1)
        f.Show()
        app.MainLoop()