Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555

Jonathan Gardner's PyQt Tutorial

Перевод Д.Бречалова

Оригинальный текст вы можете найти по адресу: http://wiki.python.org/moin/JonathanGardnerPyQtTutorial

Это короткий учебник (tutorial) по работе с библиотекой PyQt. Предполагается, что вы имеете некоторые знания по bash, Python и Qt.

Если у вас есть вопросы или комментарии (относительно английского оригинала), вы можете послать их на jgardner-AT-jonathangardner.net. Вы также можете непосредственно вносить исправления в данный текст. (Также имеется в виду английский оригинал, опубликованный на Python Wiki. Комментарии и предложения по поводу русского перевода пишите здесь, в комментариях под страницей.)

Перевод Родриго Б. Виейра (Rodrigo B. Vieira) на (бразильский) португальский доступен по адресу http://www.pythonbrasil.com.br/moin.cgi/TutorialPyQt.

Введение

Мы рассмотрим:

  • Использование Qt Designer'а для генерации ui-файлов Qt.
  • Использование программы pyuic для генерации программ на Питоне.
  • Использование сигналов и слотов Qt на Питоне.
  • Создание простого приложения для взаимодействия с программой at.

Требования

Вам потребуются:

  • Red Hat 8.0 с установленными
  • qt-devel RPM
  • PyQt-devel RPM

PyQt работает также на других системах. Приемы и программы, приведенные в руководстве могут не работать совсем или работать не надлежащим образом. Однако я не привожу всех деталей как заставить их работать на всех системах, которые могут использовать PyQt. Вы должны разобраться с этим сами.Также вы должны знать:

  • Как использовать текстовый редактор и как заставить этот редактор правильно редактировать код на Питоне.
  • Как программировать на Питоне.
  • Некоторые основные команды bash.
  • Основы программирования под Qt.

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

Использование Qt Designer'а

Первым делом о главном. Начнем, пожалуй, с начала. Откройте окно bash-консоли. Запустите Qt Desiger следующей командой:

$ designer

Вас приветсвует Qt Designer. В зависимости от версии, имеющейся у вас, его внешний вид может немного различаться.

Могу предположить, что вы совершенно не знаете, как пользоваться Qt Designer'ом. Если это так, вы легко можете изучить документауию.

Создайте новый компонент (виджет – widget). Назовите его 'at-auto'. Наполните его “внутренностями”:

  • Добавьте QLineEdit. Назовите его “command” в диалоге свойств (property dialog).
  • Добавьте QPushButton. Назовите его “schedule” в диалоге свойств. Смените его текст на “Schedule”.
  • Добавьте QDateTimeEdit. Назовите его “time”.

Теперь расположите элементы по своему вкусу, используя инструмент Qt layout. Для этого вам могут понадобиться несколько “распорок” (spacers).

Сохраните файл в каталоге проекта для этого руководства. Создайте каталог, если вы еще не сделали этого, и назовите его как-нибудь вроде “pyqt_tutorial”. Сохраните файл под именем “at.ui”.

Использование pyuic

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

$ pyuic at.ui

Следующая команда запишет сгенерированный на основании файла at.ui код на Питоне в файл at_auto.py:

$ pyuic at.ui -o at_auto.py

После каждого изменения ui-файла, нужно перегенерировать at_auto.py. Давайте поручим это команде make.

Табуляции важны!

$ cat > Makefile
at_auto.py : at.ui
pyuic at.ui -o at_auto.py
^D

Теперь запустите make:

$ make

Заметьте, что эта команда рапортует только о файлах, измененных со времени предыдущего запуска. Давайте обновим файл at.ui, чтобы он стал более новым, чем at_auto.py, и попробуем снова.

$ touch at.ui
$ make

Теперь make печатает на экране команды, которые выполняет. Вы видите, что at_auto.py перегенерирован успешно.

Теория

Основная идея здесь состоит в том, чтобы дать возможность разработчику GUI вносить изменения в дизайн (например, переместить элемент интерфейса на другое место) без влияния на логику приложения. Так что, если вы все сделаете правильно, единственное, что должен сделать разработчик GUI, это внести изменения в файл at.ui, после чего запустить make чтобы его изменения вступили в силу.

Ваш make-файл будет становиться все более сложным по мере того, как вы будете добавлять все новые и новые файлы. Будьте готовы прочитать множество документации по утилите make, чтобы принять правильные решения при разработке о том, как правильно использовать make.

