Схемы размещения виджетов, BoxSizer

После того как мы с вами разобрались с созданием меню и панелей инструментов, пришло время познакомиться с принципами размещения различных виджетов (кнопок, текстовых полей, списков и т.п) в области окна. В самом простом варианте мы можем использовать абсолютное позиционирование этих элементов. Например, сделать так. Создать два виджета для отображения картинок:

img1 = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap("images/js.jpg"))
img2 = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap("images/python.jpg"))

и разместить их в следующих позициях:

img1.SetPosition( (10, 10) )
img2.SetPosition((300, 40))

Далее, укажем размер окна:

self.SetSize(600, 300)

и запустим программу. Увидим следующий результат:

Как вы понимаете, при таком подходе есть следующие существенные недостатки:

  • при изменении размеров окна виджеты не меняют своих размеров и положения;
  • такой интерфейс будет по разному выглядеть на разных платформах (мобильных, десктопных, планшетных);
  • изменение размеров отдельных виджетов может нарушить всю конструкцию интерфейса;
  • при изменении схемы размещения элементов нам придется заново переделывать весь интерфейс.

Сайзеры (sizers)

Для решения этих проблем в wxPython используется механизм на основе так называемых сайзеров – специальных объектов, которые располагают виджеты в соответствии с предопределенной схемой (layout). Давайте на примере посмотрим работу основных из них.

BoxSizer

Этот объект позволяет располагать виджеты в столбец или в строку (по умолчанию в строку). Сначала мы должны его создать:

vbox = wx.BoxSizer()

Затем, добавить виджеты:

vbox.Add(img1, wx.ID_ANY)
vbox.Add(img2, wx.ID_ANY)

И после этого указать нашему окну использовать этот сайзер:

self.SetSizer(vbox)

Теперь, при запуске программы, мы увидим, что наши изображения автоматически располагаются в строку и выравниваются по центру.

Однако, это не лучшая практика использования сайзеров. Обычно, их связывают не с самим окном, а с панелью, которую добавляют в это окно:

panel = wx.Panel(self)

Затем, наши виджеты связываем с этой панелью:

img1 = wx.StaticBitmap(panel, wx.ID_ANY, wx.Bitmap("images/js.jpg"))
img2 = wx.StaticBitmap(panel, wx.ID_ANY, wx.Bitmap("images/python.jpg"))

И устанавливаем сайзер:

panel.SetSizer(vbox)

При запуске видим практически ту же картину (только цвет фона немного изменился).

Если же изменить позиционирование по вертикали:

vbox = wx.BoxSizer(wx.VERTICAL)

то наши виджеты автоматически выстроятся по вертикали. Видите как это удобно.

Вернем горизонтальное положение и у каждого виджета при добавлении укажем вот такой флаг:

vbox.Add(img1, wx.ID_ANY, wx.EXPAND)
vbox.Add(img2, wx.ID_ANY, wx.EXPAND)

Это означает, что размеры каждой ячейки сайзера должны быть максимальны и, в частности, это предотвратит наложение виждетов друг на друга. Чтобы это лучше увидеть, давайте создадим виджет в виде панели и расположим его по центру нашего окна:

mp = wx.Panel(panel)
mp.SetBackgroundColour('#FFFFE5')
vbox.Add(mp, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)

Смотрите, мы здесь указали два стиля: EXPAND и ALL, а затем, поставили отступ в 20 пикселей. Благодаря стилю ALL отступы будут применены ко всем четырем сторонам нашей панели, а EXPAND развернет эту панель на максимально возможный размер. В результате при изменении размеров окна панель также будет менять свои размеры:

Далее, для демонстрации возможностей использования сайзера, построим вот такой интерфейс:

Мы здесь сначала создаем общий сайзер

panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)

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

hbox1 = wx.BoxSizer(wx.HORIZONTAL)

с горизонтальным расположением элементов. В первую ячейку помещаем обычный текст «Путь к файлу» с отступом справа 8 пикселей, а во вторую – поле ввода:

st1 = wx.StaticText(panel, label='Путь к файлу:')
tc = wx.TextCtrl(panel)
 
hbox1.Add(st1, flag=wx.RIGHT, border=8)
hbox1.Add(tc, proportion=1)

Параметр proportion=1 задает растяжение этого поля по горизонтали на максимальную длину. (По умолчанию его значение равно 0). После формирования сайзера hbox1 добавляем его в сайзер vbox:

vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)

Устанавливаем флаг EXPAND и отступы по 10 пикселей слева, справа и сверху.

В следующую ячейку сайзера vbox помещаем текст «Содержимое файла», делая отступы со всех сторон по 10 пикселей:

st2 = wx.StaticText(panel, label='Содержимое файла')
vbox.Add(st2, flag=wx.EXPAND| wx.ALL, border=10)

По аналогии, ниже размещаем многострочное поле ввода:

tc2 = wx.TextCtrl(panel, style=wx.TE_MULTILINE)
vbox.Add(tc2, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=10)

И в конце размещаем две кнопки:

btnOk = wx.Button(panel, label='Да', size=(70, 30))
btnCn = wx.Button(panel, label='Отмена', size=(70, 30))

в еще одном вложенном горизонтальном сайзере с отступами слева 10 пикселей:

hbox2 = wx.BoxSizer(wx.HORIZONTAL)
hbox2.Add(btnOk, flag=wx.LEFT, border=10)
hbox2.Add(btnCn, flag=wx.LEFT, border=10)

Размещаем его в основном блоке, выравнивая по правому краю и с отступами справа и снизу по 10 пикселей:

vbox.Add(hbox2, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.RIGHT, border=10)
panel.SetSizer(vbox)

Запускаем приложение и видим ожидаемый результат:

Давайте для примера еще изменим размер шрифта в нашем интерфейсе. Для этого запишем такие строчки:

font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
font.SetPointSize(12)
panel.SetFont(font)

То есть, для нашей панели мы установили шрифт в 12 пунктов. Запустим программу и видим, что у всех виджетов шрифт был автоматически увеличен до указанного размера. При этом наш интерфейс не «поплыл» а остался в рамках указанной разметки. Вот еще одно наглядное преимущество сайзеров по сравнению с абсолютным позиционированием.

На следующем занятии мы продолжим эту тему и поговорим о других классах управления разметкой.

Видео по теме