ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
— устанавливает текстовые данные в формате HTML (MIME-тип text/html ): data.setHtml("Перетаскиваемый HTML-текст") html() — возвращает текстовые данные в формате HTML; hasHtml() — возвращает значение True , если объект содержит текстовые данные в фор- мате HTML, и False — в противном случае; setUrls(<Список URI-адресов>) — устанавливает список URI-адресов (MIME-тип text/uri-list ). В качестве значения указывается список с экземплярами класса QUrl С помощью этого MIME-типа можно обработать перетаскивание файлов: data.setUrls([QtCore.QUrl("https://www.google.ru/")]) urls() — возвращает список URI-адресов: uri = e.mimeData().urls()[0].toString() hasUrls() — возвращает значение True , если объект содержит список URI-адресов, и False — в противном случае; setImageData(<Объект изображения>) — устанавливает изображение (MIME-тип application/x-qt-image ). В качестве значения можно указать, например, экземпляр класса QImage или QPixmap : data.setImageData(QtGui.QImage("pixmap.png")) data.setImageData(QtGui.QPixmap("pixmap.png")) imageData() — возвращает объект изображения (тип возвращаемого объекта зависит от типа объекта, указанного в методе setImageData() ); hasImage() — возвращает значение True , если объект содержит изображение, и False — в противном случае; setData( — позволяет установить данные произвольного MIME- типа. В первом параметре указывается MIME-тип в виде строки, а во втором парамет- ре — экземпляр класса QByteArray с данными. Метод можно вызвать несколько раз с различными MIME-типами. Вот пример передачи текстовых данных: data.setData("text/plain", QtCore.QByteArray(bytes("Данные", "utf-8"))) data( — возвращает экземпляр класса QByteArray с данными, соответст- вующими указанному MIME-типу; hasFormat( — возвращает значение True , если объект содержит данные ука- занного MIME-типа, и False — в противном случае; formats() — возвращает список с поддерживаемыми объектом MIME-типами; removeFormat( — удаляет данные, соответствующие указанному MIME-типу; clear() — удаляет все данные. Если необходимо перетаскивать данные какого-либо специфического типа, нужно наследо- вать класс QMimeData и переопределить в нем методы retrieveData() и formats() . За под- робной информацией по этому вопросу обращайтесь к документации. Глава 19. Обработка сигналов и событий 429 19.10.3. Обработка сброса Прежде чем обрабатывать перетаскивание и сбрасывание объекта, необходимо сообщить системе, что компонент может обрабатывать эти события. Для этого внутри конструктора компонента следует вызвать метод setAcceptDrops() , унаследованный от класса QWidget , и передать этому методу True : self.setAcceptDrops(True) Обработка перетаскивания и сброса объекта выполняется следующим образом: 1. Внутри метода dragEnterEvent() компонента проверяется MIME-тип перетаскиваемых данных и действие. Если компонент способен обработать сброс этих данных и соглаша- ется с предложенным действием, необходимо вызвать метод acceptProposedAction() объекта события. Если нужно изменить действие, методу setDropAction() объекта собы- тия передается новое действие, а затем у того же объекта вызывается метод accept() вместо acceptProposedAction() 2. Если необходимо ограничить область сброса некоторым участком компонента, следует дополнительно определить в нем метод dragMoveEvent() . Этот метод будет постоянно вызываться при перетаскивании внутри области компонента. При достижении указате- лем мыши нужного участка компонента следует вызвать метод accept() и передать ему экземпляр класса QRect с координатами и размером этого участка. В этом случае при перетаскивании внутри участка метод dragMoveEvent() повторно вызываться не будет. 3. Внутри метода dropEvent() компонента производится обработка сброса. Обработать события, возникающие в процессе перетаскивания и сбрасывания, позволяют следующие методы класса QWidget : dragEnterEvent(self, — вызывается, когда перетаскиваемый объект входит в область компонента. Через параметр доступен экземпляр класса QDragEnterEvent ; dragLeaveEvent(self, — вызывается, когда перетаскиваемый объект покидает область компонента. Через параметр доступен экземпляр класса QDragLeaveEvent ; dragMoveEvent(self, — вызывается при перетаскивании объекта внутри об- ласти компонента. Через параметр доступен экземпляр класса QDragMoveEvent ; dropEvent(self, — вызывается при сбрасывании объекта в области компонен- та. Через параметр доступен экземпляр класса QDropEvent Класс QDragLeaveEvent наследует класс QEvent и не несет никакой дополнительной инфор- мации. Достаточно просто знать, что перетаскиваемый объект покинул область компонента. Цепочка наследования остальных классов выглядит так: QEvent — QDropEvent — QDragMoveEvent — QDragEnterEvent Класс QDragEnterEvent не содержит собственных методов, но наследует все методы классов QDropEvent и QDragMoveEvent Класс QDropEvent поддерживает следующие методы: mimeData() — возвращает экземпляр класса QMimeData с перемещаемыми данными и ин- формацией о MIME-типе; pos() — возвращает экземпляр класса QPoint с целочисленными координатами сбрасы- вания объекта; 430 Часть II. Библиотека PyQt 5 posF() — возвращает экземпляр класса QPointF с вещественными координатами сбрасы- вания объекта; possibleActions() — возвращает комбинацию возможных действий при сбрасывании. Вот пример определения значений: if e.possibleActions() & QtCore.Qt.MoveAction: print("MoveAction") if e.possibleActions() & QtCore.Qt.CopyAction: print("CopyAction") proposedAction() — возвращает действие по умолчанию при сбрасывании; acceptProposedAction() — сообщает о готовности принять переносимые данные и согласии с действием, возвращаемым методом proposedAction() . Метод acceptProposedAction() (или метод accept() , поддерживаемый классом QDragMoveEvent ) необходимо вызвать внутри метода dragEnterEvent() , иначе метод dropEvent() вызван не будет; setDropAction(<Действие>) — позволяет указать другое действие при сбрасывании. По- сле изменения действия следует вызвать метод accept() , а не acceptProposedAction() ; dropAction() — возвращает действие, которое должно быть выполнено при сбрасыва- нии. Оно может не совпадать со значением, возвращаемым методом proposedAction() , если действие было изменено с помощью метода setDropAction() ; keyboardModifiers() — позволяет определить, какие клавиши-модификаторы ( mouseButtons() — позволяет определить кнопки мыши, которые были нажаты в процес- се переноса данных; source() — возвращает ссылку на компонент внутри приложения, являющийся источ- ником события, или значение None , если данные переносятся из другого приложения. Теперь рассмотрим методы класса QDragMoveEvent : accept([ — сообщает о согласии с дальнейшей обработкой события. В качест- ве параметра можно указать экземпляр класса QRect с координатами и размерами прямо- угольной области, в которой будет доступно сбрасывание; ignore([ — отменяет операцию переноса данных. В качестве параметра можно указать экземпляр класса QRect с координатами и размерами прямоугольной области, в которой сбрасывание запрещено; answerRect() — возвращает экземпляр класса QRect с координатами и размерами прямо- угольной области, в которой произойдет сбрасывание, если событие будет принято. Некоторые компоненты в PyQt уже поддерживают технологию drag & drop — так, в одно- строчное текстовое поле можно перетащить текст из другого приложения. Поэтому, прежде чем изобретать свой «велосипед», убедитесь, что поддержка технологии в компоненте не реализована. 19.11. Работа с буфером обмена Помимо технологии drag & drop, для обмена данными между приложениями используется буфер обмена — одно приложение помещает данные в буфер обмена, а второе приложение Глава 19. Обработка сигналов и событий 431 (или то же самое) может их извлечь. Получить ссылку на глобальный объект буфера обмена позволяет статический метод clipboard() класса QApplication : clipboard = QtWidgets.QApplication.clipboard() Класс QClipboard поддерживает следующие методы: setText(<Текст>) — заносит текст в буфер обмена: clipboard.setText("Текст") text() — возвращает из буфера обмена текст или пустую строку; text(<Тип>) — возвращает кортеж из двух строк: первая хранит текст из буфера обмена, вторая — название типа. В параметре <Тип> могут быть указаны значения "plain" (про- стой текст), "html" (HTML-код) или пустая строка (любой тип); setImage( — заносит в буфер обмена изображение, представленное экземпля- ром класса QImage : clipboard.setImage(QtGui.QImage("image.jpg")) image() — возвращает из буфера обмена изображение, представленное экземпляром класса QImage , или пустой экземпляр этого класса; setPixmap( — заносит в буфер обмена изображение, представленное экземп- ляром класса QPixmap : clipboard.setPixmap(QtGui.QPixmap("image.jpg")) pixmap() — возвращает из буфера обмена изображение, представленное экземпляром класса QPixmap , или пустой экземпляр этого класса; setMimeData( — позволяет сохранить в буфере данные любого типа, пред- ставленные экземпляром класса QMimeData (см. разд. 19.10.2); mimeData([<Режим>]) — возвращает данные, представленные экземпляром класса QMimeData ; clear() — очищает буфер обмена. Отследить изменение состояния буфера обмена позволяет сигнал dataChanged . Назначить обработчик этого сигнала можно так: QtWidgets.qApp.clipboard().dataChanged.connect(on_change_clipboard) 19.12. Фильтрация событий События можно перехватывать еще до того, как они будут переданы компоненту. Для этого необходимо создать класс, который является наследником класса QObject , и переопределить в нем метод eventFilter(self, <Объект>, . Через параметр <Объект> доступна ссылка на компонент, а через параметр — на объект с дополнительной информаци- ей о событии. Этот объект различен для разных типов событий — так, для события MouseButtonPress объект будет экземпляром класса QMouseEvent , а для события KeyPress — экземпляром класса QKeyEvent . Из метода eventFilter() следует вернуть значение True , если событие не должно быть передано дальше, и False — в противном случае. Вот пример такого класса-фильтра, перехватывающего нажатие клавиши : class MyFilter(QtCore.QObject): def __init__(self, parent=None): QtCore.QObject.__init__(self, parent) 432 Часть II. Библиотека PyQt 5 def eventFilter(self, obj, e): if e.type() == QtCore.QEvent.KeyPress: if e.key() == QtCore.Qt.Key_B: print("Событие от клавиши не дойдет до компонента") return True return QtCore.QObject.eventFilter(self, obj, e) Далее следует создать экземпляр этого класса, передав в конструктор ссылку на компонент, а затем вызвать у того же компонента метод installEventFilter() , передав в качестве единственного параметра ссылку на объект фильтра. Вот пример установки фильтра для надписи: self.label.installEventFilter(MyFilter(self.label)) Метод installEventFilter() можно вызвать несколько раз, передавая ссылку на разные объекты фильтров. В этом случае первым будет вызван фильтр, который был добавлен по- следним. Кроме того, один фильтр можно установить сразу в нескольких компонентах. Ссылка на компонент, который является источником события, доступна через второй пара- метр метода eventFilter() Удалить фильтр позволяет метод removeEventFilter(<Фильтр>) , вызываемый у компонента, для которого был назначен этот фильтр. Если таковой не был установлен, при вызове мето- да ничего не произойдет. 19.13. Искусственные события Для создания искусственных событий применяются следующие статические методы из класса QCoreApplication : sendEvent( — немедленно посылает событие компоненту и воз- вращает результат выполнения обработчика; postEvent( — добавляет собы- тие в очередь. Параметром priority можно передать приоритет события, использовав один из следующих атрибутов класса QtCore.Qt : HighEventPriority ( 1 , высокий прио- ритет), NormalEventPriority ( 0 , обычный приоритет — значение по умолчанию) и LowEventPriority ( -1 , низкий приоритет). Этот метод является потокобезопасным, сле- довательно, его можно использовать в многопоточных приложениях для обмена собы- тиями между потоками. В параметре указывается ссылка на объект, которому посылается событие, а в параметре — объект события. Последний может быть экземпляром как стандарт- ного (например, QMouseEvent ), так и пользовательского класса, являющегося наследником класса QEvent . Вот пример отправки события QEvent.MouseButtonPress компоненту label : e = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, QtCore.QPointF(5, 5), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) QtCore.QCoreApplication.sendEvent(self.label, e) Для отправки пользовательского события необходимо создать класс, наследующий QEvent В этом классе следует зарегистрировать пользовательское событие с помощью статического метода registerEventType() и сохранить идентификатор события в атрибуте класса: Глава 19. Обработка сигналов и событий 433 class MyEvent(QtCore.QEvent): idType = QtCore.QEvent.registerEventType() def __init__(self, data): QtCore.QEvent.__init__(self, MyEvent.idType) self.data = data def get_data(self): return self.data Вот пример отправки события класса MyEvent компоненту label : QtCore.QCoreApplication.sendEvent(self.label, MyEvent("512")) Обработать пользовательское событие можно с помощью методов event(self, или customEvent(self, : def customEvent(self, e): if e.type() == MyEvent.idType: self.setText("Получены данные: {0}".format(e.get_data())) ГЛ А В А 20 Размещение компонентов в окнах При размещении в окне нескольких компонентов возникает вопрос их взаимного располо- жения и минимальных размеров. Следует помнить, что по умолчанию размеры окна можно изменять, а значит, необходимо перехватывать событие изменения размеров и производить перерасчет позиции и размера каждого компонента. Библиотека PyQt избавляет нас от лиш- них проблем и предоставляет множество компонентов-контейнеров, которые производят такой перерасчет автоматически. Все, что от нас требуется, это выбрать нужный контейнер, добавить в него компоненты в определенном порядке, а затем поместить контейнер в окно или в другой контейнер. 20.1. Абсолютное позиционирование Прежде чем изучать контейнеры, рассмотрим возможность абсолютного позиционирования компонентов в окне. Итак, если при создании компонента указана ссылка на родительский компонент, то он выводится в позицию с координатами (0, 0) . Иными словами, если мы добавим несколько компонентов, то все они отобразятся в одной и той же позиции, нало- жившись друг на друга. Последний добавленный компонент окажется на вершине этой кучи, а остальные компоненты станут видны лишь частично или вообще не видны. Размеры добавляемых компонентов будут соответствовать их содержимому. Для перемещения компонента можно воспользоваться методом move() , а для изменения размеров — методом resize() . Выполнить одновременное изменение позиции и размеров позволяет метод setGeometry() . Все эти методы, а также множество других, позволяющих изменять позицию и размеры, мы уже рассматривали в разд. 18.3 и 18.4. Если компонент не имеет родителя, эти методы изменяют характеристики окна, а если родительский компо- нент был указан, они изменяют характеристики только самого компонента. Для примера выведем внутри окна надпись и кнопку, указав позицию и размеры для каждо- го компонента (листинг 20.1). Листинг 20.1. Абсолютное позиционирование # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Абсолютное позиционирование") Глава 20. Размещение компонентов в окнах 435 window.resize(300, 120) label = QtWidgets.QLabel("Текст надписи", window) button = QtWidgets.QPushButton("Текст на кнопке", window) label.setGeometry(10, 10, 280, 60) button.resize(280, 30) button.move(10, 80) window.show() sys.exit(app.exec_()) Абсолютное позиционирование имеет следующие недостатки: при изменении размеров окна необходимо самостоятельно пересчитывать и изменять характеристики всех компонентов в программном коде; при указании фиксированных размеров надписи на компонентах могут выходить за их пределы. Помните, что в разных операционных системах используются разные стили оформления, в том числе и характеристики шрифта. Подогнав размеры в одной опера- ционной системе, можно прийти в ужас при виде приложения в другой операционной системе, где размер шрифта в два раза больше. Поэтому лучше вообще отказаться от указания фиксированных размеров или задавать размер и название шрифта для каждого компонента явно. Кроме того, приложение может поддерживать несколько языков ин- терфейса, а поскольку длина слов в разных языках различается, это также станет причи- ной искажения компонентов. |