Introduction à Qt Gilles Bailly gilles.bailly@upmc.fr Crédits • Eric Lecolinet 2 • hci.isir.upmc.fr/ihm-androide/ 3 http://pyqt.sourceforge.net/Docs/ PyQt5/index.html 4 Introduction 1 What ? Librairie graphique écrite en C++ par la société TrollTech. – Mécanisme pour interagir : avec l’utilisateur (bouton, liste déroulante, ..) avec le système (OpenGl, Xml, SQL, sockets, plugin…) Multi-Plateforme – Windows | Mac | Linux – Android | iOS | WinRT | BlackBerry* | SailFish – Embedded Linux | Embedded Android | Windows Embedded Gratuit (GPL), - mais dispose aussi d’une licence commerciale Approche : – Ecrire une fois, compiler n’importe où 7 Historique • 1988 : Haavard & Eirik ont l’idée de créer une librairie graphique orientée objet • 1993 : Le noyau est terminé et ont pour objectif « The world’s best C++ GUI framework » – Nom Qt, signaux et slots • 2001 : Qt 3.0, 500 000 lignes de codes, Linux, Windows, Mac. • 2008 : Qt 4.5 (racheté par Nokia; 250 employés) – la plateforme Symbian • 2009 : Qt 4.6 : animation; GraphicScene; machine à état; gestures • 2011 : Qt est racheté par Digia – objectif : Android, iOS et Windows 8 • 2012 : Qt 5.0 : Qt Quick (javascript) • 2016 : Qt compagny Why? • • • • Performance (C++) Relativement Simple Gratuit (GPL) et code source Nombreux outils – – – – – Générateur d’interface : Qt Designer Internationalisation : Qt Linguist Documentation : Qt Assistant Examples : Qt Examples Programmation : Qt Creator (eclipse) Qt creator • Multi-Plateformes – Look and feel simulé Designer 10 Aujourd'hui • Utilisateurs de Qt : – Adobe Photoshop, Autodesk Maya, Google Earth, Skype, Spotify, VLC media player • Bindings (java, python, c#) 12 13 14 Aujourd'hui Qt Widgets Qt Graphics view Qt quick /QML 15 Aujourd'hui v.s. Qt Widgets Qt Graphics view ▪ Widgets cannot be scaled or rotated, ▪ Widgets can only appear once on the desktop, but several views can observe one graphicsitem. ▪ Widgets express their geometries in pixels, graphics items use logical units. (…int vs. double) ▪ Widgets support tons of features that graphics items don’t understand. ▪ Widgets understand layouts, ▪ 4000000 widgets don’t work that well, but 4000000 items works perfectly. 16 17 Aujourd'hui v.s. Qt Widgets Qt quick/QML QML is based on JSON (Language); QtQuick (library) Widgets are more mature, flexible and provide rich features Language déclaratif Qt Quick focuses on animation and transition Qt Quick is mainly for mobile devices (today) Qt Quick will (maybe) remplace Widgets tomorrow Qt Quick is maybe easier to use for designers 18 19 20 Aujourd'hui v.s. QGraphicsView Qt quick/QML Qt Quick Graphics engine only works with OpenGL Drawing complex shapes easier with QGraphicView Qt Quick: QML & Javascript QML is more compatible with Widget Language déclaratif 21 Qt (QML) vs. HTML 5 22 Objectifs Introduction – Signaux et slots – Les principales classes Qt Graphisme avancé – Création de vos propres widgets – Programmation événementielle – Notion avancée de graphisme avec Qt Quelques autres notions avancées – – – – Machine à états Gestes Animation Qt Designer Au delà de Qt … Syntaxe Syntaxe C int factorielle(int n) { if (n < 2) { return 1; } else { return n * factorielle(n - 1); } } Syntaxe Python def factorielle(n): if n < 2: return 1 else: return n * factorielle(n - 1) Attention aux tabulations !!! Typage dynamique fort int a = 4 a=4 type(a) <class 'int'> a = 4.1 type(a) <class 'float'> Classes class Voiture: #commentaire nbRoues=4 ➡ Déclaration d’une classe def __init__(self,marque,couleur): ➡ Déclaration d’un constructeur self.couleur=couleur ➡ Déclaration d’une variable d’instance self.marque=marque ➡ Déclaration d’une variable d’instance def main(): ➡ Déclaration conventionnelle d’une méthode main() audirouge = Voiture('audi','rouge') print(audirouge.couleur) if __name__ == "__main__": main(sys.argv) ➡ Appel automatique Main Mes premiers pas avec Qt 2 "Hello world" 28 from PyQt5.QtCore import * from PyQt5.QtWidgets import * import sys def main(args) : app = QApplication(args) button = QPushButton("Hello World !", None) button.resize(100,30) button.show() app.exec_() if __name__ == "__main__" : main(sys.argv) 29 from PyQt5.QtCore import * from PyQt5.QtWidgets import * import sys def main(args) : app = QApplication(args) widget = QWidget(None) widget.resize(150,90) button = QPushButton("Hello World !", widget) button.resize(100,30) widget.show() app.exec_() if __name__ == "__main__" : main(sys.argv) 30 from PyQt5.QtCore import * from PyQt5.QtWidgets import * import sys def main(args) : app = QApplication(args) widget = QWidget(None) widget.resize(150,90) button = QPushButton("Hello World !", widget) button.resize(100,30) widget.show() button.clicked.connect(app.quit) app.exec_() if __name__ == "__main__" : main(sys.argv) 31 Signaux et Slots 3 Problématique • Comment – à partir d’un « clic sur un bouton » – Je peux éxécuter la partie correspondant à la logique de mon application ? (ex: fermer l’application) • Solutions – MFC (introduit un langage au dessus de C++) – Java (utilise des listeners) – Qt (utilise principalement des signaux et slots) Connecter Signaux et Slots class QSlider(QObject): …. def mousePressEvent(self): self.valueChanged.emit(value) … class QLCDNumber(QObject): def display(num): m_value = num; … lcd = QLCDNumber(none) slider = QSlider(None) 42 Signal émis connexion 42 Slot implémenté slider.valueChanged.connect(lcd.display) Une classe avec des signaux et des slots class MyClass(QObject): mySignal = pyqtSignal(int) def mySlot( self, num ): blabla • Sous class de QObject • Les signaux sont pas implémentés • Les slots doivent êtres implémentés Une classe avec des signaux et des slots class MyClass(QObject): mySignal = pyqtSignal(int) def __init__(self, parent =None): super(MyClass, self).__init__(parent) def mySlot( num ): blabla • Sous class de QObject • Les signaux sont pas implémentés • Les slots doivent êtres implémentés Signaux et Slots • Modularité, flexibilité – Connecter plusieurs signaux à un slot – Connecter un signal à plusieurs slots • Philosophie – – – – L’émetteur n’a pas besoin de connaître le(s) récepteur(s) L’émetteur ne sait pas si le signal a été reçu Le récepteur ne connaît pas non plus l’émetteur Programmation par composant (indépendant, réutilisable) • Sécurité, typage fort (en C++) – Les types des paramètres doivent être les mêmes – Un slot peut avoir moins de paramètres qu’un signal transfert d’argent entre deux banques BANK 1 BANK 2 BANK 1 BANK 2 38 transfert d’argent entre deux banques class BankAccount (QObject): balance = 0 name = “" balanceChanged = pyqtSignal(int) def setBalance(self, newValue): self.balance = newValue self.balanceChanged.emit( self.balance ) Signal Slot def main(args): account1 = BankAccount() account1.setName( “account1” ) account2 = BankAccount() account2.setName( “account2” ) account1.balancedChanged.connect( account2.setBalance ) account2.balancedChanged.connect( account1.setBalance ) if __name__ == “__main__”: main(sys.argv) Connexion Problème? transfert d’argent entre deux banques def setBalance(self, newValue): if self.balance != newValue: self.balance = newValue self.balanceChanged.emit( self.balance ) account1 <- 25 account1 emit balanceChanged(25) account2 <- 25 account2 emit balanceChanged(25) account1 <-25 … Un peu plus loin… class MyClass(QObject): mySignal = pyqtSignal(int) def __init__(self, parent =None): super(MyClass, self).__init__(parent) @pyqtSlot(int) def mySlot( num ): blabla parfois nécessaire • Sous class de QObject • Les signaux sont pas implémentés • Les slots doivent êtres implémentés • peuvent être décorés de @pyqtSlot réduit la quantité de mémoire / est plus rapide parfois utile pour les “auto-connect" 42 Encore un peu plus loin • les auto-connect class MyClass(QObject): def __init__(self, parent =None): self.buttonA = QPushButton(‘button 1’, self) self.buttonA.setObjectName('buttonA) self.spinBox = QSpinBox(self) self.spinBox.setObjectName('mySpinBox') …. QMetaObject.connectSlotsByName(self) def on_buttonA_clicked(self): blabla @pyqtSlot(int) def on_mySpinBox_valueChanged(self, arg): blabla 43 Questions Comment connecter un signal à un slot ? • EmetteurObj.<nameSignal>.connect ( Recepteur.<nameSlot> ) Quel code pour déclarer / implémenter un slot ? • rien de particulier (mais on peut rajouter @pyqtSlot() ) Est ce qu’un slot peut retourner une valeur ? • Oui Quel code pour déclarer / implémenter un signal ? • mySignal = pyqtSignal() Ce qui se cache derrière • en C++ c’est plus compliqué 45 Les principaux widgets Modules • • • • • • • • • • • • QtCore QtWidgets QtBluetooth QtOpenGL QtSript/QtScriptTools QtSql QtSvg QtWebKit QtXml/QtXmlPatterns QtMultimedia QtSensors … QtCore • QObject • Type de base : QChar, QDate, QString, QStringList, Qtime,… • File systems : QDir,QFile,… • Container : QList, Qmap, Qpair, QSet, QVector,… • Graphique : QLine, Qpoint, QRect, QSize … • Thread : QThread, Qmutex, Qsemaphore, … • Autres : QTimer, QTimeLine, … QString Codage Unicode 16 bits - Suite de QChars • 1 caractère = 1 QChar de 16 bits (cas usuel) • 1 caractère = 2 QChars de 16 bits (pour valeurs > 65535) - Conversions d’une QString : • toAscii( ) : ASCII 8 bits • toLatin1( ) : Latin-1 (ISO 8859-1) 8 bits • toUtf8( ) : UTF-8 Unicode multibyte (1 caractère = 1 à 4 octets) • toLocal8Bit( ) : codage local 8 bits - qPrintable ( const QString & str ) • équivalent à : str.toLocal8Bit( ).constData( ) QFile ouverture/fermeture de fichiers exemples : • file = QFile( fileName ) • if file.open( QFile.ReadOnly | QFile.Text) : • if file.open( QFile.WriteOnly ): • file.write( “fdsafsdf”) • file.flush() • file.read() • file.close() QTextStream lecture, écriture dans fichiers exemples : • stream = QTextStream( file ) • txtAll = stream.readAll() ou txtLine = stream.readLine() • stream.write QTextStream QTextStream - lecture ou écriture de texte depuis un QFile : • stream = QTextStream( file ); - méthodes utiles : • QString readLine( taillemax = 0 ); • QString readAll( ); // pas de limite de taille si = 0 // pratique mais à n’utiliser que pour des petits fichiers QtGUI QStyle • main(args) app = QApplication( args) ./python monAppli –style plastique Ex: windows, motif, platinum,… QMainWindow - Méthode 1: créer une instance de QMainWindow win = QMainWindow() win.resize(200,300) - Méthode 2: créer une sous-classe de QMainWindow class Win(QMainWindow): def __init__(self): self.resize(200,300) QMainWindow : Menus • bar = self.menuBar() • fileMenu = bar.addMenu( "File" ) • • • • newAct = QAction(QIcon("path/images/new.png"), “New…”, self ) newAct.setShortcut( “Ctrl+N” ) newACT.setToolTip(self.tr(“New File”)) newAct.setStatusTip(self.tr(“New file”)) • fileMenu.addAction(newAct) • newAct.triggered.connect( self.new ) si sous-classe (methode 2) sinon win.menuBar() (methode 1) QMainWindow • QMenuBar, QMenu, QAction • QToolBar – – – – fileToolBar = QToolBar("File") fileToolBar.addAction(newAct) mainWindow.addToolBar(fileToolBar) newAct.setEnabled( false ) • QToolTip, QWhatsThis • Composant central textEdit = TextEdit( self ); self.setCentralWidget( textEdit ); grise la commande dans les menus et la toolbar Buttons 57 Input Widgets 58 Containers QMidArea QScrollArea QTabWidget QGroupBox QToolBox QWidget; QFrame; QDockWidget; QStackedWidget 59 Views QCompleter 60 standard widgets use data that is part of the widget View classes operate on external data (the model) 61 class MyModel(QAbstractTableModel): def __init__(self): QAbstractTableModel.__init__(self) self.myData = <dataBase> def rowCount( self, parent ): return 2 #Type (parent) == QModelIndex def columnCount( self, parent ): return 2 def data( self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: return self.myData(index.row() + 1, index.column()+1) def main(args): app = QApplication(args) tableView = QTableView() myModel = MyModel() tableView.setModel( myModel ) tableView.show() app.exec() 62 Display Widgets 63 Boite de dialogue QProgressDialog QFileDialog QMessageBox QColorDialog QFontDialog Boîte de dialogue modale Solution simplifiée fileName = QFileDialog.getOpenFileName( self, "Open Image”, "/home/jana", "*.txt") fileName = QFileDialog.getSaveFileName(…) //parent // titre // répertoire initial // filtre Layout Problèmes - internationalisation - redimensionnement - complexité du code Layout QFormLayout QHBoxLayout QVBoxLayout QGridLayout Layout : exemple v_layout = QVBoxLayout( ) v_layout.addWidget( QPushButton( "OK" ) ) v_layout.addWidget( QPushButton( "Cancel" ) ) v_layout.addStretch( ) v_layout.addWidget( QPushButton( "Help" ) ) country_list = QListBox( ); countryList.insertItem( "Canada" ); ...etc... h_layout = QHBoxLayout( ) h_layout.addWidget( country_list ) h_layout.addLayout( v_layout ) top_layout = QVBoxLayout( ) top_layout.addWidget( QLabel( "Select a country" ) ) top_layout.addLayout( h_layout ); container = QWidget() container.setLayout( top_layout ) win.setCentralWidget(container) win.show( ) Notes sur layouts : - peuvent être emboîtés - pas liés à une hiérarchie de conteneurs comme Java - cf. le « stretch » Arbre d’héritage vs. arbre d’instanciation Arbre d’héritage Principaux widgets : Arbre d’héritage QCheckBox QWidget QAbstractButton QRadioButton QToolButton QPushButton Hiérarchie de classes (type) - chaque sous-classe hérite des variables et méthodes de sa superclasse - du plus général au plus particulier - héritage simple Arbre d’instanciation • Hiérarchie d’instance (=objets) – Arbre de filiation des objets Arbre d’instanciation • Hiérarchie d’instance (=objets) – Arbre de filiation des objets Arbre d’instanciation • Les enfants se déclarent auprès de son parent (≠ java) – label = Qlabel("Hello", parent); – Execptions • QFIle, QApplication… • Si le parent d’un Widget est nul, le Widget est une fenêtre (Window). • Que font les parents ? – – – – Ils ont une liste des enfants Ils détruisent automatiquement les enfants quand ils sont détruits Enable/disable les enfants quand ils enable/disable eux memes Pareil pour Show/Hide Arbre d’instanciation • Hiérarchie d’instance (=objets) – Arbre de filiation des objets • Chaque objet contient ses enfants – Clipping : enfants inclus dans parents – Superposition : enfants au dessus des parents • Un objet n’a qu’un seul parent Modules • • • • • • • • • • • QtCore QtWidgets QtBluetooth QtOpenGL QtSript/QtScriptTools QtSql QtSvg QtWebKit QtXml/QtXmlPatterns QtMultimedia QtSensors QtNetwork • QFtp, QHttp, QTcpSocket, QUdpSocket OpenGL : Box3D #include <QGLWidget> class Box3D : public QGLWidget { Q_OBJECT GLuint object; GLfloat rotX, rotY, rotZ; public: Box3D( QWidget *parent = 0); virtual ~Box3D(); protected: virtual void initializeGL(); virtual void paintGL(); virtual void resizeGL( int w, int h ); virtual GLuint makeObject(); public slots: void setRotationX(int deg) { rotX = deg; updateGL( ); } void setRotationY(int deg) { rotY = deg; updateGL( ); } void setRotationZ(int deg) { rotZ = deg; updateGL( ); } }; modules • • • • • • • • • • • QtCore QtGui QtNetwork QtOpenGL QtSript/QtScriptTools QtSql QtSvg QtWebKit QtXml/QtXmlPatterns Phonon Qt3Support Questions • Quelle est la différence entre l’arbre d’héritage et l’arbre d’instanciation? • héritage de classe (A hérite de B) • héritage d’instances (A contient B) • Quelle classe utiliser pour créer une fenêtre / Comment ajouter le widget central? • QMainWindow • win.setCentralWidget( myWidget ) • Quels sont les différents layouts? • QVBoxLayout; QHBoxLayout; QGridLayout; QFormLayout • Quelle classe pour un bouton? • QPushButton Outils Qt Les outils Qt • • • • • • Qt Creator Qt Assistant Qt Examples QtDemo Qt resources Qt Designer The Qt Resource System • Mechanism for storing binary files in the application’s executable • Useful if your application needs a certain set of files (icons, translation files) and you don’t want to run the risk of losing the files resources.qrc • How to use: • pyrcc5 - o resources.py resources.qrc • import resources 83 Qt Designer 84 Qt Designer • sauvegarder le projet en dialog.ui (fichier xml) • transformer en fichier python: pyuic5 dialog.ui -o dialog.py • Insérer la boite de dialogue dans votre code import dialog class Dialog(QDialog, dialog.UI_Dialog): def __init__(self, parent = None): super(Dialog, self).__init__(parent) self.setupUI(self) 85