Size: 24402
Comment:
|
Size: 24426
Comment: app.frame.Layout() was forgotten in Drag and drop listctrl
|
Deletions are marked like this. | Additions are marked like this. |
Line 650: | Line 650: |
app.frame.Layout() |
List Controls
Basic operation
- Associating python data with items
Selecting and manipulating items in wxListCtrls (using SetItem* methods)
Report-view List controls "why don't my items show up" (missing headers) self.list.InsertColumn( 0, "Items", width=-1)
See Also:
["wxListCtrl ToolTips"]
Associating Python Data with Items
When you need to associate data with an item:
1 listctrl.SetItemData( item_by_index, integer_for_item )
Yep! You get to associate a WHOLE integer with the item!
To associate more, make a num generator (or use wxNew`Id) and keep a dict binding id to whatever data you want to associate.
So, for example:
1 id = wxNewId()
2 data_dict[ id ] = some_data_to_associate
3 listctrl.InsertStringItem( index_to_place_at, string_name_of_item )
4 listctrl.SetItemData( index_to_place_at, id )
So, now you can traverse:
index --> associated data (id) --> some_data_to_associate
Selecting Items Programmatically
To select an item programmatically in a wxListCtrl (Note that this appears to be a bug? Shouldn't need to use wxLIST_STATE_SELECTED for stateMask argument, should be able to use a MASK argument instead?):
self.SetItemState ( ID, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED )
It should be (I think):
self.SetItemState( ID, wxLIST_STATE_SELECTED, wxLIST_MASK_STATE )
Note: I just tried both versions, and the first version works in wxPython 2.5; the second version (using LIST_MASK_STATE) does not.
I think only the first version is correct. The last parameter is a mask of all the states you want to modify, so it must consist only of wx.LIST_STATE_* combinations. The wx.LIST_MASK_STATE is only used in SetItem, where it tells which fields are valid. Seems the docs could be clearer though.
Retrieve Currently Selected Indices in List Control
A common task, there's no pre-built function to accomplish this, so here's a recipe:
1 def _getSelectedIndices( self, state = wxLIST_STATE_SELECTED):
2 indices = []
3 found = 1
4 lastFound = -1
5 while found:
6 index = self.GetNextItem(
7 lastFound,
8 wxLIST_NEXT_ALL,
9 state,
10 )
11 if index == -1:
12 break
13 else:
14 lastFound = index
15 indices.append( index )
16 return indices
wxListCtrl's Hello world
It took half an hour for a newbie like me to get this tiny application running. Until I saw that you must call "InsertStringItem"
1 import wx
2 class MyApp(wx.App):
3 def OnInit(self):
4 frame = wx.Frame(None, -1, "Hello from wxPython")
5
6 id=wx.NewId()
7 self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
8 self.list.Show(True)
9
10 self.list.InsertColumn(0,"Data #1")
11 self.list.InsertColumn(1,"Data #2")
12 self.list.InsertColumn(2,"Data #3")
13
14 self.list.InsertStringItem(0,"hello")
15 self.list.SetStringItem(0,1,"world")
16 self.list.SetStringItem(0,2,"!")
17
18 frame.Show(True)
19 self.SetTopWindow(frame)
20 return True
21
22 app = MyApp(0)
23 app.MainLoop()
Changing fonts in a ListCtrl
Supposing you have a List Control, and you want to set some items to, say, bold. That's all - no changing anything else about the font, so the face remains the same as the other items. (Bear in mind the face will most likely be the default face as set by the system, and hence we don't know what it is.)
Because I (Dave Cridland) am terminally stupid, I didn't figure out how to do this, and couldn't find any documentation. In case anyone else is trying to do the same thing, and can't figure it out either, here's what I did:
1 import wx
2 # I'm using the new namespace. Hoorah!
3
4 # Note this is very much incomplete, and only dwells on how to set the font stuff in an attr.
5
6 class FancyListCtrl( wx.ListCtrl ):
7 def __init__( self, parent, id ):
8 wx.ListCtrl.__init__( self, parent, id, style = wx.LC_REPORT|wx.LC_VIRTUAL )
9 # We setup our pretty attribute once, here.
10 self._funky_attr = wx.ListItemAttr()
11 # This now contains default attributes.
12 # We can set the text colour to be different:
13 self._funky_attr.SetTextColour( wx.BLUE )
14 # But we can't do either of:
15 # self._funky_attr.GetFont().SetWeight( wx.BOLD )
16 # (Above fails silently - probably GetFont() returns a copy.)
17 # new_font = wx.Font()
18 # (No default constructor in wxPython.)
19 # But we can extract the default font:
20 new_font = self._funky_attr.GetFont()
21 # Set the weight on it:
22 new_font.SetWeight( wx.BOLD )
23 # Then put it back.
24 self._funky_attr.SetFont( new_font )
25 # self._funky_attr is now ready to be returned by OnGetItemAttr()
26 # (since this happens to be virtual.)
Simply changing the font of a specific item:
Unselecting an item programmatically
It took me (Werner Bruhin) some searching, as always when one figures it out it is easy!
Type Ahead Mixin
Windows (and presumably other OS do as well) provides Type Ahead functionality by default in list controls. But not if they are Virtual. I had a need to provide Type Ahead for some virtual controls in my app, so I knocked up this Mixin class for wxListCtrl objects. Happily it works with both Virtual and Normal list controls which means if you use it in your app then all your list controls can have the same Type Ahead behaviour.
There are some things I've tried to make configurable; the type ahead timeout, case sensitivity to the search, special keycodes to avoid during typeahead, and I've provided methods that you could override to change the default behaviour that I've coded (it was almost on a whim as I didn't really like the behaviour that Windows was providing, it seemed, weird). To help understand what's going on I've tried to document the code to explain the bits I think are weird.
One thing that is notable is that the default windows type ahead doesn't seem to recognise the space character, with some playing about with EVT_KEY_DOWN and EVT_CHAR events I've made my type ahead deal with spaces. Which is nice.
Anyway, on with the code (please excuse the verbosity of my variable and method names ).
1 from wxPython.wx import *
2
3 class TypeAheadListCtrlMixin:
4 """ A Mixin Class that does type ahead scrolling for list controls.
5 Assumes that the wxListCtrl it is mixed into is sorted (it's using
6 a binary search to find the correct item).
7
8 I wrote this because on Windows there was no type ahead when you
9 used a virtual list control, and then couldn't be bohered deciphering
10 the default windows typeahead for non-virtual list controls. So I
11 made it work for both virtual and non-virtual controls so that all
12 my list controls would have the same functionality.
13
14 Things you can change programatically:
15 * expand or contract the list of keycodes that stop the type ahead
16 * expand or contract the list of keycodes that are allowed to be
17 used inside typeahead, but won't start it (e.g. space which normally
18 acts as an Activation Key)
19 * change the timeout time (init param, defaults to 500 milliseconds)
20 * change the sensitivity of the search (init param, defaults to caseinsensitive)
21 Things you can change in the class that you mix this into:
22 * override the following methods:
23 - currentlySelectedItemFoundByTypeAhead
24 - currentlySelectedItemNotFoundByTypeAhead
25 - newItemFoundByTypeAhead
26 - nothingFoundByTypeAhead
27 changing these changes the behaviour of the typeahead in various stages.
28 See doc comments on methods.
29
30 Written by Murray Steele (muz at h-lame dot com)
31 """
32
33 # These Keycodes are the ones that if we detect them we will cancel the current
34 # typeahead state.
35 stopTypeAheadKeyCodes = [
36 WXK_BACK,
37 WXK_TAB,
38 WXK_RETURN,
39 WXK_ESCAPE,
40 WXK_DELETE,
41 WXK_START,
42 WXK_LBUTTON,
43 WXK_RBUTTON,
44 WXK_CANCEL,
45 WXK_MBUTTON,
46 WXK_CLEAR,
47 WXK_PAUSE,
48 WXK_CAPITAL,
49 WXK_PRIOR,
50 WXK_NEXT,
51 WXK_END,
52 WXK_HOME,
53 WXK_LEFT,
54 WXK_UP,
55 WXK_RIGHT,
56 WXK_DOWN,
57 WXK_SELECT,
58 WXK_PRINT,
59 WXK_EXECUTE,
60 WXK_SNAPSHOT,
61 WXK_INSERT,
62 WXK_HELP,
63 WXK_F1,
64 WXK_F2,
65 WXK_F3,
66 WXK_F4,
67 WXK_F5,
68 WXK_F6,
69 WXK_F7,
70 WXK_F8,
71 WXK_F9,
72 WXK_F10,
73 WXK_F11,
74 WXK_F12,
75 WXK_F13,
76 WXK_F14,
77 WXK_F15,
78 WXK_F16,
79 WXK_F17,
80 WXK_F18,
81 WXK_F19,
82 WXK_F20,
83 WXK_F21,
84 WXK_F22,
85 WXK_F23,
86 WXK_F24,
87 WXK_NUMLOCK,
88 WXK_SCROLL]
89 # These are the keycodes that we have to catch in evt_key_down, not evt_char
90 # By the time they get to evt_char then the OS has looked at them and gone,
91 # hey, this keypress means do something (like pressing space acts as an ACTIVATE
92 # key in a list control.
93 catchInKeyDownIfDuringTypeAheadKeyCodes = [
94 WXK_SPACE
95 ]
96 # These are the keycodes that we will allow during typeahead, but won't allow to start
97 # the type ahead process.
98 dontStartTypeAheadKeyCodes = [
99 WXK_SHIFT,
100 WXK_CONTROL,
101 WXK_MENU #ALT Key, ALT Gr generates both WXK_CONTROL and WXK_MENU.
102 ]
103 dontStartTypeAheadKeyCodes.extend(catchInKeyDownIfDuringTypeAheadKeyCodes)
104
105 def __init__(self, typeAheadTimeout = 500, casesensitive = False, columnToSearch = 0):
106 # Do most work in the char handler instead of keydown.
107 # This means we get the correct keycode for the key pressed as it should
108 # appear on screen, rather than all uppercase or "default" us keyboard
109 # punctuation.
110 # However there are things that we need to catch in key_down to stop
111 # them getting sent to the underlying windows control and generating
112 # other events (notably I'm talking about the SPACE key which generates
113 # an ACTIVATE event in these list controls).
114 EVT_KEY_DOWN(self, self.OnTypeAheadKeyDown)
115 EVT_CHAR(self, self.OnTypeAheadChar)
116 timerId = wxNewId()
117 self.typeAheadTimer = wxTimer(self,timerId)
118 EVT_TIMER(self,timerId,self.OnTypeAheadTimer)
119 self.clearTypeAhead()
120 self.typeAheadTimeout = typeAheadTimeout
121 self.columnToSearch = columnToSearch
122 if not casesensitive:
123 self._GetItemText = lambda idx: self.GetItem(idx,self.columnToSearch).GetText().lower()
124 self._GetKeyCode = lambda keycode: chr(keycode).lower()
125 else:
126 self._GetItemText = lambda idx: self.GetItem(idx,self.columnToSearch).GetText()
127 self._GetKeyCode = chr
128
129 def OnTypeAheadKeyDown(self, event):
130 keycode = event.GetKeyCode()
131 if keycode in self.stopTypeAheadKeyCodes:
132 self.clearTypeAhead()
133 else:
134 if self.typeAhead == None:
135 if keycode in self.dontStartTypeAheadKeyCodes:
136 self.clearTypeAhead()
137 else:
138 if keycode in self.catchInKeyDownIfDuringTypeAheadKeyCodes:
139 self.OnTypeAheadChar(event)
140 return
141 event.Skip()
142
143 def OnTypeAheadChar(self, event):
144 # stop the timer, to make sure that it doesn't fire in the middle of
145 # doing this and screw up by None-ifying the typeAhead string.
146 # TODO: Yes some kind of lock around a typeAheadState object
147 # that contained typeAhead, lastTypeAheadFoundAnything and lastTypeAhead
148 # would be better...
149 self.typeAheadTimer.Stop()
150 keycode = event.GetKeyCode()
151 if keycode in self.stopTypeAheadKeyCodes:
152 self.clearTypeAhead()
153 event.Skip()
154 return
155 else:
156 if self.typeAhead == None:
157 if keycode in self.dontStartTypeAheadKeyCodes:
158 self.clearTypeAhead()
159 event.Skip()
160 return
161 else:
162 self.typeAhead = self._GetKeyCode(keycode)
163 else:
164 self.typeAhead += self._GetKeyCode(keycode)
165 self.doTypeAhead()
166 # This timer is used to nullify the typeahead after a while
167 self.typeAheadTimer.Start(self.typeAheadTimeout,wxTIMER_ONE_SHOT)
168
169 def inTypeAhead(self):
170 return self.typeAhead != None
171
172 def currentlySelectedItemFoundByTypeAhead(self, idx):
173 """This method is called when the typeahead string matches
174 the text of the currently selected item. Put code here if
175 you want to have something happen in this case.
176 NOTE: Method only called if there was a currently selected item.
177
178 idx refers to the index of the currently selected item."""
179 # we don't do anything as we've already selected the thing we want
180 pass
181
182 def currentlySelectedItemNotFoundByTypeAhead(self, idx):
183 """This method is called when the typeahead string matches
184 an item that isn't the currently selected one. Put code
185 here if you want something to happen to the currently
186 selected item.
187 NOTE: use newItemFoundByTypeAhead for doing something to
188 the newly matched item.
189 NOTE: Method only called if there was a currently selected item.
190
191 idx referes to the index of the currently selected item."""
192 # we deselect it.
193 self.SetItemState(idx, 0, wxLIST_STATE_SELECTED)
194 self.SetItemState(idx, 0, wxLIST_STATE_FOCUSED)
195
196 def newItemFoundByTypeAhead(self, idx):
197 """This is called when the typeahead string matches
198 an item that isn't the currently selected one. Put
199 code here if you want something to happen to the newly
200 found item.
201 NOTE: use currentlySelectedItemNotFoundByTypeAhead for
202 doing something to the previously selected item.
203
204 idx refers to the index of the newly matched item."""
205 # we select it and make sure it is focused
206 self.SetItemState(idx, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
207 self.SetItemState(idx, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED)
208 self.EnsureVisible(idx)
209
210 def nothingFoundByTypeAhead(self, idx):
211 """This method is called when the typeahead string doesn't
212 match any items. Put code here if you want something to
213 happen in this case.
214
215 idx refers to the index of the currently selected item
216 or -1 if nothing was selected."""
217 # don't do anything here, what could we do?
218 pass
219
220 def doTypeAhead(self):
221 curselected = -1
222 if self.lastTypeAheadFoundSomething:
223 curselected = self.lastTypeAhead
224 else:
225 curselected = self.GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
226 min = 0
227 if curselected != -1:
228 term_name = self._GetItemText(curselected)
229 if term_name.startswith(self.typeAhead):
230 self.currentlySelectedItemFoundByTypeAhead(curselected)
231 self.lastTypeAheadFoundAnything = True
232 self.lastTypeAhead = curselected
233 return #We don't want this edgecase falling through
234
235 new_idx = self.binary_search(self.typeAhead,min)
236 if new_idx != -1:
237 if new_idx != curselected and curselected != -1:
238 self.currentlySelectedItemNotFoundByTypeAhead(curselected)
239 self.newItemFoundByTypeAhead(new_idx)
240 self.lastTypeAheadFoundAnything = True
241 self.lastTypeAhead = new_idx
242 else:
243 self.nothingFoundByTypeAhead(curselected)
244 self.lastTypeAheadFoundAnything = False
245 self.lastTypeAhead = -1
246
247 # NOTE: Originally from ASPN. Augmented.
248 def binary_search(self, t, min = 0):
249 min = min;
250 max = self.GetItemCount() - 1
251 while 1:
252 if max < min:
253 return self.doEdgeCase(m, t)
254 m = (min + max) / 2
255 cur_term = self._GetItemText(m)
256 if cur_term < t:
257 min = m + 1
258 elif cur_term > t:
259 max = m - 1
260 else:
261 return m
262
263 def doEdgeCase(self, m, t):
264 """ This method makes sure that if we don't find the typeahead
265 as an actual string, then we will return the first item
266 that starts with the typeahead string (if there is one)"""
267 before = self._GetItemText(max(0,m-1))
268 this = self._GetItemText(m)
269 after = self._GetItemText(min((self.GetItemCount()-1),m+1))
270 slice = len(t)
271 if this[:slice] == t:
272 return m
273 elif before[:slice] == t:
274 return max(0,m-1)
275 elif after[:slice] == t:
276 return min((self.GetItemCount()-1),m+1)
277 else:
278 return -1
279
280 def clearTypeAhead(self):
281 self.typeAhead = None
282 self.lastTypeAheadFoundSomething = False
283 self.lastTypeAheadIdx = -1
284
285 def OnTypeAheadTimer(self, event):
286 self.clearTypeAhead()
Customizing ColumnSorterMixin to Sort Dates
This code augments wx.lib.mixins.listctrl.ColumnSorterMixin so that it can sort dates that match the date_re regular expression listed at the top of the code. You need to modify another section of the code to transform your date into the YYYYMMDD format so that the dates can be easily sorted numerically. Alternatively, you could use the datetime module and store and sort date objects.
1 import wx
2 import wx.lib.mixins.listctrl
3 import locale
4 import re
5
6 # Change this to your settings
7 date_re = re.compile("(\d\d-\d\d-\d\d\d\d)")
8
9 #----------------------------------------------------------------------------
10
11 class CustColumnSorterMixin(wx.lib.mixins.listctrl.ColumnSorterMixin):
12 def __init__(self, numColumns):
13 wx.lib.mixins.listctrl.ColumnSorterMixin(self, numColumns)
14
15 def GetColumnSorter(self):
16 return self.CustColumnSorter
17
18 def CustColumnSorter(self, key1, key2):
19 col = self._col
20 ascending = self._colSortFlag[col]
21 item1 = self.itemDataMap[key1][col]
22 item2 = self.itemDataMap[key2][col]
23
24 alpha = date_re.match(item1)
25 beta = date_re.match(item2)
26 if None != alpha and None != beta:
27 a = alpha.groups(1)[0]
28 b = beta.groups(1)[0]
29 # Change these from your settings to YYYYMMDD
30 item1 = a[-4:]+a[:2]+a[3:5]
31 item2 = b[-4:]+b[:2]+b[3:5]
32
33 item1 = int(item1)
34 item2 = int(item2)
35
36 #--- Internationalization of string sorting with locale module
37 if type(item1) == type('') or type(item2) == type(''):
38 cmpVal = locale.strcoll(str(item1), str(item2))
39 else:
40 cmpVal = cmp(item1, item2)
41 #---
42
43 # If the items are equal then pick something else to make the sort value unique
44 if cmpVal == 0:
45 cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
46
47 if ascending:
48 return cmpVal
49 else:
50 return -cmpVal
Drag and Drop with lists
Here's how you can use drag-and-drop to reorder a simple list, or to move items from one list to another.
As it stands, item data will be lost. Maybe in version 2! - JohnFouhy
1 """ DnD demo with listctrl. """
2
3 import wx
4 import string
5
6 class DragList(wx.ListCtrl):
7 def __init__(self, parent, **kw):
8 kw['style'] = wx.LC_SINGLE_SEL|wx.LC_LIST
9 wx.ListCtrl.__init__(self, parent, **kw)
10
11 self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._startDrag)
12
13 dt = ListDrop(self._insert)
14 self.SetDropTarget(dt)
15
16 def _startDrag(self, e):
17 """ Put together a data object for drag-and-drop _from_ this list. """
18
19 # Create the data object: Just use plain text.
20 data = wx.PyTextDataObject()
21 idx = e.GetIndex()
22 text = self.GetItem(idx).GetText()
23 data.SetText(text)
24
25 # Create drop source and begin drag-and-drop.
26 dropSource = wx.DropSource(self)
27 dropSource.SetData(data)
28 res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove)
29
30 # If move, we want to remove the item from this list.
31 if res == wx.DragMove:
32 # It's possible we are dragging/dropping from this list to this list. In which case, the
33 # index we are removing may have changed...
34
35 # Find correct position.
36 pos = self.FindItem(idx, text)
37 self.DeleteItem(pos)
38
39 def _insert(self, x, y, text):
40 """ Insert text at given x, y coordinates --- used with drag-and-drop. """
41
42 # Clean text.
43 text = filter(lambda x: x in (string.letters + string.digits + string.punctuation + ' '), text)
44
45 # Find insertion point.
46 index, flags = self.HitTest((x, y))
47
48 if index == wx.NOT_FOUND:
49 if flags & wx.LIST_HITTEST_NOWHERE:
50 index = self.GetItemCount()
51 else:
52 return
53
54 # Get bounding rectangle for the item the user is dropping over.
55 rect = self.GetItemRect(index)
56
57 # If the user is dropping into the lower half of the rect, we want to insert _after_ this item.
58 if y > (rect.y + rect.height/2):
59 index = index + 1
60
61 self.InsertStringItem(index, text)
62
63 class ListDrop(wx.PyDropTarget):
64 """ Drop target for simple lists. """
65
66 def __init__(self, setFn):
67 """ Arguments:
68 - setFn: Function to call on drop.
69 """
70 wx.PyDropTarget.__init__(self)
71
72 self.setFn = setFn
73
74 # specify the type of data we will accept
75 self.data = wx.PyTextDataObject()
76 self.SetDataObject(self.data)
77
78 # Called when OnDrop returns True. We need to get the data and
79 # do something with it.
80 def OnData(self, x, y, d):
81 # copy the data from the drag source to our data object
82 if self.GetData():
83 self.setFn(x, y, self.data.GetText())
84
85 # what is returned signals the source what to do
86 # with the original data (move, copy, etc.) In this
87 # case we just return the suggested value given to us.
88 return d
89
90 if __name__ == '__main__':
91 items = ['Foo', 'Bar', 'Baz', 'Zif', 'Zaf', 'Zof']
92
93 class MyApp(wx.App):
94 def OnInit(self):
95 self.frame = wx.Frame(None, title='Main Frame')
96 self.frame.Show(True)
97 self.SetTopWindow(self.frame)
98 return True
99
100 app = MyApp(redirect=False)
101 dl1 = DragList(app.frame)
102 dl2 = DragList(app.frame)
103 sizer = wx.BoxSizer()
104 app.frame.SetSizer(sizer)
105 sizer.Add(dl1, proportion=1, flag=wx.EXPAND)
106 sizer.Add(dl2, proportion=1, flag=wx.EXPAND)
107 for item in items:
108 dl1.InsertStringItem(99, item)
109 dl2.InsertStringItem(99, item)
110 app.frame.Layout()
111 app.MainLoop()
The content on this page came from ListAndTreeControls, which was split up into this page and TreeControls .