Программирование на языке Ruby - Хэл Фултон
Шрифт:
Интервал:
Закладка:
В самом конце мы создаем экземпляр класса Qt::HBoxLayout. При добавлении виджетов в этот контейнер он автоматически изменяет их размеры, так что нам больше не о чем беспокоиться.
12.4.4. Текстовые поля
Как видно из листинга 12.14, в QtRuby есть класс Qt::LineEdit для ввода одной строки текста. Для ввода нескольких строк предназначен класс Qt::TextEdit.
В листинге 12.15 демонстрируется многострочное текстовое поле. Под ним расположена метка, в которой отображается текущая длина текста (рис. 12.8).
Листинг 12.15. Простой редактор в Qtrequire 'Qt'
class MyTextWindow < Qt::Widget
slots 'theTextChanged()'
def initialize(parent = nil)
super(parent)
@textedit = Qt::TextEdit.new(self)
@textedit.setWordWrapMode(Qt::TextOption::WordWrap)
@textedit.setFont( Qt::Font.new("Times", 24) )
@status = Qt::Label.new(self)
box = Qt::VBoxLayout.new
box.addWidget(@textedit)
box.addWidget(@status)
setLayout(box)
@textedit.insertPlainText("This really is an editor")
connect(@textedit, SIGNAL('textChanged()'),
self, SLOT('theTextChanged()'))
end
def theTextChanged
text = "Length: " + @textedit.toPlainText.length.to_s
@status.setText(text)
end
end
app = Qt:Application.new(ARGV)
widget = MyTextWindow.new
widget.setWindowTitle("QtRuby Text Editor")
widget.show
app.exec
Рис. 12.8. Простой редактор в Qt
Виджет конструируется примерно так же, как в предыдущем примере. Но теперь мы создаем объект Qt::TextEdit, а также метку Qt::Label для показа текущего состояния.
Стоит отметить, что для объекта @textedit мы указали шрифт Times высотой 24 пункта. У каждого класса, наследующего Qt::Widget (в том числе и у Qt::TextEdit) есть свойство font, которое можно опросить или установить.
Затем мы создаем менеджер вертикального размещения (Qt::QBoxLayout), который будет контейнером для всех своих потомков, добавляем в него виджет @textedit и связываем сигнал textChanged с определенным нами слотом theTextChanged.
В методе theTextChanged мы запрашиваем у редактора текст и получаем его длину, а затем записываем возвращенное значение в метку @status.
Отметим, что весь механизм сигналов и слотов работает асинхронно. После того как приложение входит в цикл обработки событий (арр.ехес), оно уже не получает управления явно. Вот почему сигналы и слоты так важны. Мы определяем события, которые нас интересуют (сигналы), и действия, которые нужно выполнить при возникновении таких событий (слоты).
12.4.5. Прочие виджеты
В библиотеке Qt есть еще много встроенных виджетов, например переключатели, флажки и т.п. В листинге 12.16 продемонстрированы некоторые из них, а на рис. 12.9 показано, как выглядит окно приложения.
Листинг 12.16. Прочие виджеты в Qtrequire 'Qt'
class MyWindow < Qt::Widget
slots 'somethingClicked(QAbstractButton *)'
def initialize(parent = nil)
super(parent)
groupbox = Qt::GroupBox.new("Some Radio Button",self)
radio1 = Qt::RadioButton.new("Radio Button 1", groupbox)
radio2 = Qt::RadioButton.new("Radio Button 2", groupbox)
check1 = Qt::CheckBox.new("Check Box 1", groupbox)
vbox = Qt::QBoxLayout.new
vbox.addWidget(radio1)
vbox.addWidget(radio2)
vbox.addWidget(check1)
groupbox.setLayout(vbox)
bg = Qt::ButtonGroup.new(self)
bg.addButton(radio1)
bg.addButton(radio2)
bg.addButton(check1)
connect(bg, SIGNAL('buttonClicked(QAbscractButton *)'),
self, SLOT('somethingClicked(QAbstractButton *)') )
@label = Qt::Label.new(self)
vbox = Qt::VBoxLayout.new
vbox.addWidget(groupbox)
vbox.addWidget(@label)
setLayout(vbox)
end
def somethingClicked(who)
@label.setText("You clicked on a " + who.className)
end
end
app = Qt::Application.new(ARGV)
widget = MyWindow.new
widget.show
app.exec
Рис. 12.9. Простое приложение Tk
В этом классе мы сначала создаем объект Qt::GroupBox — контейнер с рамкой и необязательным заголовком, в который можно помещать другие виджеты. Далее создаются два переключателя Qt::RadioButtons и флажок Qt::CheckBox, а в качестве их родителя указывается ранее созданный контейнер.
Затем создается менеджер размещения Qt::VBoxLayout, в который помещаются переключатели и флажок, после чего этот менеджер связывается с групповым контейнером и начинает управлять его размещением на экране.
Следующий важный шаг — создание объекта Qt::ButtonGroup, в который помещаются флажок и переключатели. Qt::ButtonGroup предназначен для логической группировки кнопок, флажков и переключателей. На их визуальное расположение он никак не влияет, зато обеспечивает, к примеру, взаимное исключение (гарантирует, что только один из группы виджетов может быть отмечен). В данном случае этот объект будет источником сигнала buttonClicked, который испускается при нажатии любой кнопки в группе.
Этот сигнал отличается от виденных ранее тем, что ему сопутствует аргумент, а именно объект, по которому щелкнули мышкой. Обратите внимание на то, как синтаксис — QAbstractButton* — напоминает о C++-ных корнях Qt. В некоторых случаях употребления принятой в C++ нотации для обозначения типов параметров не избежать (хотя в будущих версиях это, возможно, и исправят).
В результате такого вызова метода connect при щелчке по любому виджету, принадлежащему группе, этот виджет будет передан слоту somethingClicked. Наконец, мы создаем метку Qt::Label, контейнер Qt::QBoxLayout и увязываем все вместе.
Внутри слота somethingClicked мы модифицируем текст метки при щелчке по любому переключателю или флажку. В данном случае выводится имя класса объекта, который испустил сигнал, приведший к вызову слота.
Если встроенных виджетов недостаточно, то Qt предоставляет мощную систему рисования для создания собственных. В листинге 12.17 приведен небольшой пример, иллюстрирующий малую часть возможностей.
Листинг 12.17. Нестандартный виджет TimerClockrequire 'Qt'
class TimerClock < Qt::Widget
def initialize(parent = nil)
super(parent)
@timer = Qt::Timer.new(self)
connect(@timer, SIGNAL('timeout()'), self, SLOT('update()'))
@timer.start(25)
setWindowTitle('Stop Watch')
resize(200, 200)
end
def paintEvent(e)
fastHand = Qt::Polygon.new([Qt::Point.new(7, 8),
Qt::Point.new(-7, 8),
Qt::Point.new(0, -80)])
secondHand = Qt::Polygon.new([Qt::Point.new(7, 8),
Qt::Point.new(-7, 8),
Qt::Point.new(0, -65)])
secondColor = Qt::Color.new(100, 0, 100)
fastColor = Qt::Color.new(0, 150, 150, 150)
side = [width, height].min
time = Qt::Time.currentTime
painter = Qt::Painter.new(self)
painter.renderHint = Qt::Painter::Antialiasing
painter.translate(width() / 2, height() / 2)
painter.scale(side / 200.0, side / 200.0)
painter.pen = Qt::NoPen
painter.brush = Qt::Brush.new(secondColor)
painter.save
painter.rotate(6.0 * time.second)
painter.drawConvexPolygon(secondHand)
painter.restore
painter.pen = secondColor
(0...12).each do |i|
painter.drawLine(88, 0, 96, 0)
painter.rotate(30.0)
end
painter.pen = Qt::NoPen
painter.brush = Qt::Brush.new(fastColor)
painter.save
painter.rotate(36.0 * (time.msec / 100.0))
painter.drawConvexPolygon(fastHand)
painter.restore
painter.pen = fastColor
(0...60).each do |j|
if (j % 5) != 0
painter.drawLine(92, 0, 96, 0)
end
painter.rotate(6.0)
end
painter.end
end
end
app = Qt::Application.new(ARGV)
wid = TimerClock.new
wid.show
app.exec
Созданный в этом примере виджет называется TimerClock. В инициализаторе мы создаем объект Qt::Timer, который конфигурируется для периодического испускания сигнала. Его сигнал timeout мы соединяем со слотом update нашего виджета. Это встроенный слот, он заставляет виджет перерисовать себя.
Таймер запускается методом start. Переданный ему аргумент говорит, что таймер должен срабатывать (и испускать сигнал timeout) каждые 25 миллисекунд. Следовательно, слот update будет вызываться каждые 25 миллисекунд.
Далее определяется метод paintEvent. Мы переопределяем одноименный метод класса Qt::Widget. Когда виджет собирается перерисовать себя (то есть при срабатывании таймера), он вызывает этот метод. Переопределяя его, мы решаем, как виджет должен отображаться на экране. Код этого метода вызывает различные графические примитивы рисования.