IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

pyinstaller et QStyle


Sujet :

PyQt Python

  1. #1
    Membre chevronné
    Homme Profil pro
    BTS SN IR
    Inscrit en
    Mai 2017
    Messages
    514
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Saône et Loire (Bourgogne)

    Informations professionnelles :
    Activité : BTS SN IR

    Informations forums :
    Inscription : Mai 2017
    Messages : 514
    Par défaut pyinstaller et QStyle
    bonjour,
    Un peu de contexte: j'utilisait cx_freeze avant, mais à chaque fois je me retrouvais avec ~200mo (~40mo une fois les trucs inutiles à mon programme enlevés, merci Qt)...
    Puis comme ça un jour j'ai testé pyinstaller, nickel, je passe de 200mo à trier à 40mo, pas besoin de faire de trie et si je veut je peut faire un seul fichier .exe (17mo) qui met au pire 5s de plus à s'ouvrir que le code source (quasi pareil sur ssd).

    Mais le problème est au niveau du Qstyle, je suis pas sur que du code soit nécessaire pour l'instant, à gauche le code source et à droite le code freezé
    Nom : Sans titre.png
Affichages : 951
Taille : 33,3 Ko

    print(QtWidgets.QStyleFactory.keys()) -> ['windowsvista', 'Windows', 'Fusion'].

    Bon la je me dit j'ai juste à faire app.setStyle('windows') mais en faite c'est tout le contraire c'est le .exe freezé qui utilise le style Windows, sinon je peut utiliser le style Fusion et la aucun soucis.. mais moi je veut pas le style fusion :'(

    J'ai feuilleté la doc sur les QStyles/QCommonStyles, y'a pourtant un paragraphe sur comment faire un Qstyle custom mais là c'est le flou total ...
    En faite je me demande bien ce qu'un un QStyle? est-ce une sorte de stylesheet ?

    j'ai naïvement tenté print(app.style()) pour voir s'il renvoyait str et qu'un petit coup de copier coller allait suffire mais non, j'ai un QCommonStyle..
    J'ai tenté de dumps le QCommonStyle, ça à bien marché mais maintenant comment utiliser ce fichier ?

    Edit: Je me suis dit il y un surement des gens qui on crée des Qstyles et qui les ont fait partager.. mais je n'est rien trouvé sur Google :/

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Voilà le "standard" que j'utilise dans mes programmes (app est QtWidgets.QApplication(sys.argv)):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        if "Fusion" in [st for st in QtWidgets.QStyleFactory.keys()]:
            app.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
        elif sys.platform=="win32":
            app.setStyle(QtWidgets.QStyleFactory.create("WindowsVista"))
        elif sys.platform=="linux":
            app.setStyle(QtWidgets.QStyleFactory.create("gtk"))
        elif sys.platform=="darwin":    
            app.setStyle(QtWidgets.QStyleFactory.create("macintosh"))
     
        app.setPalette(QtWidgets.QApplication.style().standardPalette())
    Je fais comme ça parce que mes programmes sont multiplateformes (Windows, Linux, Mac). Mais, contrairement à toi, je rends prioritaire le mode "Fusion" quand il existe.

    Avec ça, je n'ai pas de problème avec cx_freeze (j'utilise très rarement pyinstaller).

  3. #3
    Membre chevronné
    Homme Profil pro
    BTS SN IR
    Inscrit en
    Mai 2017
    Messages
    514
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Saône et Loire (Bourgogne)

    Informations professionnelles :
    Activité : BTS SN IR

    Informations forums :
    Inscription : Mai 2017
    Messages : 514
    Par défaut
    Merci pour vôtre réponse, mais quand vous utilisé cx_freeze il ne vous copie pas tout le dossier de pyqt (Python\Python36\Lib\site-packages\PyQt5) ?

    Et après est-il possible d'utiliser n'importe quel Qstyle sur n'importe quel os ? est il possible de dump sur un os et de l'utiliser sur un autre ?
    Après tout même une fois le style Fusion appliqué app.style() renvoie toujours un QCommonStyle.

  4. #4
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Même pour une fenêtre de base (QWidget), la taille obtenue pour le répertoire contenant le "exe" après traitement par cx_freeze atteint environ 200Mo. Il est vrai que les bibliothèques importées sont en trop grand nombre et concernent beaucoup de modules qu'on n'utilise pas. Mais pour moi, une taille de 200Mo est acceptable compte tenu des PC actuels. Et pour transmettre facilement ce répertoire, un "zip" avec des adresses relatives est facile à fabriquer.

    Et pour transmettre un fichier unique, on peut aussi créer un programme d'installation avec, par exemple, "innosetup" sous Windows! => http://www.jrsoftware.org/isinfo.php.

  5. #5
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Petit complément concernant le style.

    Apparemment, chaque OS a son style, et on ne peut pas appliquer à un OS un style qui n'a pas été prévu. Il semble même que PyQt n'ait pas reçu toutes les possibilités de Qt pour customiser les styles (voir la mailing-liste de RiverBankComputing: https://www.riverbankcomputing.com/pipermail/pyqt/).

    Par exemple, pour Windows:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    print([st for st in QtWidgets.QStyleFactory.keys()])
    ['Windows', 'WindowsXP', 'WindowsVista', 'Fusion']
    Si maintenant on veut voir dans un programme quel style a été appliqué:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    style = app.style().metaObject().className()
    print(style[1:-5])
    Le "[1:-5]" vient du fait que style est un string qui renvoi en fait "QFusionStyle" pour le style "Fusion" (etc...).

    Il existe cependant d'autres possibilités pour modifier le graphisme en utilisant les stylesheets. Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    # met des tooltips à fond jaune clair dans toute l'application
    app.setStyleSheet("QToolTip {background-color: #ffff99; border: 1px solid black}")
    On peut aussi avec setStyleSheet modifier l'apparence (couleur, forme, comportement à la souris, etc...), par exemple, des boutons d'une fenêtre.

    On peut aussi définir une icône d'application valable pour toutes les fenêtres du programme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    # mettre la même icone pour toutes les fenêtres de l'application
    app.setWindowIcon(QtGui.QIcon('icone.png'))

  6. #6
    Membre chevronné
    Homme Profil pro
    BTS SN IR
    Inscrit en
    Mai 2017
    Messages
    514
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Saône et Loire (Bourgogne)

    Informations professionnelles :
    Activité : BTS SN IR

    Informations forums :
    Inscription : Mai 2017
    Messages : 514
    Par défaut
    Bonjour,
    Merci pour vos réponse. C'est peut être moi mais si on ne sait pas quoi chercher dans riverbank on est perdu, il faut ouvrir une à une les archives et espérer trouver par miracle ^^
    Bon ... Grosse modo je vais passer d'un fichier stylesheet de 200ligne à plusieurs milliers si je veut imiter un style en particulier :'( je ne m'y aventurerai pas x)

    Si j'ai bien compris par contre je peut utiliser ces stylesheets (https://wiki.qt.io/Gallery_of_Qt_CSS_Based_Styles) dans mon application sans problème de droit ?

  7. #7
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Concernant les droits, j'imagine que ce sont les mêmes que pour utiliser Qt5, mais je ne suis pas spécialiste de ces questions.

    J'ai essayé d'utiliser PyInstaller pour rendre un programme autonome en un seul fichier exécutable: ça marche bien et le fichier fait environ 20Mo (pour 200Mo avec cx_freeze!).

    Versions: Python v3.5 et PyQt5 v5.9. Mon code d'essai est une simple fenêtre QWidget avec un bouton et un message d'info quand on clique dessus.

    Il y a cependant plusieurs précautions à prendre. Le fait qu'il n'y ait qu'un seul fichier exécutable est pratique, mais l'exécution commence par "désarchiver" le contenu dans un répertoire temporaire (Windows: rép._utilisateur\AppData\Local\Temp\_MEIxxxxxx), ce qui se traduit par un petit temps d'attente, mais aussi des perturbations de tout ce qui nécessite des chemins pour retrouver des données. Il faut alors adapter le code source du programme pour qu'il puisse fonctionner avec et sans traitement. On utilise ça partout où c'est nécessaire (extrait du manuel):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        if getattr(sys, 'frozen', False):
            # traitement par PyInstaller
            repert = sys._MEIPASS
        else:
            # exécution sans traitement 
            repert = os.path.dirname(os.path.abspath(__file__))
    Par exemple, si on veut mettre une icône à la fenêtre, il faut la faire recopier par PyInstaller, mais il faut aussi que PyQt5 puisse la retrouver, et donc, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        # met la même icône dans toutes les fenêtres
        if getattr(sys, 'frozen', False):
            repert = sys._MEIPASS # programma traité par pyinstaller
        else:
            repert = os.path.dirname(os.path.abspath(__file__)) # non traité
        icon = os.path.join(repert, 'iconefenetre.png')    
        app.setWindowIcon(QtGui.QIcon(icon))
    Si on veut que la traduction en français se fasse correctement (pour avoir des questions "oui/non" au lieu de "yes/no"), il faut demander à PyInstaller de recopier les fichiers de traduction, ce qu'il ne fait pas par défaut, puis ensuite au début de l'exécution, il faut dire à PyQt5 à quel endroit ils se trouvent.

    Pour que PyInstaller recopie les fichiers de traduction, il faut ajouter dans les options de la ligne de commande (mettre la bonne adresse!):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\translations;PyQt5\Qt\translations"
    Et ajouter le code suivant au lancement du programme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
        # assure la traduction automatique du conversationnel à la locale
        locale = QtCore.QLocale.system().name()
        translator = QtCore.QTranslator()
        reptrad = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
        if getattr(sys, 'frozen', False):
            # le programma a été traité par pyinstaller
            reptrad = os.path.join(sys._MEIPASS, "PyQt5", "Qt", "translations")
        translator.load("qtbase_" + locale, reptrad) # <==qtbase_fr.qm
        app.installTranslator(translator)
    Avec ça, la traduction en français se fait correctement. A noter que si on a besoin des pilotes de bases de données, il faut demander aussi à PyInstaller de les recopier:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\plugins\sqldrivers;PyQt5\Qt\plugins\sqldrivers"
    Voilà le fichier "bat" que j'utilise pour traiter le programme avec PyInstaller (le programme s'appelle "basewidget.py" avec une icône Windows "basewidget.ico" à intégrer à l'exe, et une icône "basewidget.png" à intégrer à la fenêtre PyQt):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pyinstaller ^
    --clean ^
    --noconfirm ^
    --noconsole ^
    --onefile ^
    --add-data "basewidget.png;." ^
    --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\translations;PyQt5\Qt\translations" ^
    --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\plugins\sqldrivers;PyQt5\Qt\plugins\sqldrivers" ^
    --icon "basewidget.ico" ^
    basewidget.py
    pause
    A noter qu'avec Python 3.5, il y a de nombreux warning inutiles genre "WARNING: lib not found: api-ms-win-crt......" mais qui n'empêchent pas l'exécutable de fonctionner (problème connu).

    A noter aussi que le style s'applique sans problème avec et sans traitement par PyInstaller. Par exemple si je veux "WindowsVista":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        app.setStyle(QtWidgets.QStyleFactory.create("WindowsVista"))
        app.setPalette(QtWidgets.QApplication.style().standardPalette())

  8. #8
    Membre chevronné
    Homme Profil pro
    BTS SN IR
    Inscrit en
    Mai 2017
    Messages
    514
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Saône et Loire (Bourgogne)

    Informations professionnelles :
    Activité : BTS SN IR

    Informations forums :
    Inscription : Mai 2017
    Messages : 514
    Par défaut
    bonjour, je ne sais pas trop par quoi vous répondre alors je vais faire dans l'ordre:

    J'ai regardé un peu les différents projet de stylesheet et il semblerait que pour les licences se soit au cas par cas.

    Citation Envoyé par tyrtamos Voir le message
    Il y a cependant plusieurs précautions à prendre. Le fait qu'il n'y ait qu'un seul fichier exécutable est pratique, mais l'exécution commence par "désarchiver" le contenu dans un répertoire temporaire (Windows: rép._utilisateur\AppData\Local\Temp\_MEIxxxxxx), ce qui se traduit par un petit temps d'attente, mais aussi des perturbations de tout ce qui nécessite des chemins pour retrouver des données. Il faut alors adapter le code source du programme pour qu'il puisse fonctionner avec et sans traitement. On utilise ça partout où c'est nécessaire (extrait du manuel):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        if getattr(sys, 'frozen', False):
            # traitement par PyInstaller
            repert = sys._MEIPASS
        else:
            # exécution sans traitement 
            repert = os.path.dirname(os.path.abspath(__file__))
    Par exemple, si on veut mettre une icône à la fenêtre, il faut la faire recopier par PyInstaller, mais il faut aussi que PyQt5 puisse la retrouver, et donc, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        # met la même icône dans toutes les fenêtres
        if getattr(sys, 'frozen', False):
            repert = sys._MEIPASS # programma traité par pyinstaller
        else:
            repert = os.path.dirname(os.path.abspath(__file__)) # non traité
        icon = os.path.join(repert, 'iconefenetre.png')    
        app.setWindowIcon(QtGui.QIcon(icon))
    Personnellement j'avais fait ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    try:
        basePath = sys._MEIPASS
    except :
        basePath = os.path.abspath('.')
    Merci pour l'astuce pour la traduction, même si j'ai pris l’habitude de faire des QDialog/QPushButton plutôt que des QMessageBox.

    Personnellement j'ai fait un .bat "pyinstaller script.spec"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
     
    fichier script.spec
     
    # -*- mode: python -*-
     
    block_cipher = None
     
     
    a = Analysis(['script.py'],
                 pathex=['C:\\Users\\SASA\\Dropbox\\Benoit\\dev\\python\\dev_'],
                 binaries=[],
                 datas=[
    				('icon', 'icon'),
    				('styleSheet', 'styleSheet'),
    			 ],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              name='Flapilofus',
              debug=False,
              strip=False,
              upx=True,
              runtime_tmpdir=None,
              console=False )
    Par contre je ne me suis pas encore penché sur le problème avec mon stylesheet (les urls des backgrounds/boutons notamment)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    with open(os.path.join(baseDir, stylesheet, selected_stylesheet + '.css'), 'r', encoding='utf-8') as file:
    	app.setStyleSheet(file.read())
    Je devrait remplacer par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    app.setStyleSheet(file.read().replace('url(', 'url(' + baseDir + '/'))
    Je testerait ça se soir quand je serait chez moi si j'ai le temps, mais j'ai peur que baseDir soit avec des "\\" sur Windows, or il me semble qu'il faut le chemin avec des "/" uniquement. si baseDir est bien avec des "//" sa risque de faire bidouillage entre les split et les join.




    Edit: les stylesheet ne fonctionne qu'avec les chemins relatifs en fonction du cwd ..
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if getattr(sys, 'frozen', False):
    	base_path = sys._MEIPASS
    else:
    	base_path = os.path.dirname(os.path.abspath(__file__))
    script_path = os.getcwd()
     
    ...
     
    with open(os.path.join(base_path, "styleSheet.css"), 'r', encoding='utf-8') as file:
    	os.chdir(base_path)
    	app.setStyleSheet(file.read())
    	os.chdir(script_path)
    Vus que je ne change jamais le cwd mais que je travail en fonction du chemin relatif à lui ou du chemin absolu ça ne devrait pas poser de problèmes .. mais c'est bidouillage

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. PyInstaller, un tuto ?
    Par rambc dans le forum Bibliothèques tierces
    Réponses: 2
    Dernier message: 28/02/2009, 11h30
  2. Fichier Version PyInstaller
    Par metalamania dans le forum Déploiement/Installation
    Réponses: 2
    Dernier message: 30/07/2008, 22h59
  3. Compilation avec PyInstaller
    Par Jiyuu dans le forum Déploiement/Installation
    Réponses: 1
    Dernier message: 29/04/2008, 20h01
  4. Programme pour PyInstaller
    Par metalamania dans le forum Déploiement/Installation
    Réponses: 3
    Dernier message: 01/06/2007, 16h16
  5. pyinstaller sous Linux
    Par jbjweb dans le forum Déploiement/Installation
    Réponses: 1
    Dernier message: 20/02/2007, 21h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo