Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Программирование с PyQt4 
:(
    Опции темы
Artemios
Дата 15.6.2007, 20:22 (ссылка) |    (голосов:12) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



Воодушевленный примером GrayCardinal, решил продолжить введение в Qt4, но на новых "рельсах", а именно Python+PyQt4.

Программирование на Python с использованием PyQt4

Установка.

Так как PyQt - привязка библиотеки Qt к Python-у, то в первую очередь нам требуется наличие самой Qt 4.x.

Под Linux большинство дистрибутивов включают Qt4 в стандартную поставку, при отсутствии же можно поискать в соответствующих для данной системы репозитариях. Альтернативный вариант: скачать с Trolltech-а исходники и собрать библиотеку самому. В последнем случае у меня проблем не возникало, стандартная последовательность действий:
./configure
make
sudo make install
ну и при желании в configure задать нужные ключи.

ОС Windows. Для сей операционной системы существуют варианты Qt4 под разные компиляторы, но свободной лицензией (GPL) обладает только Qt для компилятора MinGW (это реализация GNU C/C++ под Windows). Поэтому, в первую очередь качаем и ставим MinGW, в системной переменной PATH прописываем путь до директории MinGW/bin. Далее можно с Trolltech-а скачать либо исходники последней версии Qt и собрать самому (под Win я этим не занимался, поэтому здесь не советчик), либо скачать и поставить уже откомпилированную библиотеку.

Итак, мы являемся счастливыми обладателями Qt4 и на C++ уже можем ваять, но мы любим Python. Сие предполагает, что в нашей системе уже установлен интерпретатор Python-а, и нам осталось только воткнуть в него привязку PyQt4.

ОС Linux. Пока что PyQt для Qt4 предустановленной видел только в Sabayon Linux, в других линуксах (правда я их совсем не много видел) либо вообще не находил пакетов с PyQt, либо была привязка PyQt к старой библиотеке Qt3 (хотя сейчас ситуация может уже улучшилась). Итак, будем собирать из исходников (фраза не для пользователей Gentoo smile ). Для этого во-первых необходимо, чтобы в нашей операционной системе кроме собственно Python-а были еще поставлены его dev-пакеты (пакеты для разработчика). Ну или альтернативный вариант: Python также собран из исходников smile
Последовательность действий:
качаем от разработчика исходники последней версии SIP
- распаковываем
- python ./configure.py
- make
- sudo make install
качаем от разработчика исходники последней версии PyQt4
- распаковываем
- python ./configure.py
- make
- sudo make install

ОС Windows. На данный момент мы уже имеем MinGW, Qt4, Python. Качаем от разработчика уже собранную под win32 последнюю версию PyQt4, ставим, радуемся жизни.


Привет мир.

Итак, мы уже имеем рабочий вариант PyQt4 и дальнейшие наши программы не зависят от конкретной системы, одинаково запускаясь везде, где есть PyQt. В Qt имеется симпатичный набор примеров на C++ по использованию библиотеки, и в PyQt те же примеры переписаны на Python-е (еще одна демонстрация лаконичности и красоты синтаксиса Python при использовании идентичных классов/методов/объектов). Плюс обширная документация Qt по всем классам, и почти зеркальное ее отражение в документации по PyQt - все это существенным образом уменьшает ценность моих здесь стараний, но я продолжу.

Самый элементарный пример, демонстрирующий создание приложения Qt и вывод окна на экран:
Код

# импортируем необходимые модули:
import sys
from PyQt4 import QtGui

if __name__=="__main__":
    # создаем объект Qt-приложения, передаем его конструктору
    # параметры командной строки:
    app = QtGui.QApplication(sys.argv)
    # создаем объект класса QLabel (метка), в конструкторе задаем подпись для метки:
    label = QtGui.QLabel("Hello World!\n\tThis is the very simple example for PyQt4.")
    # показываем метку:
    label.show()
    # запускаем цикл обработки событий, происходящих с элементами приложения:
    sys.exit(app.exec_())

После запуска программы (в консоли линукса $ python ./helloworld01.py ,в windows можно двойным кликом мыши по *.py файлу) увидим такое окошко:
user posted image
Заметим, что здесь мы не создавали отдельно окно, как контейнер для метки. Любой графический объект Qt - это виджет (и наследуется от QWidget), и если он не привязан к какому-либо контейнеру, то отображается в отдельном окошке.

Если мы хотим использовать русские подписи, то простейшим вариантом будет использование юникода (префикс u перед записью строки):
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    label = QtGui.QLabel(u"Привет мир!\n\tЭто простейший пример для PyQt4.")
    label.show()
    sys.exit(app.exec_())

user posted image
Итак, мы можем создавать элементарное окошко и даже задавать элементарное форматирование для надписей через escape-символы (например \n - новая строка, \t - табуляция). Qt позволяет на нашем примере построить и более красивое оформление через поддержку простых элементов HTML-форматирования:
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    label = QtGui.QLabel(u"""<center><h1>Привет мир!</h1></center>
        <h2>Это <i>простейший пример</i> для <font color=red>PyQt4</font>.</h2>""")
    label.show()
    sys.exit(app.exec_())

user posted image
и окно той же самой программы, запущенной без каких либо изменений в ОС Windows:
user posted image

(На самом деле, Qt поддерживает даже CSS для своих виджетов, но об этом позднее)

Сигналы/слоты - теория.
(раздел не обязателен для прочтения, но желателен, для лучшего понимания внутреннего механизма)

Qt реализует более правильную идеологию ООП, нежели иные виденные мной GUI-библиотеки. Что у нас в теории ООП (и даже лучше сказать, не ОО Программирования, а ОО Проектирования) сказано про взаимодействие объектов? Правильно, объекты посылают сообщения (сигналы), объекты принимают сообщения, объекты выполняют операции (методы) и меняют состояние (а также испускают новые сигналы) в зависимости от принятых сообщений. В языке C++ подобная идеология не была реализована в полной мере (по крайней мере до появления Boost.Signals), потому Qt представляет надстройку над синтаксисом C++. Отсюда следует необходимость использования специального препроцессора (moc) для Qt-программы, дабы получить компилирующийся C++ код. 
Язык же Python интерпретируемый, динамически типизируемый и легко расширяемый, что дает нам возможность избежать "мучений" с препроцессором. PyQt предоставляет расширение языка, позволяющее использовать механизм сигнал/слотов Qt как "родной" для Python-а.

Итак, объект может испускать произвольное количество сигналов.
Каждый сигнал имеет свою сигнатуру: имя сигнала и типы передаваемых этим сообщением (сигналом) данных.
Объект может принимать произвольное количество сообщений от других объектов.
Механизм принятия сообщения реализуется через соединение (connect).
В соединении участвуют: 
- объект, отсылающий сигнал;
- сигнал объекта-отправителя;
- объект, принимающий сигнал;
- метод объекта-приемника (слот), выполняемый при получении сигнала.
Сигнатура (порядок и типы) входных аргументов слота должна совпадать с сигнатурой передаваемых сигналом данных.
- Вместо слота последним участником соединения может быть сигнал объекта-приемника, который приемник испускает при получении сигнала от отправителя, сигнатура параметров сигналов естественно также должна совпадать.

С одним сигналом может быть соединено несколько слотов и/или сигналов.
Один слот может быть соединен с несколькими сигналами.
Одно и то же соединение можно создать в нескольких экземплярах, тогда один сигнал будет инициировать соответственно многократное выполнение слота либо многократную генерацию нового сигнала.

Все классы Qt являются наследниками класса QObject. Класс QObject в PyQt имеет метод connect, который и осуществляет соединение сигнал-слот или сигнал-сигнал. Также класс QObject имеет метод emit, генерирующий сигнал от объекта данного класса.
Пример:
Код

# -*- coding: utf-8 -*-
from PyQt4.QtCore import QObject, SIGNAL

class A:
    # метод, который будем использовать в качестве слота:
    def main_slot(self,text):
        print "Объектом класса A получено сообщение с текстом:",text

class B(QObject):
    def send_signal(self,text):
        print "Из объекта класса B отправляется сообщение с текстом:",text
        self.emit( SIGNAL("main_signal(PyQt_PyObject)"), text )

if __name__=="__main__":
    a = A()
    b = B()
    # а вот, собственно, и соединение (Источник,Сигнал,СлотПриемника):
    QObject.connect( b, SIGNAL("main_signal(PyQt_PyObject)"), a.main_slot )
    # и теперь пошлем сигнал:
    b.send_signal("Траляля")

и выполнение:
Цитата

$ python ./signals01.py
Из объекта класса B отправляется сообщение с текстом: Траляля
Объектом класса A получено сообщение с текстом: Траляля
$

Примечание: в Python-е любые функции (а соответственно, и методы) являются полноценными объектами 1-го класса (в Python-е вообще всё является объектом), поэтому неимеет смысла в соединении указывать отдельно объект-приемник и отдельно его слот (т.к. слот также является объектом и имеет ссылку на объект, для которого он выступает методом). Отсюда отличие синтаксиса соединения Python/PyQt от C++/Qt:
(Источник,Сигнал,СлотПриемника) для Python-а и (Источник,Сигнал,Приемник,Слот) для C++.
С тем же свойством Python-овских функций связано еще одно отличие: в качестве СлотаПриемника может выступать и простая функция, не являющаяся методом какого-либо класса (объект-приемник и слот в одном лице smile ).

Для совместимости же с откомпилированными методами (слотами) классов библиотеки Qt (которые написаны на C++ и соответственно не являются объектами 1-го класса) в PyQt оставлен и 4-х аргументный вариант соединения.

Варианты соединений между Qt-шными и Python-овскими сигналами и слотами для случая сигнала без параметров:
Код

QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("qtSig()"), pyFunction_trg )
QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("qtSig()"), pyObj_trg.pyMethod )
QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("qtSig()"), qtObj_trg, QtCore.SLOT("qtSlot()") )
QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("pySig"),   pyFunction_trg )
QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("pySig"),   pyObj_trg.pyMethod )
QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("pySig()"), qtObj_trg, QtCore.SLOT("qtSlot()") )

Здесь:
qtObj_src - объект-источник сигнала, является экземпляром класса-потомка класса QObject, наследование может быть как реализованное в Qt (C++), так и как наследование Python-класса от Qt-класса;
qtSig() - сигнал источника, определенный в Qt;
pySig() - сигнал источника, определенный в Python-е;
pyFunction_trg - приемник и слот в одном лице - произвольная Python-функция;
pyObj_trg и pyMethod - приемник и его слот - произвольный Python-объект и его метод
qtObj_trg - приемник, как и qtObj_src экземпляр потомка от QObject; qtSlot() - его слот, определенный в Qt

Разрыв соединения достигается использованием метода disconnect с теми же аргументами, что использовались для connect. Допишем в конце предыдущего примера после
Код

    b.send_signal("Траляля")

следующие строки: 
Код

    QObject.disconnect( b, SIGNAL("main_signal(PyQt_PyObject)"), a.main_slot )
    # а теперь сигнал будет послан, но никем не принят:
    b.send_signal("Ту-ту!")

и выполним программу:
Цитата

$ python ./signals02.py
Из объекта класса B отправляется сообщение с текстом: Траляля
Объектом класса A получено сообщение с текстом: Траляля
Из объекта класса B отправляется сообщение с текстом: Ту-ту!
$


Соединение вида сигнал-сигнал объявляется таким образом:
Код

QtCore.QObject.connect( qtObj_src, QtCore.SIGNAL("anySig1"), qtObj_trg, QtCore.SIGNAL("anySig2") )

где и источник, и "приемо-передатчик" - оба экземпляры C++ или Python потомков класса QObject,
anySig1 и anySig2 - произвольные сигналы (либо определенные в Qt, либо в Python-программе по месту использования).
Для примера построим последовательную цепь соединений вида
user posted image
Код

# -*- coding: utf-8 -*-
from PyQt4.QtCore import QObject, SIGNAL

class A:
    def main_slot(self):
        print "Объектом класса A получен сигнал"

class B(QObject):
    pass

class C(QObject):
    def send_signal(self):
        print "Из объекта класса C отправляется сигнал"
        self.emit(SIGNAL("signal01()"))

if __name__=="__main__":
    a = A()
    b = B()
    c = C()
    QObject.connect( c, SIGNAL("signal01()"), b, SIGNAL("signal02()") )
    QObject.connect( b, SIGNAL("signal02()"), a.main_slot )
    c.send_signal()

Цитата

$ python ./sig2sig.py
Из объекта класса C отправляется сигнал
Объектом класса A получен сигнал
$


Знакомым с программированием на Qt/C++ после приведенных примеров очевидно явное отличие. В Qt/C++ необходимо было методы, являющиеся слотами, описывать в отдельных секциях, public slots например, а если программист неудовлетворялся набором стандартных сигналов класса-предка, от которого он наследовался, то каждый новый сигнал описывался в секции signals . Препроцессор же потом переводил эти объявления в пригодные для компилирования конструкции языка C++. Следствием динамической типизации Python-а является не только то, что мы можем динамически назначить слотом произвольно выбранный метод/функцию или любой иной объект с методом __call__(), но и то, что динамически можем определять для объекта новые сигналы, о которых становится известно только на этапе выполнения connect.


Сигналы/слоты - GUI-шная практика.
На самом деле все не так страшно, как может показаться прочитавшему предыдущий раздел, и сейчас мы займемся вещами более наглядными smile

Все графические объекты библиотеки Qt имеют наборы сигналов, которые они испускают при при наступлении некоторых внешних (например клик мыши) либо внутренних (например таймер) событий. Кроме того, сигналы объектами испускаются при вызове методов, изменяющих состояние объектов. В данных, передаваемых сигналами, обычно содержится информация о произошедших изменениях в состоянии объекта. Проиллюстрируем на примерах.
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    button = QtGui.QPushButton(u"Выход")
    button.setFont( QtGui.QFont("Arial", 16, QtGui.QFont.Bold) )
    QtCore.QObject.connect( button, QtCore.SIGNAL("clicked()"), app, QtCore.SLOT("quit()") )
    button.show()
    sys.exit(app.exec_())

user posted image
Здесь, как видно, мы связали сигнал clicked() кнопки со слотом quit() объекта приложения, по названиям не трудно догадаться и о значении этих сигнала и слота. Связали не-Python-овские сигнал и слот, потому использована четырёхаргументная запись соединения.

Другой пример. Создадим в окне два расположенных горизонтально объекта: SpinBox и Slider (ползунок), с помощью которых мы будем выбирать свой возраст. Создадим между ними двустороннее соединение, то есть два соединения, при которых изменение одного объекта будет сразу автоматически изменять и другой объект. И добавим Python-овский слот для отображения изменений в консоли.
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui

class AgeSelector(QtGui.QWidget):
    def __init__(self,*args):
        QtGui.QWidget.__init__(self,*args)
        self.setWindowTitle(u"Вводим свой возраст")
        # создаем объекты:
        spinbox = QtGui.QSpinBox()
        slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        # устанавливаем границы значений:
        spinbox.setRange(0, 130)
        slider.setRange(0, 130)
        # создаем соединения:
        self.connect(spinbox, QtCore.SIGNAL("valueChanged(int)"), \
                slider, QtCore.SLOT("setValue(int)"))
        self.connect(slider, QtCore.SIGNAL("valueChanged(int)"), \
                spinbox, QtCore.SLOT("setValue(int)"))
        self.connect(spinbox, QtCore.SIGNAL("valueChanged(int)"), self.log_to_console)
        # задаем начальное значение:
        spinbox.setValue(27)
        # создаем горизонтальное размещение объектов в окне:
        layout = QtGui.QHBoxLayout()
        layout.addWidget(spinbox)
        layout.addWidget(slider)
        self.setLayout(layout)
    # слот, пишущий лог изменений в консоль:
    def log_to_console(self,i):
        print i
        
if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    age_sel = AgeSelector()
    age_sel.show()
    sys.exit(app.exec_())

Имеем окно приложения:
user posted image
а при передвижении ползунка со значения 27 до значения 35 в консоли получим ряд:
Цитата

27
28
29
31
32
33
34
35


Коль скоро мы затронули взаиморасположение объектов в окне, то следующий раздел этому и посвятим:

Геометрия/размещения



Это сообщение отредактировал(а) Artemios - 20.6.2007, 02:56


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
Artemios
Дата 17.6.2007, 23:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



Геометрия/размещение.

Чтобы разместить один виджет в другом, мы могли бы и не указывать каких-либо особенностей размещения, лишь передать конструктору размещаемого виджета ссылку на виджет-контейнер в качестве необязательного аргумента parent конструктора (смотрим документацию Qt4/PyQt4 по классам). Например, кнопка в окошко могла бы быть помещена так:
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui

class AnyWidget(QtGui.QWidget):
    def __init__(self,*args):
        QtGui.QWidget.__init__(self,*args)
        button = QtGui.QPushButton(u"Кнопка",self)

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    aw = AnyWidget()
    aw.show()
    sys.exit(app.exec_())

user posted image
Однако, если бы мы захотели добавить таким же образом на parent-виджет еще один графический объект, то он разместился бы поверх предыдущего объекта, целиком или полностью его перекрывая. Некрасивый и не рекомендуемый метод разрешения подобной ситуации - "в ручную" задавать геометрическое положение объектов в контейнере, используя метод setGeometry(x, y, width, height). Допишем в предыдущий пример в конструктор класса AnyWidget следующие строки:
Код

        button2 = QtGui.QPushButton(u"Кнопка2",self)
        button.setGeometry(5, 5, 100, 20)
        button2.setGeometry(110, 5, 100, 20)

user posted image
Очевидный недостаток такого подхода проявится сразу же, как только мы начнем менять размеры окна:
user posted image
Здесь можно было бы придумать различные пути обхода такого поведения окна: либо запретить изменение размеров, либо динамически пересчитывать положение элементов в контейнере при каждом изменении. 
Однако большинство GUI-библиотек, в том числе и Qt, предоставляют более гибкие и совершенные механизмы размещения графических объектов в контейнере. В Qt для этих целей существуют менеджеры размещения, наследуемые от класса QLayout. 

Рассмотрим три основных менеджера размещений: QHBoxLayout, QVBoxLayout и QGridLayout. Для размещения объектов в один ряд по горизонтали используем QHBoxLayout, аналогично QVBoxLayout для размещения объектов по вертикали:
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui

class AnyWidget(QtGui.QWidget):
    def __init__(self,*args):
        QtGui.QWidget.__init__(self,*args)
        button1 = QtGui.QPushButton(u"Кнопка1")
        button2 = QtGui.QPushButton(u"Кнопка2")
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(button1)
        layout.addWidget(button2)

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    aw = AnyWidget()
    aw.show()
    sys.exit(app.exec_())

user posted image

Для более сложных размещений используют QGridLayout, а также комбинации из всех трех менеджеров (для добавления подчиненного виджета - метод addWidget, для добавления подчиненного менеджера размещений - метод addLayout). QGridLayout размещает добавляемые элементы в ячейках воображаемой таблицы, причем каждый элемент может занимать несколько смежных ячеек по вертикали и/или горизонтали.
Запись метода addWidget для класса QGridLayout (addLayout имеет аналогичную форму):
Код

addWidget(widget, row, col) # размещение элемента в одной ячейке
addWidget(widget, from_row, from_col, row_span, col_span) # размещение элемента в нескольких ячейках


Проиллюстрируем комбинированное использование на примере с множеством объектов.
Код

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui

class AnyWidget(QtGui.QWidget):
    def __init__(self,*args):
        QtGui.QWidget.__init__(self,*args)
        boxlay = QtGui.QHBoxLayout(self)

        frame = QtGui.QFrame(self) # Фрейм
        frame.setFrameShape(QtGui.QFrame.StyledPanel)
        frame.setFrameShadow(QtGui.QFrame.Raised)
        
        gridlay = QtGui.QGridLayout(frame) # Менеджер размещения элементов во фрейме
        
        label = QtGui.QLabel(u"Метка",frame) # Текстовая метка.
        gridlay.addWidget(label,0,0)
        
        ln_edit = QtGui.QLineEdit(u"Винни Пух", frame) # Строковое поле ввода.
        gridlay.addWidget(ln_edit,0,1)
        
        radio_group = QtGui.QGroupBox(u"Выбор из двух", frame) # Рамка с надписью вокруг группы элементов.
        radio_lay = QtGui.QVBoxLayout(radio_group)             # Менеджер размещения элементов в рамке.
        radio1 = QtGui.QRadioButton(u"Первый", radio_group) # Два зависимых
        radio2 = QtGui.QRadioButton(u"Второй", radio_group) # переключателя
        radio2.setChecked(True)
        radio_lay.addWidget(radio1)
        radio_lay.addWidget(radio2)
        gridlay.addWidget(radio_group,1,0,3,1)
        
        combo = QtGui.QComboBox(frame) # Поле ввода с раскрывающимся списком.
        combo.addItem(u"Пятачок")
        combo.setEditable(True)
        gridlay.addWidget(combo,1,1)
        
        spin = QtGui.QSpinBox(frame) # Целочисленное поле ввода с
        spin.setValue(5)             # кнопками инкремента/декремента.
        gridlay.addWidget(spin,2,1)
        
        check = QtGui.QCheckBox(u"Пометка", frame) # Независимый переключатель с
        check.setCheckState(QtCore.Qt.Checked)     # двумя состояниями.
        gridlay.addWidget(check,3,1)
        
        progress = QtGui.QProgressBar(frame) # индикатор прогресса
        progress.setValue(70)
        progress.setOrientation(QtCore.Qt.Horizontal)
        gridlay.addWidget(progress,4,0,1,2)
        
        btn_lay = QtGui.QHBoxLayout() # Менеджер размещения двух кнопок.
        button1 = QtGui.QPushButton(u"Ок", frame)
        button2 = QtGui.QPushButton(u"Отмена", frame)
        btn_lay.addWidget(button1)
        btn_lay.addWidget(button2)
        gridlay.addLayout(btn_lay,5,0,1,2)
        
        boxlay.addWidget(frame)

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    aw = AnyWidget()
    aw.show()
    sys.exit(app.exec_())

user posted image

Если нас не удовлетворяет то, как ведут себя какие-либо элементы при изменении размеров окна, то можно попытаться вызвать для них метод setSizePolicy, первый параметр которого задает политику изменения ширины элемента, а второй - его высоты. Каждый параметр может принимать одно из значений:
  •  QtGui.QSizePolicy.Fixed - размер элемента в данном направлении не изменяется;
  •  QtGui.QSizePolicy.Minimum - "идеальным" размером элемента считается его минимальный размер. Элемент может растягиваться, но не может сжиматься;
  •  QtGui.QSizePolicy.Maximum - "идеальным" размером элемента считается его максимальный размер. Элемент может сжиматься, но не может растягиваться;
  •  QtGui.QSizePolicy.Preferred - элемент "старается" поддерживать некоторый предпочтительный для него размер, но при необходимости может растянуться или сжаться;
  •  QtGui.QSizePolicy.Expanding - элемент "старается" принять максимально возможный доступный ему размер, но при необходимости может и сжиматься.
Как видим, продумывание визуального взаиморасположения графических элементов в окне может быть занятием весьма утомительным. Однако в нашем распоряжении есть Qt Designer - WYSIWYG построитель графического иннтерфейса Qt, с которым создание очередного менеджера расположений с включёнными в него элементами сводится к одному-двум кликам мыши. 

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



Это сообщение отредактировал(а) Artemios - 20.6.2007, 03:00


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
Artemios
Дата 19.6.2007, 02:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



Стили.

Qt поддерживает несколько предопределенных стилей (тем) для отображения своих виджетов. Доступные в библиотеке Qt для любой операционной системы стили: Windows, Motif, CDE, Plastique, Cleanlooks. Для пользователей ОС Windows XP доступен еще один стиль - WindowsXP, а для пользователей ОС Mac OS X - стиль Macintosh. Два последних стиля доступны (и используются по умолчанию) только на соответствующих ОС-х, так как в них организованы обертки над соответствующими системными API/движками стилей.

Задать предопределенный стиль отображения можно, и не производя каких-либо внутренних изменений в программе, лишь передавая в аргументах командной строки при запуске ключ -style=ИмяСтиля. Посмотрим, как будет отображаться в различных стилях последний пример предыдущего раздела.
user posted image
user posted image
Того же эффекта можно добиться, явно задавая в программе используемый стиль методом setStyle("ИмяСтиля"), вызывая его либо для объекта приложения (изменения вступят в силу для всех виджетов программы), либо для отдельно выбранного виджета.

Вообще, при программировании на Qt/C++ можно создавать классы со своими собственными стилями, наследуясь от предопределенных (соответствующие стилям классы - QИмястиляStyle, например QPlastiqueStyle). Однако в PyQt4 (по крайней мере, в версии PyQt 4.2) не включены Python-обертки для этих C++ классов. И сделано это, как я думаю, вполне оправданно, так как нет смысла строить свои классы стилей ввиду наличия в Qt более простого и удобного механизма построения собственных стилей: Qt Style Sheets - концепция, терминология и синтаксис которых аналогичны CSS для HTML.

Qt Style Sheets

Разжигая аппетит читателя, прежде чем давать какие-либо объяснения, приведу возможно не очень удачный с точки зрения дизайнера, но достаточно выразительный пример.

Сохраним в файл mystyle01.qss следующий CSS-подобный код:
Код

QWidget {
   background-color: beige;
}

QLineEdit, QFrame, QGroupBox, QPushButton {
    border-width: 2px;
    border-style: solid;
    border-color: darkkhaki;
    border-radius: 5px;
}

QPushButton {
    background-color: palegoldenrod;
}

QPushButton:hover {
   background-color: khaki;
}

QPushButton:pressed {
    padding-left: 2px;
    padding-top: 3px;
    background-color: #d0d67c;
}

QLabel, QAbstractButton {
    font: bold;
}

QComboBox, QLineEdit, QSpinBox {
    background-color: cornsilk;
}

QGroupBox::title {
    top: -4px;
}

и добавим в последний пример предыдущего раздела после строки
Код

    app = QtGui.QApplication(sys.argv)

следующую строку:
Код

    app.setStyleSheet(open("./mystyle01.qss","r").read())

При запуске получившейся программы будем иметь такое окошко:
user posted image

Продолжение следует.



Это сообщение отредактировал(а) Artemios - 21.6.2007, 03:13


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
Artemios
Дата 21.6.2007, 03:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



Развитие предыдущего примера.

Нарисуем и положим в директорию ./img такие примитивы:
user posted image
Сохраним в файл mystyle.qss следующий код:
Код

QWidget {
    color: black;
    background-color: beige;
    selection-color: white;
    selection-background-color: darkred;
}
QFrame, QLineEdit, QComboBox, QGroupBox, QAbstractSpinBox {
    border-width: 2px;
    border-style: solid;
    border-color: darkkhaki;
    border-radius: 5px;
}
QAbstractButton, QLabel {
    font: bold;
}
QComboBox, QLineEdit, QAbstractSpinBox {
    background-color: cornsilk;
}
QGroupBox {
    padding-top: 5px;
}
QGroupBox::title {
    padding-right: 5px;
    top: -4px;
}
QRadioButton::indicator::checked {
    image: url(./img/radio.png);
}
QRadioButton::indicator::checked:hover {
    image: url(./img/radio_hover.png);
}
QRadioButton::indicator::checked:pressed {
    image: url(./img/radio_pressed.png);
}
QRadioButton::indicator::unchecked,
QCheckBox::indicator:unchecked {
    image: url(./img/btn.png);
}
QRadioButton::indicator::unchecked:hover,
QCheckBox::indicator:unchecked:hover {
    image: url(./img/btn_hover.png);
}
QRadioButton::indicator::unchecked:pressed,
QCheckBox::indicator:unchecked:pressed {
    image: url(./img/btn_pressed.png);
}
QCheckBox::indicator:checked {
    image: url(./img/check.png);
}
QCheckBox::indicator:checked:hover {
    image: url(./img/check_hover.png);
}
QCheckBox::indicator:checked:pressed {
    image: url(./img/check_pressed.png);
}
*::down-arrow, *::menu-indicator {
    image: url(./img/down_arrow.png);
}
*::up-arrow {
    image: url(./img/up_arrow.png);
}
QComboBox {
    padding-right: 16px;
}
*::drop-down {
    subcontrol-origin: border;
    width: 13px;
    position: absolute;
    bottom: 1px;
}
QAbstractSpinBox { 
    padding-right: 15px;
}
*::up-button, *::drop-down {
    subcontrol-position: top right;
    top: 1px;
    right: 1px;
}
*::down-button {
    subcontrol-position: bottom right;
    bottom: 1px;
    right: 1px;
}
*::up-button, *::down-button {
    subcontrol-origin: border;
    width: 16px;
}
QPushButton, *::up-button,
*::down-button, *::drop-down {
    border-width: 4px;
    border-image: url(./img/btn.png);
}
QPushButton:hover, *::up-button:hover,
*::down-button:hover, *::drop-down:hover {
    border-image: url(./img/btn_hover.png) ;
}
QPushButton:pressed, *::up-button:pressed,
*::down-button:pressed, *::drop-down:on {
    border-image: url(./img/btn_pressed.png) ;
}
QPushButton:pressed {
    padding-left: 2px;
    padding-top: 1px;
}

и добавим в последний пример предыдущего раздела после строки
Код

    app = QtGui.QApplication(sys.argv)

следующие строки:
Код

    app.setStyle("Plastique")
    app.setStyleSheet(open("./mystyle.qss","r").read())

При запуске получившейся программы будем иметь такое окошко:
user posted image
Теперь любая программа на PyQt, к которой мы подобными строками подключим наше маленькое расширение стиля, будет выглядеть подобным образом.
Исходники приведенного примера присоединяю к сообщению.

Немножко теории. 
Qt Style Sheets - мощный механизм настройки внешнего стиля виджетов, выполненный, как я уже упоминал, в идеологии каскадных таблиц стилей (CSS). Используется как надстройка над основным заданным для виджетов стилем отображения (в приведенном выше примере в качестве основного был выбран Plastique).

Style Sheets состоит из последовательности стилевых правил. Стилевое правило состоит из селектора и декларации. Селектор определяет, к каким виджетам будет применяться текущее правило, в декларации задаются значения стилевых атрибутов выбранного виджета. Например правило:
Код

QAbstractButton { background-color : red }

задает красный цвет заднего фона для всех экземпляров классов-потомков класса QAbstractButton (например, QPushButton, QRadioButton). 
Несколько селекторов могут объединяться через запятую, например:
Код

QLineEdit, QComboBox { color: red }

Раздел декларации может содержать несколько пар вида атрибут:значение ,разделенных точкой с запятой:
Код

QPushButton { color: red; background-color: white }


Виды селекторов:
- Универсальный: * - сопоставляется со всеми виджетами.
- Селектор типа, например QPushButton - имя класса - сопоставляется с экземплярами данного класса и всех классов-потомков от данного.
- Атрибутный селектор: QPushButton[flat="false"] - сопоставляется только с теми экземплярами QPushButton, у которых на момент применения стиля значение атрибута flat равно false.
- Селектор класса: .QPushButton - точка перед именем класса - сопоставляется только с экземплярами данного класса, но не сопоставляется с экземплярами наследников от QPushButton.
- Селектор идентификатора: QPushButton#okButton - сопоставляется с экземпляром QPushButton, имеющем идентификатор "okButton" (каждому Qt-объекту можно задать идентификатор методом setObjectName; если мы строим форму при помощи Qt Designer-а, то у каждого виджета идентификатор будет задан автоматически в соответствии со свойством objectName в окне Property Editor)
- Селектор потомка: QDialog QPushButton - два класса, соединенные пробелом - сопоставляется со всеми экземплярами QPushButton, агрегированными в контейнере класса QDialog или в иных контейнерах, агрегированных в экземпляре QDialog (произвольной вложенности). (Следует заметить, что слово "потомок" здесь употребляется не в классическом понимании ООП, применительно к отношению наследования между классами, а применительно к отношению агрегации в контейнере, что ближе к терминологии XML/CSS)
- Селектор непосредственного потомка: QDialog > QPushButton - сопоставляется со всеми экземплярами QPushButton, являющимися непосредственными потомками (в смысле XML) экземпляра QDialog.

Кроме примитивных виджетов (кнопки, метки и т.д.), виждетов-агрегатов (контейнеров, напр. формы, фреймы, группирующие боксы и т.д.), в GUI существуют еще виджеты-композиты (SpinButton, CheckBox, ComboBox и т.д.). Для доступа к элементам композита используют селектор объекта-композита, после него два двоеточия и специальный идентификатор элемента. Например стилевое правило:
Код

QComboBox::drop-down { image: url(dropdown.png) }

задает картинку из файла dropdown.png для кнопки справа, открывающей выпадающий список, в объекте QComboBox.

Специальные идентификаторы для элементов композита. В Qt сейчас поддерживаются следующие имена:
::down-arrow - стрелка вниз для QComboBox или QSpinBox.
::down-button - нижняя кнопка для QSpinBox.
::drop-down - раскрывающая выпадающий список кнопка QComboBox.
::indicator - индикатор для QCheckBox, QRadioButton или для checkable QGroupBox.
::item - элемент QMenuBar, QMenu или QStatusBar.
::menu-indicator - индикатор меню для QPushButton.
::title - заголовок QGroupBox.
::up-arrow стрелка вверх для QSpinBox.
::up-button - верхняя кнопка QSpinBox.

Для отображения GUI в динамике в селектор можно включать псевдо-состояние. Псевдо-состояние записывается в селекторе последним элементом и отделяется от предыдущей части двоеточием. Пример:
Код

QPushButton { background-color : white }
QPushButton:hover { background-color : red }
QPushButton:pressed { background-color : green }

определяет для всех кнопок стиль с белыми кнопками, которые при наведении на них мыши становятся красными, а при нажатии - зелеными.

Поддерживаемые в Qt на данный момент псевдо-состояния:
:checked 
:disabled 
:enabled
: focus
:hover 
:indeterminate 
:off 
:on 
:pressed 
:unchecked 

Продолжение следует.

Это сообщение отредактировал(а) Artemios - 24.6.2007, 23:24

Присоединённый файл ( Кол-во скачиваний: 96 )
Присоединённый файл  qss_example.tar.bz2 5,46 Kb


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
Artemios
Дата 24.6.2007, 23:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



...

Это сообщение отредактировал(а) Artemios - 16.1.2009, 02:18


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
Goganchic
Дата 25.6.2007, 23:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 678
Регистрация: 18.6.2004

Репутация: 2
Всего: 5



А может это дело оформить как статью?

Artemios, как думаешь?
PM Jabber   Вверх
Artemios
Дата 25.6.2007, 23:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 405
Регистрация: 14.8.2006
Где: Саратов, Россия

Репутация: 9
Всего: 50



Goganchic, позже будет и статья. Я пока еще в процессе "рождения" материала, по ходу дела выкладываю сюда, редактирую...
Жду обратной реакции для коррекции своих трудов. Без отклика заинтересованного читателя трудно решить, что и как лучше писать, где лучше подробные объяснения, а где - лишь упоминание и отсылка к документации... Вот, например, на стили кажется слишком много развернулся, сокращать надо будет...
В общем, мне бы хотелось комментариев, критики, и чем больше - тем лучше для будущего материала... smile


--------------------
fib = 1: 1: [ x+y | (x,y) <- zip fib (tail fib) ]
PM MAIL   Вверх
cab
Дата 26.6.2007, 14:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 5
Регистрация: 26.6.2007

Репутация: нет
Всего: нет



Цитата(Artemios @  25.6.2007,  23:37 Найти цитируемый пост)
В общем, мне бы хотелось комментариев, критики, и чем больше - тем лучше для будущего материала...
Отличный старт. Продолжайте в том же духе. Жду с нетерпением

PM MAIL   Вверх
Polimer
Дата 10.7.2007, 16:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 1
Регистрация: 10.7.2007

Репутация: нет
Всего: нет



ЗдОрово написано!
Так популярно и на русском только здесь нашел. Очень хочется увидеть продолжение темы.
Уже качаю Qt.

Автору мегаплюс. Пишите еще, я думаю, многие заинтересованы.
PM MAIL   Вверх
pythonwin
Дата 11.7.2007, 07:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Участник
Сообщений: 2529
Регистрация: 18.4.2006
Где: за компом

Репутация: 2
Всего: 36



[оффтоп]
для тех кто использует pyQT - Artemios, как я понимаю пишет статьи на форуме чтобы их критиковали. хотя "спасибо"  думаю тоже приятно smile
[/оффтоп]
PM WWW GTalk Jabber   Вверх
maitreya
Дата 24.7.2007, 15:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 1
Регистрация: 24.7.2007

Репутация: 1
Всего: 1



Очень хорошая документация.
На мой взгляд нужно рассмотреть работу с utf
PM ICQ Jabber   Вверх
keiman
  Дата 4.8.2007, 19:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 5
Регистрация: 4.8.2007

Репутация: 1
Всего: 1



Во-первых, огромное спасибо за проделанный труд! smile 

Во-вторых, как и другие посетители форума с интересом продолжал бы следить за развитием цикла постингов на эту тему.

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

По будущему содержанию: думаю вопросам типа "Работа с дизайнером графического интерфейса" можно уделять меньше внимания, а больше "ручным" аспектам, именно аспектам внутреннего устройства PyQt и его точек соединения с Pure Python. Другими словами лучше подробнее описать связывание своего Python-кода (кода программы) c GUI, все тонкости и хитрости связанные с разработкой GUI-приложений на Python (сигналы/слоты, утечки памяти), может быть заострить внимание на разнице в логике построения между GUI-приложениями и обычными Python-скриптами.  

Также неплохо чтобы были вкратце освещены вопросы "переносимости" Python+PyQt кода в другие реализации Пайтона (возможность переноса например в IronPython или JPython, а точнее видимо невозможность), создание байт-кода и тд.

И еще благодарность за написанный труд! Ждем продолжений!

Добавлено через 5 минут и 6 секунд
Друзья, а почему так не много откликов?

Неужели лень написать благодарность и дать небольшие комментарии или когда дело касается не личного (шкурного) интереса типа "посмотрите, мой код не исполняется. где ошибка?" вам уже ничего не интересно???
PM MAIL   Вверх
keiman
Дата 4.8.2007, 20:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 5
Регистрация: 4.8.2007

Репутация: 1
Всего: 1



Цитата(Artemios @ 24.6.2007,  23:21)
В планах на будущее:
Завершить рассмотрение Qt Style Sheets.

Работа с дизайнером графического интерфейса;
система ресурсов приложения;
реализованная в Qt технология MVC на примере работы с базами данных и XML;
и много иного, оставайтесь с нами.
Artemios.

Да, вдогонку.

Уделите, пожалуйста, внимание оптимальному способу "прикручивания" *.ui к Python-коду (генерирование в pyQt, pyuic и тд)
PM MAIL   Вверх
setq
Дата 5.8.2007, 07:36 (ссылка)    |    (голосов: 0) Загрузка ... Загрузка ... Быстрая цитата Цитата


Unregistered











Цитата(keiman @  4.8.2007,  19:34 Найти цитируемый пост)
Неужели лень написать благодарность и дать небольшие комментарии


Мы просто ленивые. Отличные статьи, Artemios)
  Вверх
FunnyFalcon
Дата 5.8.2007, 18:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 76
Регистрация: 27.3.2006

Репутация: нет
Всего: 7



Цитата

Мы просто ленивые. Отличные статьи, Artemios) 

+1 . Если б мог, то и на плюсик нажал бы.
Очень жду продолжение цикла.
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Python: Разработка GUI | Следующая тема »


 




[ Время генерации скрипта: 0.2034 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.