Pull to refresh

PyQt4 и QML

Reading time6 min
Views13K
Совсем недавно, разработчики фреймворка Qt Software, обрадовали нас появлением GUI, альтернативного стандартному, со своим, довольно несложным, языком разметки — QML.
Связкой QML с основной программой является модуль Qt Declarative. Начиная с версии 4.7 — PyQt4 поддерживает этот модуль.
QML значительно проще и гибче основного GUI, помимо того является и языком программирования, так как позволяет писать функции на javascript. В то время как Python довольно простой и гибкий интерпретируемый язык.


Начнем


Сначала QML форма. Она полностью готова и, в то же время, работоспособна, поскольку, при запуске программы, ошибки в ней не прекращают ее работу. Позже будет рассмотрены некоторые части кода.

import Qt 4.7
Rectangle {
    //Описание сигнала
    signal wantquit 

    property int qwX: 0;  property int qwY: 0
    property int owX: 0;    property int owY: 0 
    property bool first: true	

/*Функция передающая текст для вывода текстовым виджетом*/
    function updateMessage(text) {
        messageText.text = text
    }
    anchors.fill: parent; color: "black"

//Текстовый виджет
    Text {
        id: messageText; anchors.centerIn: parent; color: "white"
    }

 //Обработка событий вызванных мышью
    MouseArea {
        anchors.fill: parent
        
        onClicked: 
        {
        
        //Взятие текста методом класса файла python
        messageText.text = someone.some_id
        
        first = true
        }
        
        onPositionChanged: 
        {
		owX = first? mouseX : owX
		owY = first? mouseY : owY
		first = false
		qwX+=mouseX-owX
		qwY+=mouseY-owY
		
		//Перемещение окна
		main.form_move(qwX, qwY)
	    }
	    
                  onDoubleClicked:
	    {
	    //Отправка сигнала
	    wantquit()
	    }
    }
}


В этом случае форма будет сохранена с именем «form.qml», в одном директории с файлом python.
Теперь выведем эту форму с помощью PyQt. Для этого в модуле QtDeclarative есть элемент QDeclarativeView. Он наследует свойства и функции QWidget, поэтому может быть как отдельным окном, так и встроенным в качестве потомка, соответственно и обладает методом connect.

from PyQt4 import QtCore, QtGui, Qt, QtDeclarative

import sys
app = QtGui.QApplication(sys.argv)

# Создание QML формы
view = QtDeclarative.QDeclarativeView()
view.setSource(QtCore.QUrl('form.qml'))
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
view.setGeometry(100, 100, 400, 240)
view.show()
sys.exit(app.exec_()) 


В итоге получился урезанный qmlviewer.
Далее для удобства и работоспособности некоторых методов, создадим класс наследующий QDeclarativeView, немного изменим внешний облик формы и, как подобает нисходящему программированию, создадим дополнительные функции «заглушки», которые будут вызываться при инициализации класса.
При запуске появится просто черный прямоугольник. (Поскольку рамки окна нет, его можно будет закрыть только из панели задач).

from PyQt4 import QtCore, QtGui, Qt, QtDeclarative

class I_will_be_form(QtDeclarative.QDeclarativeView):    
    def __init__(self, parent=None):
        QtDeclarative.QDeclarativeView.__init__(self, parent)
        
        #Убираем рамку окна
        self.setWindowFlags(Qt.Qt.FramelessWindowHint) 
         
        self.setSource(QtCore.QUrl.fromLocalFile('form.qml'))
        self.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
        self.setGeometry(100, 100, 400, 240)
        
        self.signal_func_Qml()
        self.signalThis()
        self.slot()
        self.prop()
   
    def signal_func_Qml(self):     
        print "Qml's signal"
    
    def signalThis(self):   
        print "Signal of PyQt"   

    def slot(self):     
        print "Property"    

    def prop(self):     
        print "Slot "
        
import sys
app = QtGui.QApplication(sys.argv)
Iwbf = I_will_be_form()
Iwbf.show() 
sys.exit(app.exec_())     


Доступ к QML


Начнем с заполнения, функции signal_func_Qml. QDeclarativeView имеет доступ к структуре файла QML, который он загрузил, с помощью метода rootObject(). Этот метод возвращает корневой объект. Соответственно мы можем манипулировать функциями и сигналами этого файла. Напрямую присвоить какому-то элементу QML свойство мы не можем. (Даже если бы и могли, то было бы разумнее сделать это через функцию).
Итак в файле QML у нас уже описан сигнал wantquit, который посылается по двойному клику на пространство корневого виджета. И функция updateMessage, которая записывает переданный в нее текст в текстовый виджет.


def signal_func_Qml(self):     
    print "Qml's signal"
    root = self.rootObject() #(1)
    root.wantquit.connect(app.quit) #(2)
    root.updateMessage(QtCore.QString('From root')) #(3)


Вот так будет заполнена функция. В строке с номером (1), мы получаем корневой объект в локальную переменную root, в строке (2) мы привязываем к сигналу wantquit функцию завершения приложения, в строке (3) мы выполняем функцию updateMessage. Стоит отметить, что строковые значения, передаваемые QML файлу, должны быть обязательно переведены в тип QString, поскольку обычный тип str QML-файл не воспримет, с числовыми же форматами таких проблем нет.
Аналогично можно обработать и сигнал посланный классом файла Python. Для этого пропишем в классе I_will_be_form, сигнал inited (перед инициализацией класса и на том же с ней уровне):

inited = QtCore.pyqtSignal(str)    
def __init__(self, parent=None):
    ......


Так же заполним функцию signalThis:


def signalThis(self):   
    print "Signal of PyQt"
    root = self.rootObject() #(1)
    self.inited.connect(root.updateMessage) #(2)
    self.inited.emit(QtCore.QString("I'm ready!"))    #(3)


В строке (1) мы снова получаем корневой объект (поскольку в предыдущей функции он был в локальной переменной). В строке (2) мы привязываем сигналу inited функцию QML-файла updateMessage. Соответственно текст который отошлет сигнал будет передан функции в качестве параметра. В строке (3) мы посылаем сигнал с текстом «I'm ready!». (Опять же не забываем перебрасывать в QString, хотя тут и не обязательно, но все таки неплохо бы лишний раз перестраховаться).

Доступ к PyQt


Помимо доступа из PyQt в QML, также есть и обратная возможность. Начнем c заполнения функции slot.


def slot(self):     
    print "Property"
    self.engine().rootContext().setContextObject(self) #(1)
    self.engine().rootContext().setContextProperty('main', self) #(2)


В обеих строках мы открываем доступ из QML к объекту PyQt. Только в первом случае функции объекта self (здесь это I_will_be_form), становятся функциями корневого виджета QML, и доступ к функциям класса I_will_be_form осуществляется по их именам. Во втором же случае класс I_will_be_form становится виджетом корневого объекта с идентификатором main, и доступ к функциям соответственно main.<имя функции>, что исключает конфликты имен и упрощает понимание кода. Но доступ все равно открыт не ко всем функциям.
Изначально QML адаптирован под C++, который является строго типизированным языком, а также его классы имеют такие понятия как private, public и protected. В Python же нет ни типизации, ни этих понятий. Разработчикам PyQt пришлось исправить эту проблему. Итак опишем некую функцию всё в том же классе I_will_be_form:


 @QtCore.pyqtSlot(int, int)    #(1)
 def form_move(self, x, y):
      self.move(x, y)   


Функция передвигает окно QDeclarativeView в соответствии с переданными координатами x и y. Теперь обратим внимание на строку (1), она делает нашу функцию слотом, что дает возможность доступа к этой функции из QML, но в этом случае она не может возвращать значение.
В итоге фрагмент кода QML в блоке onPositionChanged начинает иметь смысл, он передает значения функции form_move, чтобы та осуществляла передвижение окна, и, при запуске, по зажатию кнопки мыши на прямоугольнике, можно его перемещать.
Теперь заполним функцию prop.


 def prop(self):     
     print "Slot"
     self.engine().rootContext().setContextProperty('someone', so)


А также дополнительно опишем еще один класс Someone перед I_will_be_form и сразу инициализируем его в глобальной переменной so.


class Someone(QtCore.QObject):
	def __init__(self):
	    QtCore.QObject.__init__(self)
	    self.my_id = QtCore.QString("I'm first")

	@QtCore.pyqtProperty(QtCore.QString) #(1)
	def some_id(self):
	    return self.my_id
		
so = Someone()


Сначала рассмотрим саму функцию prop: аналогично предыдущей функции открывается доступ к объекту, но на этот раз им является класс Someone. Обратим внимание на то, что он обязательно наследует свойства QObject, иначе QML не воспримет этот класс. Теперь перейдем к функции some_id, она в отличие от ранее рассмотренной form_move возвращает значение. В строке с номером (1) описан тип этого значения и одновременно открыт доступ к этой функции из QML. Опять же тип значения QString вместо str.
Теперь блок onClicked в QML файле работает, по клику на прямоугольник его текст меняется.

Заключение


На мой взгляд следует использовать в основном доступ из PyQt в QML, так как это не так загромождает QML-код, как во втором случае. Да и такой способ значительно проще и требует меньше кода, что улучшает читаемость программы.
Код Python
Код QML

UPD: Извеняюсь за пропавшие отступы. Незнал, что code их не распознает, а внимание не обратил, но уже исправил.
Tags:
Hubs:
Total votes 48: ↑45 and ↓3+42
Comments16

Articles