Запуск приложения

Итак, у нас есть файлы at.ui и at_auto.py. Как же нам теперь запустить приложение?

Мы должны создать at.py. Вот, как он должен выглядеть.

#!/usr/bin/env python
from qt import *
from at_auto import at_auto
 
class at(at_auto):
    def __init__(self, parent=None, name=None, fl=0):
        at_auto.__init__(self,parent,name,fl)
 
if __name__ == "__main__":
    import sys
    a = QApplication(sys.argv)
    QObject.connect(a,SIGNAL("lastWindowClosed()"),a,
    SLOT("quit()"))
    w = at()
    a.setMainWidget(w)
    w.show()
    a.exec_loop()

Теперь запустите его.

$ python at.py

Вуаля! Вот наше приложение.

Примечание

Если вы используете Qt designer с Qt версии 3.3.0, ваш .ui-файл содержит такой заголовок:

<!DOCTYPE UI><UI version="3.3" stdsetdef="1">

и pyuic (по крайней мере 3.8.1) жалуется, что версия слишком новая для него, и что он не может обработать ее.

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

#!/bin/bash
sed -i s/3.3/3.3.0/g at.ui
make
exec python at.py

Затем сделайте его исполняемым: chmod 700 myscript.

Значения даты и времени по умолчанию

Давайте установим значение по умолчанию для компонента QDateTimeEdit. Метод setDateTime компонента QDateTimeEdit ожидает объект класса QDateTime в качестве аргумента. Значит нужно его создать, но как задать время для QDateTime? Изучая документацию, мы обнаруживаем, что метод setTime_t позволит нам установить дату со временем в секундах с начала эпохи Unix. Мы можем его получить от функции time() стандартного встроенного модуля time.

Вот код, который делает это. Мы поместим его в конструктор init, чтобы дата со временем устанавливались правильно с самого начала. Не забудьте импортировать time!

# Set the date to now
now = QDateTime()
# Time in seconds since Unix Epoch
now.setTime_t(time.time())
self.time.setDateTime(now)

Этот отрывок кода показывает, как легко работают друг с другом Питон и PyQt. Также он должен продемонстрировать мыслительный процесс, который вы должны пройти чтобы работать с компонентами Qt.

Сигналы и Слоты

Все, чем вы будете теперь заниматься – это соединять Сигналы со Слотами. Это оченю просто, за что я и люблю PyQt.

Питон – не C++. Поэтому он должен обращаться с Сигналами и Слотами по-своему.

Во-первых, в Питоне слотом может быть любой вызываемый (исполняемый, callable) объект. Это может быть метод объекта, функция или даже лямбда-выражение. Во-вторых, сигнал в Питоне – просто лишенный смысла текст.

Позвольте мне объяснить различие между Сигналами/Слотами в C++ и Питоне. Там, где создается объект, делать нечего, все должно делаться там, где возникает Сигнал и там, где расположен Слот. Например, QPushButton имеет Сигнал C++ “clicked()”. Если вы создаете ваш собственный подкласс на Питоне, например, “PyPushButton”, он также имеет Сигнал C++ “clicked()”. Если вы создадите новый сигнал на Питоне, скажем, “GobbledyGook()”, это будет Сигнал Питона, так как ничто в C++ даже не знает о его существовании.

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

Связать Сигнал C++ со Слотом Питона

Вы будете заниматься этим весь день напролет. Это делается примерно так:

QObject.connect(some_object, SIGNAL('toggled(bool)'), some_python_callable)

Связать Сигнал C++ со Слотом C++

Вы не часто будете это делать, тем не менее это искуссный прием.

QObject.connect(some_object, SIGNAL('toggled(bool)'), some_object, SLOT('the_slot(bool)'))

Связать Сигнал Питона со Слотом C++ или Питона

Вряд ли вы будете часто использовать Сигналы Питона, но мы опишем, как это делать. Придумайте имя нового сигнала. Затем поменяйте в примере выше “SIGNAL” на “PYSIGNAL”.

Может оказаться оправданным обойти сигнальную библиотеку Qt и испозльзовать вашу собственную, если вы используете большое количество сигналов Питона. Однако, если вы намереваетесь когда-нибудь переписать код на C++, это не имеет смысла. Я это сделал, потому что нашел синтаксис обременительным и трудным для отладки.

