Using custom data types in DataViewCustomRenderer

I’m trying to implement a custom renderer for a column in a wx.dataview.DataViewCtrl. However different rows may have different value types for that column. So I’m trying to implement a custom renderer that can handle the different types. The problem I’m having is that the renderer isn’t even being called, apparently because the type of the value is not correct.

I tried overriding GetDefaultType() in my DataViewCustomRenderer to return “object” in the hope that would allow any object to be passed through. That didn’t work, so I thought maybe it’s a more specific check and it has to be that exact class and not a descendant. So I made a wrapper class:

class AnyContainer:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self.value)

…and wrap all values returned from my wx.dataview.PyDataViewModel in instances of that class. Then I modify my GetDefaultType() to return “AnyContainer”. Still no go. The renderer is not being called.

How can I get my renderer to be called for custom data types? I can’t find an example of this being done anywhere.

Also unless I missed it I guess there’s no python glue code for this that I can look at? It’s all invisible auto-generated c or something? I tried to see what it’s looking for but if the code is there I missed it.

If you override the renderer’s IsCompatibleVariantType() method you can specify which variant types the renderer will accept.

The variant types will be strings that the underlying wxWidgets C++ code will recognise, rather than python types e.g. “long”, “double”, “datetime”, “string”.

Below is a very simple example that uses this technique. It handles some variant types that can be represented as strings, so doesn’t handle graphical types such as bitmaps, checkboxes, choices, icons, progress bars or spin controls.

import wx
import wx.dataview as dv
from datetime import date

DATA = (42, 3.14159, "GREEN", date.today())

class TestCustomRenderer(dv.DataViewCustomRenderer):
    def __init__(self):
        super().__init__()
        self.value = None

    @staticmethod
    def GetFormattedValue(value):
        if isinstance(value, float):
            value = f"{value:0.3f}"
        elif isinstance(value, wx.DateTime):
            value = value.Format("%d/%m/%Y")
        else:
            value = str(value)
        return value

    def IsCompatibleVariantType(self, variant):
        return variant in ("long", "double", "datetime", "string")

    def SetValue(self, value):
        self.value = value
        return True

    def GetValue(self):
        return self.value

    def GetSize(self):
        value = self.GetFormattedValue(self.GetValue())
        size = self.GetTextExtent(value)
        size += (2, 2)
        return size

    def Render(self, cell, dc, state):
        value = self.GetFormattedValue(self.GetValue())
        self.RenderText(value, 0, cell, dc, state)
        return True

class TestFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="Variant Types")
        self.SetSize((300, 300))
        self.dvc = dv.DataViewListCtrl(self, style=dv.DV_HORIZ_RULES | dv.DV_VERT_RULES)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self.dvc, 1, wx.EXPAND|wx.TOP, 4)
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.close_button = wx.Button(self, label="Close")
        button_sizer.Add(self.close_button, 0, wx.RIGHT, 8)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT|wx.ALL, 8)
        self.SetSizer(main_sizer)
        self.Layout()

        self.Bind(wx.EVT_CLOSE,  self.OnClose)
        self.Bind(wx.EVT_BUTTON, self.OnClose, self.close_button)

        renderer = TestCustomRenderer()
        col = dv.DataViewColumn("Values", renderer, 0)
        self.dvc.AppendColumn(col)

        for value in DATA:
            self.dvc.AppendItem([value])

    def OnClose(self, _evt):
        self.Destroy()


if __name__ == '__main__':
    app = wx.App()
    frame = TestFrame(None)
    frame.Show()
    app.MainLoop()

Tested using wxPython 4.2.2 gtk3 (phoenix) wxWidgets 3.2.6 + Python 3.12.3 + Linux Mint 22.1