#!/usr/bin/env python # Tags: phoenix-port, py3-port import wx import wx.dataview as dv #---------------------------------------------------------------------- # This model class provides the data to the view when it is asked for. # Since it is a list-only model (no hierarchical data) then it is able # to be referenced by row rather than by item object, so in this way # it is easier to comprehend and use than other model types. In this # example we also provide a Compare function to assist with sorting of # items in our model. Notice that the data items in the data model # object don't ever change position due to a sort or column # reordering. The view manages all of that and maps view rows and # columns to the model's rows and columns as needed. # # For this example our data is stored in a simple list of lists. In # real life you can use whatever you want or need to hold your data. class TestModel(dv.DataViewIndexListModel): def __init__(self, data, log): dv.DataViewIndexListModel.__init__(self, len(data)) self.data = data self.log = log # All of our columns are strings. If the model or the renderers # in the view are other types then that should be reflected here. def GetColumnType(self, col): return "string" # This method is called to provide the data object for a # particular row,col def GetValueByRow(self, row, col): return self.data[row][col] # This method is called when the user edits a data item in the view. def SetValueByRow(self, value, row, col): self.log.write("SetValue: (%d,%d) %s\n" % (row, col, value)) self.data[row][col] = value return True # Report how many columns this model provides data for. def GetColumnCount(self): return len(self.data[0]) # Report the number of rows in the model def GetCount(self): #self.log.write('GetCount') return len(self.data) # Called to check if non-standard attributes should be used in the # cell at (row, col) def GetAttrByRow(self, row, col, attr): ##self.log.write('GetAttrByRow: (%d, %d)' % (row, col)) if col == 3: attr.SetColour('blue') attr.SetBold(True) return True return False # This is called to assist with sorting the data in the view. The # first two args are instances of the DataViewItem class, so we # need to convert them to row numbers with the GetRow method. # Then it's just a matter of fetching the right values from our # data set and comparing them. The return value is -1, 0, or 1, # just like Python's cmp() function. def Compare(self, item1, item2, col, ascending): if not ascending: # swap sort order? item2, item1 = item1, item2 row1 = self.GetRow(item1) row2 = self.GetRow(item2) a = self.data[row1][col] b = self.data[row2][col] if col == 0: a = int(a) b = int(b) if a < b: return -1 if a > b: return 1 return 0 def DeleteRows(self, rows): # make a copy since we'll be sorting(mutating) the list # use reverse order so the indexes don't change as we remove items rows = sorted(rows, reverse=True) for row in rows: # remove it from our data structure del self.data[row] # notify the view(s) using this model that it has been removed self.RowDeleted(row) def AddRow(self, value): # update data structure self.data.append(value) # notify views self.RowAppended() class TestPanel(wx.Panel): def __init__(self, parent, log, model=None, data=None): self.log = log wx.Panel.__init__(self, parent, -1) # Create a dataview control self.dvc = dv.DataViewCtrl(self, style=wx.BORDER_THEME | dv.DV_ROW_LINES # nice alternating bg colors #| dv.DV_HORIZ_RULES | dv.DV_VERT_RULES | dv.DV_MULTIPLE ) # Create an instance of our simple model... if model is None: self.model = TestModel(data, log) else: self.model = model # ...and associate it with the dataview control. Models can # be shared between multiple DataViewCtrls, so this does not # assign ownership like many things in wx do. There is some # internal reference counting happening so you don't really # need to hold a reference to it either, but we do for this # example so we can fiddle with the model from the widget # inspector or whatever. self.dvc.AssociateModel(self.model) # Now we create some columns. The second parameter is the # column number within the model that the DataViewColumn will # fetch the data from. This means that you can have views # using the same model that show different columns of data, or # that they can be in a different order than in the model. self.dvc.AppendTextColumn("Artist", 1, width=170, mode=dv.DATAVIEW_CELL_EDITABLE) self.dvc.AppendTextColumn("Title", 2, width=260, mode=dv.DATAVIEW_CELL_EDITABLE) self.dvc.AppendTextColumn("Genre", 3, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) # There are Prepend methods too, and also convenience methods # for other data types but we are only using strings in this # example. You can also create a DataViewColumn object # yourself and then just use AppendColumn or PrependColumn. c0 = self.dvc.PrependTextColumn("Id", 0, width=40) # The DataViewColumn object is returned from the Append and # Prepend methods, and we can modify some of it's properties # like this. c0.Alignment = wx.ALIGN_RIGHT c0.Renderer.Alignment = wx.ALIGN_RIGHT c0.MinWidth = 40 # Through the magic of Python we can also access the columns # as a list via the Columns property. Here we'll mark them # all as sortable and reorderable. for c in self.dvc.Columns: c.Sortable = True c.Reorderable = True # Let's change our minds and not let the first col be moved. c0.Reorderable = False # set the Sizer property (same as SetSizer) self.Sizer = wx.BoxSizer(wx.VERTICAL) self.Sizer.Add(self.dvc, 1, wx.EXPAND) # Add some buttons to help out with the tests b1 = wx.Button(self, label="New View", name="newView") self.Bind(wx.EVT_BUTTON, self.OnNewView, b1) b2 = wx.Button(self, label="Add Row") self.Bind(wx.EVT_BUTTON, self.OnAddRow, b2) b3 = wx.Button(self, label="Delete Row(s)") self.Bind(wx.EVT_BUTTON, self.OnDeleteRows, b3) btnbox = wx.BoxSizer(wx.HORIZONTAL) btnbox.Add(b1, 0, wx.LEFT|wx.RIGHT, 5) btnbox.Add(b2, 0, wx.LEFT|wx.RIGHT, 5) btnbox.Add(b3, 0, wx.LEFT|wx.RIGHT, 5) self.Sizer.Add(btnbox, 0, wx.TOP|wx.BOTTOM, 5) # Bind some events so we can see what the DVC sends us self.Bind(dv.EVT_DATAVIEW_ITEM_EDITING_DONE, self.OnEditingDone, self.dvc) self.Bind(dv.EVT_DATAVIEW_ITEM_VALUE_CHANGED, self.OnValueChanged, self.dvc) def OnNewView(self, evt): f = wx.Frame(None, title="New view, shared model", size=(600,400)) TestPanel(f, self.log, self.model) b = f.FindWindowByName("newView") b.Disable() f.Show() def OnDeleteRows(self, evt): # Remove the selected row(s) from the model. The model will take care # of notifying the view (and any other observers) that the change has # happened. items = self.dvc.GetSelections() rows = [self.model.GetRow(item) for item in items] self.model.DeleteRows(rows) def OnAddRow(self, evt): # Add some bogus data to a new row in the model's data id = len(self.model.data) + 1 value = [str(id), 'new artist %d' % id, 'new title %d' % id, 'genre %d' % id] self.model.AddRow(value) def OnEditingDone(self, evt): self.log.write("OnEditingDone\n") def OnValueChanged(self, evt): self.log.write("OnValueChanged\n") #---------------------------------------------------------------------- def runTest(frame, nb, log): # Get the data from the ListCtrl sample to play with, converting it # from a dictionary to a list of lists, including the dictionary key # as the first element of each sublist. import ListCtrl musicdata = sorted(ListCtrl.musicdata.items()) musicdata = [[str(k)] + list(v) for k,v in musicdata] win = TestPanel(nb, log, data=musicdata) return win #---------------------------------------------------------------------- overview = """

DataViewCtrl with DataViewIndexListModel

This sample shows how to derive a class from PyDataViewIndexListModel and use it to interface with a list of data items. (This model does not have any hierarchical relationships in the data.)

See the comments in the source for lots of details. """ if __name__ == '__main__': import sys,os import run run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])