Наше приложение будет отвечать только на один сигнал: нажатие на кнопку “Schedule”. При этом оно будет запускать команду “at” с соответствующими аргументами.

Вот код для соединения сигнала со слотом:

self.connect(
    self.schedule, SIGNAL('clicked()'),
    self.schedule_clicked
)

Заметьте, что мы соединяем сигнал C++ со слотом Питона. Однако, этот слое еще не существует. Давайте добавим его в класс 'at'.

def schedule_clicked(self):
    if not str(self.command.text()):
        QMessageBox.critical(self,
            "Invalid event", "You must specify an event",
            QMessageBox.Ok)
        return
 
    t = str(self.time.dateTime().toString('hh:mm MM/dd/yyyy'))
    p = os.popen('at -m "%s"'%t, 'w')
    p.write(str(self.command.text()))
    self.close()

Этот процесс состоит из двух частей. Сначала мы проверяем введено ли что-нибудь в строку ввода “command”. Если нет, мы показываем сообщение об ошибке при помощи QMessageBox.

Если в строке ввода что-то есть, мы открываем канал (pipe) с командой 'at'. 'at' ожидает ввода команды с sdtin. Затем мы пишем в ее stdin команду, которую хотим исполнить. Заметьте, что здесь мы не делаем никаких проверок на возможные ошибки.

Идем вперед и запускаем наше приложение, затем используем команду 'atq' чтобы посмотреть задание, поставленное в очередь 'at'.Получилось? Хорошо.

Вот окончательный вариант 'at.py'.

#!/usr/bin/env python
from qt import *
from at_auto import at_auto
import time
import sys
import os
 
class at(at_auto):
 
    def __init__(self, parent=None, name=None, fl=0):
        at_auto.__init__(self,parent,name,fl)
 
        # Set the date to now
        now = QDateTime()
        # Time in seconds since Unix Epoch
        now.setTime_t(time.time())
        self.time.setDateTime(now)
 
        self.connect(
            self.schedule, SIGNAL('clicked()'),
            self.schedule_clicked
        )
 
    def schedule_clicked(self):
        if not str(self.command.text()):
            QMessageBox.critical(self,
                "Invalid event", "You must specify an event",
                QMessageBox.Ok)
        return
 
        t = str(self.time.dateTime().toString('hh:mm MM/dd/yyyy'))
        p = os.popen('at -m "%s"'%t, 'w')
        p.write(str(self.command.text()))
        self.close()
 
if __name__ == "__main__":
    a = QApplication(sys.argv)
    QObject.connect(a,SIGNAL("lastWindowClosed()"),a,SLOT("quit()"))
    w = at()
    a.setMainWidget(w)
    w.show()
    a.exec_loop()

Домашняя работа

В оставшееся время вы можете внести некоторые усовершенствования.

  • Используя Qt Designer добавьте метки QLabels описывающие что значит каждое из полей ввода. Обратите внимание, что вам не нужно ничего менять в коде, всего лишь отредактируйте файл 'au.ui' и запустите 'make'.
  • Вы возможно захотите, чтобы приложение закрывалось после того, как задание будет назначено. Найдите подходящий слот чтобы закрыть главное окно 'at' или все приложение. Вопрос: почему при закрытии виджета 'at' завершается все приложение?
  • Добавьте проверки на возможные ошибки при назначении задания. Если появляются какие-либо сообщения, покажите их пользователю при помощи QMessageBox.
  • Напишите приложение для показа списка назначенных задач для 'at'.
  • Добавьте функциональность, позволяющую изменять или удалять назначенные задания. Попробуйте повторно использовать как можно больше из уже существующего кода. (Подсказка: у вас уже есть полностью готовый виджет, который позволяет назначить новое задание. Попробуйте вставить его в диалог QDialog.)

Дальнейшее развитие

Это приложение могло бы стать частью пакета интерфейсов к консольным командам Unix. Какими еще командами вы хотели бы заняться? Я бы порекомендовал попробовать “crontab” и “ps”. Парсинг вывода этих команд не очень сложен и взаимодействовать с ними очень легко.

Вы также можете попытаться объединить ваше новое приложение с только что созданным 'at'. Если хотите, можете продавать их вместе как графический интерфейс Unix, но в этом случае вы должны будете приобрести коммерческие лицензии на Qt и PyQt, если вы не собираетесь придерживаться лицензии, подобной GPL.