Версия 5 от 2010-05-21 20:20:43

Убрать это сообщение

Размещение виджетов с sizers

Эта глава включает:

Традиционно, одна из наиболее раздражающих проблем в программировании интерфейса - управление физическим размещением виджетов в пределах окна. В начале, было абсолютное позиционирование, и программист явно устанавливал размер и позицию каждого виджета на экране. Процесс мягко выражаясь утомительный, не говоря уже о боли в шее. Хуже того, абсолютное позиционирование заставляет вас постоянно контролировать размер окна и расположение виджетов. Если вы позволяете пользователю, изменять размер окна, то вы должны обработать это событие, и явно изменить размер и позицию каждого виджета. После этого удостоверьтесь, что виджеты все еще выглядят хорошо, не накладываются друг на друга и не вылезают за край окна. Это даже хуже боли в шее. Ситуация еще более ухудшается, если пользователь может изменять число и тип виджетов на экране. И, конечно, если вы или ваш клиент решаете, что нужно внести изменения в интерфейс, вы должны пройти этот процесс снова и снова.

Нужна структура, которая решает, как изменить размеры и переместить виджеты, основанная на предопределенном шаблоне. Было предложено несколько решений этой проблемы. Рекомендованный способ работы со сложным размещением виджетов - использование sizers. Sizer содержит автоматизированный алгоритм для размещения группы виджетов. Sizer связан с контейнером, обычно с фреймом или панелью. Виджеты, которые созданы в пределах родительского контейнера, должны также быть добавлены в sizer. Когда sizer связан с контейнером, именно он управляет размещением элементов.

Преимущества использования sizers существенны. Sizer автоматически обрабатывает событие изменения размера контейнера, повторно вычисляя размещение его виджетов. С другой стороны, если изменились размеры одного из виджетов, sizer может автоматически обновить размещение. Кроме того, sizers просты в использовании, когда вы хотите изменить размещение. Использование sizers наклкдывает определенные ограничения на размещение объектов. Однако, самые гибкие sizers - grid bag и box - будут в состоянии сделать почти все, что вы захотите.

Что такое - sizer?

В wxPython sizer – это объект, единственная цель которого состоит в том, чтобы управлять размещением виджетов в пределах контейнера. Sizer не контейнер и не виджет. Он только представление алгоритма размещения объектов на экране. Все sizers - объекты классов, производных от абстрактного класса wx.Sizer. Есть пять sizers представленных в wxPython. Они перечислены в таблице 11.1. Помните, sizer могжет быть вложен в другой sizer, чтобы предоставить вам больше гибкости.

Таблица 11.1 Предопределенные sizers в wxPython

Sizer

Описание

Grid

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

Flex grid

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

Grid bag

Самый гибкий представитель семейства grid sizer. Используется для произвольного размещение виджетов в сетке.

Box

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

Static box

Стандартный sizer с рамкой и заголовком.

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

Все sizers знают минимальный размер каждого из дочерних виджетов. Как правило, sizer также учитывает дополнительную информацию о размещении, например, промежутки между виджетами, или насколько можно увеличить размер виджета, и как выровнять виджеты, когда они занимают места меньше, чем выделено. Эту информацию использует алгоритм размещения, чтобы определить размер и позицию каждого виджета. Каждый вид sizers в wxPython произведет различное размещение той же самой группы виджетов. Вы увидите это далее, в этой главе, поскольку мы используем похожие размещения, чтобы демонстрировать каждый тип sizers.

