четверг, 24 января 2013 г.

wxPython in Action. Глава 13. Создание списков (list control) и управление им (Перевод)

В этой главе мы поговорим о:

  • создании списков в различных стилях
  • работа с элементами в списке
  • реакции на выбор пользователем элемента из списка
  • редактирование меток и сортировка списка
  • создание большого списка (large list)
У всех нас есть списки, которые мы хотим просмотреть, и программисты на wxPython не исключение. В wxPython есть два управляющих элемента, которые Вы можете использовать для отображения информации при помощи списков. Самый простой - list box - обычный список из одной колонки, который можно прокручивать, похожий на то, что Вы можете получить в HTML при помощи <select>. Об этом мы говорили в главе 8 (видимо, всё же имеется ввиду глава 7) и больше их затрагивать не будем.
Эта глава будет говорить о более сложном способе отображения информации в форме списка - элементе управления list (list control), полноценном виджете списка. Он отображает ListCtrl с несколькими колонками информации для каждой строки, который можно отсортировать по каждой колонке и стиль отображения которого можно настроить. У Вас есть достаточно гибкости в настройке каждой части этого элемента.

13.1 Создание элемента управления list

List может быть создан в одной из 4-х форм:
  • иконка (плитка)
  • маленькая иконка (значки)
  • список
  • отчёт
Суть этого должна быть понятна всем, кто использует Проводник в Windows или Mac Finder - это соответствует способам отображения элементов в проводнике. Мы начнём наше исследование этого элемента с того, что посмотрим как его создать в каждой из этих форм.

13.1.1 Что такое режим "иконка"

List выглядит как панель отображения дерева файловой системы в MS Windows Explorer. Этот элемент управления отображает информацию в одном из четырёх режимов. По умолчанию используется режим иконка, где каждый элемент списка представлен иконкой с текстом под ней. Рисунок 13.1 показывает простой пример этого режима.
Код для отображения этого примера приведён в листинге 13.1. Обратите внимание, что этот код зависит от некоторых .png файлов, которые расположены в той же папке, что и наш модуль. Эти файлы можно получить с сайта этой книги.
Листинг 13.1

import wx
import sys, glob

class DemoFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, \
            "wx.ListCtrl in wx.LC_ICON mode",size=(600,400))
        il = wx.ImageList(32,32, True)
        # Создаём image list
        for name in glob.glob("icon??.png"):
            bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG)
            il_max = il.Add(bmp)
        
        # Создаём list
        self.list = wx.ListCtrl(self, -1
            style=wx.LC_ICON | wx.LC_AUTOARRANGE)
        self.list.AssignImageList(il, wx.IMAGE_LIST_NORMAL)
        for x in range(25):
        # Заполняем list
            img = x % (il_max+1)
            self.list.InsertImageStringItem(x,
                "This is item %02d" % x, img)

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


Рисунок 13.1 Простой список в режиме "иконка"

В листинге 13.1 демонстрационный фрейм создаёт список изображений (image list), который содержит ссылки на изображения, которые мы будем отображать; затем мы создаём и заполняем элемент list. Список изображения мы обсудим позже в этой главе.

13.1.2 Что такое режим "маленькая иконка"

Режим "маленькая иконка" (small icon) похож на режим "иконка", только использует маленькие значки. Рисунок 13.2 приводит пример того же самого списка в режиме "маленькой иконки".
Этот режим особенно полезен когда Вы хотите уместить в виджете много элементов, особенно если сами иконки не настолько детализированы, чтобы показывать их в исходном размере.
Рисунок 13.2 Простой list в режиме "маленькая иконка"


13.1.3 Что такое режим списка (list mode)?

В режиме списка элементы отображаются в нескольких колонках, автоматически заполняя каждую из них по очереди, как показано на рисунке 13.3
Этот режим имеет те же достоинства, что и режим "маленькая иконка"; выбор между ними (т.е. между расположением в строчку или в столбик) зависит от ваших предпочтений.
Рисунок 13.3 Простой list в режиме "список"

13.1.4 Что такое режим отчёта (report mode)?

В режиме отчёта list отображается в формате нескольких колонок, где каждый строк может иметь любое число столбцов, связанных с ним, как показано на рисунке 13.4

Рисунок 13.4 Простой list в режиме "отчёт"
Этот режим настолько отличается от режима "иконка", что стоит привести код, который нужен для создания такого отображения:
Листинг 13.2 Пример создания list'a в режиме "отчёт"

import wx
import sys, glob, random
import data

class DemoFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "wx.ListCtrl in wx.LC_REPORT mode",
            size=(600,400))

        il = wx.ImageList(16,16, True)
        for name in glob.glob("smicon??.png"):
            bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG)
            il_max = il.Add(bmp)
        # Создаём list
        self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
        self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL)

        # Добавляем колонки
        for col, text in enumerate(data.columns):
            self.list.InsertColumn(col, text)
        # Добавляем строки
        for item in data.rows:
            index = self.list.InsertStringItem(sys.maxint, item[0])
            for col, text in enumerate(item[1:]):
                self.list.SetStringItem(index, col+1, text)

            # даём каждому элементу случайное изображение
            img = random.randint(0, il_max)
            self.list.SetItemImage(index, img, img)

        # Настраиваем ширину колонок
        self.list.SetColumnWidth(0, 120)
        self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER)

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

В следующем разделе мы обсудим как поместить значение в соответствующем месте. Логика отображения этого режима должна быть не так уж и сложна. Лучше всего использовать этот режим в случае, когда список содержит одну-две дополнительные колонки данных. Если же ваш список сложнее, или содержит больше данных, то тогда лучше использовать grid control, описанный в 14 главе.

13.1.5 Как я могу создать list?

Элемент list в wxPython является экземпляром класса wx.ListCtrl. Его конструктор похож на конструкторы других виджетов:
wx.ListCtrl(parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_ICON, validator=wx.DefaultValidator,  name="listCtrl")
Эти параметры содержат значения, которые мы уже видели в конструкторах других виджетов. parent - это родительский виджет, в котором будет расположен list; id - идентификатор wxPython, где -1 обозначает автоматически сгенерированное значение. Явно расположение задаётся параметрами pos и size. style задаёт режим и остальные опции отображения - об этом мы ещё поговорим в этой главе. validator используется для проверки ввода и о нём мы говорили в 9 главе. Параметр name используется достаточно редко.
Флаг style - это битовая маска, которая определяет некоторые возможности этого элемента управления. Первый набор значений - это режим отображения элемента. Режим по умолчанию - wx.LC_ICON. Таблица 13.1 содержит остальные доступные режимы.
Стиль Описание
wx.LC_ICON Режим "иконка"
wx.LC_LIST Режим "список"
wx.LC_REPORT Режим "отчёт"
wx.LC_SMALL_ICON Режим "маленькая иконка"
В режимах "иконка" и "маленькая иконка" доступны ещё три флага, которые определяют расположение иконки относительно списка. Значение по умолчанию - wx.LC_ALIGN_TOP, которе выравнивает иконки по вершине списка. Для выравнивания по левой стороне используйте wx.LC_ALIGN_LEFT. Стиль LC.AUTOARRANGE заворачивает иконки по достижении ими нижнего или правого угла окна.
Таблица 13.2 содержит стили отображения для режима "отчёт":
Стиль Значение по умолчанию
wx.LC_HRULES Рисует линии между строками list'a
wx.LC_NO_HEADER Не отображает заголовки колонок
wx.LC_VRULES Рисует линии между колонками list'a
Флаги битовых масок могут быть скомбинированы при помощи операции или. Используйте wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES для того чтобы получить вид, похожий на решётку. По умолчанию все элементы list позволяют осуществлять множественный выбор. Для того, чтобы это изменить используйте флаг wx.LC_SINGLE_SEL.
В отличие от остальных виджетов, которые мы видели до сих пор, list имеет ещё несколько методов, которые позволяют Вам изменить его флаги стилей уже в процессе выполнения. Метод SetSingleStyle(style, add=True) позволяет Вам добавить или удалить один флаг стиля, в зависимости от параметра add. Вызов listCtrl.SetSingleStyle(LC_HRULES, True) добавит горизонтальную линейку, тогда как listCtrl.SetSingleStyle(LC_HRULES, False) удалит её. Вызов SetWindowStyleFlag(style) позволяет Вам переустановить весь стиль, задав новые флаги, например, SetWindowsStyleFlag(LC_REPORT | LC_NO_HEADER). Эти методы полезны для изменения list'a на лету.

13.2 Управление элементами в list'e

После того, как list создан, Вы можете добавлять информацию в него. В wxPython для этой цели используются разные способы, зависящие от того, что Вы хотите добавить - текстовую информацию или изображения, ассоциированные с элементами list'a. В следующих разделах мы покажем как добавлять текст и изображения в ваш list.

13.2.1 Что такое image list и как к нему добавлять изображения

Перед тем, как мы сможем говорить о том, как информация добавляется в list, мы должны сказать несколько слов о том, как list работает с изображениями. Все изображения, используемые в этом элементе, должны быть сперва добавлены в image list, который представляет из себя проиндексированный массив изображений, сохранённых в элементе list. Когда Вы связываете изображение с определённым элементом в list, то для ссылки на изображение используется его индекс. Этот механизм позволяет быть уверенным в том, что каждое изображение загружено только один раз, вне зависимости от того, как часто оно используется в list. Что, в свою очередь, помогает сэкономить память в случаях, когда изображения используются по нескольку раз. Кроме того, это обегчает связь между несколькими версиями одного и того же изображения, что может быть полезно для различных режимов. Более подробно создание изображений и bitmap в wxPython обсуждается в главе 12.

Создание image list

image list является экземпляром wx.ImageList и для его создания используется следующий конструктор:
wx.ImageList(width, height, mask=True, initialCount=1)
Параметры width и height задают в пикселях размер изображения, которое будет добавлено в list. Изображения больше, чем указанный размер добавить нельзя. Если параметр mask = True, nj изображение рисуется с его маской, если она у него есть. Параметр initialCount задаёт начальный внутренний размер list'a. Если Вы знаете, что list будет большой, то указание этого размера может сэкономить память и время позже.

Добавление и удаление изображений

Вы можете добавить изображение в list при помощи метода Add(bitmap, mask=wx.NullBitmap), где bitmap и mask оба являются экземплярами wx.Bitmap. Аргумент mask является монохромным битовым изображением, которое определяет прозрачные части изображения, если они должны быть. Если битовое изображение уже имеет свою маску, то его маска и будет использоваться по умолчанию. Если маски у изображения нет и у Вас нет монохромной карты прозрачности, но Вы хотите чтобы какой-то цвет в изображении служил маской, Вы можете использовать метод AddWithColourMask(bitmap, colour), где colour - цвет wxPython (или его имя), который будет использоваться в качестве маски. Если у Вас есть объект wx.Icon, который Вы хотите добавить в image list, то используйте метод AddIcon(icon). Все методы добавления возвращают индекс изображения в list'e, который Вы можете позже использовать.
Следующий отрывок кода показывает пример создания image list (похоже на листинг 13.1)
il = wx.ImageList(32, 32, True)
for name in glob.glob("icon??.png"):
    bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG)
    il_max = il.Add(bmp)
image list должен быть привязан к list при помощи следующего метода:
self.list.AssignImageList(il, wx.IMAGE_LIST_NORMAL)
Для того, чтобы удалить изображение из image list, воспользуйтесь методом Remove(index), где index - индекс изображения в списке. Этот метод изменяет индексы всех изображений после удалённого, что может создать проблему, если Вы ссылаетесь на конкретный индекс где-либо в ваше программе. Для того чтобы отчистить весь image list используйте RemoveAll(). Вы можете изменить изображение с заданным индексом при помощи метода Replace(index, bitmap, mask=wx.NullBitmap), где index - индекс изображения в image list, bitmap и mask имеют то же самое значение, что и в Add(). Если Вы хотите заменить иконку, то используйте метод ReplaceIcon(index, icon). Метода для замены цвета маски нет.

Использование image list

Вы можете получить длину image list при помощи метода GetImageCount(), а размер кэша конкретного изображения при помощи GetSize(), который возвращает кортеж (width, height).
Хотя это и не относится к теме list'a, тем не менее Вы можете нарисовать изображение из image list на устройство контекста. Более подробно об этом говорилось в главах 6 и 12. Для этого нужен метод Draw():
Draw(index, dc, x, y, flags=wx.IMAGELIST_DRAW_NORMAL, solidBackground=False)
В этом случае index - индекс изображения в image list, dc - устройство контекста wx.DC. Параметры x и y - начальные координаты в устройстве контекста для отрисовки изображения. flags управляет отрисовкой изображения, а bitmask - одна или более из констант wx.IMAGELIST_DRAW_NORMAL, wx.IMAGELIST_DRAW_TRANSPARENT, wx.IMAGELIST_DRAW_SELECTED и wx.IMAGELIST_DRAW_FOCUSED. Если solidBackground=True, то метод будет использовать более быстрый алгоритм, который работает только при сплошном фоне.
После того, как у Вас есть image list, Вы должны прикрепить его к list'y. Это делается одним из двух способов: AssignImage(imageList, which) или SetImage(imageList, which). Оба метода принимают идентичные параметры, первый из которых image list, а второй - числовой флаг со значением wx.IMAGE_LIST_NORMAL или wx.IMAGE_LIST_SMALL. Единственная разница между методами заключается в том, как image list управляется со стороны C++. При использовании AssignImage() image list становится частью list и уничтожается вместе с ним, а при использовании SetImage() image list продолжает жить своей жизнью и уничтожается лишь когда он сам покидает область видимости.
list может иметь два image list, подключённых к себе. Обычный image list, который подключён с wx.IMAGE_LIST_NORMAL, отображается в режиме "иконки", тогда как маленький image list, подключённый с wx.IMAGE_LIST_SMALL, используется в режимах "отчёт" и "маленькие иконки". Скорее всего Вам нужен только один из них, но если Вы хотите, чтобы ваш list мог отображаться в разных режимах (то есть пользователь сможет переключаться между режимами "иконка" и "маленькая иконка"), Вам нужно подключить оба листа. Но в таком случае стоит помнить, что list знает об изображениях только их индексы в image list, так что оба image list должны иметь для идентичных изображений идентичные индексы. То есть, если иконка документа в обычном image list имеет индекс 2, то и в маленьком image list у неё должен быть индекс 2.
Кроме того, у list есть метод-получатель GetImageList(which), который возвращает image list, подключённый с флагом which.

13.2.2 Как я могу добавлять и удалять элементы из list?

Перед тем, как Вы сможете отобразить list, Вы должны добавить к нему текстовую информацию. В режиме "иконка" Вы можете добавить новый элемент как иконку, строку или как то и другое. В режиме "отчёт" Вы так же можете задать информацию для различных столбцов после того, как Вы добавите иконку или строку. API и соглашения о наименовании методов для управления элементами отличаются от тех, которые мы видели до сих пор, так что даже если Вы понимаете, как управлять элементами меню и list box, Вы всё равно должны прочитать этот раздел.
Добавление текстовой информации в list состоит из одного шага для режима "иконка" и из нескольких шагов для режима "отчёт". Первый шаг, общий для любого режима, это добавить первый элемент в строку. Для режима "отчёт" Вы должны будете ещё добавить столбцы и информацию для этих столбцов.

Добавление новой строки

Для того, чтобы добавить новую строку, воспользуйтесь одним из методов InsertItem(). Какой из них использовать зависит от типа элемента, который Вы добавляете. Если Вы просто хотите добавить строку в list, используйте InsertStringItem(index, label), где index - та строка в list, куда Вы добавляете элемент для отображения. Если у Вас есть только изображение, тогда используйте InsertImageItem(index, imageIndex). В этом случае первый index - строка в list, а imageIndex - индекс изображения в image list, прикреплённом к list. Перед этим image list должен быть уже создан и подключён к list'y. Если Вы указываете индекс за пределами image list, то Вы получите пустое изображение. Если Вы хотите добавить элемент с тестом и изображением, используйте InsertImageStringItem(index, label, imageIndex). Этот метод комбинирует параметры предыдущих двух, так что index - строка в list'e, label - строка для отображения, imageIndex - индекс в image list.
Сам list для управления информацией о добавленных элементах использует экземпляры wx.ListItem. Мы лишь упомянем, что последний метод для добавления элемента - InsertItem(index, item), где item - это экземпляр wx.ListItem, который Вы уже создали. Мы не будем вдаваться в детали этого процесса, так как скорее всего Вы не будете с этим сталкиваться, да и сам класс не так сложен - практически только методы установки и получения. Практически все методы элемента list'a доступны посредством методов самого list'a.

Добавление столбца

Для того, чтобы добавить столбец в режим "отчёт", создайте колонку, после чего задайте данные для каждой строки. Столбец создаётся методом InsertColumn():
InsertColumn(col, heading, format=wx.LIST_FORMAT_LEFT, width=-1)
В этом методе параметр col - индекс нового столбца в list'e. Параметр heading - строка текста задающая заголовок столбца. format управляет выравниваем текста в колонке; для него доступны значения wx.LIST_FORMAT_CENTRE, wx.LIST_FORMAT_LEFT, wx.LIST_FORMAT_RIGHT. Параметр width - первоначальная ширина отображаемого столбца в пикселях; понятно, что пользователь может изменить её ширину позже. Для того, чтобы использовать wx.ListItem объект для задания колонки есть метод InsertColumnInfo(info), который принимает элемент list в качестве параметра.

Установка значений для list c несколькими колонками

Вы можете обратить внимание, что вставка элемента с использованием метода, описанного выше, задаёт значение только первой колонки режима "отчёт". Для того, чтобы задать значения другим колонкам используйте метод SetStringItem()
SetStringItem(index, col, label, imageId=-1)
Параметры index и col - индексы строки и колонки для ячейки, значение которой Вы задаёте. Для параметра col можно использовать значение 0, чтобы указать первую колонку, но index должен соответствовать строке, которая уже существует в list'e; другими словами этот метод можно использовать только для строк, которые уже добавлены. Аргумент label - это текст для отображения в ячейке, а imageId - индекс в соответствующем image list, если в ячейке Вы хотите показать изображение.
Метод SetStringItem() технически является специальным случаем метода SetItem(info), который принимает экземпляр wx.ListItem. Для того, чтобы использовать этот метод задайте строку, колонку и любые другие параметры экземпляра элемента list перед его добавлением. Так же Вы можете получить экземпляр wx.ListItem в ячейке при помощи метода GetItem(index, col=0), который возвращает элемент указанной строки и переданной колонки (первой, по умолчанию).

Свойства элемента

Есть некоторое количество задающих и получающих методов, которые позволяют Вам определить часть элемента. По большей части эти методы работают с первой колонкой строки. Для того, чтобы получить доступ к другой колонке Вы должны получить весь элемент при помощи GetItem() и затем уже использовать получающие и задающие методы класса элемента. Вы можете задать изображение для элемента при помощи SetItemImage(item, image, selImage), где item - это индекс элемента в list'e, image и selImage - индексы в image list, которые соответствуют обычному изображению и выбранному изображению. Вы можете получить или задать текст элемента при помощи GetItemText(item) и SetItemText(item, text).
Вы можете получить или задать статус индивидуального элемента при помощи GetItemState(item, stateMask) и SetItemState(item, state, stateMask). В этом случае state и state Mask - значения из таблицы 13.3. Параметр state (и возвращаемое значение от соответствующего метода) - это актуальное состояние элемента, stateMask - маска всех возможных состояний.
Состояние Описание
wx.LIST_STATE_CUT Элемент вырезан. Применимо только под MS Windows
wx.LIST_STATE_DONTCARE Состояние не имеет значения. Применимо только под MS Windows
wx.LIST_STATE_DROPHILITED Элемент подсвечен потому как был перетащен. Применимо только под MS Windows
wx.LIST_STATE_FOCUSED Элемент получил фокус
wx.LIST_STATE_SELECTED Элемент выделен
Вы можете получить специфическую колонку при помощи метода GetColumn(col), который возвращает экземпляр wx.ListItem для колонки с индексом col - то есть элемент заголовка колонки. Вы можете задать колонку которая уже существует при помощи метода SetColumn(col, item). Можно программно получить ширину колонки при помощи метода GetColumnWidth(col), который возвращает ширину колонки в пикселях - обычно это полезно только для режима "отчёт". Вы можете задать ширину колонки при помощи SetColumnWidth(col, width).  Ширина является либо числом, либо одним из специальных значений wx.LIST_AUTOSIZE, который задаёт ширину согласно самому длинному элементу, или wx.LIST_AUTOSIZE_USEHEADER, который задаёт ширину в соответствии с шириной заголовка столбца. Под ОС, кроме Widnows, wx.LIST_AUTOSIZE_USEHEADER может просто задать ширину столбца в 80 пикселей.
Если Вы сомневаетесь в индексах, которые Вы уже добавили, Вы можете запросить список уже добавленных индексов. Метод GetColumnCount() возвращает число колонок, определённых в list, а метод GetItemCount() возвращает число строк. Если ваш list находится в режиме "список", тогда метод GetCountPerPage() возвратит число элементов в каждом столбце.
Для того, чтобы удалить элемент из list'a используйте DeleteItem(item), который принимает индекс элемента в list'e. Если Вы хотите удалить все элементы разом, воспользуйтесь методом DeleteAllItems() или ClearAll(). Вы можете удалить колонку методом DelectColumn(col), который принимает индекс удаляемой колонки.

13.3 Ответ на действия пользователя

Обычно задача элемента list - сделать что-то, когда пользователь выбирает элемент в list'e. В следующем разделе мы покажем на какие события list'a можно реагировать и приведём пример использования событий этого элемента.

