Search code examples
wxpythonwxwidgets

How do I synchronize data between an editable ListCtrl and a data source?


I'm trying to build a prototype application that consists of a table of data linked to a graph, which will be displayed alongside the table and updated as the data changes.

For the table I'm using a ListCtrl-derived object and, because I want to be able to edit the data in-place, I'm also inheriting the TextEditMixin class:

class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
             size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
    listmix.TextEditMixin.__init__(self)

I want to keep my back-end data separate from its display, so my wx.Frame-derived object has a data source object from which it reads data to populate the ListCtrl.

    self.list = EditableListCtrl(panel, style=wx.LC_REPORT)
    self.list.InsertColumn(0, 'A', width=140)
    self.list.InsertColumn(1, 'B', width=130)

    for i in range(0, self.db.getNumRecords()):
        item = self.db.getRecord(i)
        index = self.list.InsertStringItem(sys.maxint, str(item[0]))
        self.list.SetStringItem(index, 1, str(item[1]))

Since I now essentially have two copies of the data, I'd like to make sure that the data source is updated whenever the ListCtrl is edited by the user.

Is there a standard way to do this?

I have tried binding to the EVT_LIST_ITEM_DESELECTED event, but it triggers before the TextEditMixin functionality changes the data in the ListCtrl - the data retrieved from the ListCtrl by the callback function is the old data.


Solution

  • There are two ways to synchronise the data.

    1. Use a Virtual ListCtrl

    When using a Virtual ListCtrl, you don't have to add data manually. It pulls data from your data source.

    To make a ListCtrl virtual, initialise the ListCtrl with the wx.LC_VIRTUAL style flag.

    For the Virtual ListCtrl to pull the data, you need to override the following functions (obviously you need to subclass ListCtrl first):

    OnGetItemText(self, item, column)
    OnGetItemAttr(self, item)
    OnGetItemImage(self, item)
    

    The first of these handles string data. I haven't used the other two. (If you're not using them, just return None and -1 respectively.)

    You also need to call SetItemCount(item_count) to tell the ListCtrl how many records to retrieve.

    To update the data source when the user modifies a cell, you need to implement SetVirtualData(self, row, col, text).

    See the presentation "Advanced wxPython Nuts and Bolts" by Robin Dunn for more information.

    2. Use a regular ListCtrl

    Subclass ListCtrl and override the function SetStringItem(self, row, col, text). In your new implementation, update your data source. Don't forget to also call the base class SetStringItem() though! Otherwise, the ListCtrl appearance won't change.

    Virtual ListCtrl is a bit more work, but recommended because you no longer end up with two copies of the data.

    (Thanks to Mike Driscoll for pointing me in the right direction to find this information!)