Статьи

wxPython: wx.ListCtrl Советы и хитрости

Ранее мы рассмотрели некоторые советы и рекомендации по управлению сеткой. В этой статье мы рассмотрим несколько советов и приемов для виджета wx.ListCtrl, когда он находится в режиме «отчета». Посмотрите на советы ниже:

  • Как создать простой ListCtrl
  • Как отсортировать строки ListCtrl
  • Как сделать ячейки ListCtrl редактируемыми на месте
  • Связывание объектов со строками ListCtrl
  • Чередуйте цвета строк ListCtrl

Как создать простой ListCtrl

listctrl_simple.png

Элемент управления списком является довольно распространенным виджетом. В Windows вы увидите элемент управления списком в проводнике Windows. Он имеет четыре режима: значок, маленькая иконка, список и отчет. Они примерно совпадают с представлениями значков, плиток, списков и подробностей в Windows Explorer соответственно. Мы собираемся сосредоточиться на ListCtrl в режиме отчета, потому что это режим, в котором большинство разработчиков используют его. Вот простой пример того, как создать элемент управления списком:

import wx
 
########################################################################
class MyForm(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        self.index = 0
 
        self.list_ctrl = wx.ListCtrl(panel, size=(-1,100),
                         style=wx.LC_REPORT
                         |wx.BORDER_SUNKEN
                         )
        self.list_ctrl.InsertColumn(0, 'Subject')
        self.list_ctrl.InsertColumn(1, 'Due')
        self.list_ctrl.InsertColumn(2, 'Location', width=125)
 
        btn = wx.Button(panel, label="Add Line")
        btn.Bind(wx.EVT_BUTTON, self.add_line)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)
 
    #----------------------------------------------------------------------
    def add_line(self, event):
        line = "Line %s" % self.index
        self.list_ctrl.InsertStringItem(self.index, line)
        self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
        self.list_ctrl.SetStringItem(self.index, 2, "USA")
        self.index += 1
 
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Как вы, вероятно, можете сказать из приведенного выше кода, создать экземпляр ListCtrl действительно легко. Обратите внимание, что мы установили стиль для режима отчета, используя флаг wx.LC_REPORT . Чтобы добавить заголовки столбцов, мы вызываем метод InsertColumn ListCtrl и передаем целое число, чтобы сообщить ListCtrl, какой столбец есть какой, и строку для удобства пользователя. Да, столбцы начинаются с нуля, поэтому первый столбец — номер ноль, второй столбец — номер один и т. Д.

Следующая важная часть содержится в обработчике события кнопки add_line , где мы узнаем, как добавлять строки данных в ListCtrl. Типичный метод для использования — метод InsertStringItem . Если вы хотите, чтобы изображение также добавлялось в каждую строку, вы бы использовали более сложный метод, такой как InsertColumnInfo, вместе с методом InsertImageStringItem . Вы можете увидеть, как использовать их в демонстрационной версии wxPython. Мы придерживаемся простых вещей в этой статье.

В любом случае, когда вы вызываете InsertStringItem, вы даете ему правильный индекс строки и строку. Вы используете метод SetStringItem, чтобы установить данные для других столбцов строки. Обратите внимание, что для метода SetStringItem требуются три параметра: индекс строки, индекс столбца и строка. Наконец, мы увеличиваем индекс строки, чтобы ничего не перезаписывать. Теперь вы можете выйти и сделать свой собственный! Давайте продолжим и узнаем, как сортировать строки!

Как отсортировать строки ListCtrl

listctrl_sorting.png

Для виджета ListCtrl были написаны дополнительные скрипты, которые добавляют функциональность виджету. Эти сценарии называются миксинами. Вы можете прочитать о них здесь . Для этого рецепта мы будем использовать миксин ColumnSorterMixin . Приведенный ниже код является урезанной версией одного из демонстрационных примеров wxPython.

import wx
import wx.lib.mixins.listctrl as listmix
 
musicdata = {
0 : ("Bad English", "The Price Of Love", "Rock"),
1 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
2 : ("George Michael", "Praying For Time", "Rock"),
3 : ("Gloria Estefan", "Here We Are", "Rock"),
4 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
5 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
6 : ("Paul Young", "Oh Girl", "Rock"),
}
 
########################################################################
class TestListCtrl(wx.ListCtrl):
 
    #----------------------------------------------------------------------
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
 
########################################################################
class TestListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
 
        self.index = 0
 
        self.list_ctrl = TestListCtrl(self, size=(-1,100),
                         style=wx.LC_REPORT
                         |wx.BORDER_SUNKEN
                         |wx.LC_SORT_ASCENDING
                         )
        self.list_ctrl.InsertColumn(0, "Artist")
        self.list_ctrl.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
        self.list_ctrl.InsertColumn(2, "Genre")
 
        items = musicdata.items()
        index = 0
        for key, data in items:
            self.list_ctrl.InsertStringItem(index, data[0])
            self.list_ctrl.SetStringItem(index, 1, data[1])
            self.list_ctrl.SetStringItem(index, 2, data[2])
            self.list_ctrl.SetItemData(index, key)
            index += 1
 
        # Now that the list exists we can init the other base class,
        # see wx/lib/mixins/listctrl.py
        self.itemDataMap = musicdata
        listmix.ColumnSorterMixin.__init__(self, 3)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list_ctrl)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)
 
    #----------------------------------------------------------------------
    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
    def GetListCtrl(self):
        return self.list_ctrl
 
    #----------------------------------------------------------------------
    def OnColClick(self, event):
        print "column clicked"
        event.Skip()
 
########################################################################
class MyForm(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = TestListCtrlPanel(self)
 
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Этот код немного странен в том смысле, что мы наследуем mixin в классе, основанном на wx.Panel, а не в классе wx.ListCtrl. Вы можете сделать это в любом случае, если вы правильно измените код. В любом случае, мы рассмотрим ключевые отличия этого примера от предыдущего. Первое важное отличие заключается в конструкции цикла, в которую мы вставляем данные элемента управления списком. Здесь мы включаем метод SetItemData элемента управления списком, чтобы включить необходимые внутренние операции , позволяющие выполнить сортировку. Как вы уже догадались, этот метод связывает индекс строки с ключом dict музыкальных данных.

Затем мы создаем экземпляр ColumnSorterMixin и сообщаем ему, сколько столбцов в элементе управления списком. Мы могли бы оставить привязку EVT_LIST_COL_CLICK к этому примеру, поскольку она не имеет ничего общего с фактической сортировкой строк, но в интересах расширения ваших знаний она была оставлена. Все, что она делает, это показывает, как перехватить столбец пользователя событие клика. Остальная часть кода не требует пояснений. Если вы хотите узнать о требованиях для этого миксина, особенно если у вас есть изображения в ваших строках, пожалуйста, обратитесь к соответствующему разделу в источнике (то есть listctrl.py). Теперь, разве не так просто? Давайте продолжим наше путешествие и узнаем, как сделать ячейки редактируемыми!

Как сделать ячейки ListCtrl редактируемыми на месте

listctrl_editable.png

 

Иногда программист захочет позволить пользователю щелкнуть ячейку и отредактировать ее на месте. Это своего рода облегченная версия элемента управления wx.grid.Grid. Вот пример:

import wx
import wx.lib.mixins.listctrl  as  listmix
 
########################################################################
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
    ''' TextEditMixin allows any column to be edited. '''
 
    #----------------------------------------------------------------------
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        """Constructor"""
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        listmix.TextEditMixin.__init__(self)
 
########################################################################
class MyPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
 
        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]
        self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)
 
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")
 
        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            index += 1
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)
 
 
########################################################################
class MyFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control")
        panel = MyPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

В этом сценарии мы помещаем TextEditMixin в наш класс wx.ListCtrl вместо нашего wx.Panel, что противоположно предыдущему примеру. Сам миксин делает всю тяжелую работу. Опять же, вам придется проверить источник миксина, чтобы действительно понять, как он работает.

Связывание объектов со строками ListCtrl

Эта тема часто возникает: как связать данные (т.е. объекты) со строками моего ListCtrl? Что ж, мы собираемся выяснить, как именно это сделать с помощью следующего кода:

import wx
 
########################################################################
class Car(object):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, make, model, year, color="Blue"):
        """Constructor"""
        self.make = make
        self.model = model
        self.year = year
        self.color = color
 
 
########################################################################
class MyPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
 
        rows = [Car("Ford", "Taurus", "1996"),
                Car("Nissan", "370Z", "2010"),
                Car("Porche", "911", "2009", "Red")
                ]
 
        self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
                                style=wx.LC_REPORT
                                |wx.BORDER_SUNKEN
                                )
        self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")
 
        index = 0
        self.myRowDict = {}
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row.make)
            self.list_ctrl.SetStringItem(index, 1, row.model)
            self.list_ctrl.SetStringItem(index, 2, row.year)
            self.list_ctrl.SetStringItem(index, 3, row.color)
            self.myRowDict[index] = row
            index += 1
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)
 
    #----------------------------------------------------------------------
    def onItemSelected(self, event):
        """"""
        currentItem = event.m_itemIndex
        car = self.myRowDict[currentItem]
        print car.make
        print car.model
        print car.color
        print car.year
 
########################################################################
class MyFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
        panel = MyPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

Виджет управления списком на самом деле не имеет встроенного способа совершить это умение. Если вы хотите этого, то вам нужно проверить виджет ObjectListView , который оборачивает ListCtrl и дает ему гораздо больше функциональности. А пока мы возьмем минуту и ​​рассмотрим код выше. Первая часть — это просто класс Car с четырьмя атрибутами. Затем в классе MyPanel мы создаем список объектов Car, которые мы будем использовать для данных ListCtrl.

