Files
Phoenix/demo/DVC_IndexListModel.py
2020-04-02 16:35:07 -07:00

258 lines
9.3 KiB
Python

#!/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 = """<html><body>
<h2><center>DataViewCtrl with DataViewIndexListModel</center></h2>
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.)
<p> See the comments in the source for lots of details.
</body></html>
"""
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])