понеділок, 7 вересня 2009 р.

wxRuby → Сайзери (2 частина)

У попередній статті ми познайомилися з позиціонуванням віджетів. А тепер перейдемо до найцікавішого, до огляду сайзерів, які є у бібліотеці wxRuby. І на прикладах побачимо, як впливають на розміщення елементів прапорці з методу Wx::Sizer#add().
У якості прикладу розглянемо код, на основі якого ми будемо експериментувати з сайзерами надалі.
# -*- encoding: utf-8 -*-

require 'wx'

class MainWindow < Wx::Frame
def initialize
super(nil, :id => -1, :title => 'Розмір має значення')
self.size = [300, 200]
self.do_sizer
show
end

def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)
set_sizer(sizer)
end
end

class SimpleApp < Wx::App
def on_init
MainWindow.new
end
end

app = SimpleApp.new
app.main_loop()


У цьому прикладі оголошується клас вікна MainWindow, у конструкторі якого викликається метод do_sizer(). Всередині цього методу у майбутніх прикладах відбуватиметься все найцікавіше, в тому сенсі, що у ньому ми і будемо розміщувати елементи керування. Інший код змінюватись не буде, тому в усіх наступних прикладах буде наводитись тільки цей метод.



У даному прикладі цей метод містить створення порожнього сайзера Wx::BoxSizer (без заповнення його елементами керування), і цей сайзер додається у вікно. Запустивши цей приклад, ви побачите порожнє вікно розміром 300x200 пікселів:



Wx::BoxSizer

Wx::BoxSizer є найпростішим сайзером, який дозволяє розміщувати елементи вздовж однієї лінії - по вертикалі або по горизонталі. Конструктор класу Wx::BoxSizer приймає один параметр, який вказує як потрібно розміщувати елементи: по вертикалі чи по горизонталі. Відповідно параметр orient може приймати два значення: Wx::HORIZONTAL або Wx::VERTICAL. Підкласи Wx::HBoxSizer і Wx::VBoxSizer є посиланнями для створення горизонтального і вертикального Wx::BoxSizer, відповідно. Наступні рядки еквівалентні:


Wx::BoxSizer.new(Wx::HORIZONTAL)

Wx::HBoxSizer.new


Якщо orient = Wx::HORIZONTAL, то сайзер можна уявити у вигляді книжної полиці, розділеної по горизонталі на комірки, всередині яких розміщені елементи керування. При чому розмір кожної комірки рівний ширині елемента, який міститься всередині з урахуванням рамки(border). Якщо при цьому сайзер використовується як основний для вікна(встановлений за допомогою методу set_sizer()), то по вертикалі "полиця" буде займати все батьківське вікно. Якщо сайзер буде знаходитись всередині іншого сайзера (і не буде розтягуватись за допомогою прапорця Wx::EXPAND), то в цьому випадку його розмір по вертикалі буде обмежений елементом з найбільшою висотою.

Якщо orient = Wx::VERTICAL, то сайзер можна уявити у вигляді книжкової шафки, розділеної на горизонтальні полиці. Тепер вже висота кожної "полиці" рівна розміру, розміщеного у ній елемента (разом із рамкою). Якщо сайзер використовується як основний для вікна, то по горизонталі "шафка" займатиме всю ширину вікна. Інакше її ширина дорівнюватиме ширині найширшого елемента керування.
Розглянемо приклади:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add(button1)
sizer.add(button2)

set_sizer(sizer)
end


У цьому прикладі ми створюємо вертикальний Wx::BoxSizer і додаємо у нього дві кнопки(button1 і button2). Для всіх параметрів використовуємо значення до замовчуванню. Результат розміщення кнопок:


Як ми і очікували, кнопки розмістилися по вертикалі одна під одною. Для кращого розуміння роботи сайзера на наступному малюнку червоним прямокутником намальовані комірки сайзера. Підкреслю, що "фізично", ніяких поличок і комірок у вигляді будь-яких панелей не існує, але така абстракція допоможе легше зрозуміти роботу сайзерів.


Як бачите, полиці сайзера зайняли все вікно по горизонталі, і справа є ще вільне місце, яке утворилося завдяки тому, що сайзер є основним для вікна і він намагається зайняти по можливості якомога більше місця. Щоб переконатися у цьому, вирівняємо першу кнопку по правому краю. Для цього будемо використовувати прапорець Wx::ALIGN_RIGHT при додаванні button1.
Зараз ми познайомимося з методом Wx::Sizer#add_item(), який є практично ідентичним до Wx::Sizer#add(), але, на відміну від свого брата, додаткові параметри можуть бути визначеними за допомогою ключових слів у будь-якому порядку, наприклад:
sizer.add_item(an_item, :index => 1, :proportion => 1, :flag => Wx::EXPAND)


  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1, :flag => Wx::ALIGN_RIGHT)