Чтобы добавить данные в ListCtrl, мы используем цикл for для перебора списка. Мы также связываем каждую строку с объектом Car, используя словарь Python. Мы используем индекс строки для ключа, и значение dict оказывается объектом Car. Это позволяет нам получить доступ ко всем данным объекта Car / row позже в методе onItemSelected . Давайте проверим это!

В onItemSelected мы берем индекс строки со следующим маленьким трюком: event.m_itemIndex . Затем мы используем это значение в качестве ключа для нашего словаря, чтобы мы могли получить доступ к объекту Car, связанному с этой строкой. На этом этапе мы просто распечатываем все атрибуты объекта Car, но вы можете делать все, что захотите. Эта основная идея может быть легко расширена для использования набора результатов из запроса SqlAlchemy для данных ListCtrl. Надеюсь, вы получите общее представление.

Теперь, если вы уделяете пристальное внимание, как Робин Данн (создатель wxPython), вы можете заметить некоторые действительно глупые логические ошибки в этом коде. Вы их нашли? Ну, вы не увидите его, если не отсортируете строки, не удалите строку или не вставите строку. Вы видите это сейчас? Да, я тупо основал «уникальный» ключ в своем словаре на позиции строки, которая изменится, если произойдет какое-либо из этих событий. Итак, давайте посмотрим на лучший пример:

import wx
 
########################################################################
class Car(object):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, make, model, year, color="Blue"):
        """Constructor"""
        self.id = id(self)
        self.make = make
        self.model = model
        self.year = year
        self.color = color
 
 
########################################################################
class MyPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
 
        rows = [Car("Ford", "Taurus", "1996"),
                Car("Nissan", "370Z", "2010"),
                Car("Porche", "911", "2009", "Red")
                ]
 
        self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
                                style=wx.LC_REPORT
                                |wx.BORDER_SUNKEN
                                )
        self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")
 
        index = 0
        self.myRowDict = {}
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row.make)
            self.list_ctrl.SetStringItem(index, 1, row.model)
            self.list_ctrl.SetStringItem(index, 2, row.year)
            self.list_ctrl.SetStringItem(index, 3, row.color)
            self.list_ctrl.SetItemData(index, row.id)
            self.myRowDict[row.id] = row
            index += 1
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)
 
    #----------------------------------------------------------------------
    def onItemSelected(self, event):
        """"""
        currentItem = event.m_itemIndex
        car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]
        print car.make
        print car.model
        print car.color
        print car.year
 
########################################################################
class MyFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
        panel = MyPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

В этом примере мы добавляем новый атрибут в наш класс Car, который создает уникальный идентификатор для каждого экземпляра, который создается с помощью встроенного встроенного идентификатора Python . Затем в цикле, где мы добавляем данные в элемент управления списком, мы вызываем метод SetItemData виджета и присваиваем ему индекс строки и уникальный идентификатор экземпляра автомобиля. Теперь не имеет значения, где заканчивается строка, потому что к ней прикреплен уникальный идентификатор. Наконец, мы должны изменить onItemSelected, чтобы получить правильный объект. Волшебство происходит в этом коде:

# this code was helpfully provided by Robin Dunn
car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]

Круто, да? В нашем последнем примере будет рассказано, как чередовать цвета строк, поэтому давайте посмотрим!

Alternate the row colors of a ListCtrl

listctrl_altcolors.png

 

As this section’s title suggests, we will look at how to alternate colors of the rows of a ListCtrl. Here’s the code:

import wx
import wx.lib.mixins.listctrl  as  listmix
 
########################################################################
class MyPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
 
        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]
        self.list_ctrl = wx.ListCtrl(self, style=wx.LC_REPORT)
 
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")
 
        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            if index % 2:
                self.list_ctrl.SetItemBackgroundColour(index, "white")
            else:
                self.list_ctrl.SetItemBackgroundColour(index, "yellow")
            index += 1
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)
 
 
########################################################################
class MyFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY,
                          "List Control w/ Alternate Colors")
        panel = MyPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

The code above will alternate each row’s background color. Thus you should see yellow and white rows. We do this by calling the ListCtrl instance’s SetItemBackgroundColour method. If you were using a virtual list control, then you’d want to override the OnGetItemAttr method. To see an example of the latter method, open up your copy of the wxPython demo; there’s one in there.

Wrapping Up

We’ve covered a lot of ground here. You should now be able to do a lot more with your wx.ListCtrl than when you started, assuming you’re new to using it, of course. Feel free to ask questions in the comments or suggest future recipes. I hope you found this helpful!

Note: All examples were tested on Windows XP with Python 2.5 and wxPython 2.8.10.1. They were also tested on Windows 7 Professional with Python 2.6

Additional Reading

Source Code

Source: http://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/