Продвинутая работа с виджетами

Продолжим рассматривать виджеты wxPython и построим вот такой интерфейс:

В его основе будет лежать объект класса Notebook, создающий панели с вкладками. Первой вкладкой «Главная» будет объект SplitterWindow, содержащий виджеты ListBox слева и HtmlWindow справа. На второй вкладке «Список книг» будет расположен виджет ListCtrl со списком книг.

Вначале мы воспользуемся вот такой заготовкой, в которой импортируются два модуля: wx и wx.html (второй нужен для класса HtmlWindow):

import wx
import wx.html
 
class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, pos=(0,0), size=(1000, 600))
 
app = wx.App()
frame = MyFrame(None, 'wxPython')
frame.Show()
app.MainLoop()

Далее, в конструкторе класса MyFrame создадим панель с вкладками, используя класс Notebook:

tabs = wx.Notebook(self, id=wx.ID_ANY)

Затем, подготовим первую вкладку, то есть, создадим окно с разделителем:

splitter = wx.SplitterWindow(tabs, wx.ID_ANY, style=wx.SP_LIVE_UPDATE)

Подготовим два виджета, которые расположим слева и справа в этом окне. Слева будет находиться ListBox со списком страниц:

self.urls = ["htm/1_1.htm", "htm/1_2.htm", "htm/1_3.htm",
                "htm/1_4.htm", "htm/1_5.htm"]
listbox = wx.ListBox(splitter, choices=self.urls)

а слева – виджет HtmlWindow для отображения этих страниц:

self.htmlwin = wx.html.HtmlWindow(splitter, wx.ID_ANY, style=wx.NO_BORDER)
self.htmlwin.SetStandardFonts(12)
self.htmlwin.LoadPage(self.urls[0])

И, далее, размещаем эти элементы в SplitterWindow. Для этого используем метод SplitVertically, который создает два окна с вертикальным разделением:

splitter.SplitVertically(listbox, self.htmlwin)
splitter.SetMinimumPaneSize(200)

А через метод SetMinimumPaneSize определяем минимальный размер добавленных элементов в этом окне. Это по умолчанию создаст ширину виджета ListBox размером в 200 пикселей.

Здесь нужно сказать пару слов о способе отображения HTML-страниц. Во-первых, они должны располагаться в том же каталоге, что и выполняемый файл Питона (точнее в подкаталоге htm). И, во-вторых, их кодировка должна совпадать с кодировкой, указанной в заголовке HTML-файла. В примере этого занятия, мы создадим файлы по такому шаблону:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
… содержимое страницы (с HTML-тегами)</body>
</html>

В заголовке файла указана кодировка utf-8 и сам файл также записан в этой кодировке. Поэтому проблем с его отображением не возникнет. И, еще один момент. Виджет HtmlWindow способен отображать только сам HTML-документ без CSS (каскадных таблиц стилей) и сценариев JavaScript. То есть, это не браузер, а всего лишь небольшой модуль для обработки тегов HTML-документа. Если нужно отображать полноценные страницы, то для этого следует воспользоваться другим виджетом – WebView.

Далее, разместим SplitterWindow на первой вкладке объекта Notebook:

tabs.InsertPage(0, splitter, "Главная", select=True)

Здесь первая цифра – это номер вкладки, затем, размещаемый элемент, далее, заголовок вкладки и отмечаем, чтобы она отображалась по умолчанию. Если теперь запустить нашу программу, то она будет выглядеть вот так:

Давайте добавим интерактивности нашему интерфейсу и сделаем выбор отображаемых страниц. Для этого повесим обработчик на список ListBox:

listbox.Bind(wx.EVT_LISTBOX, self.OnSelectUrl)

а сам метод OnSelectUrl запишем следующим образом:

    def OnSelectUrl(self, e):
        urlId = e.GetEventObject().GetSelection()
        if urlId != wx.NOT_FOUND:
            self.htmlwin.LoadPage(self.urls[urlId])

Мы здесь получаем объект, который сгенерировал данное событие, то есть, ListBox и затем, с помощью метода GetSelection определяем индекс выбранного элемента. Если этот индекс равен NOT_FOUND, значит, ничего не выбрано, а иначе, мы в виджет HtmlWindow загружаем отмеченную страницу.

ListCtrl

Теперь создадим следующий виджет ListCtrl для отображения списка книг. Это будет выглядеть так:

self.list = wx.ListCtrl(tabs, wx.ID_ANY, style=wx.LC_REPORT)
self.list.SetFont(wx.Font( wx.FontInfo(12) ))
self.list.SetBackgroundColour("#f0f0f0")
 
self.list.InsertColumn(0, 'Название', width=200)
self.list.InsertColumn(1, 'Автор', width=200)
self.list.InsertColumn(2, 'Год издания', wx.LIST_FORMAT_RIGHT, 140)
self.list.InsertColumn(3, 'Цена', wx.LIST_FORMAT_RIGHT, 90)

Мы здесь указываем стиль LC_REPORT, который позволяет формировать список с несколькими колонками. Далее, задаем размер шрифта в 12 пунктов и цвет фона #f0f0f0 – светло-серый. После этого создаем колонки с помощью метода InsertColumn. В параметрах указываем номер колонки, название и начальную ширину.

Давайте добавим этот список второй панелью объекта Notebook:

tabs.InsertPage(1, self.list, "Список книг")

и при запуске увидим следующее:

Добавим сюда список книг. Создадим их список вначале программы в виде вот такого списка кортежей:

books = [('Евгений Онегин', 'Пушкин А.С.', 2000, 192),
         ('Пиковая дама', 'Пушкин А.С.', 2004, 150.53),
         ('Мастер и Маргарита', 'Булгаков М.А.', 2005, 500),
         ('Роковые яйца', 'Булгаков М.А.', 2003, 400),
         ('Белая гвардия', 'Булгаков М.А.', 2010, 340)]

и, далее, в конструкторе MyFrame добавим их в ListCtrl:

for b in books:
    self.list.Append(b)

Запустим программу и увидим наш список на второй вкладке:

Mixins (смешиватели)

Далее рассмотрим некоторые возможности по расширению функциональности виджета ListCtrl с помощью следующих minins (смешивателей):

  • wx.ColumnSorterMixin – для сортировки столбцов;
  • wx.ListCtrlAutoWidthMixin – для выравнивания по всей ширине;
  • wx.ListCtrlSelectionManagerMix – для управления выделением;
  • wx.TextEditMixin – для редактирования отдельных элементов списка;
  • wx.CheckListCtrlMixin – для создания checkbox элементов в списке;
  • wx.ListRowHighlighter – для подсвечивания строк определенным образом.

Рассмотрим примеры их использования. Вначале подключим вот такой модуль:

import wx.lib.mixins.listctrl

чтобы мы могли использовать классы mixins. Затем, создадим свой класс списка ListCtrlMixins, который будет наследоваться вначале от ListCtrl, а затем от одного (или нескольких) mixins. Например, так:

class ListCtrlMixins(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
    def __init__(self, parent, *args, **kw):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT)
        wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)

Смотрите, мы здесь в качестве второго наследуемого класса указали ListCtrlAutoWidthMixin, который добавляет функционал нашему классу ListCtrlMixins, обеспечивая выравнивание по ширине наших столбцов. Причем, сначала нужно вызвать конструктор базового класса ListCtrl, а уже потом конструкторы смешивателей.

Далее, в конструкторе класса MyFrame мы воспользуемся этим классом для создания списка:

self.list = ListCtrlMixins(tabs)

Запустим программу и теперь видим, что ширина последнего столбца доходит до конца окна:

ColumnSorterMixin

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

class ListCtrlMixins(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin,
                        wx.lib.mixins.listctrl.ColumnSorterMixin):
    def __init__(self, parent, *args, **kw):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT)
        wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
        wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(books))

и вызовем его конструктор в конструкторе класса ListCtrlMixins. Далее, нам необходимо преобразовать наш список книг и представить его в виде такого словаря:

books = {1: ('Евгений Онегин', 'Пушкин А.С.', 2000, 192),
         2: ('Пиковая дама', 'Пушкин А.С.', 2004, 150.53),
         3: ('Мастер и Маргарита', 'Булгаков М.А.', 2005, 500),
         4: ('Роковые яйца', 'Булгаков М.А.', 2003, 400),
         5: ('Белая гвардия', 'Булгаков М.А.', 2010, 340)}

Здесь ключ – это номер записи, а значение – сама запись в виде кортежа. После этого в конструкторе класса ListCtrlMixins необходимо дать ссылку на этот словарь специальному свойству:

self.itemDataMap = books

(оно впоследствии используется смешивателем ColumnSorterMixin) и переопределить специальный метод:

    def GetListCtrl(self):
        return self

(он возвращает ссылку на объект ListCtrl, столбцы которого будут подвергаться сортировке).

Ну а в конструкторе класса MyFrame добавим элементы списка следующим образом:

for key, b in books.items():
    index = self.list.Append(b)
    self.list.SetItemData(index, key)

Здесь мы дополнительно связываем индекс добавленной строки с индексом (ключом) этой записи в словаре с помощью метода SetItemData. Теперь все готово и при запуске программы мы видим этот функционал сортировки в нашем списке.

Подробное описание работы с этим смешивателем можно посмотреть на странице официальной документации:

docs.wxpython.org/wx.lib.mixins.listctrl.ColumnSorterMixin.html

Конечно, приведенный текст программы выглядит несколько коряво. Его можно значительно улучшить, например, перенести функционал по добавлению строк списка в класс ListCtrlMixins. Я здесь лишь привожу принцип работы с ними и именно так следует воспринимать этот листинг.

CheckListCtrlMixin

И в заключение этого занятия добавим еще один достаточно простой смешиватель – CheckListCtrlMixin. Для этого мы его укажем в качестве одного из базовых классов:

class ListCtrlMixins(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin,
                        wx.lib.mixins.listctrl.ColumnSorterMixin,
                        wx.lib.mixins.listctrl.CheckListCtrlMixin):

и вызовем его конструктор:

wx.lib.mixins.listctrl.CheckListCtrlMixin.__init__(self)

Все, теперь при запуске программы у наших записей появились checkbox’ы:

Как вы понимаете рассказать подробно обо всем в рамках видеоуроков просто невозможно. Поэтому здесь я вам показал возможности создания интерфейса в wxPython и привел принцип использования более сложных виджетов. Но, зная этот принцип, вы по аналогии сможете уже самостоятельно использовать другие подобные элементы управления и, при необходимости, погрузиться во все детали их работы, руководствуясь официальной документацией:

https://docs.wxpython.org

Видео по теме