Question

My problem

I’m using the wxWidgets library in Python (through the wxPython wrapper). I’m trying to implement a class, PaddedStaticText, that behaves identically to the WX StaticText class, but which has a user-configurable padding on each side. Ideally PaddedStaticText would be a drop-in replacement for StaticText. That kind of screams “subclass”, right? Unfortunately, I think that if I were to subclass StaticText I’d have to basically reimplement all of its drawing routines, and I don’t have the WX expertise to do that.

Solution one

The solution I came up with was to implement a class that had a StaticText attribute that would be placed within a couple of Sizers to effect the padding:

class PaddedStaticText(wx.Window):
    def __init__(self, parent, label='', padding=(0, 0, 0, 0), **kwargs):
        wx.Window.__init__(self, parent)

        self.text = wx.StaticText(self, label=label, **kwargs)

        inner_sizer = wx.BoxSizer(wx.VERTICAL)

        inner_sizer.Add((0, padding[0]))
        inner_sizer.Add(self.text, proportion=1, flag=wx.EXPAND)
        inner_sizer.Add((0, padding[2]))

        outer_sizer = wx.BoxSizer(wx.HORIZONTAL)

        outer_sizer.Add((padding[3], 0))
        outer_sizer.Add(inner_sizer, proportion=1, flag=wx.EXPAND)
        outer_sizer.Add((padding[1], 0))

        self.SetSizer(outer_sizer)

This class inherits from wx.Window, so it can be used in many of the same contexts in which I could have used a StaticText—adding it to a Sizer, for instance. Since a PaddedStaticText is not actually a StaticText, though, I couldn’t just call SetFont() on one of the instances. I had to make my own proxy method:

    def SetFont(self, *args, **kwargs):
        self.text.SetFont(*args, **kwargs)

Of course, I had to write such a stub for each of the methods of StaticText that I wanted to be available. This approach works, but it didn’t feel elegant at all. I figured that in Python there must be a better way to do something like this.

Solution two

Based on this SO answer I wrote this simple Proxy class:

class Proxy(object):
    def __init__(self, underlying_object):
        self._underlying_object = underlying_object

    def __getattribute__(self, name):
        obj = object.__getattribute__(self, '_underlying_object')
        return object.__getattribute__(obj, name)

Then I could express PaddedStaticText as a subclass of Proxy with the underlying_object set to a StaticText instance. Unfortunately, WX balked when I tried to add my new PaddedStaticText instances to a Sizer. Since it’s a wrapper around a C++ library I guess WX doesn’t really respect Python’s duck typing: even though a PaddedStaticText can respond appropriately whenever a StaticText method is called on it, all wxPython cared about was the fact that it inherited from Proxy and not from wx.Window.

From there I briefly played around with making PaddedStaticText inherit from both Proxy and from wx.Window, but that didn’t work (and it’s an example of an inheritance diamond anyway). How can I implement my PaddedStaticText class in a way that Just Works? I’d appreciate suggestions on either of my solution ideas, or something completely different that solves the problem.

Was it helpful?

Solution

what i would do is:

class PaddedStaticText(wx.Window):

    def __init__(...):
       self.text = ...
       ...

    def __getattr__(self, name):
        '''delegate missing attributes to text'''
        return getattr(self.text, name)

which extends your basic inheritance approach with failover (__getattr__ is only called if nothing else works) to delegation.

that keeps things close to what wxwindows expects, and reduces the amount of magic, but still gives you access to things like SetFont.

alternatively, if you're going to use .text (as opposed to .__text) then you could just use that:

foo = PaddedStaticText(...)
foo.text.SetFont(...)

but you won't be able to replace existing StaticText instances.

[edit] it's late so i may improve this tomorrow, but if you need to use __getattribute__ then i would suggest being explicit about which methods you want. it's more typing, but in my experience fully automatic proxying in python often ends up surprising you in nasty ways. so something like:

def __getattribute__(self, name):
    if name in ('SetFont', ...):
        return getattr(self.text, name)
    else:
        return super(...).__getattribute__(self, name)

or (likely faster, but consuming more memory per instance):

def __init__(...):
    self.text = ...
    for name in ('SetFont', ...):
        setattr(self, name, getattr(self.text, name))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top