13.3.1 Как я могу реагировать на выбор пользователя?

Как и другие элементы управления, list генерирует события в ответ на действия пользователя. Вы можете установить обработчик события при помощи метода Bind(), как это показывалось в главе 3. Все обработчики событий являются экземплярами класса wx.ListEvent, подкласса wx.CommandEvent. У wx.ListEvent есть несколько установочных методов, специфичных для класса. Некоторые из них специфичны для некоторых типов событий, как описано в других разделах. Общие свойства для всех типов событий описаны в таблице 13.4
Метод Описание
GetData() Элемент данных, ассоциированный с элементом list'a события
GetKeyCode() В событии нажатия кнопки - код нажатой кнопки
GetIndex() Индекс элемента, задействованного в событии
GetItem() wx.ListItem, задействованный в событии
GetImage() Изображение в ячейке, задействованной в событии
GetMask() Битовая маска ячейки, задействованной в событии
GetPoint() Положение мыши на момент возникновения события
GetText() Текст в ячейке, задействованной в событии
Есть несколько типов событий wx.ListEvent, каждый из которых может быть передан своему обработчику. Некоторые из них мы обсудим позже. В таблице 13.5 перечислены все типы событий, которые работают с выбранным элементом list'a.
Тип События Описание
EVT_LIST_BEGIN_DRAG Вызывается, когда пользователь начинает операцию перетаскивания при помощи левой клавиши мыши
EVT_LIST_BEGIN_RDRAG Вызывается, когда пользователь начинает операцию перетаскивания при помощи правой клавиши мыши
EVT_LIST_DELETE_ALL_ITEMS Вызывается методом DeleteAll() list'a
EVT_LIST_DELETE_ITEM Вызывается методом Delete() list'a
EVT_LIST_INSERT_ITEM Вызывается при вставке элемента в list
EVT_LIST_ITEM_ACTIVATED Вызывается при активации выбранного элемента двойным кликом или нажатием enter
EVT_LIST_ITEM_DESELECTED Вызывается когда элемент теряет выделение
EVT_LIST_ITEM_FOCUSED Вызывается при изменении фокуса элемента
EVT_LIST_MIDDLE_CLICK Вызывается при нажатии средней кнопки мыши на list'e
EVT_LIST_RIGHT_CLICK Вызывается при нажатии правой кнопки мыши на list'e
EVT_LIST_SELECTED Вызывается при нажатии левой кнопки мыши на list'e и выделении элемента
EVT_LIST_ITEM_KEY_DOWN Вызывается при нажатии клавиши, если list имеет при этом фокус
В листинге 13.3 мы покажем пример использования большинства этих типов.

13.3.2 Как я могу реагировать на выбор пользователем заголовка колонки?

В дополнение к событиям, которые возникают, когда пользователь действует в "теле" листа, есть события, возникающие при взаимодействиях пользователя с заголовками колонок. Объект wx.ListEvent, создаваемый колонкой имеет соответствующий метод GetColumn(), который возвращает индекс колонки, вызвавшей событие. Если это событие перетаскивания границы колонки, то возвращается индекс колонки, находящейся слева от перетаскиваемой границы. Если событие вызывается кликом вне колонки, то метод вернёт -1. Таблица 13.6 содержит типы событий для колонок.
Тип события Описание
EVT_LIST_COL_BEGIN_DRAG Вызывается, когда пользователь начинает перетаскивать границу колонки
EVT_LIST_COL_CLICK Вызывается кликом в заголовке столбца
EVT_LIST_COL_RIGHT_CLICK Вызывается правым кликом в заголовке столбца
EVT_LIST_COL_END_DRAG Вызывается, когда пользователь заканчивает перетаскивать границу колонки
Листинг 13.3 покажет обработку некоторых этих событий (кроме того, тут будут использованы некоторые методы, ещё нами не описанные). Обратите внимание на события, вызываемые при помощи меню.
Листинг 13.3 Пример событий и свойств list'a


import wx
import sys, glob, random
import data

class DemoFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,"Other wx.ListCtrl Stuff",
                          size=(700,500))
        self.list = None
        self.editable = False
        self.MakeMenu()
        self.MakeListCtrl()

    # Создаём list
    def MakeListCtrl(self, otherflags=0):
        if self.list:
            self.list.Destroy()
        if self.editable:
            otherflags |= wx.LC_EDIT_LABELS

        # Загружаем изображения в image list
        il = wx.ImageList(16,16, True)
        for name in glob.glob("smicon??.png"):
            bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG)
            il_max = il.Add(bmp)

        self.list = wx.ListCtrl(self, -1,
                                style=wx.LC_REPORT|otherflags)
        self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL)
        # Добавляем колонки
        for col, text in enumerate(data.columns):
            self.list.InsertColumn(col, text)
        # Добавляем строки
        for row, item in enumerate(data.rows):
            index = self.list.InsertStringItem(sys.maxint, item[0])
            for col, text in enumerate(item[1:]):
                self.list.SetStringItem(index, col+1, text)
            # Соединяем со случайным изображением
            img = random.randint(0, il_max)
            self.list.SetItemImage(index, img, img)
            # Задаём элемент данных
            self.list.SetItemData(index, row)

        # Устанавливаем ширину колонок
        self.list.SetColumnWidth(0,120)
        self.list.SetColumnWidth(1,wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(2,wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(3,wx.LIST_AUTOSIZE_USEHEADER)

        # Задаём обработчик событий пользователя
        self.Bind(wx.EVT_LIST_ITEM_SELECTED,
                    self.OnItemSelected,self.list)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED,
                    self.OnItemDeselected,self.list)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated,
                    self.list)
        self.SendSizeEvent()

    # Создаём меню
    def MakeMenu(self):
        mbar = wx.MenuBar()
        menu = wx.Menu()
        item = menu.Append(-1, "E&xit\tAlt-X")
        self.Bind(wx.EVT_MENU, self.OnExit, item)
        mbar.Append(menu, "&File")

        menu = wx.Menu()
        item = menu.Append(-1, "Sort ascending")
        self.Bind(wx.EVT_MENU, self.OnSortAscending, item)
        item = menu.Append(-1, "Sort descending")
        self.Bind(wx.EVT_MENU, self.OnSortDescending, item)
        item = menu.Append(-1, "Sort by submitter")
        self.Bind(wx.EVT_MENU, self.OnSortBySubmitter, item)

        menu.AppendSeparator()
        item = menu.Append(-1, "Show selected")
        self.Bind(wx.EVT_MENU, self.OnShowSelected, item)
        item = menu.Append(-1, "Select all")
        self.Bind(wx.EVT_MENU, self.OnSelectAll, item)
        item = menu.Append(-1, "Select none")
        self.Bind(wx.EVT_MENU, self.OnSelectNone, item)

        menu.AppendSeparator()
        item = menu.Append(-1, "Set item text colour")
        self.Bind(wx.EVT_MENU, self.OnSetTextColour, item)
        item = menu.Append(-1, "Set item background colour")
        self.Bind(wx.EVT_MENU, self.OnSetBGColour, item)

        menu.AppendSeparator()
        item = menu.Append(-1, "Enable item editing",
                            kind=wx.ITEM_CHECK)
        self.Bind(wx.EVT_MENU, self.OnEnableEditing, item)
        item = menu.Append(-1, "Edit current item")
        self.Bind(wx.EVT_MENU, self.OnEditItem, item)
        mbar.Append(menu, "&Demo")

        self.SetMenuBar(mbar)

    def OnExit(self, evt):
        self.Close()

    # Обработчик выбора элемента
    def OnItemSelected(self, evt):
        item = evt.GetItem()
        print "Item selected:", item.GetText()

    # Обработчик снятия выбора элемента
    def OnItemDeselected(self, evt):
        item = evt.GetItem()
        print "Item deselected:", item.GetText()

    # Обработчик активирования элемента
    def OnItemActivated(self, evt):
        item = evt.GetItem()
        print "Item activated:", item.GetText()

    # Перестраиваем list, задав стиль сортировки
    def OnSortAscending(self, evt):
        self.MakeListCtrl(wx.LC_SORT_ASCENDING)
    def OnSortDescending(self, evt):
        self.MakeListCtrl(wx.LC_SORT_DESCENDING)

    # Сортируем при помощи вложенной функции
    def OnSortBySubmitter(self, evt): 
        def compare_func(row1, row2):
            # сравниваем значения 4 столбца данных
            val1 = data.rows[row1][3]
            val2 = data.rows[row2][3]
            if val1 < val2: return -1
            if val1 > val2: return 1
            return 0
        
        self.list.SortItems(compare_func)

    # Показываем выбранный элемента
    def OnShowSelected(self, evt):
        print "These items are selected:"
        index = self.list.GetFirstSelected()
        if index == -1:
            print "\tNone"
            return
        while index != -1:
            item = self.list.GetItem(index)
            print "\t%s" % item.GetText()
            index = self.list.GetNextSelected(index)

    # Выбираем все элементы
    def OnSelectAll(self, evt):
        for index in range(self.list.GetItemCount()):
            self.list.Select(index, True)

    # Отменяем выбор элемента
    def OnSelectNone(self, evt):
        index = self.list.GetFirstSelected()
        while index != -1:
            self.list.Select(index, False)
            index = self.list.GetNextSelected(index)

    def OnSetTextColour(self, evt):
        dlg = wx.ColourDialog(self)
        # Изменяем цвет текста
        if dlg.ShowModal() == wx.ID_OK:
            colour = dlg.GetColourData().GetColour()
            index = self.list.GetFirstSelected()
            while index != -1:
                self.list.SetItemTextColour(index, colour)
                index = self.list.GetNextSelected(index)
        dlg.Destroy()

    # Изменяем цвет фона
    def OnSetBGColour(self, evt):
        dlg = wx.ColourDialog(self)
        if dlg.ShowModal() == wx.ID_OK:
            colour = dlg.GetColourData().GetColour()
            index = self.list.GetFirstSelected()
            while index != -1:
                self.list.SetItemBackgroundColour(index, colour)
                index = self.list.GetNextSelected(index)
        dlg.Destroy()

    # Включаем редактирование
    def OnEnableEditing(self, evt):
        self.editable = evt.IsChecked()
        self.MakeListCtrl()

    # Редактируем выделенный элемент
    def OnEditItem(self, evt):
        index = self.list.GetFirstSelected()
        if index != -1:
            self.list.EditLabel(index)

class DemoApp(wx.App):
    def OnInit(self):
        frame = DemoFrame()
        self.SetTopWindow(frame)
        print "Program output appears here..."
        frame.Show()
        return True

app = DemoApp(redirect=True)
app.MainLoop()



После того, как Вы запустите этот код, Вы увидите демонстрацию возможностей элемента list, включая те, что мы ещё не обсуждали.

13.4 Редактирование и сортировка list'a

В этом разделе мы обсудим редактирование, сортировку и поиск элементов в list'e.

13.4.1 Как я могу редактировать надпись?

Редактирование элементов в list'e не составляет проблемы, за исключением режима "отчёт", где пользователь может редактировать только первую колонку каждого столбца. Для остальных режимов никакой проблемы нет - можно спокойно редактировать надпись каждого элемента.
Для того, чтобы сделать list редактируемым, добавьте флаг wx.LC_EDIT_LABELS в конструктор экземпляра.
list = wx.ListCtrl(self, -1, style = wx.LC_REPORT | wx.LC_EDIT_LABELS)
Если флаг редактирования установлен, то пользователь может начать редактирование нажав на уже выделенный элемент (это не должен быть двойной клик). Редактирование похоже на переименование в Windwos Explorer. Пользователю даётся небольшое поле для редактирования надписи там, где она до этого находилась. Нажатие Enter завершает редактирование и текст становится новой надписью. Клик мышкой где-либо вне пределов окна редактирования тоже завершает редактирование. Одновременно не может быть больше одной сессии редактирования. Нажатие Escape тоже завершает редактирование, но все изменения отменяются.
Следующие два события возникают во время сессии редактирования:
  • EVT_LIST_BEGIN_LABEL_EDIT
  • EVT_LIST_END_LABEL_EDIT
Помните, что если Вы хотите нормально передать событие дальше после вашего собственного обработчика, Вам надо включить в обработчик вызов метода Skip(). Событие типа  EVT_LIST_BEGIN_LABEL_EDIT возникает в начале сессии редактирования, а EVT_LIST_END_LABEL_EDIT в конце сессии (не важно, как именно она завершается). Вы можете заветировать событие начала редактирования, в таком случае сессия редактирования даже не начнётся. Ветирование события конца редактирования поможет защитить текст list'a от изменения.
Класс wx.ListEvent имеет несколько свойств, интересных только при обработке события EVT_LIST_END_LABEL_EDIT. Метод GetLabel() возвращает новый текст элемента листа, после того, как его редактирование было завершено и подтверждено. Если редактирование было завершено нажатием escape, то метод возвращает пустую строку. Таким образом, Вы не можете использовать GetLabel(), чтобы отличить отмену редактирования от сознательного изменения текста элемента на пустую строку. Если это нужно узнать, то метод IsEditCancelled() возвращает True, если редактирование было отменено, и False в противном случае.
Если Вы хотите чтобы другие пользовательские события начинали сессию редактирования элемента, используйте метод EditLabel(item), где item - индекс элемента листа, который нужно отредактировать. Этот метод вызывает событие EVT_LIST_BEGIN_LABEL_EDIT и сессия редактирования продолжается, как если бы она была начата непосредственно пользователем.
Если Вы хотите напрямую манипулировать элементом текста, который используется для редактирования, то Вы можете получить его при помощи метода list'a GetEditControl(). Этот метод возвращает элемент text, который используется для текущего редактирования. Если сейчас ничего не редактируется, то метод вернёт None. На данный момент метод работает только под Windows.

13.4.2 Как я могу отсортировать мой list?

Есть три способа отсортировать list в wxPython, которые мы обсудим в этом разделе, расположив по возрастанию сложности.

Сортировка list'a при создании

Самый простой способ отсортировать list - сообщить о необходимости сортировки конструктору при создании. Это можно сделать при помощи флагов стиля wx.LC_SORT_ASCENDING или wx.LC_SORT_DESCENDING. Этот флаг сортирует list при первоначальном отображении и, на MS Windows, ghb каждом добавлении элемента. Сортировка основана на тексте для каждого элемента и является простым сравнением строк. Если list отображается в режиме "отчёт", сортировка происходит на основании колонки 0 (самой левой) каждой строки.

Сортировка на основании данных, отличных от отображаемого текста

Случается, что Вы хотите отсортировать свой list не на основании отображаемого элементами текста, а на основании чего-то другого. Это можно сделать в wxPython, но это потребует от Вас некоторых усилий. Во-первых, Вам нужно задать данные для каждого элемента list'a при помощи метода SetItemData(item, data). item - индекс элемента в не отсортированном list'e, а data - данные, которые Вы хотите ассоциировать с этим элементом. data должно быть либо числом, либо long (в соответствии с C++ типами данных), что ограничивает полезность этого механизма. Для того, чтобы получить значение данных для строки, используйте метод GetItemData(item).
После того, как всем элементам присвоены данные, Вы можете использовать метод SortItems(func), чтобы отсортировать элемента. Аргументом является вызываемый объект, принимающий два целых числа. Функция вызывается с данными двух элементов, которые надо сравнить - ссылки на сами элементы не передаются. Функция должна вернуть положительное число, если первый элемент больше второго (т.е. должен быть раньше в отсортированном list'e), отрицательное, если первый элемент меньше второго (т.е. должен быть позже второго) и ноль, если оба элемента равны. Хотя самый простой способ сделать это - просто сравнить два числа, это не единственное, что Вы можете сделать. Например, данные элемента могут служить ключом к внешнему словарю или списку, который содержит уже более сложные данные, которые и надо сравнить для вычисления порядка сортировки.

Сортировка колонок с примесью

Самый частый случай сортировки list'a - позволить пользователю отсортировать list в режиме "отчёт" по любому столбцу, кликнув на него. Вы можете сделать это при помощи механизма SortItems(), но в таком случае будет сложновато отследить колонки. К счастью примесь wxPython, называемая ColumnSorterMixin, управляет информацией за Вас; живёт от в wx.lib.mixins.listctrl. На рисунке 13.5 показана сортировка столбцов при помощи примеси.
Рисунок 13.5 Сортировка при помощи примеси - обратите внимание на стрелочку в заголовке колонки,  обозначающую направление сортировки

Примесь объявляется так же как и любое множественное наследование:

import wx.lib.mixins.listctrl as listmix

class ListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
    def __init__ (self, parent, log):
        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
        self.list = TestListCtrl(self, tID)
        self.itemDataMap = musicdata
        listmix.ColumnSorterMixin.__init__(self, 3)

Обратите внимание, что примесь не расширяет сам list, хотя она может использоваться и для этого, но скорее всего Вы создадите с ней свою панель. Панель получает код для управления сортировкой и связывает с ним событие клика мышью на заголовке колонки. Листинг 13.4 отображает пример такой сортировки.
Листинг 13.4 list в режиме "отчёт" с сортировкой при помощи примеси

import wx
import wx.lib.mixins.listctrl
import sys, glob, random
import data

# Наследуем из примеси
class DemoFrame(wx.Frame, wx.lib.mixins.listctrl.ColumnSorterMixin):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,"wx.ListCtrl with
                            ColumnSorterMixin", size=(600,400))

        il = wx.ImageList(16,16, True)
        for name in glob.glob("smicon??.png"):
            bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG)
            il_max = il.Add(bmp)

        # Добавляем стрелочки в image list
        self.up = il.AddWithColourMask(
            wx.Bitmap("sm_up.bmp", wx.BITMAP_TYPE_BMP), "blue")
        self.dn = il.AddWithColourMask(
            wx.Bitmap("sm_down.bmp", wx.BITMAP_TYPE_BMP), "blue")

        self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
        self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) 
        for col, text in enumerate(data.columns):
            self.list.InsertColumn(col, text)
        # Создаём карту данных
        self.itemDataMap = {}
        for item in data.rows:
            index = self.list.InsertStringItem(sys.maxint, item[0])
            for col, text in enumerate(item[1:]):
                self.list.SetStringItem(index, col+1, text)
            # Ассоциируем данные с картой
            self.list.SetItemData(index, index)
            self.itemDataMap[index] = item
            img = random.randint(0, il_max)
            self.list.SetItemImage(index, img, img)

        self.list.SetColumnWidth(0,120)
        self.list.SetColumnWidth(1,wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(2,wx.LIST_AUTOSIZE)
        self.list.SetColumnWidth(3,wx.LIST_AUTOSIZE_USEHEADER)

        # Инициализируем сортировки колонки
        wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self,
                                    len(data.columns))
    def GetListCtrl(self):
        return self.list

    def GetSortImages(self):
        return (self.dn, self.up)

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

Для того, чтобы примесь заработала, Вы должны сделать следующее в вашем классе панели:
  1. Класс, который расширяется ColumnSorterMixin, должен иметь метод GetListCtrl(), который возвращает list, который и нужно отсортировать. Этот метод используется примесью чтобы получить ссылку на list.
  2. В методе __init__() расширяемого класса Вы должны создать list, на который будет ссылаться GetListCtrl() до того, как Вы вызовите метод __init__() ColumnSorterMixin. Метод __init__() примеси принимает один аргумент - количество колонок в list'e.
  3. Вы должны использовать SetListData() для того, чтобы присвоить уникальные данные для каждой строки list'a. Это может быть (и скорее всего будет) индекс, или более сложная структура.
  4. Расширяемый класс должен иметь атрибут itemDataMap. Этот атрибут должен быть словарём. Ключами этого словаря являются данные, установленные SetListData(). Значениями словаря должны быть кортежи значений, которые Вы хотите использовать для каждой колонки. (Скорее всего это будет просто текст каждой колонки). Другими словами, itemDataMap представляет данные list'a в форме, удобной для сортировки.
Обычно при применении ColumnSorterMixin Вы либо создаёте itemDataMap при добавлении элементов в ваш list, либо Вы сперва создаёте itemDataMap, а затем используете его для построения самого list'a.
Хотя этот метод может быть сложен в использовании, ColumnSorterMixin - хороший выбор для большинства случаев сортировки.

13.4.3 Как я могу узнать больше о list'e?