Три основных шага для использования sizer:

  1. Добавьте sizer к контейнеру. Подключите sizer к виджету, дочерними объектами которого он управляет. Sizer добавляется к контейнерному виджету, используя метод SetSizer(sizer) класса wx.Window. Так как это метод класса wx.Window, это означает, что любой виджет wxPython может иметь sizer, хотя sizer используется только для виджетов, которые являются контейнерами.

  2. Добавьте каждый дочерний виджет к sizer. Все дочерние виджеты должны быть отдельно добавлены к sizer. Простого создания дочерних виджетов в родительском контейнере - недостаточно. Виджет добавляется к sizer методом Add(). Метод Add() имеет несколько различных сигнатур, которые будут рассмотрены в следующем разделе.
  3. (дополнительный) Разрешить sizer вычислить его размер. Скажите sizer вычислить его размер, основанный на его дочерних виджетах, вызвав метод Fit() класса wx.Window на родительском объекте окна или метод Fit(window) sizer. (Метод окна переадресовывает к методу sizer.) В любом случае, метод Fit() просит, чтобы sizer вычислил его размер, основанный на дочерних виджетах, и изменяет размеры родительского виджета, чтобы соответствовать этому размеру. Есть связанный метод, FitInside(), который не изменяет размер отображения родительского виджета, но действительно изменяет его виртуальные размеры так, что, если бы виджет находился в прокручиваемой панели, wxPython повторно вычислил бы, действительно ли необходимы полосы прокрутки.

Далее мы должны рассмотреть поведение определенных видов sizers и поведение, общее для всех sizers. Всегда есть вопрос, с чего начать рассмотрение. Наше решение состоит в том, чтобы начать, с представления grid sizer, который является самым легким для понимания. После этого, мы обсудим поведение, обычное для всех sizers, используя grid sizer как пример. Затем, мы покажем остальные типы sizers.

Grid sizer

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

Листинг 11.1 Виджет, используемый в более поздних примерах

   1 import wx
   2 
   3 class BlockWindow(wx.Panel):
   4     def __init__(self, parent, ID=-1, label="",
   5                  pos=wx.DefaultPosition, size=(100, 25)):
   6         wx.Panel.__init__(self, parent, ID, pos, size,
   7                           wx.RAISED_BORDER, label)
   8         self.label = label
   9         self.SetBackgroundColour("white")
  10         self.SetMinSize(size)
  11         self.Bind(wx.EVT_PAINT, self.OnPaint)
  12 
  13     def OnPaint(self, evt):
  14         sz = self.GetClientSize()
  15         dc = wx.PaintDC(self)
  16         w,h = dc.GetTextExtent(self.label)
  17         dc.SetFont(self.GetFont())
  18         dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)

Мы будем помещать несколько таких виджетов во фрейм, используя различные sizers далее в этой главе. Начнем с grid sizer.

Что такое - grid sizer?

Самый простой sizer, предлагаемый wxPython - grid. Название подразумевает, что grid sizer размещает виджеты в двумерной сетке. Первый виджет в списке дочерних объектов sizer помещается в левый верхний угол сетки, остальные размещаются слева направо и сверху вниз, пока последний виджет не займет правый нижний угол сетки. На рисунке 11.1 показан пример с девятью виджетами, помещенными в сетку 3 x 3. Заметьте, что есть небольшой промежуток между виджетами.

Рисунок 11.1

Когда вы изменяете размеры grid sizer, промежутки становятся больше, но по умолчанию размеры виджетов и расположение левого верхнего угла не изменяется. На рисунке 11.2 показано то же самое окно после изменений.

Рисунок 11.2

В листинге 11.2 показан код для рисунков 11.1 и 11.2.

Листинг 11.2 Использование grid sizer

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 
   6 class GridSizerFrame(wx.Frame):
   7     def __init__(self):
   8         wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
   9         sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
  10         for label in labels:
  11             bw = BlockWindow(self, label=label)
  12             sizer.Add(bw, 0, 0)
  13         self.SetSizer(sizer)
  14         self.Fit()
  15 
  16 app = wx.PySimpleApp()
  17 GridSizerFrame().Show()
  18 app.MainLoop()

Как видно из листинга 11.2, grid sizer - объект класса wx.GridSizer. Конструктор явно устанавливает четыре свойства, которые являются уникальными для grid sizer:

   1 wx.GridSizer(rows, cols, vgap, hgap)

Параметры rows и cols - целые числа, которые определяют размер сетки — количество виджетов, которые будут помещены в строке и в столбце. Если какой-либо из параметров равен нулю, то значение другого параметра вычисляется на основе общего количества виджетов в sizer. Например, если sizer, созданный конструктором wx.GridSizer(2, 0, 0, 0) будет иметь восемь виджетов, то он должен будет иметь четыре столбца, чтобы соответствовать двум строкам.

Параметры vgap и hgap позволяют задать размер промежутков между виджетами. Параметр vgap - число пикселей между смежными столбцами, и hgap - число пикселей между смежными строками. Свойства rows, cols, vgap и hgap имеют соответствующие Set и Get методы: GetRows(), SetRows(rows), GetCols(), SetCols(cols), GetVGap(), SetVGap(gap), GetHGap() и SetHGap(gap).

Алгоритм установки размеров и размещения grid sizer является прямым. Он создает начальное размещение сетки, когда вызывает Fit(). В случае необходимости, число строк и столбцов вычисляется от числа элементов в списке. Каждая ячейка в сетке имеет одинаковый размер, даже если размеры виджетов различны. Она имеет размер самого большого дочернего виджета. Из-за этого, grid sizer лучше всего использовать для размещений, где виджеты имеют одинаковый размер (классический пример - вспомогательная клавиатура калькулятора). Если grid sizer содержит виджеты, сильно различающиеся по размерам, то лучше использовать flex grid sizer или grid bag sizer.

Как добавить или удалить объект из sizer?

Порядок, в котором дочерние виджеты добавляются в sizer, очень важен. Это отличается от общего случая добавления дочерних объектов к родительскому виджету. Типичный алгоритм размещения для sizer берет каждый дочерний виджет по очереди, чтобы определить его место при отображении. Размещение следующего элемента зависитот того, как разместились предыдущие элементы. Например, grid sizer двигается слева направо и сверху вниз по списку виджетов. Обычно, если вы создаете sizer в родительском конструкторе виджета, вы будете в состоянии добавить элементы в правильном порядке. Однако, в некоторых случаях, вам будет нужна большая гибкость, особенно если вы будете динамически изменять размещение во время выполнения.

Использование метода Add()

Самый общий метод для добавления виджета к sizer – это метод Add(). Он добавляет виджет в конец дочернего списка sizer. Метод Add() имеет три различных стиля:

   1 Add(window, proportion=0, flag=0, border=0, userData=None)
   2 Add(sizer, proportion=0, flag=0, border=0, userData=None)
   3 Add(size, proportion=0, flag=0, border=0, userData=None)

Первая версия используется чаще всего. Она позволяет добавлять виджет в sizer. Вторая версия используется, чтобы вложить один sizer в другой — обычно это делается с box sizers, но вы можете сделать это с любым типом sizer. Третья версия позволяет вам добавлять пустое пространство в sizer. Параметр size – это объекта wx.Size или кортеж (width, height). Пустое пространство используется как разделитель (например, в панели инструментов). Обычно это используется в box sizers, но может использоваться в любом sizer, чтобы добавить пустое пространство в область окна или разделить виджеты.

Другие параметры определяют, как элемент отображен в пределах sizer. Некоторые из этих параметров имеют значение только для определенного вида sizers. Элемент proportion используется только с box sizers, и указывает, насколько элемент деформируется, когда родительское окно изменяет размер. Это будет рассмотрено вместе с box sizers, позже в этой главе.

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

Использование метода Insert() Как вы могли бы ожидать, прочитав главу о Меню (глава 10), есть связанные методы, чтобы вставить новый виджет в sizer. Метод Insert() позволяет вам помещать новый виджет в список, используя произвольный индекс. Он также имеет три разновидности:

   1 Insert(index, window, proportion=0, flag=0, border=0, userData=None)
   2 Insert(index, sizer, proportion=0, flag=0, border=0, userData=None)
   3 Insert(index, size, proportion=0, flag=0, border=0, userData=None)

Использование метода Prepend() Есть также метод Prepend(), который добавляет новый виджет, sizer или разделитель в начало списка sizer:

Prepend(window, proportion=0, flag=0, border=0, userData=None) Prepend(sizer, proportion=0, flag=0, border=0, userData=None) Prepend(size, proportion=0, flag=0, border=0, userData=None)

Рисунок 11.3 показывает, на что было бы похоже размещение сетки из листинга 11.1, если бы вместо метода Add() использовался бы Prepend().

Рисунок 11.3

Если вы добавляете новые элементы к sizer после того, как он уже был отображен на экране, вы должны вызвать метод Layout(), чтобы заставить sizer перестроится.

Использование метода Detach()

Чтобы удалить элемент из sizer, используйте метод Detach(), который удаляет элемент из sizer, но не разрушает его. Это полезно, если вы хотите использовать этот элемент в будущем. Есть три способа использовать Detach(). Вы можете передать ему в качестве параметра окно, sizer, который вы хотите отделить, или вы можете передать целочисленный индекс объекта:

   1 Detach(window)
   2 Detach(sizer)
   3 Detach(index)

Во всех трех случаях, метод Detach() возвращает булевское значение показывающее, был ли элемент фактически удален. Метод возвратит False, если вы пробуете удалить элемент, отсутствующий в sizer. В отличие от некоторых других методов удаления, которые мы рассматривали, Detach() не возвращает удаляемый элемент, поэтому, если вы хотите продолжить работу с этим объектом, вы должны уже иметь переменную, ссылающуюся на этот объект.

Удаление элемента из sizer не изменяет экран автоматически. Вы должны вызвать метод Layout(), чтобы обновить отображение.

Вы всегда можете получать ссылку на sizer, содержащий виджет, используя метод GetContainingSizer() класса wx.Window. Метод возвращает None, если виджет не содержится в sizer.

Как sizers управлют размером и выравниванием дочерних виджетов?

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

Когда родительский виджет sizer-а изменяется, sizer в ответ должен изменить размер своих компонентов. По умолчанию виджет в слоте выровнен по левому и по верхнему краям.

Вы можете корректировать поведение при изменении размеров каждого виджета, присваивая определенные значения параметру flag, когда вы добавляете виджет в sizer. На рисунке 11.4 показан результат действая нескольких различных флагов, после того, как пользователь увеличивает размер окна.

Рисунок 11.4

Листинг 11.3 содержит код для рисунка 11.4. Он идентичен предыдущему листингу, за исключением дополнительного словаря значений flags, которые будут применены к виджетам при добавлении. Эти значения показаны полужирным шрифтом.

Листинг 11.3 Grid sizer с флажками для выравнивания и установки размеров

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 flags = {"one": wx.ALIGN_BOTTOM, "two": wx.ALIGN_CENTER, 
   6          "four": wx.ALIGN_RIGHT, "six": wx.EXPAND, "seven": wx.EXPAND,
   7          "eight": wx.SHAPED}
   8 
   9 class TestFrame(wx.Frame):
  10     def __init__(self):
  11         wx.Frame.__init__(self, None, -1, "GridSizer Resizing")
  12         sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
  13         for label in labels:
  14             bw = BlockWindow(self, label=label)
  15             flag = flags.get(label, 0)
  16             sizer.Add(bw, 0, flag)
  17         self.SetSizer(sizer)
  18         self.Fit()
  19 
  20 app = wx.PySimpleApp()
  21 TestFrame().Show()
  22 app.MainLoop()

В этом примере, у виджетов “one,” “two,” и “four” изменен тип выравнивания использованием флагов wx.ALIGN_BOTTOM, wx.ALIGN_CENTER и wx.ALIGN_RIGHT, соответственно. Вы можете видеть, что поведение этих виджетов соответствует их типу выравнивания (поведение виджета “three” соответствует поведению по умолчанию). Виджеты “six” и “seven” используют флаг wx.EXPAND, заставляя sizer изменять их размер так, чтобы заполнить доступное пространство слота, в то время как виджет "eight" использует флаг wx.SHAPED, указывающий, что при изменении должны сохраняться его пропорции.

В таблице 11.2 перечислены возможные значения flag, которые можно использовать для установки размеров и выравнивания.

Таблица 11.2 Флаги размеров и выравнивания

Флаг

Описание

wx.ALIGN_BOTTOM

Выравнивает виджет по нижнему краю слота.

wx.ALIGN_CENTER

Размещает виджет так, чтобы центр виджета находился в центре слота.

wx.ALIGN_CENTER_HORIZONTAL

Размещает виджет в центре по горизонтали.

wx.ALIGN_CENTER_VERTICAL

Размещает виджет в центре по вертикали.

wx.ALIGN_LEFT

Выравнивает виджет по левому краю слота. Установлен по умолчанию.

wx.ALIGN_TOP

Выравнивает виджет по верхнему краю слота. Установлен по умолчанию.

wx.EXPAND

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

wx.FIXED_MINSIZE

Заставляет sizer сохранять минимальный размер элемента.

wx.GROW

То же самое что и wx.EXPAND.

wx.SHAPED

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

Так как флажки – это битовые маски, они могут быть объединены, используя операцию поразрядное ИЛИ (|), в случаях, где такая комбинация имела бы смысл. Так, wx.ALIGN_TOP | wx.ALIGN_RIGHT сохраняет виджет в правом верхнем углу слота. (Отметьте, что взаимно исключающие значения, типа wx.ALIGN_TOP | wx.ALIGN_BOTTOM будут всегда приводится к значению не по умолчанию, потому что значение по умолчанию - целое число 0. Оно не изменяет значение другого операнда в операции поразрядное ИЛИ.)

Есть несколько методов, которые вы можете использовать, чтобы управлять размером и позиционированием sizer или его составляющих виджетов во время выполнения. Вы можете получить текущий размер и позицию sizer, используя методы GetSize() и GetPosition() — позиция относительно контейнера, с которым связан sizer. Это является полезным, если sizer вложен в другой sizer. Вы можете принудительно задать размер sizer, вызвав метод SetDimension(x, y, width, height). После этого, sizer повторно вычислит размер своих дочерних виджетов.

Как задать минимальный размер для sizer и его дочерних виджетов?

Часто, вы не хотите, чтобы элемент управления или sizer стал меньше определенного размера. К счастью wxPython позволяет определить минимальный размер для sizer и его дочерних виджетов.

На рисунке 11.5 показан пример установки минимального размера для определенного виджета. Это окно не было изменено пользователем.

Рисунок 11.5

Листинг 11.4 показывает код для рисунка. Он подобен основному коду сетки с единственным дополнением - вызовом метода SetMinSize().

Листинг 11.4 grid sizer с установкой минимального размера

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 
   6 class TestFrame(wx.Frame):
   7 
   8     def __init__(self):
   9         wx.Frame.__init__(self, None, -1, "GridSizer Test")
  10         sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
  11         for label in labels:
  12             bw = BlockWindow(self, label=label)
  13             sizer.Add(bw, 0, 0)
  14         center = self.FindWindowByName("five")
  15         center.SetMinSize((150,50))
  16         self.SetSizer(sizer)
  17         self.Fit()
  18 
  19 app = wx.PySimpleApp()
  20 TestFrame().Show()
  21 app.MainLoop()

Когда sizer создан, он неявно задает минимальный размер, основанный на объединенном минимальном размере его виджетов. Минимальный размер элемента управления может также быть установлен, используя методы SetMinSize(width, height) и SetSizeHints(minW, minH, maxW, maxH). Последний метод также разрешает определить максимальный размер. Виджет будет обычно корректировать наилучший размер, если изменяются его атрибуты — например, тип шрифта или текст метки.

Вы можете получить минимальный размер для всего sizer, используя метод GetMinSize(). Если вы хотите установить больший минимальный размер sizer, используйте метод SetMinSize(width, height), который вы можете также вызвать с параметром wx.Size, хотя в wxPython редко явно создаются объекты wx.Size. После того, как минимальный размер установлен, метод GetMinSize() возвращает или установленный размер или объединенный размер дочерних виджетов, если он больше.

Если вы хотите только установить минимальный размер определенного дочернего виджета в пределах sizer, используйте метод SetItemMinSize() объекта sizer. Аналогично методу Detach(), есть три способа вызова SetItemMinSize(), с window, sizer или индексом:

   1 SetItemMinSize(window, size)
   2 SetItemMinSize(sizer, size)
   3 SetItemMinSize(index, size)

В этом случае, window или sizer должны быть дочерними объектами sizer. Метод просматривает все вложенное дерево sizers для того, чтобы найти определенное окно или sizer. Параметр index - индекс в списке элементов sizer. Параметр size - объект wx.Size или кортеж (width, height), задающий явный минимальный размер элемента в пределах sizer. Если минимальный размер, больше текущего размера виджета, он автоматически изменяется. Вы не можете установить максимальный размер методом sizer, его можно установить только методом виджета, используя SetSizeHints().

