PySide

Comment produire des applications bureaux simplement et rapidement

1

QT : qu'est ce que c'est et ça sert à quoi ?

2

Une brève histoire des bindings QT : de C++ vers Python

3

Comment designer de manière simple son application ?

5

Exemple d'une application simple en PySide

4

Comment ajouter des fonctionnalités à son design ?

6

Comment finaliser le déploiement de son application ?

Sommaire

Qt

Qu'est ce que c'est & ça sert à quoi ?

à feur

Qu'est ce que QT ?

Design

1.

2.

Develop

3.

Test

# QT Philosophy

QT : Un SDK basé sur C++

Pourquoi choisir QT ?

Pourquoi ne pas choisir QT ?

  • Simple d'utilisation
  • Cross platform
  • Open Source
  • Grande communauté
  • Beaucoup d'outils
  • Rapidité de conception
  • Documentation
  • Exécution lente
  • Complexe pour certaines taches
  • Consommation mémoire importante

Documentation QT

# Documentation QT

Environnement QT

# Environnement QT

De C++ vers Python

# de C++ vers Python
Simple d'utilisation & Rapide de conception

PySide VS PyQT

Une brève histoire des bindings QT

de C++ vers Python

# Bindings QT

C'est quoi un Binding ?

En informatique, un "binding" est une interface de programmation d'application (API) qui permet d'executer du Code d'un langage

étranger depuis un autre langage

# Bindings QT

Pourquoi utiliser un Binding ?

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, QLineEdit, QGridLayout, QMessageBox)

class LoginForm(QWidget):
	def __init__(self):
		super().__init__()
		self.setWindowTitle('Login Form')
		self.resize(500, 120)

		layout = QGridLayout()

		label_name = QLabel('<font size="4"> Username </font>')
		self.lineEdit_username = QLineEdit()
		self.lineEdit_username.setPlaceholderText('Please enter your username')
		layout.addWidget(label_name, 0, 0)
		layout.addWidget(self.lineEdit_username, 0, 1)

		label_password = QLabel('<font size="4"> Password </font>')
		self.lineEdit_password = QLineEdit()
		self.lineEdit_password.setPlaceholderText('Please enter your password')
		layout.addWidget(label_password, 1, 0)
		layout.addWidget(self.lineEdit_password, 1, 1)

		button_login = QPushButton('Login')
		button_login.clicked.connect(self.check_password)
		layout.addWidget(button_login, 2, 0, 1, 2)
		layout.setRowMinimumHeight(2, 75)

		self.setLayout(layout)

	def check_password(self):
		msg = QMessageBox()

		if self.lineEdit_username.text() == 'Usernmae' and self.lineEdit_password.text() == '000':
			msg.setText('Success')
			msg.exec_()
			app.quit()
		else:
			msg.setText('Incorrect Password')
			msg.exec_()

if __name__ == '__main__':
	app = QApplication(sys.argv)

	form = LoginForm()
	form.show()

	sys.exit(app.exec_())
#ifndef _LoginForm_CPP
#define	_LoginForm_CPP

#include "LoginForm.h"
#include "MySQL.cpp"

LoginForm::LoginForm()
{
    setupUi(this);
    connect(loginbtn, SIGNAL(clicked()), this, SLOT(loginclick()));
    connect(quitbtn, SIGNAL(clicked()), this, SLOT(close()));
}

void LoginForm::setupUi(QDialog *LoginForm)
{
    if (LoginForm->objectName().isEmpty())
        LoginForm->setObjectName(QString::fromUtf8("LoginForm"));
    LoginForm->setWindowModality(Qt::NonModal);
    LoginForm->resize(627, 414);
    loginbtn = new QPushButton(LoginForm);
    loginbtn->setObjectName(QString::fromUtf8("loginbtn"));
    loginbtn->setGeometry(QRect(140, 250, 114, 32));
    quitbtn = new QPushButton(LoginForm);
    quitbtn->setObjectName(QString::fromUtf8("quitbtn"));
    quitbtn->setGeometry(QRect(300, 250, 114, 32));
    usernametext = new QLineEdit(LoginForm);
    usernametext->setObjectName(QString::fromUtf8("usernametext"));
    usernametext->setGeometry(QRect(240, 140, 113, 22));
    passtext = new QLineEdit(LoginForm);
    passtext->setObjectName(QString::fromUtf8("passtext"));
    passtext->setGeometry(QRect(240, 190, 113, 22));
    passtext->setInputMask(QString::fromUtf8(""));
    passtext->setMaxLength(32767);
    passtext->setEchoMode(QLineEdit::Password);
    password = new QLabel(LoginForm);
    password->setObjectName(QString::fromUtf8("password"));
    password->setGeometry(QRect(130, 190, 62, 16));
    username = new QLabel(LoginForm);
    username->setObjectName(QString::fromUtf8("username"));
    username->setGeometry(QRect(130, 140, 62, 16));

    retranslateUi(LoginForm);

    QMetaObject::connectSlotsByName(LoginForm);
} // setupUi

void LoginForm::retranslateUi(QDialog *LoginForm)
{
    LoginForm->setWindowTitle(QApplication::translate("LoginForm", "User System Login", 0, QApplication::UnicodeUTF8));
    loginbtn->setText(QApplication::translate("LoginForm", "Login", 0, QApplication::UnicodeUTF8));
    quitbtn->setText(QApplication::translate("LoginForm", "Quit", 0, QApplication::UnicodeUTF8));

#ifndef QT_NO_TOOLTIP
    usernametext->setToolTip(QApplication::translate("LoginForm", "Enter Username", 0, QApplication::UnicodeUTF8));
#endif // QT_NO_TOOLTIP

#ifndef QT_NO_TOOLTIP
    passtext->setToolTip(QApplication::translate("LoginForm", "Enter Password", 0, QApplication::UnicodeUTF8));
#endif // QT_NO_TOOLTIP

    passtext->setText(QString());
    passtext->setPlaceholderText(QString());
    password->setText(QApplication::translate("LoginForm", "Password", 0, QApplication::UnicodeUTF8));
    username->setText(QApplication::translate("LoginForm", "Username", 0, QApplication::UnicodeUTF8));
} // retranslateUi

LoginForm::~LoginForm()
{
}

void LoginForm::loginclick()
{
    MySQL* conn = new MySQL();
    bool ret = conn->query(usernametext->text(), passtext->text());
    if (ret)//(usernametext->text() == "Admin" && passtext->text() == "123456")
    {
        QMessageBox::information(this, "Success", "Password Correct");
    }
    else
    {
        QMessageBox::information(this, "Failure", "Password Incorrect");
    }
}

#endif

PyQT

PySide

  • Premier binding QT pour Python
  • Créer par Riverbank Computing
  • Open Source
  • Licenses Commerciales & GPL
  • Binding officiel de QT pour Python
  • Créer en open source et intégré par QT Company
  • Open Source
  • License LGPL
# Installer Pyside

Comment installer Pyside ?

1. Installer Python

 

https://www.python.org

------

2. Installer Pyside

 

#> python -m pip install PySide2

QTDesigner

Comment designer l'interface de votre application bureau ?

QTDesigner

# QtDesigner

QTDesigner

# QtDesigner

QTDesigner

# QtDesigner
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>317</width>
    <height>175</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <property name="autoFillBackground">
   <bool>false</bool>
  </property>
  <widget class="QWidget" name="centralwidget">
   <property name="autoFillBackground">
    <bool>false</bool>
   </property>
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1">
      <property name="sizeConstraint">
       <enum>QLayout::SetDefaultConstraint</enum>
      </property>
      <item>
       <layout class="QFormLayout" name="formLayout">
        <item row="0" column="0">
         <widget class="QLabel" name="label">
          <property name="font">
           <font>
            <pointsize>16</pointsize>
           </font>
          </property>
          <property name="text">
           <string>Login</string>
          </property>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QLineEdit" name="lineEdit">
          <property name="placeholderText">
           <string>ArnoBL42</string>
          </property>
         </widget>
        </item>
        <item row="1" column="0">
         <widget class="QLabel" name="label_2">
          <property name="font">
           <font>
            <pointsize>16</pointsize>
           </font>
          </property>
          <property name="text">
           <string>Password</string>
          </property>
         </widget>
        </item>
        <item row="1" column="1">
         <widget class="QLineEdit" name="lineEdit_2">
          <property name="focusPolicy">
           <enum>Qt::WheelFocus</enum>
          </property>
          <property name="echoMode">
           <enum>QLineEdit::Password</enum>
          </property>
          <property name="placeholderText">
           <string>***********</string>
          </property>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="font">
         <font>
          <pointsize>16</pointsize>
         </font>
        </property>
        <property name="text">
         <string>LOGIN</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

QTDesigner

# QtDesigner

Formulaire > Prévisualisation

QTDesigner

# QtDesigner

Formulaire > View C++ Code ...

/********************************************************************************
** Form generated from reading UI file 'arnoblbLKxeO.ui'
**
** Created by: Qt User Interface Compiler version 5.15.11
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef ARNOBLBLKXEO_H
#define ARNOBLBLKXEO_H

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow
{
public:
    QWidget *centralwidget;
    QGridLayout *gridLayout;
    QVBoxLayout *verticalLayout_2;
    QFormLayout *formLayout;
    QLabel *label;
    QLineEdit *lineEdit;
    QLabel *label_2;
    QLineEdit *lineEdit_2;
    QPushButton *pushButton;

    void setupUi(QMainWindow *MainWindow)
    {
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
        MainWindow->resize(317, 175);
        MainWindow->setAutoFillBackground(false);
        centralwidget = new QWidget(MainWindow);
        centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
        centralwidget->setAutoFillBackground(false);
        gridLayout = new QGridLayout(centralwidget);
        gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
        verticalLayout_2 = new QVBoxLayout();
        verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2"));
        verticalLayout_2->setSizeConstraint(QLayout::SetDefaultConstraint);
        formLayout = new QFormLayout();
        formLayout->setObjectName(QString::fromUtf8("formLayout"));
        label = new QLabel(centralwidget);
        label->setObjectName(QString::fromUtf8("label"));
        QFont font;
        font.setPointSize(16);
        label->setFont(font);

        formLayout->setWidget(0, QFormLayout::LabelRole, label);

        lineEdit = new QLineEdit(centralwidget);
        lineEdit->setObjectName(QString::fromUtf8("lineEdit"));

        formLayout->setWidget(0, QFormLayout::FieldRole, lineEdit);

        label_2 = new QLabel(centralwidget);
        label_2->setObjectName(QString::fromUtf8("label_2"));
        label_2->setFont(font);

        formLayout->setWidget(1, QFormLayout::LabelRole, label_2);

        lineEdit_2 = new QLineEdit(centralwidget);
        lineEdit_2->setObjectName(QString::fromUtf8("lineEdit_2"));
        lineEdit_2->setFocusPolicy(Qt::WheelFocus);
        lineEdit_2->setEchoMode(QLineEdit::Password);

        formLayout->setWidget(1, QFormLayout::FieldRole, lineEdit_2);


        verticalLayout_2->addLayout(formLayout);

        pushButton = new QPushButton(centralwidget);
        pushButton->setObjectName(QString::fromUtf8("pushButton"));
        pushButton->setFont(font);

        verticalLayout_2->addWidget(pushButton);

        verticalLayout_2->setStretch(0, 1);
        verticalLayout_2->setStretch(1, 1);

        gridLayout->addLayout(verticalLayout_2, 0, 0, 1, 1);

        MainWindow->setCentralWidget(centralwidget);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
    } // setupUi

    void retranslateUi(QMainWindow *MainWindow)
    {
        MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
        label->setText(QCoreApplication::translate("MainWindow", "Login", nullptr));
        lineEdit->setPlaceholderText(QCoreApplication::translate("MainWindow", "ArnoBL42", nullptr));
        label_2->setText(QCoreApplication::translate("MainWindow", "Password", nullptr));
        lineEdit_2->setPlaceholderText(QCoreApplication::translate("MainWindow", "***********", nullptr));
        pushButton->setText(QCoreApplication::translate("MainWindow", "LOGIN", nullptr));
    } // retranslateUi

};

namespace Ui {
    class MainWindow: public Ui_MainWindow {};
} // namespace Ui

QT_END_NAMESPACE

#endif // ARNOBLBLKXEO_H

QTDesigner

# QtDesigner

Formulaire > View Python Code ...

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'arnoblMxNCkI.ui'
##
## Created by: Qt User Interface Compiler version 5.15.11
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *  # type: ignore
from PySide2.QtGui import *  # type: ignore
from PySide2.QtWidgets import *  # type: ignore


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(317, 175)
        MainWindow.setAutoFillBackground(False)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.centralwidget.setAutoFillBackground(False)
        self.gridLayout = QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName(u"gridLayout")
        self.verticalLayout_2 = QVBoxLayout()
        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
        self.verticalLayout_2.setSizeConstraint(QLayout.SetDefaultConstraint)
        self.formLayout = QFormLayout()
        self.formLayout.setObjectName(u"formLayout")
        self.label = QLabel(self.centralwidget)
        self.label.setObjectName(u"label")
        font = QFont()
        font.setPointSize(16)
        self.label.setFont(font)

        self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)

        self.lineEdit = QLineEdit(self.centralwidget)
        self.lineEdit.setObjectName(u"lineEdit")

        self.formLayout.setWidget(0, QFormLayout.FieldRole, self.lineEdit)

        self.label_2 = QLabel(self.centralwidget)
        self.label_2.setObjectName(u"label_2")
        self.label_2.setFont(font)

        self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)

        self.lineEdit_2 = QLineEdit(self.centralwidget)
        self.lineEdit_2.setObjectName(u"lineEdit_2")
        self.lineEdit_2.setFocusPolicy(Qt.WheelFocus)
        self.lineEdit_2.setEchoMode(QLineEdit.Password)

        self.formLayout.setWidget(1, QFormLayout.FieldRole, self.lineEdit_2)


        self.verticalLayout_2.addLayout(self.formLayout)

        self.pushButton = QPushButton(self.centralwidget)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setFont(font)

        self.verticalLayout_2.addWidget(self.pushButton)

        self.verticalLayout_2.setStretch(0, 1)
        self.verticalLayout_2.setStretch(1, 1)

        self.gridLayout.addLayout(self.verticalLayout_2, 0, 0, 1, 1)

        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.label.setText(QCoreApplication.translate("MainWindow", u"Login", None))
        self.lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"ArnoBL42", None))
        self.label_2.setText(QCoreApplication.translate("MainWindow", u"Password", None))
        self.lineEdit_2.setPlaceholderText(QCoreApplication.translate("MainWindow", u"***********", None))
        self.pushButton.setText(QCoreApplication.translate("MainWindow", u"LOGIN", None))
    # retranslateUi

QTDesigner

# QtDesigner
Une démonstration vaut milles diapositives

QT Objects

Comment coder la logique de votre application bureau ?

Les Objets QT : Architecture

# Fonctionnement PySide

Les Objets QT : Noms des objets

# Fonctionnement PySide

Les Objets QT : Créer un objet

import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = QMainWindow()
    window.setWindowTitle("Example simple")
    window.setGeometry(100, 100, 300, 200)

    line_edit = QLineEdit(window)
    line_edit.setPlaceholderText("ArnoBL42")
    line_edit.setGeometry(10, 10, 280, 30)
    

    

    window.show()

    sys.exit(app.exec_())
# Fonctionnement PySide

Les Objets QT : Documentation

# Fonctionnement PySide

Les Objets QT : Signaux et Slots

# Fonctionnement PySide

Les Objets QT

# Fonctionnement PySide
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit
from PySide2.QtCore import Slot

@Slot()
def print_text(text):
    print(text)
    
if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = QMainWindow()
    window.setWindowTitle("Example simple")
    window.setGeometry(100, 100, 300, 200)

    line_edit = QLineEdit(window)
    line_edit.setPlaceholderText("ArnoBL42")
    line_edit.setGeometry(10, 10, 280, 30)
    
    //Link signal to slot
	line_edit.textChanged.connect(print_text)
    

    window.show()

    sys.exit(app.exec_())

Explication du code généré

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'arnoblMxNCkI.ui'
##
## Created by: Qt User Interface Compiler version 5.15.11
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *  # type: ignore
from PySide2.QtGui import *  # type: ignore
from PySide2.QtWidgets import *  # type: ignore


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(317, 175)
        MainWindow.setAutoFillBackground(False)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.centralwidget.setAutoFillBackground(False)
        self.gridLayout = QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName(u"gridLayout")
        self.verticalLayout_2 = QVBoxLayout()
        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
        self.verticalLayout_2.setSizeConstraint(QLayout.SetDefaultConstraint)
        self.formLayout = QFormLayout()
        self.formLayout.setObjectName(u"formLayout")
        self.label = QLabel(self.centralwidget)
        self.label.setObjectName(u"label")
        font = QFont()
        font.setPointSize(16)
        self.label.setFont(font)

        self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)

        self.lineEdit = QLineEdit(self.centralwidget)
        self.lineEdit.setObjectName(u"lineEdit")

        self.formLayout.setWidget(0, QFormLayout.FieldRole, self.lineEdit)

        self.label_2 = QLabel(self.centralwidget)
        self.label_2.setObjectName(u"label_2")
        self.label_2.setFont(font)

        self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)

        self.lineEdit_2 = QLineEdit(self.centralwidget)
        self.lineEdit_2.setObjectName(u"lineEdit_2")
        self.lineEdit_2.setFocusPolicy(Qt.WheelFocus)
        self.lineEdit_2.setEchoMode(QLineEdit.Password)

        self.formLayout.setWidget(1, QFormLayout.FieldRole, self.lineEdit_2)


        self.verticalLayout_2.addLayout(self.formLayout)

        self.pushButton = QPushButton(self.centralwidget)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setFont(font)

        self.verticalLayout_2.addWidget(self.pushButton)

        self.verticalLayout_2.setStretch(0, 1)
        self.verticalLayout_2.setStretch(1, 1)

        self.gridLayout.addLayout(self.verticalLayout_2, 0, 0, 1, 1)

        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.label.setText(QCoreApplication.translate("MainWindow", u"Login", None))
        self.lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"ArnoBL42", None))
        self.label_2.setText(QCoreApplication.translate("MainWindow", u"Password", None))
        self.lineEdit_2.setPlaceholderText(QCoreApplication.translate("MainWindow", u"***********", None))
        self.pushButton.setText(QCoreApplication.translate("MainWindow", u"LOGIN", None))
    # retranslateUi

# Fonctionnement PySide

Intégration du code généré

# Fonctionnement PySide

Application.ui

Application.py

Main.py

import

copy

Intégration du code généré

# Fonctionnement PySide
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit
from PySide2.QtCore import Slot
from Ui_MainWindow import Ui_MainWindow

    
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.button_clicked)
    
    
    @Slot()
    def button_clicked(self):
        if self == window:
            window2.show()
        else:
            window.show()
        self.hide()
        

App = QApplication(sys.argv)

window = MainWindow()
window2 = MainWindow()

window.show()
App.exec_()

Exemple Simple

Assembler la partie design et code pour obtenir votre application

Application

(en live)

Déploiement 

Comment déployer et distribuer votre application grâce à PyInstaller

# Principe PyInstaller

Main.py

PyInstaller

Main.exe

#> pip install pyinstaller

# Principe PyInstaller
# Principe PyInstaller

#> sudo pyinstaller main.py

build

dist

main

main.exe

# Principe PyInstaller
# Principe PyInstaller

Main.py

PHOTO.png

main.exe

PHOTO.png

# Principe PyInstaller

#> sudo pyinstaller --add-data "PHOTO.png:." main.py

def resource_path(relative_path):
  try:
    base_path = sys._MEIPASS
  except Exception:
    base_path = os.path.dirname(__file__)
    return os.path.join(base_path, relative_path)

main.exe

PHOTO.png

MEIPASS_#RUN

Bonus

Ajouter du style à votre application

PySide Course

By red4game

PySide Course

Cours sur Pyside : comment développer une application bureau simplement et rapidement.

  • 52