Иногда Вам требуется определить какой элемент выбран в list'e из какого-то другого места вашей программы, или Вам требуется изменить элемент, выбранный на данный момент в ответ на действия пользователя, или на что-то, произошедшее в вашей программе.
Есть несколько методов для поиска индекса элемента в list'e, предоставляющих и другую информацию об элементе, как это описано в таблице 13.7
Метод Описание
FindItem(start, str, partial=False) Находит первый элемент list'a, чья метка соответствует str. Если start = -1, nj поиск начинается с начала, иначе - с индекса start. Если partial = True, тогда достаточно, чтобы начало метки элемента совпадало с указанной строкой, а не вся метка.
FindItemAtPos(start, point, direction) Находит первый элемент рядом с point, экземляром wx.Point, указывающим положение относительно верхнего левого угла list'a. Параметр direction указывает wxPython в каком направлении двигаться от point в поисках элемента. Возможные значения - wx.LIST_FIND_LEFT, wx.LIST_FIND_RIGHT, wx.LIST_FIND_UP.
FindItemData(start, data) Ищет элемент с указанными данными data (данные задаются при помощи SetItemData()). Параметр start идентичен аналогу из метода FindItem.
HitTest(point) Возвращает кортеж в форме (index, flags). index - индекс элемента в этой точке или -1, если такого элемента нет. Параметр flags содержит информацию о точке и элементе. Это битовая маска, чьи значения приведены в таблице 13.8
Таблица 13.8 содержит возможные компоненты возвращаемого значения flags HitTest(). Если это применимо, то может быть возвращено более одного флага.
Флаг Описание
wx.LIST_HITTEST_ABOVE Точка находится выше клиентской зоны list'a
wx.LIST_HITTEST_BELOW Точка находится ниже клиентской зоны list'a
wx.LIST_HITTEST_NOWHERE Точка находится в клиентской зоне list'a, но не является частью какого-либо элемента. Обычно причина в том, что точка находится после всех элементов
wx.LIST_HITTEST_ONITEM Точка находится где-то в границах элемента с индексом index
wx.LIST_HITTEST_ONITEMICON Точка находится на иконке элемента с индексом index
wx.LIST_HITTEST_ONITEMLABEL Точка находится на метке элемента с индексом index
wx.LIST_HITTEST_ONITEMRIGHT Точка находится в пустой области справа от элемента с индексом index
wx.LIST_HITTEST_ONITEMSTATEICON Точка находится на иконке состояния элемента. Это подразумевает, что list находится в режиме "дерева" (??? tree mode ???) и есть определённое пользователем состояние.
wx.LIST_HITTEST_TOLEFT Точка находится левее клиентской зоны list'a
wx.LIST_HITTEST_TORIGHT Точка находится правее клиентской зоны list'a
Чтобы двигаться в другом направлении у нас есть несколько методов, которые предоставляют информацию об элементе по имеющемуся индексу. Методы GetItem() и GetItemText() были рассмотрены раньше. Остальные методы перечислены в таблице 13.9
Метод Описание
GetItemPosition(item) Возвращает позицию элемента в качестве wx.Point. Интересно только в режимах "иконка" или "маленькая иконка". Возвращается точка верхнего левого угла элемента.
GetItemRect(item, code=wx.LIST_RECT_BOUNDS) Возвращает wx.Rect, очерчивающий прямоугольник вокруг элемента с индексом item. Параметр code опциональный. Значение по умолчанию - wx.LIST_RECT_BOUDS, в результате чего возвращается прямоугольник, очерчивающий границы элемента. Другим значением может быть wx.LIST_RECT_ICON, в результате чего вернётся прямоугольник, очерчивающий только иконку, или wx.LIST_RECT_LABEL, где прямоугольник будет очерчивать только метку.
GetNextItem(item, geometry=wx.LIST_ALL, state=wx.LIST_STATE_DONTCARE) Возвращает следующий элемент в list'e после указанного индекса item, на основе параметров geometry и state. Значения этих параметров перечислены в следующих таблицах.
SetItemPosition(item, pos) Перемещает элемент с индексом item в позицию wx.Point из параметра pos. Имеет смысл только для режимов "иконка" и "маленькая иконка"
Таблица 13.10 содержит значения параметра geometry метода GetNextItem(). Параметр geometry используется только под MS Windows.
Значение Описание
wx.LIST_NEXT_ABOVE Найти элемент с указанным статусом, который отображается выше, чем стартовый элемент.
wx.LIST_NEXT_ALL Найти элемент с указанным статусом, следующий за стартовым индексом по порядку.
wx.LIST_NEXT_BELOW Найти элемент с указанным статусом, который отображается ниже, чем стартовый элемент.
wx.LIST_NEXT_LEFT Найти элемент с указанным статусом, который отображается левее, чем стартовый элемент.
wx.LIST_NEXT_RIGHT Найти элемент с указанным статусом, который отображается правее, чем стартовый элемент.
Таблица 13.11 содержит возможные значения параметра state метода GetNextItem()
Значение Описание
wx.LIST_STATE_CUT Искать только среди тех элементов, которые "вырезаны"
wx.LIST_STATE_DONTCARE Искать среди всех элементов, вне зависимости от их состояния
wx.LIST_STATE_DROPHILITED Искать только среди тех элементов, которые являются целью для "перетаскивания"
wx.LIST_STATE_FOCUSED Искать только среди элементов, у которых есть фокус
wx.LIST_STATE_SELECED Искать только среди выбранных элементов
Таблица 13.12 содержит методы, используемые для изменения цвета и шрифта текста, отображаемого на элементе
Методы Описание
GetBackgroundColour()
SetBackgroundColour(col)
Работает с фоновым цветом всего list'a. Параметр col - wx.Colour или строка с названием цвета
GetItemBackgroundColour(item)
SetItemBackgroundColour(item,col)
Работает с фоновым цветом элемента с индексом item. Эти методы можно использовать только в режиме "отчёт"
GetItemTextColour(item)
SetItemColour(item,col)
Работает с цветом текста элемента с индексом item. Эти методы можно использовать только в режиме "отчёт"
GetTextColour()
SetTextColour(col)
Работает с цветом текста всего list'a.
Таблица 13.13 содержит оставшиеся методы, не попавшие ни в одну из предыдущих групп, но тоже очень полезные
Методы Описание
GetItemSpacing() Возвращает wx.Size элемента с пространством между элементами в пикселях
GetSelectedItemCount() Возвращает число элементов в list'e, которые на данный момент выбраны
GetTopItem() Возвращает индекс элемента в верху видимой части. Полезно только для режимов "список" и "отчёт"
GetViewRect() Возвращает wx.Rect, соответствующий минимальному прямоугольнику, который нужен для того, чтобы вместить все элементы без прокручивания. Полезно только для режимов "иконка" и "маленькая иконка"
ScrollList(dx,dy) Прокручивает list. Параметр dy - изменение вертикальной координаты в пикселях. dx - горизонтальной. Для режимов "иконка", "маленькая иконка" и "отчёт" размер даётся в пикселях. Для "списка" - в колонках отображаемых элементов.
Эти таблицы раскрывают большую часть функциональности list'a. Однако, все list'ы, которые мы видели до сих пор, имеют одно ограничение - данные всех элементов должны находиться в памяти. В следующем разделе мы обсудим способ, который позволяет обойтись только теми данными, которые нужны для отображения элемента.

13.5 Создание виртуального list'a

Давайте предположим, что ваше wxPython приложение должно отобразить список всех ваших клиентов. Сперва Вы использовали обычный list и всё было хорошо. Время идёт, и ваше использование wxPython делает Вас всё больше и больше успешным. Ваш список клиентов становится всё больше и больше. Очень много клиентов - и у вашего приложения начинаются проблемы. Скорее всего оно будет тратить много времени на запуск. Возможно оно начинает потреблять всё больше и больше памяти. Что Вы можете сделать? Вы можете создать виртуальный list.
Описанная проблема - результат того, каким образом управляется list. Обычно данные копируются в list оттуда, где они генерируются. Это потенциальная трата ресурсов и, хотя для небольших list'ов разница может быть незаметна, для больших листов это может сэкономить большое количество памяти и существенно сократить время загрузки.
Для того, чтобы сократить затраты памяти и ускорить загрузку list'a, wxPython позволяет объявить виртуальный list, что подразумевает, что информация для каждого элемента создаётся при необходимости отобразить этот элемент. Соответственно нам не нужно хранить каждый элемент в памяти и мы не объявляем весь list при запуске. C другой стороны, поиск элементов в таком list'e может быть медленнее, чем в обычном. На рисунке 13.6 Вы можете видеть пример такого list'a. Код этого примера приведён в листинге 13.5.
Рисунок 13.6 Виртуальный list
Листинг 13.5 Виртуальный list

import wx
import sys, glob, random
import data

# Источник данных
class DataSource:
    def GetColumnHeaders(self):
        return data.columns
    defGetCount(self):
        return len(data.rows)
    def GetItem(self, index):
        return data.rows[index]
    def UpdateCache(self, start, end):
        pass

# Объявляем виртуальный list
class VirtualListCtrl(wx.ListCtrl):
    def __init__(self, parent, dataSource):
        # Создаём list c флагом виртуальности
        wx.ListCtrl.__init__(self, parent,
                    style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL)
        self.dataSource = dataSource
        self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems)
        # Задаём размер list'a
        self.SetItemCount(dataSource.GetCount())
        columns = dataSource.GetColumnHeaders()
        for col, text in enumerate(columns):
            self.InsertColumn(col, text)

    def DoCacheItems(self, evt):
        self.dataSource.UpdateCache(
            evt.GetCacheFrom(), evt.GetCacheTo())

    # Получаем текст по запросу
    def OnGetItemText(self, item, col):
        data = self.dataSource.GetItem(item)
        return data[col]

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

class DemoFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,"Virtual wx.ListCtrl",
                                                    size=(600,400))
        self.list = VirtualListCtrl(self, DataSource())

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


Класс источника данных - простой пример, который хранит наши элементы. Настоящий класс источника данных может получать данные из БД или делать ещё что-то, но главное он должен предоставлять тот же интерфейс, что и наш простой пример.
Для того чтобы создать виртуальный list, сперва мы передаём флаг wx.LC_VIRTUAL при создании обычного list'a. Обычно Вы будете создавать list в качестве подкласса wx.ListCtrl, а не просто используя конструктор. Причина в том, что Вам потребуется переопределить некоторые методы wx.ListCtrl для заполнения виртуального list'a. Это будет выглядеть как-то так:

class MyVirtualList (wx.ListCtrl):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_VIRTUAL)

Виртуальный list должен также иногда вызывать метод SetItemCount() в процессе своей инициализации. Таким образом он узнаёт сколько данные содержится в источнике данных, устанавливает соответствующий лимит и настраивает полосы прокрутки. Вы можете снова вызвать SetItemCount() если количество элементов в источнике данных изменяется. Любой метод On, который Вы переопределяете должен быть способным обработать любое значение между 0 и SetItemCount()-1.
Ваш виртуальный list может переопределить три метода родительского класса для того, чтобы определить что будет отображаться. Самый важный метод - OnGetItemText(item, col). item и col - строка и столбец ячейки, которая будет отображена, возвращаться должна строка, которая будет отображаться в этой ячейке. Например, этот метод просто отображает координаты ячейки:

def OnGetItemText(self, item, col):
    return "Item %d, column %d" % (item, col)

Если Вы хотите отобразить изображение, Вы должны переопределить метод OnGetItemImage(item), который должен вернуть индекс изображения в связанном image list. Если Вы не переопределяете этот метод, базовый класс вернёт -1, что означает, что никакого изображения не будет показано. Если Вы хотите изменить какой-то из атрибутов отображения строки, тогда Вы должны переопределить метод OnGetItemAttr(item), который так же принимает индекс строки и возвращает экземпляр класса wx.ListItemAttr. У этого класса есть несколько устанавливающих и получающих методов, которые могут быть использованы для настройки цвета, выравнивания или других свойств строки.
Если данные, на которых основывается ваш виртуальный list, меняются и Вы хотите обновить их отображение, Вы можете использовать метод RefreshItem(item), который принудительно перерисовывает указанную строку. Метод RefreshItems(itemFrom, itemTo) перерисовывает элементы между двумя индексами.
Чтобы помочь оптимизировать получение данных в вашем источнике данных, виртуальный list может посылает EVT_LIST_CACHE_HINT когда он хочет отобразить новую страницу данных. Это даёт вашему источнику данных возможность получить несколько записей из вашей БД (или откуда-бы то ни было) и сохранить их. Последующий вызов OnGetItemText() для этих элементов будет работать быстрее, чем если бы ему надо было получать данные для каждого элемента по отдельности.

13.6 Итоги


  • list в wxPython - это виджет для отображения списка информации. Он сложнее и более функционален, чем простой list box. list является экземпляром класса wx.ListCtrl. Он может быть отображён в режиме "иконка" и "маленькая иконка", в котором каждый элемент представлен в виде иконки; "список", где элементы расположены в столбцах и "отчёт", который представляет элементы с несколькими колонками, заголовками колонок, по одному элементу на строку.
  • Изображения для list'a управляются элементом image list - массивом изображений с доступом по индексу. list может работать с разными image list'ами для разных режимов, позволяя легко переключаться между режимом "иконка" и "маленькая иконка".
  • Для добавления текста в list Вы используете метод InsertStringItem(index, label), а для изображения - InsertImageItem(index, imageIndex). Чтобы добавить и то и другое сразу Вам нужен InsertImageStringItem(index, label, imageIndex). Чтобы добавить колонку в режиме "отчёт" нужен метод InsertColumn(col, heading, format=wx.LIST_FORMAT_LEFT, width=-1). После того, как колонка добавлена, Вы можете добавить текст в новую колонку при помощи метода SetStringItem(index, col, label, imageId=-1).
  • list генерирует различные события, которые могут быть привязаны к действиям программы. События являются экземплярами класса wx.ListEvent. Стандартные типы событий: EVT_LIST_INSERT_ITEM, EVT_LIST_ITEM_ACTIVATED, EVT_LIST_ITEM_SELECTED.
  • Если list объявлен с флагом wx.LC_EDIT_LABELS, то пользователь может редактировать текст элементов при его выборе. Редактирование подтверждается нажатием enter или кликом вне элемента, для отказа от редактирования надо нажать escape.
  • Вы можете отсортировать list, объявив его с флагом wx.LC_SORT_ASCENDING или wx.LC_SORT_DESCENDING. В этом случае элементы будут отсортированы в соответствии со значениями строк. В режиме "отчёт" для этого используются строки из колонки 0. Кроме того, Вы можете использовать метод SortItem(func), чтобы создать свою собственную функцию сортировки. Кроме того, если list находится в режиме "отчёт", Вы можете использовать примесь wx.lib.mixins.listctrl.ColumnSorterMixin, чтобы дать пользователю возможность сортировки по выбранной колонке.
  • List, объявленный с флагом wx.LC_VIRTUAL, является виртуальным list'ом, что означает, что данные для отображения в нём получаются динамически по мере необходимости. Для такого list'a Вы должны переопределить метод OnGetItemText(item, col), чтобы он возвращал текст, который должен отображаться в указанной строке и колонке. Кроме того, Вы можете переопределить методы OnGetItemImage(item) и OnGetItemAttr(item), чтобы они возвращали изображение или атрибуты отображения для каждой строки. Если данные для отображения изменяются, то обновить отдельную строку можно при помощи метода RefreshItem(item), а несколько строк - RefreshItems(itemFrom, itemTo).
Иногда ваши данные слишком сложны для хранения в простом list'e. Вам нужно что-то более двухмерное. Это называется grid и мы обсудим его в следующей главе.

2 комментария: