📜 ⬆️ ⬇️

Creating Qt + PySide Hybrid Applications

Perhaps you know about PySide, python binding to Qt. There is also a PyQt library that provides similar functionality but unfortunately is limited to the GPL or the commercial version. PySide is distributed under the LGPL license, which makes it convenient to use in commercial applications.

Python makes it possible to create working applications very quickly and PySide just provides such an opportunity for Qt developers. In my experience, creating similar functionality is possible 1.5-2 times faster than in C ++.

Often there is a need to quickly connect a C ++ library (Qt-like interface) to a PySide application, or vice versa the task is to use Python scripts in Qt (C ++) applications. In both cases, you need automatic support for API classes from Qt and the ability to signal-slot connections between Qt (C ++) and Python (PySide) parts of the application. Unfortunately, this question is practically not covered and it is quite difficult to do everything and immediately working.
')
So, we will create a hybrid application consisting of C ++ and Python parts at the same time and using Qt as the main GUI library.

We need Qt, Python, and PySide installed.
Create the following directory and file structure:
 HybridApp /
  | -data /
  |  | -global.h
  |  | -typesystem.xml
  | -hybrid /
  |  | -MainWindow.h
  |  | -MainWindow.cpp
  |  | -hybrid.pro
  | -hybridpy /
  |  | -hybridpy.pro
  | -build.sh
  | -Main.py


The hybrid directory contains the C ++ part of the application that will be compiled into dll / dylib / so
Katalog hybridpy contains the wrapper of the C ++ part that will be imported into the Python part of the application.
The data directory contains descriptions of the types (types system) used in the C ++ module. This is an Xml file describing the types of objects and their features when they are represented as Python objects. More information on creating typesystems can be found on the PySide website ( http://www.pyside.org/docs/shiboken/contents.html ).

In the hybrid directory, create a C ++ (Qt) part of the application. Let's create the simplest QMainWindow application:
hybrid / hybrid.pro :
TEMPLATE = app CONFIG += qt QT += core gui UI_DIR = build RCC_DIR = build MOC_DIR = build OBJECTS_DIR = build HEADERS += MainWindow.h SOURCES += MainWindow.cpp Main.cpp 

hybrid / Main.cpp :
 #include <QtGui> #include "MainWindow.h" int main(int argc, char ** argv) { QApplication app(argc, argv); MainWindow window; window.resize(1000,700); window.show(); return app.exec(); } 

hybrid / MainWindow.h :
 #ifndef MainWindow_H #define MainWindow_H #include <QMainWindow> class QPushButton; class QGraphicsView; class QGraphicsScene; class QPlainTextEdit; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget * parent = 0L); virtual ~MainWindow(); signals: void runPythonCode(QString); private slots: void runPythonCode(); public: QGraphicsView * viewer; QGraphicsScene * scene; QPlainTextEdit * editor; QPushButton * pb_commit; }; #endif // MainWindow_H 

hybrid / MainWindow.cpp :
 #include <QtGui> #include "MainWindow.h" MainWindow::MainWindow(QWidget * parent):QMainWindow(parent) { QSplitter * splitter = new QSplitter; setCentralWidget(splitter); QWidget * editorContent = new QWidget; splitter->addWidget(editorContent); QVBoxLayout * layout = new QVBoxLayout; editorContent->setLayout(layout); editor = new QPlainTextEdit; layout->addWidget(editor); pb_commit = new QPushButton(tr("Commit")); connect(pb_commit, SIGNAL(clicked()), this, SLOT(runPythonCode())); layout->addWidget(pb_commit); scene = new QGraphicsScene(this); viewer = new QGraphicsView; viewer->setScene(scene); splitter->addWidget(viewer); splitter->setSizes(QList<int>() << 400 << 600); } MainWindow::~MainWindow() {;} void MainWindow::runPythonCode() { emit runPythonCode(editor->toPlainText()); } 

After compiling we have an application with an editor in the left panel and a Canvas on the right:
image

We can prepare this part for PySide:
1. Modify the pro file to build dll / dylib / so
2. Create a wrapper for the MainWindow class
3. Use Main.py instead of C main () input function

Changing the pro file is quite simple: change TEMPLATE to lib and remove main.cpp from the list, also put TARGET at the root of our application (HybridApp, project root)
hybrid / hybrid.pro :
 TEMPLATE = lib TARGET = ../Hybrid CONFIG += qt QT += core gui UI_DIR = build RCC_DIR = build MOC_DIR = build OBJECTS_DIR = build HEADERS += MainWindow.h SOURCES += MainWindow.cpp 

The result will be libHybrid.dylib in the HybridApp folder (project root)
Now create a wrapper for the C ++ part of the application. The build will be using the build.sh script in the project root. (The script will collect everything: C ++ part and wrapper)
build.sh :
 #!/bin/sh cd hybrid qmake make cd .. cd hybridpy QTGUI_INC=/Library/Frameworks/QtGui.framework/Versions/4/Headers QTCORE_INC=/Library/Frameworks/QtCore.framework/Versions/4/Headers QTTYPESYSTEM=/usr/local/share/PySide/typesystems generatorrunner --generatorSet=shiboken \ ../data/global.h \ --include-paths=../hybrid:$QTCORE_INC:$QTGUI_INC:/usr/include \ --typesystem-paths=../data:$QTTYPESYSTEM \ --output-directory=. \ ../data/typesystem.xml qmake make cd .. rm -rf PyHybrid.so ln -s libPyHybrid.dylib PyHybrid.so 

All wrapper creation is a single generatorrunner call with a path to Qt headers, Qt typesystems and our typesystem (type description). Then in the folder hybridpy call qmake and build the python module.
data / typesystem.xml :
 <?xml version="1.0"?> <typesystem package="PyHybrid"> <load-typesystem name="typesystem_core.xml" generate="no"/> <load-typesystem name="typesystem_gui.xml" generate="no"/> <object-type name="MainWindow"/> </typesystem> 


data / global.h :
 #undef QT_NO_STL #undef QT_NO_STL_WCHAR #ifndef NULL #define NULL 0 #endif #include <MainWindow.h> 

And the pro file to build the python module:
hybridpy / hybridpy.pro :
 TEMPLATE = lib QT += core gui INCLUDEPATH += hybrid INCLUDEPATH += ../hybrid INCLUDEPATH += /usr/include/python2.6 INCLUDEPATH += /usr/local/include/shiboken INCLUDEPATH += /usr/local/include/PySide INCLUDEPATH += /usr/local/include/PySide/QtCore INCLUDEPATH += /usr/local/include/PySide/QtGui LIBS += -ldl -lpython2.6 LIBS += -lpyside LIBS += -lshiboken LIBS += -L.. -lHybrid TARGET = ../PyHybrid SOURCES += \ pyhybrid/pyhybrid_module_wrapper.cpp \ pyhybrid/mainwindow_wrapper.cpp \ 

Perhaps for this assembly it is better to use cmake with which you can automatically find the paths to shiboken, etc., but to illustrate which files are used, a qmake project was chosen.

It looks almost ready - after running build.sh, we have a python module with a C ++ part of the application. The moment to connect both parts in Main.py :
Main.py :
 import sys from PySide.QtCore import * from PySide.QtGui import * from PyHybrid import * class RunScript(QObject): def __init__(self, mainWindow): QObject.__init__(self) self.mainWindow = mainWindow def runScript(self, script): mainWindow = self.mainWindow exec(str(script)) a = QApplication(sys.argv) w = MainWindow() r = RunScript(w) w.setWindowTitle('PyHybrid') w.resize(1000,800) w.show() a.connect(w, SIGNAL('runPythonCode(QString)'), r.runScript) a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()') ) a.exec_() 

After running this python script, we will get the same window of our C ++ application, but with some difference - now the python code entered in the editor can be executed directly in the application itself with access to C ++ application objects.

Let's try a small code:
 mainWindow.statusBar().show() 

and our application has a status bar.
Change the background Canvas?
 mainWindow.scene.setBackgroundBrush(QColor('#e0e0ff')) 

We can create objects:
 li1 = QGraphicsLineItem(10,10, 500,500) li1.setPen(QPen(QBrush(QColor("#ff0000")), 3.0, Qt.DashLine)) mainWindow.scene.addItem(li1) 

Further, we can create new classes right at runtime, and why not?
 mainWindow.viewer.setRenderHint(QPainter.Antialiasing) class MyItem(QGraphicsItem): def boundingRect(self): return QRectF(-100,-100,200,200) def paint(self, painter, option, widget): g = QLinearGradient(-100,-100, 100,100) g.setColorAt(0, QColor('#00ff00')) g.setColorAt(1, QColor('#ffffff')) painter.setBrush(g) p = QPen(QBrush(QColor("#ff0000")), 4, Qt.DashLine) painter.setPen(p) painter.drawRoundedRect(-100,-100,200,200, 30,30) my1 = MyItem() mainWindow.scene.addItem(my1) my1.setPos(200,200) 

image

Our small application received a small unique opportunity: we can program it directly at runtime, and this programming is not limited to existing APIs, but we can create our Python classes to pass them to the C ++ part where they have a common, for example QOBject / QWidget , interface, can be processed and used.

The use of Qt + PySide hybridization is quite wide - the development of games, where you often need the opportunity to correct something right at runtime, the scripting for objects in games. Then, using PySide, we can quickly create new classes, test ideas, and then transfer them to C ++ (or fairly quickly transfer only fragments that are critical to speed to C ++).

As for the above application, it demonstrates a slightly different approach to programming when an application is encoded and modified right at runtime. Of course, you need to make automatic saving of all executed fragments into files or all code will disappear after the application is stopped.

Source: https://habr.com/ru/post/120479/


All Articles