PyQt4 and QML

More recently, the developers of the Qt Software framework have pleased us with the appearance of a GUI alternative to the standard one, with its own, quite uncomplicated, markup language - QML.
A bundle of QML with the main program is the Qt Declarative module. Starting with version 4.7 - PyQt4 supports this module.
QML is much simpler and more flexible than the main GUI, in addition it is also a programming language, as it allows you to write functions in javascript. While Python is a fairly simple and flexible interpreted language.


Let's start


First a QML form. It is completely ready and, at the same time, efficient, because when the program starts, errors in it do not stop its work. Some parts of the code will be discussed later.

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()
	    }
    }
}


In this case, the form will be saved with the name "form.qml", in the same directory as the python file.
Now print this form using PyQt. To do this, the QtDeclarative module has a QDeclarativeView element. It inherits the properties and functions of QWidget, so it can be either a separate window or built-in as a child, respectively, and has a connect method.

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_()) 


The result is a stripped-down qmlviewer.
Further, for the convenience and operability of some methods, we will create a class that inherits QDeclarativeView, slightly change the appearance of the form, and, as befits top-down programming, create additional “stub” functions that will be called when the class is initialized.
At startup, just a black rectangle will appear. (Since there is no window frame, it can only be closed from the taskbar).

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_())     


Access to QML


Let's start by filling in the signal_func_Qml function. QDeclarativeView has access to the structure of the QML file that it loaded using the rootObject () method. This method returns the root object. Accordingly, we can manipulate the functions and signals of this file. We cannot directly assign a property to any QML element. (Even if they could, it would be wiser to do this through a function).
So in the QML file we already have the wantquit signal, which is sent by double-clicking on the space of the root widget. And the updateMessage function, which writes the text passed to it into a text widget.


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)


This is how the function will be filled. In line number (1), we get the root object in the local root variable, in line (2) we attach the application termination function to the wantquit signal, in line (3) we execute the updateMessage function. It is worth noting that the string values ​​passed to the QML file must be converted to the QString type, since the regular str type will not accept the QML file, but there are no such problems with the numeric formats.
Similarly, you can process the signal sent by the Python file class. To do this, we write in the class I_will_be_form, the inited signal (before initializing the class and at the same level with it):

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


Also fill in the signalThis function:


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)


In line (1) we again get the root object (since in the previous function it was in a local variable). In line (2) we bind the updateMessage function of the QML file to the inited signal. Accordingly, the text that sends the signal will be passed to the function as a parameter. In line (3) we send a signal with the text “I'm ready!”. (Again, do not forget to transfer to QString, although it is not necessary here, but still it would be nice to play it safe again).

Access to PyQt


In addition to access from PyQt to QML, there is also the opposite possibility. Let's start by populating the slot function.


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


In both lines, we open access from QML to the PyQt object. Only in the first case, the functions of the self object (here it is I_will_be_form) become the functions of the QML root widget, and the functions of the I_will_be_form class are accessed by their names. In the second case, the class I_will_be_form becomes the widget of the root object with the identifier main, and access to the functions is main. <Function name>, which eliminates name conflicts and simplifies the understanding of the code. But access is still not open to all functions.
Initially, QML is adapted for C ++, which is a strongly typed language, and its classes have concepts such as private, public and protected. In Python, there is neither typification nor these concepts. PyQt developers had to fix this problem. So, we will describe a certain function all in the same class I_will_be_form:


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


The function moves the QDeclarativeView window in accordance with the transmitted x and y coordinates. Now let's pay attention to line (1), it makes our function a slot, which makes it possible to access this function from QML, but in this case it cannot return a value.
As a result, the QML code fragment in the onPositionChanged block begins to make sense, it passes the values ​​to the form_move function so that it moves the window, and, when launched, by holding the mouse button on the rectangle, you can move it.
Now fill in the prop function.


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


We also additionally describe another Someone class before I_will_be_form and immediately initialize it in the global variable 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()


First, consider the prop function itself: similarly to the previous function, access to the object is opened, but this time it is the Someone class. Note that it necessarily inherits the properties of QObject, otherwise QML will not accept this class. Now let's move on to the some_id function, which, unlike the previously considered form_move, returns a value. The line with the number (1) describes the type of this value and at the same time open access to this function from QML. Again, the value type is QString instead of str.
Now the onClicked block in the QML file works, by clicking on the rectangle its text changes.

Conclusion


In my opinion, you should mainly use access from PyQt to QML, since this does not clutter up the QML code as in the second case. Yes, and this method is much simpler and requires less code, which improves the readability of the program.
Python
Code QML Code

UPD: Sorry for missing padding. I didn’t know that code wouldn’t recognize them, but I hadn’t paid attention, but had already fixed it.

Also popular now: