wx.CLientDC overdraws other widgets

The following code shows two buttons for resizing a window for drawing. The buttons on the left side are changing the size of the window on the right side.
With EVT_PAINT a circle is drawn into this window.
If button B2 is pressed, the window gets larger than the main frame, holding the buttons and the drawing window. Scrollbars will appear to move the window.
If you now move the horizontal scroll bar to the right, the circle will dissapear.
So far so good.
However if (having the circle moved) a focus change happens or the frame gets resized, the circle will be drawn over the B1 button (see picture):

Here is the code showing this behaviour:

#!/usr/bin/env python
import wx


class Frame(wx.Frame):

    def __init__(self, *args, **kw):
        super(Frame,self).__init__(*args, **kw)
        self.Centre()
        Gui.window(self,self)


class Gui():

    def window(self, frame):

        def b1f(ev):
            ww.SetSize(300,300)
            sw.SetVirtualSize(ww.GetSize())

        def b2f(ev):
            ww.SetSize(40,40)
            sw.SetVirtualSize(ww.GetSize())

        def pt(ev):
            z = wx.ClientDC(ww)
            z.DrawCircle(30,30,30)


        pl = wx.Panel(frame)
        pls = wx.BoxSizer(wx.HORIZONTAL)

        b1 = wx.Button(pl, label='B1')
        b2 = wx.Button(pl, label='B2')
        b1.Bind(wx.EVT_BUTTON,b1f)
        b2.Bind(wx.EVT_BUTTON,b2f)

        sw = wx.ScrolledWindow(pl)
        sw.SetScrollRate(1,1)
        ww = wx.Window(sw,style=wx.SIMPLE_BORDER)
        sw.SetVirtualSize(300,300)

        sw.Bind(wx.EVT_PAINT,pt)

        pls.Add(b1,1,wx.ALIGN_BOTTOM|wx.ALL, 5)
        pls.Add(b2,1,wx.ALL, 5)
        pls.Add(sw,1,wx.EXPAND|wx.ALL,5)
        pl.SetSizer(pls)


if __name__ == '__main__':
    app = wx.App()
    window = Frame(None)
    window.Show()
    app.MainLoop()

Is there a way to stop this overdrawing on resizing / refoccusing the main frame?
Many thanks for all tips!

Does this work?
z = wx.PaintDC(ww)

see wx.PaintEvent — wxPython Phoenix 4.1.1 documentation

Why do you place a Window in the ScrolledWindow?
You probably can just use the ScrolledWindow for your purpose.

Hi Dietmar
I tried it - but in this case the scroll bars are disapearing, if a button is pressed. They will be shown again, if the size of the main window is changed manually.
And secondly - the circle is not moving anymore.
Here the changed code I used:

#!/usr/bin/env python
import wx


class Frame(wx.Frame):

    def __init__(self, *args, **kw):
        super(Frame,self).__init__(*args, **kw)
        self.Centre()
        Gui.window(self,self)


class Gui():

    def window(self, frame):

        def b1f(ev):
            sw.SetSize(300,300)                 # changed from 'ww' to 'sw'
            sw.SetVirtualSize(sw.GetSize())     # ...

        def b2f(ev):
            sw.SetSize(40,40)                   # ...
            sw.SetVirtualSize(sw.GetSize())     # ...

        def pt(ev):
            z = wx.ClientDC(sw)                 # Made the ScrolledWindow as 
                                                # the DC
            z.DrawCircle(30,30,30)

        pl = wx.Panel(frame)
        pls = wx.GridSizer(1,3,5,5)

        b1 = wx.Button(pl, label='B1')
        b2 = wx.Button(pl, label='B2')
        b1.Bind(wx.EVT_BUTTON,b1f)
        b2.Bind(wx.EVT_BUTTON,b2f)

        sw = wx.ScrolledWindow(pl)
        sw.SetScrollRate(1,1)
        # ww = wx.Window(sw,style=wx.SIMPLE_BORDER)
        sw.SetVirtualSize(300,300)
        frame.PostSizeEvent()           # added on trying to get 
                                        # scrollbars visible after pressing
                                        # a button - without success

        sw.Bind(wx.EVT_PAINT,pt)

        pls.Add(b1,1,wx.ALIGN_BOTTOM|wx.ALL, 5)
        pls.Add(b2,1,wx.ALL, 5)
        pls.Add(sw,1,wx.EXPAND|wx.ALL,5)

        pl.SetSizer(pls)


if __name__ == '__main__':
    app = wx.App()
    window = Frame(None)
    window.Show()
    app.MainLoop()

Well, whatever you do, the first step is to use a PaintDC, not ClientDC.

wx.PaintEvent — wxPython Phoenix 4.1.2a1 documentation

Also, you probably have to call DoPrepareDC for a scrolled window.

wx.ScrolledWindow — wxPython Phoenix 4.1.2a1 documentation
wx.Scrolled — wxPython Phoenix 4.1.2a1 documentation

Most people use standard object oriented application design for their wxPython applications. Relying on nested functions and scopes is rather unusual. If you want support, it’s better to follow common standards.

Also, it’s always a good idea to include platform and version information.

Hi Dietmar

Many thanks!
Let me share the results in folllowing your suggestions.

I use

>>> wx.VERSION
(4, 0, 7, '.post2')
[mg@manjaro-mh ~]$ uname -a
Linux manjaro-mh 5.4.188-1-MANJARO #1 SMP PREEMPT Mon Mar 28 07:56:17 UTC 2022 x86_64 GNU/Linux

=> Using PaintDC instead of ClientDC produces:

Traceback (most recent call last):
  File "/home/mhehnen/Dokumente/Python/Tools/wxPython/scroll.py", line 47, in pt
    z = wx.PaintDC(self.ww)
wx._core.wxAssertionError: C++ assertion "IsOk()" failed at ./src/common/dcgraph.cpp(437) in SetTextBackground(): wxGCDC(cg)::SetTextBackground - invalid DC

=>I rewrote the cde to become object oriented and tested it in drawing to the wx.ScrolledWindow as well as to a extra wx.Window. Both variants created the same behaviour as decribed previously: Using wx.Window shows scrollbars - but overdraws other widgets, directly drawing to wx.ScrolledWindow shows scrollbars only after manually resizing the window and does not move the circle.
=> I’ve tested also in all varients self.sw.DoPrepareDC(z) but could not see any changes.

Here the new code:


#!/usr/bin/env python
import wx


class Frame(wx.Frame):

    def __init__(self, *args, **kw):
        super(Frame,self).__init__(*args, **kw)

        self.InitUI()


    def InitUI(self):

        pl = wx.Panel(self)
        pls = wx.GridSizer(1,3,5,5)

        b1 = wx.Button(pl, label='B1')
        b2 = wx.Button(pl, label='B2')
        b1.Bind(wx.EVT_BUTTON,self.b1f)
        b2.Bind(wx.EVT_BUTTON,self.b2f)

        self.sw = wx.ScrolledWindow(pl)
        self.sw.SetScrollRate(1,1)
        self.sw.SetVirtualSize(300,300)

        self.ww =wx.Window(self.sw,style=wx.SIMPLE_BORDER)

        self.sw.Bind(wx.EVT_PAINT,self.pt)

        pls.Add(b1,1,wx.ALIGN_BOTTOM|wx.ALL, 5)
        pls.Add(b2,1,wx.ALL, 5)
        pls.Add(self.sw,1,wx.EXPAND|wx.ALL,5)

        pl.SetSizer(pls)

    def b1f(self,ev):
        self.ww.SetSize(300,300)
        self.ww.SetVirtualSize(self.ww.GetSize())

    def b2f(self,ev):
        self.ww.SetSize(40,40)
        self.ww.SetVirtualSize(self.ww.GetSize())

    def pt(self,ev):
        z = wx.ClientDC(self.ww)
        z.DrawCircle(30,30,30)
        self.sw.DoPrepareDC(z)

if __name__ == '__main__':
    app = wx.App()
    window = Frame(None)
    window.Show()
    app.MainLoop()

I have that issue also with some other code.
WIth that the issue looks as follows:

Drawing not moved:

Drawing moved to the left using scroll bars:

Thanks a lot for sharing more ideas …

The argument needs to be the window that received the paint event. Could be that this is causing the exception.
I’m mostly on Windows where I don’t see the assertion / exception but I see differences in painting.
With PaintDC you should see overdrawing only if the elements don’t fit the sizer.

Thanks. The code is easier to read now :slight_smile:

Hi Dietmar

It’s working now.
Looks like, that the proper use of DoPrepareDC(needs to be a method of wx.ScrolledWindow) as well as using ‘PaintDC’ (using the wx.Window as an argument) fixed the issue:


#!/usr/bin/env python
import wx


class Frame(wx.Frame):

    def __init__(self, *args, **kw):
        super(Frame,self).__init__(*args, **kw)

        self.InitUI()


    def InitUI(self):

        pl = wx.Panel(self)
        pls = wx.GridSizer(1,3,5,5)

        b1 = wx.Button(pl, label='B1')
        b2 = wx.Button(pl, label='B2')
        b1.Bind(wx.EVT_BUTTON,self.b1f)
        b2.Bind(wx.EVT_BUTTON,self.b2f)

        self.sw = wx.ScrolledWindow(pl)
        self.sw.SetScrollRate(1,1)
        self.sw.SetVirtualSize(300,300)

        self.ww =wx.Window(self.sw,style=wx.SIMPLE_BORDER)

        self.ww.Bind(wx.EVT_PAINT,self.pt)

        pls.Add(b1,1,wx.ALIGN_BOTTOM|wx.ALL, 5)
        pls.Add(b2,1,wx.ALL, 5)
        pls.Add(self.sw,1,wx.EXPAND|wx.ALL,5)

        pl.SetSizer(pls)

    def b1f(self,ev):
        self.ww.SetSize(300,300)
        self.ww.SetVirtualSize(self.ww.GetSize())

    def b2f(self,ev):
        self.ww.SetSize(40,40)
        self.ww.SetVirtualSize(self.ww.GetSize())

    def pt(self,ev):
        z = wx.PaintDC(self.ww)
        z.DrawCircle(30,30,30)
        self.sw.DoPrepareDC(z)

if __name__ == '__main__':
    app = wx.App()
    window = Frame(None)
    window.Show()
    app.MainLoop()

Thank you!

1 Like