Creating a virtual list control

Let's assume that your wxPython application needs to display a list of all your clients. Initially you use a regular list control, and it works fine. Eventually your use of wxPython makes you more and more successful. Your client list gets longer and longer. Too many clients and your application starts to have performance problems. Perhaps it takes a longer amount of time to start up. Probably it starts using more and more memory. What can you do? You can create a virtual list control.

The problem is a result of the way list control data is managed. Typically, the data is copied into the list control from wherever the data is generated. This is potentially wasteful of resources, and while in a small list it is unlikely to make any difference, creating a larger list control could use a significant amount of memory as well as a lot of startup time.

To minimize the memory and startup requirements of a list control, wxPython allows you to declare a virtual list control, which means that the information about each item is only generated on demand when the control needs to display the item. This prevents the control from needing to store each item in its own memory space, and also means that the entire control isn't declared at startup. The drawback is that retrieval of the list items may be slower in a virtual list. Figure 13.6 displays a virtual list in action.

Listing 13.5 displays the entire code example that produces the virtual list control.

n Virtual wx.ListCtrl

Request ID Summary

Date

I Submitte...

937141

additions...

2004-07-..

goofy

846368

wxTextCt...

2003-11-..

ryannpcs

346367

Less flick...

2003-11-..

ryannpcs

346366

Wishlist-...

2003-11-..

ryannpcs

346364

wxPostsc...

2003-11-..

ryannpcs

346363

Wishlist-...

2003-11-..

ryannpcs

346362

Wishlist-...

2003-11-..

ryannpcs

953341

Support f...

2004-05-..

tonye

952466

mac men...

2004-05-..

pimbuur

923399

FloatCanv...

2004-01-..

glchaprnan

912714

wxGrid: S...

2004-03-..

rclund

901061

wxCombo...

2004-02-..

tomash

900763

Please ad...

2004-02-..

jsat66

394921

trigger on...

2004-02-..

goofy

369303

HitTest in...

2004-01-..

dickkniep

363306

wxGrid - ...

2003-12-..

zinrt

975435

wxMenu ...

2004-06-..

]mt2715

969311

wxColour...

2004-06-..

wyo

959349

wx.Grid g...

2001-05-..

dodywijaya

959153

wxGrid: A...

2001-05-..

somecoder

Figure 13.6 A virtual list control

Listing 13.5 A virtual list control import wx import sys, glob, random import data class DataSource:

A data source def GetColumnHeaders(self): return data.columns def GetCount(self):

return len(data.rows)

def GetItem(self, index):

return data.rows[index]

def UpdateCache(self, pass start, end)

Declaring the virtual list

Creating a list with virtual flag class VirtualListCtrl(wx.ListCtrl)

def _init_(self, parent, dataSource):

wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL) self.dataSource = dataSource self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems) self.SetItemCount(dataSource.GetCount()) columns = dataSource.GetColumnHeaders() for col, text in enumerate(columns): self.InsertColumn(col, text)

<1—| Setting list size def DoCacheItems(self, evt)

self.dataSource.UpdateCache(

evt.GetCacheFrom(), evt.GetCacheTo())

def OnGetItemText(self, item, col): <-J Getting text on demand data = self.dataSource.Getltem(item) return data[col]

def OnGetItemAttr(self, item): return None def OnGetItemImage(self, item): return -1

class DemoFrame(wx.Frame):

"Virtual wx.ListCtrl", size=(600,400))

self.list = VirtualListCtrl(self, DataSource())

app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop()

The data source class is a simple example that stores our sample data items. A real data source class would manage fetching items from a database or similar, but only needs to implement the same interface as our simple example.

To create a virtual list, the first step is to make the flag wx.lc_virtual one of the flags passed to the list control on initialization O. Typically, you need to create your virtual list control by subclassing wx.ListCtrl, rather than just using the constructor. This is because you need to override some methods of wx.ListCtrl in order to populate the virtual list. The declaration will look something like the following:

class MyVirtualList(wx.ListCtrl):

wx.ListCtrl._init_(self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL)

The virtual list control must also call the SetltemCount() method sometime during its initialization. This tells the control how many data items exist in the data source so it sets appropriate limits and can manage the scrollbars. You can call SetltemCount() again if the number of items in the data source changes. Any of the On methods you override must be able to handle any value between 0 and Set-ItemCount() - 1.

Your virtual list control can override three methods of the parent class in order to determine what is displayed in the list control. The most important method to override is OnGetltemText(item, col). The item and col parameters are the row and column of the cell to be drawn and the return value is the string of the text to be displayed in that cell. For example, the following method just displays the coordinates of the cell.

def OnGetItemText(self, item, col):

return "Item %d, column %d" % (item, col)

If you want an image to be displayed in a row, you need to override the method OnGetltemlmage(item). The return value is an integer index into the list control's image list described earlier. If you do not override the method, the base class version returns -1, indicating no image to be displayed. If you want to change some of the display attributes of the row, then you can override the method OnGetltem-Attr(item), which again takes a row index as a parameter and returns an instance of the class wx.ListltemAttr. That class has a number of getters and setters that can be used to set the color, alignment, or other display settings for the row.

If the data on which you are basing the virtual list control changes and you wish to update the display, you can use the list control method Refreshltem(item) that forces a redraw of that particular row. The related method Refreshltems (itemFrom, itemTo) redraws all the rows between the two indexes.

To help you optimize fetching of data items in your data source, the virtual list control will send the evt_list_cache_hint event whenever it is about to display a new page of data. This gives your data source an opportunity to fetch several records at once from the database (or whatever) and save them. The subsequent OnGetltemText() calls for those items can be much quicker than having to go back to the database for each record.

+1 0

Post a comment