Как sizer управляет границей вокруг каждого элемента?

В wxPython sizer может поместить границу вокруг любого из своих элементов. Граница - пустое пространство, отделяющего виджет от его соседей. Размер границы учитывается, когда sizer вычисляет размещение своих объектов. Размер границы не изменяется, когда sizer изменяет размеры.

На рисунке 11.6 показана границе в 10 пикселей, помещенная вокруг всех или части виджетов в нашем основном размещении сетки. В каждой строке, средний элемент имеет границу вокруг всех четырех сторон, в то время как другие виджеты ограничены не со всех сторон. Добавление границы не делает виджеты меньшими; скорее это делает фрейм большим.

Рисунок 11.6

Листинг 11.5 содержит код для рисунка 11.6. Снова, он подобен основному grid sizer, но теперь мы добавили словарь значений границы и параметр размера границы в 10 пикселей в методе Add().

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 flags = {"one": wx.BOTTOM, "two": wx.ALL, "three": wx.TOP, 
   6          "four": wx.LEFT, "five": wx.ALL, "six": wx.RIGHT, 
   7          "seven": wx.BOTTOM | wx.TOP, "eight": wx.ALL,
   8          "nine": wx.LEFT | wx.RIGHT}
   9 
  10 class TestFrame(wx.Frame):
  11     def __init__(self):
  12         wx.Frame.__init__(self, None, -1, "GridSizer Borders")
  13         sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
  14         for label in labels:
  15             bw = BlockWindow(self, label=label)
  16             flag = flags.get(label, 0)
  17             sizer.Add(bw, 0, flag, 10)
  18         self.SetSizer(sizer)
  19         self.Fit()
  20 
  21 app = wx.PySimpleApp()
  22 TestFrame().Show()
  23 app.MainLoop()

Помещение границы вокруг виджета в sizer - двухступенчатый процесс. На первом шаге нужно передать дополнительные флажки в параметре flags при добавлении виджета в sizer. Вы можете определить границу вокруг всего виджета, используя флаг wx.ALL, или ограничить виджет с определенной стороны, используя wx.BOTTOM, wx.LEFT, wx.RIGHT или wx.TOP. Естественно, флаги могут быть объединены для любого набора сторон, который вам нужен, например, флаг wx.RIGHT | wx.BOTTOM создаст границу справа и внизу виджета. Признаки границы, изменения размеров и выравнивания передают через тот же самый параметр flags. Вы часто будете использовать операцию поразрядное ИЛИ (|), чтобы объединить признаки границы с признаками установки размеров и выравнивания для виджета.

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

   1 sizer.Add(widget, 0, wx.ALL | wx.EXPAND, 5)

При расширении виджет всегда будет оставлять вокруг себя пространство шириной в пять пикселей.

Использование других типов sizer

Рассмотрев основы, мы можем идти дальше к более сложным и гибким типам sizer. Два из них — flex grid sizer и grid bag sizer — являются по существу расширениями темы сетки. Другие два — box и static box sizers — используют различную и более гибкую структуру размещения.

Что такое - flex grid sizer?

Flex grid sizer - более гибкая версия grid sizer, которая почти идентична регулярному grid sizer, со следующими исключениями:

Flex grid sizer определяет отдельный размер для каждой строки и столбца. По умолчанию, размеры ячеек не изменяются. Вы можете определить, какие строки или столбцы должны изменять размеры. Flex grid sizer может изменяться гибко в любом направлении.

Рисунок 11.7 показывает flex grid sizer в действии, используя то же самое размещение с девятью ячейками, которое используется для основного grid sizer. В этом случае, центральная ячейка была сделана большей.

Рисунок 11.7

Сравните это изображение с рисунком 11.5, на котором изображено то же самое размещение в обычном grid sizer. В обычном grid sizer каждая ячейка имеет такой же размер как и средняя. В flex grid sizer, ячейки установлены по размеру согласно строке и столбцу, частью которых они являются. Они берут ширину самого широкого элемента в их столбце и высоту самого высокого элемента в их строке. В этом случае, ячейки для элементов “four” и “six” выше остальных, потому что они находятся в той же самой строке, что и элемент “five”, аналогично ячейки для элементов “two” и “seven” шире остальных. Ячейки для “one”, “three”, “seven” и “nine” - нормального размера, так как не затронуты большим виджетом.

На рисунке 11.8 показано поведению flex grid sizer по умолчанию при изменении размеров окна. Размер ячеек остается неизменным.

Рисунок 11.8

Листинг 11.6 содержит код для рисунка 11.8.

Листинг 11.6 Создание flex grid sizer

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 
   6 class TestFrame(wx.Frame):
   7     def __init__(self):
   8         wx.Frame.__init__(self, None, -1, "FlexGridSizer")
   9         sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
  10         for label in labels:
  11             bw = BlockWindow(self, label=label)
  12             sizer.Add(bw, 0, 0)
  13         center = self.FindWindowByName("five")
  14         center.SetMinSize((150,50))
  15         self.SetSizer(sizer)
  16         self.Fit()
  17 
  18 app = wx.PySimpleApp()
  19 TestFrame().Show()
  20 app.MainLoop()

Flex grid sizer – объект класса wx.FlexGridSizer. Класс wx.FlexGridSizer является производным от wx.GridSizer, таким образом все методы и свойства wx.GridSizer доступны. Конструктор для wx.FlexGridSizer идентичен конструктору родительского класса:

   1 wx.FlexGridSizer(rows, cols, vgap, hgap)

Чтобы размеры строки или столбца изменялись при изменении sizer, вы должны явно указать, что строка или столбец являются изменяемыми (growable), используя соответствующий метод:

   1 AddGrowableCol(idx, proportion=0)
   2 AddGrowableRow(idx, proportion=0)

Когда sizer увеличивается по горизонтали, новая ширина будет распределена одинаково между всеми изменяемыми столбцами. Это поведение по умолчанию. Аналогично, при изменении размера по вертикали, новая высота будет равномерно распределена между всеми изменяемыми строками. Чтобы изменить поведение по умолчанию и сделать так, чтобы строки или столбцы изменялись неравномерно, используйте параметр proportion. Если параметр proportion используется, то новое место будет распределено между строками или столбцами относительно их параметров пропорции. Например, если у вас есть две строки, и их пропорции - 2 и 1, то тогда первая строка получит 2/3 нового места, а вторая строка получит 1/3. Рисунок 11.9 показывает flex grid sizer с пропорциональными интервалами. В этом случае, средняя строка и столбец имеют пропорцию 2, а строки и столбцы в конце имеют пропорцию 1. Рисунок 11.9 показывает на что это похоже.

Рисунок 11.9

Как видите, в то время как все ячейки увеличились, средняя строка и столбец стали вдвое больше других строк и столбцов. Виджеты не изменили размеры, чтобы заполнить ячейки, хотя могли бы это сделать, если бы использовался флаг wx.EXPAND, когда они добавлялись к sizer. Листинг 11.7 показывает код для создания flex grid sizer. Обратите внимание на использование growable-методов.

Листинг 11.7 Flex grid sizer с изменяемыми (growable) элементами

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 
   6 class TestFrame(wx.Frame):
   7     def __init__(self):
   8         wx.Frame.__init__(self, None, -1, "Resizing Flex Grid Sizer")
   9         sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
  10         for label in labels:
  11             bw = BlockWindow(self, label=label)
  12             sizer.Add(bw, 0, 0)
  13         center = self.FindWindowByName("five")
  14         center.SetMinSize((150,50))
  15         sizer.AddGrowableCol(0, 1)
  16         sizer.AddGrowableCol(1, 2)
  17         sizer.AddGrowableCol(2, 1)
  18         sizer.AddGrowableRow(0, 1)
  19         sizer.AddGrowableRow(1, 2)
  20         sizer.AddGrowableRow(2, 1)
  21         self.SetSizer(sizer)
  22         self.Fit()
  23 
  24 app = wx.PySimpleApp()
  25 TestFrame().Show()
  26 app.MainLoop()

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

Есть еще один механизм, чтобы управлять изменением размеров ячейки в flex grid sizer. По умолчанию, пропорциональная установка размеров воздействует на оба направления flex grid (и по вертикали, и по горизонтали); однако, вы можете определить, что только одно направление должно изменить размеры пропорционально, используя метод SetFlexibleDirection (direction), где значения direction - wx.HORIZONTAL, wx.VERTICAL или wx.BOTH (по умолчанию). Затем вы определяете поведение в другом направлении, используя SetNonFlexibleGrowMode(mode). Например, если вы вызываете SetFlexibleDirection (wx.HORIZONTAL), столбцы ведут себя как определено при использовании AddGrowableCol(), и вызов SetNonFlexibleGrowMode() определяет поведение строк. В таблице 11.3 показаны доступные значения для параметра mode.

Таблица 11.3 Non-flexible grow mode values

Режим

Описание

wx.FLEX_GROWMODE_ALL

Flex grid изменяет размеры всех ячеек в направлении non-flexible одинаково. Это отменяет любое поведение, заданное growable-методами — все ячейки изменяются независимо от их пропорции.

wx.FLEX_GROWMODE_NONE

Ячейки в направлении non-flexible не изменяются, независимо от того, были ли они определены как изменяемые.

wx.FLEX_GROWMODE_SPECIFIED

Sizer изменяет только те ячейки в направлении non-flexible, которые были определены как изменяемые. Однако, sizer игнорирует любую информацию о пропорции и изменяет все ячейки одинаково. Это - поведение по умолчанию.

Каждый из методов, обсуждаемых в предыдущем параграфе имеет связанный Get-метод, GetFlexibleDirection() и GetNonFlexibleGrowMode(), который возвращает целое число – значение флага.

Что такое - grid bag sizer?

Grid bag sizer - дальнейшее расширение flex grid sizer. В grid bag sizer есть два новшества.

Способность добавлять виджет в заданную ячейку сетки. Способность иметь виджет, охватывающий несколько ячеек сетки (как ячейка в таблице HTML).

На рисунке 11.10 показан типичный grid bag sizer. Он подобен примеру, который мы использовали всюду в этой главе, с добавлением новых виджетов.

Рисунок 11.10

Листинг 11.8 содержит код для рисунка 11.10. Заметьте, что метод Add() выглядит немного иначе.

Листинг 11.8 Grid bag sizer

   1 import wx
   2 from blockwindow import BlockWindow
   3 
   4 labels = "one two three four five six seven eight nine".split()
   5 
   6 class TestFrame(wx.Frame):
   7     def __init__(self):
   8         wx.Frame.__init__(self, None, -1, "GridBagSizer Test")
   9         sizer = wx.GridBagSizer(hgap=5, vgap=5)
  10         for col in range(3):
  11             for row in range(3):
  12                 bw = BlockWindow(self, label=labels[row*3 + col])
  13                 sizer.Add(bw, pos=(row,col))
  14 
  15         # add a window that spans several rows
  16         bw = BlockWindow(self, label="span 3 rows")
  17         sizer.Add(bw, pos=(0,3), span=(3,1), flag=wx.EXPAND)
  18 
  19         # add a window that spans all columns
  20         bw = BlockWindow(self, label="span all columns")
  21         sizer.Add(bw, pos=(3,0), span=(1,4), flag=wx.EXPAND)
  22 
  23         # make the last row and col be stretchable
  24         sizer.AddGrowableCol(3)
  25         sizer.AddGrowableRow(3)
  26 
  27         self.SetSizer(sizer)
  28         self.Fit()
  29         
  30 
  31 app = wx.PySimpleApp()
  32 TestFrame().Show()
  33 app.MainLoop()

Grid bag sizer – объект класса wx.GridBagSizer, который является производным от wx.FlexGridSizer. Это означает, что можно использовать все методы flex grid sizer, включая добавление изменяемых строк и столбцов.

Конструктор для wx.GridBagSizer немного отличяется от конструктора родительского класса:

   1 wx.GridBagSizer(vgap=0, hgap=0)

Вы не должны определять количество строк и столбцов в grid bag sizer, потому что дочерние виджеты добавляются непосредственно в заданные ячейки сетки, а sizer сам вычисляет размерность сетки.

Перевод: Володеев Сергей