www.apress.com

18-1-17

Moving items in ObjectListView with wxPython

By Mike Driscoll

I was recently asked about how to implement drag-and-drop of items in a wx.ListCtrl or in ObjectListView. Unfortunately neither control has this built-in although I did find an article on the wxPython wiki that demonstrated one way to do drag-and-drop of the items in a ListCtrl.

However I did think that implementing some buttons to move items around in an ObjectListView widget should be fairly easy to implement. So that’s what this article will be focusing on.

Changing Item Order


If you don’t have wxPython and ObjectListView installed, then you will want to use pip to install them:

pip install wxPython objectlistview

Once that is done, open up your favorite text editor or IDE and enter the following code:

import wx
from ObjectListView import ObjectListView, ColumnDefn

class Book(object):
     """
     Model of the Book object
     Contains the following attributes:
     'ISBN', 'Author', 'Manufacturer', 'Title'
     """
 

     def __init__(self, title, author, isbn, mfg):
          self.isbn = isbn
          self.author = author
          self.mfg = mfg
          self.title = title
 

     def __repr__(self):
          return "<Book: {title}>".format(title=self.title)

class MainPanel(wx.Panel):
     def __init__(self, parent):
          wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
          self.current_selection = None
          self.products = [Book("wxPython in Action", "Robin Dunn", "1932394621", "Manning"),
                                      Book("Hello World", "Warren and Carter Sande", "1933988495", "Manning"),
                                      Book("Core Python Programming", "Wesley Chun", "0132269937", "Prentice Hall"),
                                      Book("Python Programming for the Absolute Beginner", "Michael Dawson", "1598631128", "Course Technology"),
                                      Book("Learning Python", "Mark Lutz", "0596513984", "O'Reilly")
                                      ]

          self.dataOlv = ObjectListView(self, wx.ID_ANY,
                                                                style=wx.LC_REPORT|wx.SUNKEN_BORDER)
          self.setBooks()

          # Allow the cell values to be edited when double-clicked
          self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK

          # create up and down buttons
          up_btn = wx.Button(self, wx.ID_ANY, "Up")
          up_btn.Bind(wx.EVT_BUTTON, self.move_up)

          down_btn = wx.Button(self, wx.ID_ANY, "Down")
          down_btn.Bind(wx.EVT_BUTTON, self.move_down)

          # Create some sizers
          mainSizer = wx.BoxSizer(wx.VERTICAL)

          mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
          mainSizer.Add(up_btn, 0, wx.ALL|wx.CENTER, 5)
          mainSizer.Add(down_btn, 0, wx.ALL|wx.CENTER, 5)
          self.SetSizer(mainSizer)

     def move_up(self, event):
     """
     Move an item up the list
     """
     self.current_selection = self.dataOlv.GetSelectedObject()
     data = self.dataOlv.GetObjects()
     if self.current_selection:
          index = data.index(self.current_selection)
          if index > 0:
               new_index = index - 1
          else:
               new_index = len(data)-1
          data.insert(new_index, data.pop(index))
          self.products = data
          self.setBooks()
          self.dataOlv.Select(new_index)
 

     def move_down(self, event):
          """
          Move an item down the list
          """
          self.current_selection = self.dataOlv.GetSelectedObject()
          data = self.dataOlv.GetObjects()
          if self.current_selection:
               index = data.index(self.current_selection)
               if index < len(data) - 1:
                    new_index = index + 1
               else:
                    new_index = 0
               data.insert(new_index, data.pop(index))
               self.products = data
               self.setBooks()
               self.dataOlv.Select(new_index)
 

     def setBooks(self):
          self.dataOlv.SetColumns([
               ColumnDefn("Title", "left", 220, "title"),
               ColumnDefn("Author", "left", 200, "author"),
               ColumnDefn("ISBN", "right", 100, "isbn"),
               ColumnDefn("Mfg", "left", 180, "mfg")
          ])
 

          self.dataOlv.SetObjects(self.products)
 

class MainFrame(wx.Frame):
     def __init__(self):
          wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title="ObjectListView Demo", size=(800,600))
          panel = MainPanel(self)
          self.Show()

if __name__ == "__main__":
     app = wx.App(False)
     frame = MainFrame()
     app.MainLoop()

The code we care about most in this example are the move_up() and move_down() methods. Each of these methods will check to see if you have an item in the ObjectListView widget selected. It will also grab the current contents of the widgets. If you have an item selected, then it will grab that item’s index from the ObjectListView widget’s data that we grabbed when we called GetObjects(). Then we can use that index to determine whether we should increment (move_down) or decrement (move_up) its index depending on which of the buttons we press.

After we update the list with the changed positions, then we update self.products, which is our class variable that we use in the setBooks() to update our ObjectListView widget. Finally we actually call setBooks() and we reset the selection since our original selection moved.

Wrapping Up

The code we care about most in this example are the move_up() and move_down() methods. Each of these methods will check to see if you have an item in the ObjectListView widget selected. It will also grab the current contents of the widgets. If you have an item selected, then it will grab that item’s index from the ObjectListView widget’s data that we grabbed when we called GetObjects(). Then we can use that index to determine whether we should increment (move_down) or decrement (move_up) its index depending on which of the buttons we press.

After we update the list with the changed positions, then we update self.products, which is our class variable that we use in the setBooks() to update our ObjectListView widget. Finally we actually call setBooks() and we reset the selection since our original selection moved.

​​​​​​​About the Author

Mike Driscoll started coding in Python in 2006, where his first assignments included porting Windows login scripts and VBA to Python, which introduced him to wxPython. He's done backend programming and front end user interfaces, writes documentation for wxPython, and currently maintains an automated testing framework in Python. He also owns the popular site "Mouse vs Python" at http://pythonlibrary.org and has written for the Python Software Foundation, DZone and published Python 101 and Python 201.

This article originally appeared on the Mouse vs Python blog. Get more great wxPython tips in wxPython Recipes by Mike Driscoll, available now!