Differences between revisions 34 and 35
Revision 34 as of 2019-08-17 10:26:20
Size: 39367
Editor: Ecco
Comment:
Revision 35 as of 2019-08-17 10:45:16
Size: 39389
Editor: Ecco
Comment:
Deletions are marked like this. Additions are marked like this.
Line 5: Line 5:
 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)}}}  Selecting and manipulating items in wx.ListCtrls (using SetItem* methods) Report-view List controls "why don't my items show up" (missing headers) {{{self.list.InsertColumn( 0, "Items", width=-1)}}}
Line 15: Line 15:
== Associating Python Data with Items == == Associating Python Data with Items (Phoenix) ==
Line 19: Line 19:
listctrl.SetItemData( item_by_index, integer_for_item ) listctrl.SetItemData(item_by_index, integer_for_item)
Line 23: Line 23:
To associate more, make a num generator (or use wxNew`Id) and keep a dict binding id to whatever data you want to associate. To associate more, make a num generator (or use wx.NewIdRef) and keep a dict binding id to whatever data you want to associate.
Line 28: Line 28:
id = wxNewId()
data_dict[ id ] = some_data_to_associate
listctrl.InsertStringItem( index_to_place_at, string_name_of_item )
listctrl.SetItemData( index_to_place_at, id )
id = wx.NewIdRef()
data_dict[id] = some_data_to_associate
listctrl.InsertItem(index_to_place_at, string_name_of_item)
listctrl.SetItemData(index_to_place_at, id)
Line 79: Line 79:
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.''
To select an item programmatically in a wx.ListCtrl (note that this appears to be a bug ? Shouldn't need to use wx.LIST_STATE_SELECTED for stateMask argument, should be able to use a MASK argument instead ?) :

 . {{{self.SetItemState(ID, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)}}}

It should be (I think) :

 . {{{self.SetItemState(ID, wx.LIST_STATE_SELECTED, wx.LIST_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.''
Line 95: Line 95:
def _getSelectedIndices( self, state = wxLIST_STATE_SELECTED): def _getSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
Line 111: Line 111:
== 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" :-) I was trying to use InsertItem.
== wx.ListCtrl's Hello world (Phoenix) ==
It took half an hour for a newbie like me to get this tiny application running. Until I saw that you must call "[[InsertStringItem|InsertItem]]" :-) .
Line 120: Line 120:
        id=wx.NewId()
        self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        id = wx.NewIdRef()
        self.list = wx.ListCtrl(frame, id, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
Line 124: Line 124:
        self.list.InsertColumn(0,"Data #1")
        self.list.InsertColumn(1,"Data #2")
        self.list.InsertColumn(2,"Data #3")
        self.list.InsertColumn(0, "Data #1")
        self.list.InsertColumn(1, "Data #2")
        self.list.InsertColumn(2, "Data #3")
Line 129: Line 129:
        pos = self.list.InsertStringItem(0,"hello")
        # add values in the other columns on the same row
        self.list.SetStringItem(pos,1,"world")
        self.list.SetStringItem(pos,2,"!")
        pos = self.list.InsertItem(0, "hello")
        # Add values in the other columns on the same row
        self.list.SetItem(pos, 1, "world")
        self.list.SetItem(pos, 2, "!")
Line 141: Line 141:
== Changing fonts in a ListCtrl == == Changing fonts in a ListCtrl (Phoenix) ==
Line 153: Line 153:
class FancyListCtrl( wx.ListCtrl ):
    def __init__( self, parent, id ):
        wx.ListCtrl.__init__( self, parent, id, style = wx.LC_REPORT|wx.LC_VIRTUAL )
class FancyListCtrl(wx.ListCtrl):
    def __init__(self, parent, id):
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT|wx.LC_VIRTUAL)
Line 159: Line 159:
        # We can set the text colour to be different:
        self._funky_attr.SetTextColour( wx.BLUE )
        # But we can't do either of:
        # self._funky_attr.GetFont().SetWeight( wx.BOLD )
        # (Above fails silently - probably GetFont() returns a copy.)
        # We can set the text colour to be different :
        self._funky_attr.SetTextColour(wx.BLUE)
        # But we can't do either of :
        # self._funky_attr.GetFont().SetWeight(wx.BOLD)
        # (Above fails silently - probably GetFont() returns a copy).
Line 165: Line 165:
        # (No default constructor in wxPython.)
        # But we can extract the default font:
        # (No default constructor in wxPython).
        # But we can extract the default font :
Line 168: Line 168:
        # Set the weight on it:
        new_font.SetWeight( wx.BOLD )
        # Set the weight on it :
        new_font.SetWeight(wx.BOLD)
Line 171: Line 171:
        self._funky_attr.SetFont( new_font )         self._funky_attr.SetFont(new_font)
Line 173: Line 173:
        # (since this happens to be virtual.)
}}}
Simply changing the font of a specific item:

{{{#!python
# Get the item at a specific index:
        # (since this happens to be virtual).
}}}
Simply changing the font of a specific item :

{{{#!python
# Get the item at a specific index :
Line 180: Line 180:
# Get its font, change it, and put it back: # Get its font, change it, and put it back :
Line 187: Line 187:
== Unselecting an item programmatically == == Unselecting an item programmatically (Phoenix) ==
Line 191: Line 191:
# selecting as shown in the demo
self.SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)

# unselecting
self.SetItemState(item, 0, wxLIST_STATE_SELECTED)
# Selecting as shown in the demo
self.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)

# Unselecting
self.SetItemState(item, 0, wx.LIST_STATE_SELECTED)
Line 849: Line 849:
This striped drag list allows for dragging and dropping within itself while maintaining its appearance. While not used in the example stub, it'll also stripe itself on an insert event (like from a pop-up file dialog) or on a delete event. This striped drag list allows for dragging and dropping within itself while maintaining its appearance.

While not used in the example stub, it'll also stripe itself on an insert event (like from a pop-up file dialog) or on a delete event.

List Controls

Basic operation

  • Associating python data with items

    Selecting and manipulating items in wx.ListCtrls (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:

Associating Python Data with Items (Phoenix)

When you need to associate data with an item:

Toggle line numbers
   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 wx.NewIdRef) and keep a dict binding id to whatever data you want to associate.

So, for example:

Toggle line numbers
   1 id = wx.NewIdRef()
   2 data_dict[id] = some_data_to_associate
   3 listctrl.InsertItem(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

Python Data Mixin

I'm surprised I haven't seen one of these yet.

Toggle line numbers
   1 class ListCtrlPyDataMixin(object):
   2     def __init__(self):
   3         import sys
   4 
   5         self.id = -sys.maxint
   6         self.map = {}
   7 
   8         self.Bind(wx.EVT_LIST_DELETE_ITEM, self.OnDeleteItem)
   9         self.Bind(wx.EVT_LIST_DELETE_ALL_ITEMS, self.OnDeleteAllItems)
  10 
  11     def SetPyData(self, item, data):
  12         self.map[self.id] = data
  13         self.SetItemData(item, self.id)
  14         self.id += 1
  15 
  16     def SortPyItems(self, fn):
  17         from functools import wraps
  18 
  19         @wraps(fn)
  20         def wrapper(a, b):
  21             return fn(self.map[a], self.map[b])
  22 
  23         self.SortItems(wrapper)
  24 
  25     def OnDeleteItem(self, event):
  26         try:
  27             del self.map[event.Data]
  28         except KeyError:
  29             pass
  30         event.Skip()
  31 
  32     def OnDeleteAllItems(self, event):
  33         self.map.clear()
  34         event.Skip()

Have fun. -- ChristopherMonsanto

Selecting Items Programmatically

To select an item programmatically in a wx.ListCtrl (note that this appears to be a bug ? Shouldn't need to use wx.LIST_STATE_SELECTED for stateMask argument, should be able to use a MASK argument instead ?) :

  • self.SetItemState(ID, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)

It should be (I think) :

  • self.SetItemState(ID, wx.LIST_STATE_SELECTED, wx.LIST_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:

Toggle line numbers
   1 def _getSelectedIndices(self, state =  wx.LIST_STATE_SELECTED):
   2         indices = []
   3         lastFound = -1
   4         while True:
   5                 index = self.GetNextItem(
   6                         lastFound,
   7                         wxLIST_NEXT_ALL,
   8                         state,
   9                 )
  10                 if index == -1:
  11                         break
  12                 else:
  13                         lastFound = index
  14                         indices.append( index )
  15         return indices

wx.ListCtrl's Hello world (Phoenix)

It took half an hour for a newbie like me to get this tiny application running. Until I saw that you must call "InsertItem" :-) .

Toggle line numbers
   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.NewIdRef()
   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         # 0 will insert at the start of the list
  15         pos = self.list.InsertItem(0, "hello")
  16         # Add values in the other columns on the same row
  17         self.list.SetItem(pos, 1, "world")
  18         self.list.SetItem(pos, 2, "!")
  19 
  20         frame.Show(True)
  21         self.SetTopWindow(frame)
  22         return True
  23 
  24 app = MyApp(0)
  25 app.MainLoop()

Changing fonts in a ListCtrl (Phoenix)

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:

Toggle line numbers
   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 :

Toggle line numbers
   1 # Get the item at a specific index :
   2 item = self.myListControl.GetItem(index)
   3 # Get its font, change it, and put it back :
   4 font = item.GetFont()
   5 font.SetWeight(wx.FONTWEIGHT_BOLD)
   6 item.SetFont(font)
   7 # This does the trick:
   8 self.myListControl.SetItem(item)

Unselecting an item programmatically (Phoenix)

It took me (Werner Bruhin) some searching, as always when one figures it out it is easy!

Toggle line numbers
   1 # Selecting as shown in the demo
   2 self.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
   3 
   4 # Unselecting
   5 self.SetItemState(item, 0, wx.LIST_STATE_SELECTED)

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 :) ).

Toggle line numbers
   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         elif self.typeAhead == None:
 134             if keycode in self.dontStartTypeAheadKeyCodes:
 135                 self.clearTypeAhead()
 136         else:
 137             if keycode in self.catchInKeyDownIfDuringTypeAheadKeyCodes:
 138                 self.OnTypeAheadChar(event)
 139                 return
 140         event.Skip()
 141 
 142     def OnTypeAheadChar(self, event):
 143         # stop the timer, to make sure that it doesn't fire in the middle of
 144         # doing this and screw up by None-ifying the typeAhead string.
 145         # TODO: Yes some kind of lock around a typeAheadState object
 146         # that contained typeAhead, lastTypeAheadFoundAnything and lastTypeAhead
 147         # would be better...
 148         self.typeAheadTimer.Stop()
 149         keycode = event.GetKeyCode()
 150         if keycode in self.stopTypeAheadKeyCodes:
 151             self.clearTypeAhead()
 152             event.Skip()
 153             return
 154         else:
 155             if self.typeAhead == None:
 156                 if keycode in self.dontStartTypeAheadKeyCodes:
 157                     self.clearTypeAhead()
 158                     event.Skip()
 159                     return
 160                 else:
 161                     self.typeAhead = self._GetKeyCode(keycode)
 162             else:
 163                 self.typeAhead += self._GetKeyCode(keycode)
 164             self.doTypeAhead()
 165             # This timer is used to nullify the typeahead after a while
 166             self.typeAheadTimer.Start(self.typeAheadTimeout,wxTIMER_ONE_SHOT)
 167 
 168     def inTypeAhead(self):
 169         return self.typeAhead != None
 170 
 171     def currentlySelectedItemFoundByTypeAhead(self, idx):
 172         """This method is called when the typeahead string matches
 173            the text of the currently selected item.  Put code here if
 174            you want to have something happen in this case.
 175            NOTE: Method only called if there was a currently selected item.
 176 
 177            idx refers to the index of the currently selected item."""
 178         # we don't do anything as we've already selected the thing we want
 179         pass
 180 
 181     def currentlySelectedItemNotFoundByTypeAhead(self, idx):
 182         """This method is called when the typeahead string matches
 183            an item that isn't the currently selected one.  Put code
 184            here if you want something to happen to the currently
 185            selected item.
 186            NOTE: use newItemFoundByTypeAhead for doing something to
 187            the newly matched item.
 188            NOTE: Method only called if there was a currently selected item.
 189 
 190            idx referes to the index of the currently selected item."""
 191         # we deselect it.
 192         self.SetItemState(idx, 0, wxLIST_STATE_SELECTED)
 193         self.SetItemState(idx, 0, wxLIST_STATE_FOCUSED)
 194 
 195     def newItemFoundByTypeAhead(self, idx):
 196         """This is called when the typeahead string matches
 197            an item that isn't the currently selected one.  Put
 198            code here if you want something to happen to the newly
 199            found item.
 200            NOTE: use currentlySelectedItemNotFoundByTypeAhead for
 201            doing something to the previously selected item.
 202 
 203            idx refers to the index of the newly matched item."""
 204         # we select it and make sure it is focused
 205         self.SetItemState(idx, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
 206         self.SetItemState(idx, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED)
 207         self.EnsureVisible(idx)
 208 
 209     def nothingFoundByTypeAhead(self, idx):
 210         """This method is called when the typeahead string doesn't
 211            match any items.  Put code here if you want something to
 212            happen in this case.
 213 
 214            idx refers to the index of the currently selected item
 215            or -1 if nothing was selected."""
 216         # don't do anything here, what could we do?
 217         pass
 218 
 219     def doTypeAhead(self):
 220         curselected = -1
 221         if self.lastTypeAheadFoundSomething:
 222             curselected = self.lastTypeAhead
 223         else:
 224             curselected = self.GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
 225         min = 0
 226         if curselected != -1:
 227             term_name = self._GetItemText(curselected)
 228             if term_name.startswith(self.typeAhead):
 229                 self.currentlySelectedItemFoundByTypeAhead(curselected)
 230                 self.lastTypeAheadFoundAnything = True
 231                 self.lastTypeAhead = curselected
 232                 return #We don't want this edgecase falling through
 233 
 234         new_idx = self.binary_search(self.typeAhead,min)
 235         if new_idx != -1:
 236             if new_idx != curselected and curselected != -1:
 237                 self.currentlySelectedItemNotFoundByTypeAhead(curselected)
 238             self.newItemFoundByTypeAhead(new_idx)
 239             self.lastTypeAheadFoundAnything = True
 240             self.lastTypeAhead = new_idx
 241         else:
 242             self.nothingFoundByTypeAhead(curselected)
 243             self.lastTypeAheadFoundAnything = False
 244             self.lastTypeAhead = -1
 245 
 246     # NOTE: Originally from ASPN. Augmented.
 247     def binary_search(self, t, min = 0):
 248         min = min;
 249         max = self.GetItemCount() - 1
 250         while True:
 251             if max < min:
 252                 return self.doEdgeCase(m, t)
 253             m = (min + max) / 2
 254             cur_term = self._GetItemText(m)
 255             if cur_term < t:
 256                 min = m + 1
 257             elif cur_term > t:
 258                 max = m - 1
 259             else:
 260                 return m
 261 
 262     def doEdgeCase(self, m, t):
 263         """ This method makes sure that if we don't find the typeahead
 264          as an actual string, then we will return the first item
 265          that starts with the typeahead string (if there is one)"""
 266         before = self._GetItemText(max(0,m-1))
 267         this = self._GetItemText(m)
 268         after = self._GetItemText(min(self.GetItemCount()-1,m+1))
 269         if this.startswith(t):
 270             return m
 271         elif before.startswith(t):
 272             return max(0,m-1)
 273         elif after.startswith(t):
 274             return min(self.GetItemCount()-1,m+1)
 275         else:
 276             return -1
 277 
 278     def clearTypeAhead(self):
 279         self.typeAhead = None
 280         self.lastTypeAheadFoundSomething = False
 281         self.lastTypeAheadIdx = -1
 282 
 283     def OnTypeAheadTimer(self, event):
 284         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.

Toggle line numbers
   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{2})-(\d{2})-(\d{4})")
   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 alpha and beta:
  27             # Change these from your settings to YYYYMMDD
  28             item1 = alpha.group(3)+alpha.group(1)+alpha.group(2)
  29             item2 =  beta.group(3)+ beta.group(1)+ beta.group(2)
  30 
  31             item1 = int(item1)
  32             item2 = int(item2)
  33 
  34         #--- Internationalization of string sorting with locale module
  35         if type(item1) == type('') or type(item2) == type(''):
  36             cmpVal = locale.strcoll(str(item1), str(item2))
  37         else:
  38             cmpVal = cmp(item1, item2)
  39         #---
  40 
  41         # If the items are equal then pick something else to make the sort value unique
  42         if cmpVal == 0:
  43             cmpVal = cmp(*self.GetSecondarySortValues(col, key1, key2))
  44 
  45         if ascending:
  46             return cmpVal
  47         else:
  48             return -cmpVal

Drag and Drop with lists - 1

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

Toggle line numbers
   1 """ DnD demo with listctrl. """
   2 
   3 import wx
   4 
   5 class DragList(wx.ListCtrl):
   6     def __init__(self, *arg, **kw):
   7         if 'style' in kw and (kw['style']&wx.LC_LIST or kw['style']&wx.LC_REPORT):
   8             kw['style'] |= wx.LC_SINGLE_SEL
   9         else:
  10             kw['style'] = wx.LC_SINGLE_SEL|wx.LC_LIST
  11 
  12         wx.ListCtrl.__init__(self, *arg, **kw)
  13 
  14         self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._startDrag)
  15 
  16         dt = ListDrop(self._insert)
  17         self.SetDropTarget(dt)
  18 
  19     def _startDrag(self, e):
  20         """ Put together a data object for drag-and-drop _from_ this list. """
  21 
  22         # Create the data object: Just use plain text.
  23         data = wx.PyTextDataObject()
  24         idx = e.GetIndex()
  25         text = self.GetItem(idx).GetText()
  26         data.SetText(text)
  27 
  28         # Create drop source and begin drag-and-drop.
  29         dropSource = wx.DropSource(self)
  30         dropSource.SetData(data)
  31         res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove)
  32 
  33         # If move, we want to remove the item from this list.
  34         if res == wx.DragMove:
  35             # It's possible we are dragging/dropping from this list to this list.  In which case, the
  36             # index we are removing may have changed...
  37 
  38             # Find correct position.
  39             pos = self.FindItem(idx, text)
  40             self.DeleteItem(pos)
  41 
  42     def _insert(self, x, y, text):
  43         """ Insert text at given x, y coordinates --- used with drag-and-drop. """
  44 
  45         # Clean text.
  46         import string
  47         text = filter(lambda x: x in (string.letters + string.digits + string.punctuation + ' '), text)
  48 
  49         # Find insertion point.
  50         index, flags = self.HitTest((x, y))
  51 
  52         if index == wx.NOT_FOUND:
  53             if flags & wx.LIST_HITTEST_NOWHERE:
  54                 index = self.GetItemCount()
  55             else:
  56                 return
  57 
  58         # Get bounding rectangle for the item the user is dropping over.
  59         rect = self.GetItemRect(index)
  60 
  61         # If the user is dropping into the lower half of the rect, we want to insert _after_ this item.
  62         if y > rect.y + rect.height/2:
  63             index += 1
  64 
  65         self.InsertStringItem(index, text)
  66 
  67 class ListDrop(wx.PyDropTarget):
  68     """ Drop target for simple lists. """
  69 
  70     def __init__(self, setFn):
  71         """ Arguments:
  72          - setFn: Function to call on drop.
  73         """
  74         wx.PyDropTarget.__init__(self)
  75 
  76         self.setFn = setFn
  77 
  78         # specify the type of data we will accept
  79         self.data = wx.PyTextDataObject()
  80         self.SetDataObject(self.data)
  81 
  82     # Called when OnDrop returns True.  We need to get the data and
  83     # do something with it.
  84     def OnData(self, x, y, d):
  85         # copy the data from the drag source to our data object
  86         if self.GetData():
  87             self.setFn(x, y, self.data.GetText())
  88 
  89         # what is returned signals the source what to do
  90         # with the original data (move, copy, etc.)  In this
  91         # case we just return the suggested value given to us.
  92         return d
  93 
  94 if __name__ == '__main__':
  95     items = ['Foo', 'Bar', 'Baz', 'Zif', 'Zaf', 'Zof']
  96 
  97     class MyApp(wx.App):
  98         def OnInit(self):
  99             self.frame = wx.Frame(None, title='Main Frame')
 100             self.frame.Show(True)
 101             self.SetTopWindow(self.frame)
 102             return True
 103 
 104     app = MyApp(redirect=False)
 105     dl1 = DragList(app.frame)
 106     dl2 = DragList(app.frame)
 107     sizer = wx.BoxSizer()
 108     app.frame.SetSizer(sizer)
 109     sizer.Add(dl1, proportion=1, flag=wx.EXPAND)
 110     sizer.Add(dl2, proportion=1, flag=wx.EXPAND)
 111     for item in items:
 112         dl1.InsertStringItem(99, item)
 113         dl2.InsertStringItem(99, item)
 114     app.frame.Layout()
 115     app.MainLoop()

Drag and Drop with lists - 2 (Phoenix)

Toggle line numbers
   1 # dragAndDropList.py
   2 
   3 """
   4 
   5 Drag and drop (DnD) demo with listctrl :
   6 - Dragging of multiple selected items.
   7 - Dropping on an empty list.
   8 - Dropping of items on a list with a different number of columns.
   9 - Dropping on a different applications.
  10 
  11 """
  12 
  13 import pickle
  14 import sys
  15 from   random import choice
  16 import wx
  17 
  18 # class MyDragList
  19 # class MyListDrop
  20 # class MyApp
  21 
  22 #-------------------------------------------------------------------------------
  23 
  24 class MyDragList(wx.ListCtrl):
  25     def __init__(self, *arg, **kw):
  26         wx.ListCtrl.__init__(self, *arg, **kw)
  27 
  28         self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._startDrag)
  29 
  30         dt = MyListDrop(self)
  31         self.SetDropTarget(dt)
  32 
  33     def getItemInfo(self, idx):
  34         """Collect all relevant data of a listitem, and put it in a list"""
  35         l = []
  36         l.append(idx) # We need the original index, so it is easier to eventualy delete it
  37         l.append(self.GetItemData(idx)) # Itemdata
  38         l.append(self.GetItemText(idx)) # Text first column
  39         for i in range(1, self.GetColumnCount()): # Possible extra columns
  40             l.append(self.GetItem(idx, i).GetText())
  41         return l
  42 
  43     def _startDrag(self, e):
  44         """ Put together a data object for drag-and-drop _from_ this list. """
  45         l = []
  46         idx = -1
  47         while True: # Find all the selected items and put them in a list
  48             idx = self.GetNextItem(idx, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
  49             if idx == -1:
  50                 break
  51             l.append(self.getItemInfo(idx))
  52 
  53         # Pickle the items list.
  54         itemdata = pickle.dumps(l, 1)
  55         # Create our own data format and use it in a
  56         # Custom data object
  57         ldata = wx.CustomDataObject("ListCtrlItems")
  58         ldata.SetData(itemdata)
  59         # Now make a data object for the  item list.
  60         data = wx.DataObjectComposite()
  61         data.Add(ldata)
  62 
  63         # Create drop source and begin drag-and-drop.
  64         dropSource = wx.DropSource(self)
  65         dropSource.SetData(data)
  66         res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove)
  67 
  68         # If move, we want to remove the item from this list.
  69         if res == wx.DragMove:
  70             # It's possible we are dragging/dropping from this list to this list.
  71             # In which case, the index we are removing may have changed...
  72 
  73             # Find correct position.
  74             l.reverse() # Delete all the items, starting with the last item
  75             for i in l:
  76                 pos = self.FindItem(i[0], i[2])
  77                 self.DeleteItem(pos)
  78 
  79     def _insert(self, x, y, seq):
  80         """ Insert text at given x, y coordinates --- used with drag-and-drop. """
  81 
  82         # Find insertion point.
  83         index, flags = self.HitTest((x, y))
  84 
  85         if index == wx.NOT_FOUND: # Not clicked on an item
  86             if flags & (wx.LIST_HITTEST_NOWHERE|wx.LIST_HITTEST_ABOVE|wx.LIST_HITTEST_BELOW): # Empty list or below last item
  87                 index = self.GetItemCount() # Append to end of list
  88             elif self.GetItemCount() > 0:
  89                 if y <= self.GetItemRect(0).y: # Clicked just above first item
  90                     index = 0 # Append to top of list
  91                 else:
  92                     index = self.GetItemCount() + 1 # Append to end of list
  93         else: # Clicked on an item
  94             # Get bounding rectangle for the item the user is dropping over.
  95             rect = self.GetItemRect(index)
  96 
  97             # If the user is dropping into the lower half of the rect, we want to insert _after_ this item.
  98             # Correct for the fact that there may be a heading involved
  99             if y > rect.y - self.GetItemRect(0).y + rect.height/2:
 100                 index += 1
 101 
 102         for i in seq: # Insert the item data
 103             idx = self.InsertItem(index, i[2])
 104             self.SetItemData(idx, i[1])
 105             for j in range(1, self.GetColumnCount()):
 106                 try: # Target list can have more columns than source
 107                     self.SetItem(idx, j, i[2+j])
 108                 except:
 109                     pass # Ignore the extra columns
 110             index += 1
 111 
 112 #-------------------------------------------------------------------------------
 113 
 114 class MyListDrop(wx.DropTarget):
 115     """ Drop target for simple lists. """
 116 
 117     def __init__(self, source):
 118         """ Arguments:
 119          - source: source listctrl.
 120         """
 121         wx.DropTarget.__init__(self)
 122 
 123         self.dv = source
 124 
 125         # Specify the type of data we will accept
 126         self.data = wx.CustomDataObject("ListCtrlItems")
 127         self.SetDataObject(self.data)
 128 
 129     # Called when OnDrop returns True.  We need to get the
 130     # data and do something with it.
 131     def OnData(self, x, y, d):
 132         # Copy the data from the drag source to our data object
 133         if self.GetData():
 134             # Convert it back to a list and give it to the viewer
 135             ldata = self.data.GetData()
 136             l = pickle.loads(ldata)
 137             self.dv._insert(x, y, l)
 138 
 139         # What is returned signals the source what to do
 140         # with the original data (move, copy, etc.)  In this
 141         # case we just return the suggested value given to us.
 142         return d
 143 
 144 #-------------------------------------------------------------------------------
 145 # Main
 146 
 147 if __name__ == '__main__':
 148     items = ['Foo', 'Bar', 'Baz', 'Zif', 'Zaf', 'Zof']
 149 
 150     #---------------------------------------------------------------------------
 151 
 152     class MyApp(wx.App):
 153         def OnInit(self):
 154             self.frame = wx.Frame(None, title='Main Frame')
 155             self.frame.Show(True)
 156             self.SetTopWindow(self.frame)
 157             return True
 158 
 159     app = MyApp(redirect=False)
 160     dl1 = MyDragList(app.frame, style=wx.LC_LIST)
 161     dl2 = MyDragList(app.frame, style=wx.LC_REPORT)
 162     dl2.InsertColumn(0, "Column #0", wx.LIST_FORMAT_LEFT)
 163     dl2.InsertColumn(1, "Column #1", wx.LIST_FORMAT_RIGHT)
 164     dl2.InsertColumn(2, "Column #2", wx.LIST_FORMAT_LEFT)
 165     sizer = wx.BoxSizer()
 166     app.frame.SetSizer(sizer)
 167     sizer.Add(dl1, proportion=1, flag=wx.EXPAND)
 168     sizer.Add(dl2, proportion=1, flag=wx.EXPAND)
 169 
 170     maxs = -sys.maxsize - 1
 171 
 172     for item in items:
 173         dl1.InsertItem(maxs, item)
 174         idx = dl2.InsertItem(maxs, item)
 175         dl2.SetItem(idx, 1, choice(items))
 176         dl2.SetItem(idx, 2, choice(items))
 177     app.frame.Layout()
 178     app.MainLoop()

Drag and Drop with a striped drag list (Phoenix)

This striped drag list allows for dragging and dropping within itself while maintaining its appearance.

While not used in the example stub, it'll also stripe itself on an insert event (like from a pop-up file dialog) or on a delete event.

Toggle line numbers
   1 # dragListStriped.py
   2 
   3 import wx
   4 
   5 # class MyDragList
   6 # class MyApp
   7 
   8 #-------------------------------------------------------------------------------
   9 
  10 class MyDragListStriped(wx.ListCtrl):
  11     def __init__(self, *arg, **kw):
  12         wx.ListCtrl.__init__(self, *arg, **kw)
  13 
  14         self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._onDrag)
  15         self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._onSelect)
  16         self.Bind(wx.EVT_LEFT_UP,self._onMouseUp)
  17         self.Bind(wx.EVT_LEFT_DOWN, self._onMouseDown)
  18         self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
  19         self.Bind(wx.EVT_ENTER_WINDOW, self._onEnterWindow)
  20         self.Bind(wx.EVT_LIST_INSERT_ITEM, self._onInsert)
  21         self.Bind(wx.EVT_LIST_DELETE_ITEM, self._onDelete)
  22 
  23         #---------------
  24         # Variables
  25         #---------------
  26         self.IsInControl = True
  27         self.startIndex = -1
  28         self.dropIndex = -1
  29         self.IsDrag = False
  30         self.dragIndex = -1
  31 
  32     def _onLeaveWindow(self, event):
  33         self.IsInControl = False
  34         self.IsDrag = False
  35         event.Skip()
  36 
  37     def _onEnterWindow(self, event):
  38         self.IsInControl = True
  39         event.Skip()
  40 
  41     def _onDrag(self, event):
  42         self.IsDrag = True
  43         self.dragIndex = event.Index
  44         event.Skip()
  45         pass
  46 
  47     def _onSelect(self, event):
  48         self.startIndex = event.Index
  49         event.Skip()
  50 
  51     def _onMouseUp(self, event):
  52         # Purpose: to generate a dropIndex.
  53         # Process: check self.IsInControl, check self.IsDrag, HitTest, compare HitTest value
  54         # The mouse can end up in 5 different places:
  55         # Outside the Control
  56         # On itself
  57         # Above its starting point and on another item
  58         # Below its starting point and on another item
  59         # Below its starting point and not on another item
  60 
  61         if self.IsInControl == False:       #1. Outside the control : Do Nothing
  62             self.IsDrag = False
  63         else:                                   # In control but not a drag event : Do Nothing
  64             if self.IsDrag == False:
  65                 pass
  66             else:                               # In control and is a drag event : Determine Location
  67                 self.hitIndex = self.HitTest(event.GetPosition())
  68                 self.dropIndex = self.hitIndex[0]
  69                 # -- Drop index indicates where the drop location is; what index number
  70                 #---------
  71                 # Determine dropIndex and its validity
  72                 #--------
  73                 if self.dropIndex == self.startIndex or self.dropIndex == -1:    #2. On itself or below control : Do Nothing
  74                     pass
  75                 else:
  76                     #----------
  77                     # Now that dropIndex has been established do 3 things
  78                     # 1. gather item data
  79                     # 2. delete item in list
  80                     # 3. insert item & it's data into the list at the new index
  81                     #----------
  82                     dropList = []         # Drop List is the list of field values from the list control
  83                     thisItem = self.GetItem(self.startIndex)
  84                     for x in range(self.GetColumnCount()):
  85                         dropList.append(self.GetItem(self.startIndex, x).GetText())
  86                     thisItem.SetId(self.dropIndex)
  87                     self.DeleteItem(self.startIndex)
  88                     self.InsertItem(thisItem)
  89                     for x in range(self.GetColumnCount()):
  90                         self.SetItem(self.dropIndex, x, dropList[x])
  91             #------------
  92             # I don't know exactly why, but the mouse event MUST
  93             # call the stripe procedure if the control is to be successfully
  94             # striped. Every time it was only in the _onInsert, it failed on
  95             # dragging index 3 to the index 1 spot.
  96             #-------------
  97             # Furthermore, in the load button on the wxFrame that this lives in,
  98             # I had to call the _onStripe directly because it would occasionally fail
  99             # to stripe without it. You'll notice that this is present in the example stub.
 100             # Someone with more knowledge than I probably knows why...and how to fix it properly.
 101             #-------------
 102         self._onStripe()
 103         self.IsDrag = False
 104         event.Skip()
 105 
 106     def _onMouseDown(self, event):
 107         self.IsInControl = True
 108         event.Skip()
 109 
 110     def _onInsert(self, event):
 111         # Sequencing on a drop event is:
 112         # wx.EVT_LIST_ITEM_SELECTED
 113         # wx.EVT_LIST_BEGIN_DRAG
 114         # wx.EVT_LEFT_UP
 115         # wx.EVT_LIST_ITEM_SELECTED (at the new index)
 116         # wx.EVT_LIST_INSERT_ITEM
 117         #--------------------------------
 118         # this call to onStripe catches any addition to the list; drag or not
 119         self._onStripe()
 120         self.dragIndex = -1
 121         event.Skip()
 122 
 123     def _onDelete(self, event):
 124         self._onStripe()
 125         event.Skip()
 126 
 127     def _onStripe(self):
 128         if self.GetItemCount() > 0:
 129             for x in range(self.GetItemCount()):
 130                 if x % 2 == 0:
 131                     self.SetItemBackgroundColour(x, wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
 132                 else:
 133                     self.SetItemBackgroundColour(x, wx.WHITE)
 134 
 135 #-------------------------------------------------------------------------------
 136 
 137 if __name__ == "__main__":
 138     firstNameList = ["Ben", "Bruce", "Clark", "Dick"]
 139     lastNameList =  ["Grimm","Wayne", "Kent", "Grayson"]
 140     superNameList = ["The Thing", "Batman", "Superman", "Robin"]
 141 
 142     #---------------------------------------------------------------------------
 143 
 144     class MyApp(wx.App):
 145         def OnInit(self):
 146             self.myFrame = wx.Frame(None, title='Drag List Striped Example')
 147             self.myFrame.Show(True)
 148             self.SetTopWindow(self.myFrame)
 149             return True
 150 
 151     myApp = MyApp(redirect=False)
 152     dls = MyDragListStriped(myApp.myFrame, style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
 153     dls.InsertColumn(0, "First Name", wx.LIST_FORMAT_LEFT, 125)
 154     dls.InsertColumn(1, "Last Name", wx.LIST_FORMAT_LEFT, 125)
 155     dls.InsertColumn(2, "Superhero Name", wx.LIST_FORMAT_LEFT, 130)
 156     sizer = wx.BoxSizer()
 157     myApp.myFrame.SetSizer(sizer)
 158     sizer.Add(dls, proportion=1, flag=wx.EXPAND)
 159 
 160     for index in range(len(firstNameList)):
 161         dls.InsertItem(index, firstNameList[index])
 162         dls.SetItem(index, 1, lastNameList[index])
 163         dls.SetItem(index, 2, superNameList[index])
 164     myApp.myFrame.Layout()
 165     dls._onStripe()
 166     myApp.MainLoop()

The content on this page came from ListAndTreeControls, which was split up into this page and TreeControls .

How to create a list control (info) (last edited 2020-12-13 13:46:55 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.