sizer.add_item(button2)

set_sizer(sizer)
end


В результаті виконання скрипта ми побачимо наступне вікно:


Або те саме з намальованим сайзером:


Тепер подивимось, як поводить себе горизонтальний сайзер. Запустимо скрипт з наступним методом, що встановлює розташування елементів керування:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1)
sizer.add_item(button2)

set_sizer(sizer)
end


У результаті отримаємо таке розміщення кнопок:


І те саме з намальованим сайзером:


Щоб знову ж таки переконатися, що сайзер займає по вертикалі весь розмір вікна, вирівняємо першу кнопку по нижньому краю за допомогою прапорця Wx::ALIGN_BOTTOM:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1)
sizer.add_item(button2, :flag => Wx::ALIGN_BOTTOM)

set_sizer(sizer)
end


Результат виконання скрипта (відразу з намальованим сайзером):


Розглянемо відступи навколо елементу. Оточимо другу кнопку межею розміром у 8 пікселів з усіх сторін, а параметр border буде рівний 8:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1)
sizer.add_item(button2, :border => 8, :flag => Wx::ALL)

set_sizer(sizer)
end


Результат виконання скрипта:


І те ж вікно з намальованим сайзером:


А тепер розглянемо приклади, коли всередину одного сайзера додається інший. Для початку розглянемо, як зміниться поведінка сайзера, якщо він буде вкладеним у інший сайзер. Змінимо вищезгаданий приклад з вертикальним сайзером, таким чином, щоб кнопки додавалися не у головний сайзер, а у дочірній, який теж буде вертикальним:
  def do_sizer
parent_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
child_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

child_sizer.add_item(button1, :flag => Wx::ALIGN_RIGHT)
child_sizer.add_item(button2)

parent_sizer.add(child_sizer)

set_sizer(parent_sizer)
end


У цьому прикладі для змінної parent_sizer ми використовуємо метод add(), який приймає не елемент керування, а інший сайзер. В результаті виконання скрипта ми побачимо, що не дивлячись на те, що першу кнопку ми вирівнюємо по правому краю. Вона залишається зліва, так як, на відміну від головного сайзера, дочірній не розтягується на всю ширину вікна.


На наступному малюнку батьківський сайзер намальований червоним кольором, а дочірній - синім:


Щоб дочірній сайзер по розмірах співпадав з головним, достатньо його розтягнути, вказавши прапорець Wx::EXPAND:
  def do_sizer
parent_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
child_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

child_sizer.add_item(button1, :flag => Wx::ALIGN_RIGHT)
child_sizer.add_item(button2)

parent_sizer.add(child_sizer)

set_sizer(parent_sizer)
end


В результаті ми побачимо вже знайоме нам розміщення кнопок у вікні:


Цікавим може виявитися приклад, який показує, що вкладений сайзер по поведінці нічим не відрізняється від вкладеного елементу керування. У наступному прикладі вкладений сайзер вирівнюється по правому краю разом з усіма кнопками, які він містить:
  def do_sizer
parent_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
child_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

child_sizer.add_item(button1)
child_sizer.add_item(button2)

parent_sizer.add_item(child_sizer, :flag => Wx::ALIGN_RIGHT)

set_sizer(parent_sizer)
end


Результат виконання скрипта:


І те ж вікно з намальованими сайзерами:


У наступному прикладі чотири кнопки розташовуються в два рядки і в два стовпці:
  def do_sizer
vert_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

hor1_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
hor2_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

vert_sizer.add(hor1_sizer)
vert_sizer.add(hor2_sizer)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')
button3 = Wx::Button.new(self, :label => 'Button 3')
button4 = Wx::Button.new(self, :label => 'Button 4')

hor1_sizer.add(button1)
hor1_sizer.add(button2)

hor2_sizer.add(button3)
hor2_sizer.add(button4)

set_sizer(vert_sizer)
end


Тут структура трохи складніша. По-перше, ми створюємо головний вертикальний сайзер, який буде прив'язаний до вікна і, відповідно, розтягнутий на всю ширину вікна (тому що сайзер вертикальний). В коді він називається vert_sizer. Він у нас буде відігравати роль горизонтальних полиць, на кожній з яких може розташовуватись тільки один елемент. Цим елементом у нас буде інший сайзер, вже горизонтальний. На першу полицю ми покладемо сайзер hor1_sizer, а на другу - hor2_sizer. Ці горизонтальні сайзери будуть містити в собі кнопки, кожен сайзер по дві кнопки. hor1_sizer містить кнопки button1 і button2, а hor2_sizer - button3 і button4.
Результат виконання скрипта:


Те ж вікно із намальованими сайзерами:


Тут червоним кольором намальований вертикальний сайзер vert_sizer, а синім і зеленим - горизонтальні сайзери, відповідно, hor1_sizer і hor2_sizer.
Як видно з малюнка, вертикальний червоний сайзер займає всю ширину вікна, але кожна його комірка прагне зайняти якомога менше місця по вертикалі (при умові, що параметр proportion методу add() рівний 0). Саме цим розміром і обмежується вертикальний розмір вкладених сайзерів, які, навпаки, намагаються по можливості зменшити свої горизонтальні розміри (при тій же умові), але зайняти якомога більше місця по вертикалі.

А тепер розглянемо простий приклад, який показує як одні комірки впливають на розміри інших комірок того ж сайзера.
Змінимо попередній приклад і додамо навколо другої кнопки відступи:
  def do_sizer
vert_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

hor1_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
hor2_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

vert_sizer.add(hor1_sizer)
vert_sizer.add(hor2_sizer)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')
button3 = Wx::Button.new(self, :label => 'Button 3')
button4 = Wx::Button.new(self, :label => 'Button 4')

hor1_sizer.add(button1)
hor1_sizer.add_item(button2, :flag => Wx::ALL, :border => 8)

hor2_sizer.add(button3)
hor2_sizer.add(button4)

set_sizer(vert_sizer)
end


Результат виконання скрипта виглядає наступним чином:


І те ж саме з намальованими сайзерами:


Як видно з малюнку, кнопка "Button 2" збільшила висоту і першої комірки сайзера hor1_sizer. Тобто, в горизонтального сайзера Wx::BoxSizer всі комірки мають однакову висоту. Аналогічний експеримент можна провести з вертикальним сайзером, у нього всі комірки будуть мати однакову ширину.

Наступний приклад буде показувати використання прапорця Wx::EXPAND, який, нагадаю, вказує, що елемент керування повинен займати все вдоступне місце у комірці. Заодно цей приклад допоможе нам переконатися, що розмір першої комірки hor1_sizer дійсно займає все вільне місце. Отже, виправимо трохи попередній приклад:
  def do_sizer
vert_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

hor1_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
hor2_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

vert_sizer.add(hor1_sizer)
vert_sizer.add(hor2_sizer)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')
button3 = Wx::Button.new(self, :label => 'Button 3')
button4 = Wx::Button.new(self, :label => 'Button 4')

hor1_sizer.add_item(button1, :flag => Wx::EXPAND)
hor1_sizer.add_item(button2, :flag => Wx::ALL, :border => 8)

hor2_sizer.add(button3)
hor2_sizer.add(button4)

set_sizer(vert_sizer)
end


Після запуску скрипта ми побачимо наступне розміщення кнопок у вікні:


І те ж вікно з намальованими сайзерами:


Як ми і очікували кнопка "Button 1" розтягнулася на всю комірку.

Повернемося до методу add() класу Wx::Sizer. Як ви пам'ятаєте, він має один необов'язковий параметр proportion, який визначає у яких співвідношеннях елементи керування одного сайзеру повинні займати вільне місце.
До цього усюди ми неявно використовували значення, прийняте по замовчуванню, тобто proportion = 0. У цьому випадку вертикальний сайзер намагається якомога більше зменшити висоту своїх комірок. Якщо значення параметру proportion відмінне від 0, то поведінка сайзера різко змінюється. В цьому випадку вертикальний сайзер буде займати все вільне місце по вертикалі, а висота кожної комірки буде розраховуватись виходячи з відповідних значень параметра proportion.

Розглянемо приклад з однією кнопкою:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')

sizer.add_item(button1, :proportion => 1)

set_sizer(sizer)
end


Розміщення елементів у вікні виглядає наступним чином:


Тепер єдина комірка сайзера у цьому випадку займає все вікно:


По горизонталі вона займає все вікно, тому що вертикальний сайзер намагається зайняти якомога більше місця по ширині, а по вертикалі - тому що значення параметра proportion відмінне від нуля.

А тепер додамо у той же вертикальний сайзер другу кнопку також із значенням параметра proportion = 1 :
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1, :proportion => 1)
sizer.add_item(button2, :proportion => 1)

set_sizer(sizer)
end


Вікно з таким розміщенням кнопок буде виглядати наступним чином:


Якщо змінити розміри вікна, то висота кнопок збільшиться на однакову кількість пікселів:


Тобто співвідношення між висотами кнопок завжди рівне 1:1, у відповідності з значеннями параметрів proportion.
Змінимо попередній приклад таким чином, щоб висота верхньої кнопки завжди була вдвічі більша нижньої. Для цього встановимо значення proportion першої кнопки рівне 2:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1, :proportion => 2)
sizer.add_item(button2, :proportion => 1)

set_sizer(sizer)
end


У результаті отримаємо:


Нам залишилося розглянути ще два прапорці, які ми не використовували до цього. Один з них це Wx::SHAPED, який вказує, що при зміні розмірів елемент повинен зберігати вихідні співвідношення сторін. Напишемо код для вікна з двома кнопками, одна з яких буде додана в сайзер із використанням прапорця Wx::SHAPED. Крім цього ми будемо використовувати значення параметру proportion = 1, щоб розмір кнопок змінювався при зміні розмірів вікна.
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')

sizer.add_item(button1, :proportion => 1)
sizer.add_item(button2, :proportion => 1, :flag => Wx::SHAPED)

set_sizer(sizer)
end


Результат виконання скрипта:


Якщо змінити розмір вікна, то друга кнопка зменшить не тільки свою висоту, але й ширину, щоб відношення сторін залишилось попереднім:


І коротко розглянемо прапорець Wx::FIXER_MINSIZE. Цей прапорець має специфічне призначення. При використанні цього прапорця сайзер не буде враховувати мінімальний розмір елементу, який задається за допомогою методу set_min_size(), а буде використовувати мінімальний розмір, встановлений для деяких елементів.
Нехай у нас є кнопка, для якої встановлений мінімальний розмір 200х35:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button1.set_min_size([200, 35])

sizer.add_item(button1)

set_sizer(sizer)
end


Тоді ми побачимо наступне розміщення елементів:


Встановивши прапорець Wx::FIXED_MINSIZE, кнопка змінить розмір до звичних значень:
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button1.set_min_size([200, 35])

sizer.add_item(button1, :flag => Wx::FIXED_MINSIZE)

set_sizer(sizer)
end




Ми розглянули як в комірки сайзера можна додавати елементи керування та інші сайзери. Залишилось розглянути, як додавати порожні комірки. Достатньо у якості перших двох параметрів методу add() передати ширину і висоту, тобто розміри невидимого елементу керування, розмір якого і буде визначати розміри комірки. Розглянемо приклад:
  def do_sizer
vert_sizer = Wx::BoxSizer.new(Wx::VERTICAL)

hor1_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
hor2_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

vert_sizer.add(hor1_sizer)
vert_sizer.add(10, 100)
vert_sizer.add(hor2_sizer)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')
button3 = Wx::Button.new(self, :label => 'Button 3')
button4 = Wx::Button.new(self, :label => 'Button 4')

hor1_sizer.add(button1)
hor1_sizer.add(100, 10)
hor1_sizer.add(button2)

hor2_sizer.add(button3)
hor2_sizer.add(100, 10)
hor2_sizer.add(button4)

set_sizer(vert_sizer)
end


Тут між рядками вертикального сайзера, який містить кнопки, ми додаємо порожній рядок, розмір якого задається шириною і висотою у пікселях. В нашому випадку горизонтальний розмір не впливає, так як він менший ширини вікна. А ось вертикальний розмір розсуне полиці з кнопками на 100 пікселів. Аналогічно між комірками горизонтального сайзера додане порожнє місце 100х10, яке розсуне по горизонталі кнопки, але в нашому випадку не буде впливати на вертикальний розмір, так як розмір 10 пікселів буде меншим, ніж вертикальний розмір кнопок. Вікна з таким розміщенням елементів керування виглядає наступним чином:


І той же малюнок з намальованими сайзерами:


Крім того в класі Wx::Sizer є допоміжний метод add_spacer(), який дозволяє спростити додавання порожніх комірок, у яких ширина рівна висоті.
add_spacer(Integer size)


В якості параметра задається сторона квадрата. Наступні два рядки рівносильні:
sizer.add(20, 20)

sizer.add_spacer(20)


Також ви можете вставити порожній простір між елементами (наприклад між двома кнопками) за допомогою методу add_stretch_spacer(), і змусити цей простір розтягуватись за допомогою параметру proportion. У результаті ліва кнопка буде притиснена до лівої стінки сайзера, а права - до правої, а простір між ними буде тягучим під час зміни розмірів вікна.
  def do_sizer
sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)

button1 = Wx::Button.new(self, :label => 'Button 1')
button2 = Wx::Button.new(self, :label => 'Button 2')
button3 = Wx::Button.new(self, :label => 'Button 3')

sizer.add(button1)
sizer.add_stretch_spacer
sizer.add(button2)
sizer.add(button3)

set_sizer(sizer)
end


Результат виконання скрипта:


І той же малюнок із намальованим сайзером:


Якщо змінити розмір вікна, то перша кнопка залишиться притисненою до лівого краю, а друга і третя - до правого:


Ось ми і розглянули всі можливі прапорці і параметри, які використовуються для додавання елементів у сайзер. Заодно і познайомилися з сайзером Wx::BoxSizer. У наступній статті ми оглянемо інші сайзерами, які є у wxRuby.

На все добре і успіхів.

Немає коментарів: