Comment construire une application graphique Python avec wxPython – Real Python

By | avril 11, 2019

trouver un expert Python

Il existe de nombreux kits d'outils d'interface utilisateur graphique (GUI) que vous pouvez utiliser avec le langage de programmation Python. Les trois grands sont Tkinter, wxPython et PyQt. Chacune de ces boîtes à outils fonctionnera sous Windows, macOS et Linux, PyQt ayant la capacité supplémentaire de fonctionner sur mobile.

Une interface utilisateur graphique est une application dotée de boutons, de fenêtres et de nombreux autres widgets que l'utilisateur peut utiliser pour interagir avec votre application. Un bon exemple serait un navigateur Web. Il comporte des boutons, des onglets et une fenêtre principale dans laquelle tout le contenu est chargé.

Dans cet article, vous apprendrez à créer une interface utilisateur graphique avec Python à l'aide de la boîte à outils de l'interface graphique wxPython.

Voici les sujets abordés:

  • Débuter avec wxPython
  • Définition d'une interface graphique
  • Création d'une application squelette
  • Création d'une application opérationnelle

Commençons à apprendre!

Débuter avec wxPython

La boîte à outils de l'interface graphique wxPython est un wrapper Python autour d'une bibliothèque C ++ appelée wxWidgets. La première version de wxPython date de 1998. WxPython existe depuis assez longtemps. La principale différence de wxPython par rapport à d’autres outils, tels que PyQt ou Tkinter, est-ce que wxPython utilise les widgets réels sur la plate-forme native autant que possible. Cela donne aux applications wxPython une apparence native pour le système d'exploitation sur lequel elles s'exécutent.

PyQt et Tkinter dessinent tous deux leurs widgets eux-mêmes. C’est pourquoi ils ne correspondent pas toujours aux widgets natifs, bien que PyQt soit très proche.

Cela ne veut pas dire que wxPython ne supporte pas les widgets personnalisés. En fait, la boîte à outils wxPython contient de nombreux widgets personnalisés, ainsi que des dizaines sur des dizaines de widgets principaux. La page de téléchargements wxPython comporte une section intitulée Fichiers supplémentaires cela vaut la peine de vérifier.

Ici, il y a un téléchargement du paquet de démonstration wxPython. Ceci est une jolie petite application qui montre la grande majorité des widgets inclus dans wxPython. La démo permet à un développeur de visualiser le code dans un onglet et de l'exécuter dans un deuxième onglet. Vous pouvez même modifier et ré-exécuter le code dans la démo pour voir comment vos modifications affectent l'application.

Installer wxPython

Vous utiliserez la dernière version de wxPython pour cet article, à savoir wxPython 4, également connu sous le nom de Phoenix. Les versions wxPython 3 et wxPython 2 sont construites uniquement pour Python 2. Lorsque Robin Dunn, le principal responsable de la maintenance de wxPython, a créé la version wxPython 4, il a déconseillé de nombreux alias et nettoyé beaucoup de code pour rendre wxPython plus pythonique et plus facile à gérer.

Vous voudrez peut-être consulter les liens suivants si vous migrez d’une ancienne version de wxPython vers wxPython 4 (Phoenix):

Le paquet wxPython 4 est compatible avec Python 2.7 et Python 3.

Vous pouvez maintenant utiliser pépin pour installer wxPython 4, ce qui n’était pas possible dans les anciennes versions de wxPython. Vous pouvez procéder comme suit pour l’installer sur votre ordinateur:

Heureusement, les messages d'erreur qui pépin les affichages sont utiles pour déterminer ce qui manque, et vous pouvez utiliser la section des conditions préalables de la page wxPython Github pour vous aider à trouver les informations dont vous avez besoin si vous souhaitez installer wxPython sur Linux.

Il existe des molettes Python disponibles pour les versions les plus courantes de Linux que vous pouvez trouver dans la section Extras Linux avec les versions GTK2 et GTK3. Pour installer l'une de ces roues, utilisez la commande suivante:

$ pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/ wxPython

Assurez-vous que vous avez modifié la commande ci-dessus pour qu'elle corresponde à votre version de Linux.

Définition d'une interface graphique

Comme cela a été mentionné dans l'introduction, une interface utilisateur graphique (GUI) est une interface qui est dessinée à l'écran pour permettre à l'utilisateur d'interagir.

Les interfaces utilisateur ont des composants communs:

  • Fenêtre principale
  • Menu
  • Barre d'outils
  • Boutons
  • Entrée de texte
  • Étiquettes

Tous ces articles sont connus sous le nom générique widgets. Il existe de nombreux autres widgets courants et de nombreux widgets personnalisés pris en charge par wxPython. Un développeur prend les widgets et les organise de manière logique sur une fenêtre avec laquelle l'utilisateur peut interagir.

Boucles d'événement

Une interface utilisateur graphique fonctionne en attendant que l'utilisateur fasse quelque chose. Le quelque chose s'appelle un un événement. Les événements se produisent lorsque l'utilisateur tape quelque chose alors que votre application est active ou lorsqu'il utilise sa souris pour appuyer sur un bouton ou un autre widget.

Sous les couvertures, la boîte à outils GUI exécute une boucle infinie appelée un boucle d'événement. La boucle d'événements n'attend que les événements, puis agit en fonction de ce que le développeur a codé pour l'application. Lorsque l'application n'intercepte pas un événement, elle ignore en fait qu'il s'est même produit.

Lorsque vous programmez une interface utilisateur graphique, gardez à l’esprit que vous devrez connecter chacun des widgets à gestionnaires d'événements afin que votre application fasse quelque chose.

Lorsque vous travaillez avec des boucles d'événement, vous devez prendre en compte une considération particulière: elles peuvent être bloquées. Lorsque vous bloquez une boucle d'événement, l'interface graphique devient insensible et semble geler pour l'utilisateur.

Tout processus que vous lancez dans une interface utilisateur graphique qui prend plus d'un quart de seconde devrait probablement être lancé en tant que thread ou processus distinct. Cela empêchera votre interface graphique de geler et donnera à l'utilisateur une meilleure expérience utilisateur.

Le framework wxPython dispose de méthodes spéciales thread-safe que vous pouvez utiliser pour communiquer avec votre application afin de lui indiquer que le thread est terminé ou de le mettre à jour.

Créons une application squelette pour montrer le fonctionnement des événements.

Création d'une application squelette

Un squelette d’application dans un contexte d’interface graphique est une interface utilisateur avec des widgets n’ayant aucun gestionnaire d’événements. Celles-ci sont utiles pour le prototypage. En gros, vous créez simplement l'interface graphique et la présentez à vos parties prenantes pour approbation avant de passer beaucoup de temps sur la logique d'arrière-plan.

Commençons par créer un Bonjour le monde application avec wxPython:

importation wx

app = wx.App()
Cadre = wx.Cadre(parent=Aucun, Titre='Bonjour le monde')
Cadre.Spectacle()
app.Boucle principale()

Dans cet exemple, vous avez deux parties: wx.App et le wx.Frame. le wx.App est l’application wxPython et est nécessaire pour exécuter votre interface graphique. le wx.App commence quelque chose appelé un .Boucle principale(). C'est la boucle d'événement que vous avez apprise dans la section précédente.

L’autre pièce du puzzle est wx.Frame, ce qui créera une fenêtre avec laquelle l’utilisateur pourra interagir. Dans ce cas, vous avez dit à wxPython que le cadre n’a pas de parent et que son titre est Bonjour le monde. Voici à quoi ça ressemble quand vous exécutez le code:

Bonjour tout le monde dans wxPython

Par défaut, un wx.Frame comprendra des boutons Réduire, Agrandir et Quitter en haut. Normalement, vous ne créez pas d’application de cette manière. La plupart du code wxPython vous obligera à sous-classer le wx.Frame et d'autres widgets afin que vous puissiez obtenir toute la puissance de la boîte à outils.

Prenons un moment pour réécrire votre code en tant que classe:

importation wx

classe MyFrame(wx.Cadre):    
    def __init__(soi):
        super().__init__(parent=Aucun, Titre='Bonjour le monde')
        soi.Spectacle()

si __prénom__ == '__principale__':
    app = wx.App()
    Cadre = MyFrame()
    app.Boucle principale()

Vous pouvez utiliser ce code comme modèle pour votre application. Cependant, cette application n’a pas beaucoup d’effet, prenons donc quelques instants pour en apprendre un peu plus sur certains des autres widgets que vous pourriez ajouter.

Widgets

La boîte à outils wxPython propose plus de cent widgets. Cela vous permet de créer des applications riches, mais il peut également être difficile d’essayer de déterminer quel widget utiliser. C'est pourquoi le wxPython Demo est utile car il possède un filtre de recherche que vous pouvez utiliser pour vous aider à trouver les widgets pouvant s’appliquer à votre projet.

La plupart des applications graphiques permettent à l'utilisateur de saisir du texte et d'appuyer sur un bouton. Continuons et ajoutons ces widgets:

importation wx

classe MyFrame(wx.Cadre):    
    def __init__(soi):
        super().__init__(parent=Aucun, Titre='Bonjour le monde')
        panneau = wx.Panneau(soi)

        soi.text_ctrl = wx.TextCtrl(panneau, pos=(5, 5))
        mon_btn = wx.Bouton(panneau, étiquette='Press Me', pos=(5, 55))

        soi.Spectacle()

si __prénom__ == '__principale__':
    app = wx.App()
    Cadre = MyFrame()
    app.Boucle principale()

Lorsque vous exécutez ce code, votre application devrait ressembler à ceci:

Hello World dans wxPython avec des widgets

Le premier widget que vous devez ajouter est quelque chose appelé wx.Panel. Ce widget n'est pas obligatoire, mais recommandé. Sous Windows, vous devez utiliser un panneau afin que la couleur d'arrière-plan du cadre soit la bonne nuance de gris. La traversée des onglets est désactivée sans Panel sous Windows.

Lorsque vous ajoutez le widget de panneau à un cadre et que celui-ci est l'unique enfant du cadre, il se développe automatiquement pour remplir le cadre avec lui-même.

La prochaine étape consiste à ajouter un wx.TextCtrl au panneau. Le premier argument pour presque tous les widgets est le parent sur lequel le widget doit aller. Dans ce cas, vous voulez que le contrôle de texte et le bouton soient au-dessus du panneau, c'est donc le parent que vous spécifiez.

Vous devez également indiquer à wxPython où placer le widget, ce que vous pouvez faire en passant une position via le pos paramètre. Dans wxPython, l'emplacement d'origine est (0,0), qui est le coin supérieur gauche du parent. Ainsi, pour le contrôle de texte, vous indiquez à wxPython que vous souhaitez positionner son coin supérieur gauche à 5 pixels de la gauche (x) et à 5 pixels du haut (y).

Ensuite, vous ajoutez votre bouton au panneau et lui donnez une étiquette. Pour éviter le chevauchement des widgets, vous devez définir la coordonnée y sur 55 comme position du bouton.

Positionnement absolu

Lorsque vous fournissez des coordonnées exactes pour la position de votre widget, la technique que vous avez utilisée est appelée positionnement absolu. La plupart des kits d’outils graphiques offrent cette possibilité, mais ce n’est pas vraiment recommandé.

Au fur et à mesure que votre application devient plus complexe, il devient difficile de garder une trace de tous les emplacements des widgets et si vous devez déplacer les widgets. Réinitialiser toutes ces positions devient un cauchemar.

Heureusement, tous les kits d’outils d’interface graphique modernes offrent une solution, ce que vous apprendrez ensuite.

Calibreurs (Dimensionnement dynamique)

La boîte à outils wxPython comprend calibreurs, qui sont utilisés pour créer des mises en page dynamiques. Ils gèrent l'emplacement de vos widgets pour vous et les ajustent lorsque vous redimensionnez la fenêtre de l'application. D'autres boîtes à outils de l'interface graphique feront référence aux calibreurs en tant que dispositions, ce que fait PyQt.

Voici les principaux types de calibreurs que vous verrez le plus souvent utilisés:

  • wx.BoxSizer
  • wx.GridSizer
  • wx.FlexGridSizer

Ajoutons un wx.BoxSizer à votre exemple et voir si nous pouvons le faire fonctionner un peu plus bien:

importation wx

classe MyFrame(wx.Cadre):    
    def __init__(soi):
        super().__init__(parent=Aucun, Titre='Bonjour le monde')
        panneau = wx.Panneau(soi)        
        mon_sizer = wx.BoxSizer(wx.VERTICALE)        
        soi.text_ctrl = wx.TextCtrl(panneau)
        mon_sizer.Ajouter(soi.text_ctrl, 0, wx.TOUT | wx.DÉVELOPPER, 5)        
        mon_btn = wx.Bouton(panneau, étiquette='Press Me')
        mon_sizer.Ajouter(mon_btn, 0, wx.TOUT | wx.CENTRE, 5)        
        panneau.SetSizer(mon_sizer)        
        soi.Spectacle()

si __prénom__ == '__principale__':
    app = wx.App()
    Cadre = MyFrame()
    app.Boucle principale()

Ici, vous créez une instance de wx.BoxSizer et le passer wx.VERTICAL, l’orientation à laquelle les widgets sont ajoutés à la calculatrice.

Dans ce cas, les widgets seront ajoutés verticalement, ce qui signifie qu'ils seront ajoutés un à un, de haut en bas. Vous pouvez également définir l’orientation d’un BoxSizer sur wx.HORIZONTAL. Lorsque vous faites cela, les widgets sont ajoutés de gauche à droite.

Pour ajouter un widget à un classeur, vous utiliserez .Ajouter(). Il accepte jusqu'à cinq arguments:

  • la fenêtre (le widget)
  • proportion
  • drapeau
  • frontière
  • données d'utilisateur

le la fenêtre l'argument est le widget à ajouter en proportion définit combien d'espace par rapport aux autres widgets du sizer ce widget devrait prendre. Par défaut, c'est zéro, ce qui indique à wxPython de laisser le widget à sa proportion par défaut.

Le troisième argument est drapeau. Vous pouvez en fait passer plusieurs drapeaux si vous le souhaitez tant que vous les séparez avec un caractère de pipe: |. Le toolkit wxPython utilise | d'ajouter des drapeaux en utilisant une série de blocs d'opérations au niveau des bits.

Dans cet exemple, vous ajoutez le contrôle de texte avec le wx.ALL et wx.EXPAND drapeaux. le wx.ALL drapeau indique à wxPython que vous souhaitez ajouter une bordure sur tous les côtés du widget en wx.EXPAND permet aux widgets de s’étendre autant que possible au sein de la dimensionnante.

Enfin, vous avez le frontière paramètre qui indique à wxPython le nombre de pixels de bordure que vous souhaitez entourer le widget. le données d'utilisateur Le paramètre est utilisé uniquement lorsque vous souhaitez effectuer une opération complexe avec le dimensionnement du widget et est en fait assez rare à voir dans la pratique.

L'ajout du bouton au calibreur suit exactement les mêmes étapes. Cependant, pour rendre les choses un peu plus intéressantes, je suis allé de l'avant et ai changé le wx.EXPAND drapeau pour wx.CENTER afin que le bouton soit centré à l'écran.

Lorsque vous exécutez cette version du code, votre application devrait ressembler à ceci:

Hello World dans wxPython avec Sizers

Si vous souhaitez en savoir plus sur les calibreurs, la documentation de wxPython contient une belle page sur le sujet.

Ajout d'un événement

Bien que votre application semble plus intéressante visuellement, elle ne fait toujours rien. Par exemple, si vous appuyez sur le bouton, rien ne se passe vraiment.

Donnons un emploi au bouton:

importation wx

classe MyFrame(wx.Cadre):    
    def __init__(soi):
        super().__init__(parent=Aucun, Titre='Bonjour le monde')
        panneau = wx.Panneau(soi)        
        mon_sizer = wx.BoxSizer(wx.VERTICALE)        
        soi.text_ctrl = wx.TextCtrl(panneau)
        mon_sizer.Ajouter(soi.text_ctrl, 0, wx.TOUT | wx.DÉVELOPPER, 5)        
        mon_btn = wx.Bouton(panneau, étiquette='Press Me')
        mon_btn.Lier(wx.EVT_BUTTON, soi.on_press)
        mon_sizer.Ajouter(mon_btn, 0, wx.TOUT | wx.CENTRE, 5)        
        panneau.SetSizer(mon_sizer)        
        soi.Spectacle()

    def on_press(soi, un événement):
        valeur = soi.text_ctrl.GetValue()
        si ne pas valeur:
            impression("Tu n'as rien entré!")
        autre:
            impression(F'Vous avez tapé: "valeur"')

si __prénom__ == '__principale__':
    app = wx.App()
    Cadre = MyFrame()
    app.Boucle principale()

Les widgets de wxPython vous permettent de leur attacher des liaisons d’événements afin qu’ils puissent répondre à certains types d’événements.

Vous voulez que le bouton fasse quelque chose lorsque l'utilisateur appuie dessus. Vous pouvez y parvenir en appelant le bouton .Lier() méthode. .Lier() prend l'événement que vous souhaitez lier, le gestionnaire à appeler lorsque l'événement se produit, une source facultative et quelques identificateurs facultatifs.

Dans cet exemple, vous liez votre objet bouton à la wx.EVT_BUTTON événement et lui dire d'appeler on_press () quand cet événement est renvoyé.

Un événement est "déclenché" lorsque l'utilisateur effectue l'événement auquel vous êtes lié. Dans ce cas, l’événement que vous configurez est l’événement de pression de bouton, wx.EVT_BUTTON.

.on_press () accepte un deuxième argument que vous pouvez appeler un événement. C'est par convention. Vous pouvez appeler cela autre chose si vous le souhaitez. Cependant, le paramètre event fait ici référence au fait que, lorsque cette méthode est appelée, son deuxième argument doit être un objet événement.

Dans .on_press (), vous pouvez obtenir le contenu du contrôle de texte en appelant son GetValue () méthode. Vous imprimez ensuite une chaîne sur stdout en fonction du contenu du contrôle de texte.

Maintenant que vous en avez terminé avec les bases, apprenons à créer une application qui fasse quelque chose d’utile!

Création d'une application opérationnelle

La première étape pour créer quelque chose de nouveau consiste à déterminer ce que vous voulez créer. Dans ce cas, je me suis permis de prendre cette décision pour vous. Vous allez apprendre à créer un éditeur de balises MP3! La prochaine étape lors de la création de quelque chose de nouveau consiste à déterminer quels packages peuvent vous aider à accomplir votre tâche.

Si vous effectuez une recherche Google pour Marquage mp3 Python, vous trouverez que vous avez plusieurs options:

J'ai essayé quelques-uns d'entre eux et j'ai décidé que eyeD3 eu une belle API que vous pouviez utiliser sans vous enliser avec les spécifications MP3 de l’ID3. Vous pouvez installer eyeD3 en utilisant pépin, comme ça:

Lors de l’installation de ce paquet sur macOS, vous devrez peut-être installer libmagic en utilisant brasser. Les utilisateurs Windows et Linux ne devraient pas avoir de problèmes d’installation de eyeD3.

Conception de l'interface utilisateur

Lorsqu’il s’agit de concevoir une interface, il est toujours agréable d’écrire à quoi devrait ressembler l’interface utilisateur.

Vous devrez être capable de faire ce qui suit:

  • Ouvrir un ou plusieurs fichiers MP3
  • Afficher les tags MP3 actuels
  • Modifier une balise MP3

La plupart des interfaces utilisateur utilisent un menu ou un bouton pour ouvrir des fichiers ou des dossiers. Vous pouvez aller avec un Fichier menu pour cela. Comme vous voudrez probablement voir les tags de plusieurs fichiers MP3, vous devrez trouver un widget capable de le faire de manière agréable.

Quelque chose de tabulaire avec des colonnes et des rangées serait idéal car vous pouvez alors avoir des colonnes étiquetées pour les balises MP3. La boîte à outils wxPython contient quelques widgets qui fonctionneraient pour cela, les deux premiers étant les suivants:

Tu devrais utiliser wx.ListCtrl dans ce cas comme la grille widget est exagéré, et franchement, il est également un peu plus complexe. Enfin, vous avez besoin d’un bouton pour éditer la balise MP3 sélectionnée.

Maintenant que vous savez ce que vous voulez, vous pouvez le rédiger:

MP3 Editor dans wxPython

L'illustration ci-dessus nous donne une idée de l'apparence de l'application. Maintenant que vous savez ce que vous voulez faire, c’est le moment de coder!

Création de l'interface utilisateur

Il existe de nombreuses approches différentes pour écrire une nouvelle application. Par exemple, devez-vous suivre le modèle de conception Model-View-Controller? Comment divisez-vous les cours? Une classe par fichier? Il existe de nombreuses questions de ce type, et plus vous maîtriserez la conception d’interface graphique, plus vous saurez comment vous souhaitez y répondre.

Dans votre cas, vous n'avez vraiment besoin que de deux cours:

  • UNE wx.Panel classe
  • UNE wx.Frame classe

Vous pouvez également plaider en faveur de la création d’un module de type contrôleur, mais vous n’en avez vraiment pas besoin. Vous pouvez également plaider en faveur de chaque classe dans son propre module, mais pour rester compact, vous créerez un fichier Python unique pour tout votre code.

Commençons par les importations et la classe de panneaux:

importation eyed3
importation glob
importation wx

classe Mp3Panel(wx.Panneau):    
    def __init__(soi, parent):
        super().__init__(parent)
        main_sizer = wx.BoxSizer(wx.VERTICALE)
        soi.row_obj_dict = 

        soi.list_ctrl = wx.ListCtrl(
            soi, Taille=(-1, 100), 
            style=wx.LC_REPORT | wx.BORDER_SUNKEN
        )
        soi.list_ctrl.InsertColumn(0, 'Artiste', largeur=140)
        soi.list_ctrl.InsertColumn(1, 'Album', largeur=140)
        soi.list_ctrl.InsertColumn(2, 'Titre', largeur=200)
        main_sizer.Ajouter(soi.list_ctrl, 0, wx.TOUT | wx.DÉVELOPPER, 5)        
        edit_button = wx.Bouton(soi, étiquette='Modifier')
        edit_button.Lier(wx.EVT_BUTTON, soi.on_edit)
        main_sizer.Ajouter(edit_button, 0, wx.TOUT | wx.CENTRE, 5)        
        soi.SetSizer(main_sizer)

    def on_edit(soi, un événement):
        impression('in on_edit')

    def update_mp3_listing(soi, folder_path):
        impression(folder_path)

Ici, vous importez le eyed3 paquet, Python glob paquet, et le wx package pour votre interface utilisateur. Ensuite, vous sous-classe wx.Panel et créez votre interface utilisateur. Vous avez besoin d’un dictionnaire pour stocker des données sur vos MP3, que vous pouvez nommer. row_obj_dict.

Ensuite, vous créez un wx.ListCtrl et le mettre en mode rapport (wx.LC_REPORT) avec une bordure creuse (wx.BORDER_SUNKEN). Le contrôle de liste peut prendre quelques autres formes en fonction du drapeau de style que vous transmettez, mais le drapeau de rapport est le plus populaire.

Pour faire le ListCtrl avoir les en-têtes corrects, vous devrez appeler .InsertColumn () pour chaque en-tête de colonne. Vous fournissez ensuite l'index de la colonne, son étiquette et sa largeur en pixels.

La dernière étape consiste à ajouter votre modifier bouton, un gestionnaire d'événements et une méthode. Vous pouvez créer la liaison à l'événement et laisser la méthode qu'elle appelle vide pour l'instant.

Maintenant, vous devriez écrire le code pour le cadre:

classe Mp3Frame(wx.Cadre):    
    def __init__(soi):
        super().__init__(parent=Aucun,
                         Titre='Mp3 Tag Editor')
        soi.panneau = Mp3Panel(soi)
        soi.Spectacle()

si __prénom__ == '__principale__':
    app = wx.App(Faux)
    Cadre = Mp3Frame()
    app.Boucle principale()

Cette classe est beaucoup plus simple que la première en ce sens que tout ce que vous avez à faire est de définir le titre du cadre et d’instancier la classe du panneau. Mp3Panel. Lorsque vous avez tous terminé, votre interface utilisateur devrait ressembler à ceci:

wxPython MP3 Tag Editor

L’interface utilisateur semble presque correcte, mais vous n’avez pas de Fichier menu. Cela rend impossible l'ajout de fichiers MP3 à l'application et la modification de leurs balises!

Laissez-nous réparer cela maintenant.

Faire une application qui fonctionne

La première étape pour que votre application fonctionne consiste à mettre à jour l’application afin qu’elle ait une Fichier menu car alors vous pouvez ajouter des fichiers MP3 à votre création. Les menus sont presque toujours ajoutés aux wx.Frame classe, donc c'est la classe que vous devez modifier.

Apprenons comment ajouter une barre de menus à notre application:

classe Mp3Frame(wx.Cadre):

    def __init__(soi):
        wx.Cadre.__init__(soi, parent=Aucun, 
                          Titre='Mp3 Tag Editor')
        soi.panneau = Mp3Panel(soi)
        soi.create_menu()
        soi.Spectacle()

    def create_menu(soi):
        barre de menu = wx.Barre de menu()
        Menu Fichier = wx.Menu()
        open_folder_menu_item = Menu Fichier.Ajouter(
            wx.ID_ANY, 'Dossier ouvert', 
            'Ouvrir un dossier avec des MP3'
        )
        barre de menu.Ajouter(Menu Fichier, '&Fichier')
        soi.Lier(
            un événement=wx.EVT_MENU, 
            gestionnaire=soi.on_open_folder,
            la source=open_folder_menu_item,
        )
        soi.SetMenuBar(barre de menu)

    def on_open_folder(soi, un événement):
        Titre = "Choisissez un répertoire:"
        dlg = wx.DirDialog(soi, Titre, 
                           style=wx.DD_DEFAULT_STYLE)
        si dlg.ShowModal() == wx.ID_OK:
            soi.panneau.update_mp3_listing(dlg.GetPath())
        dlg.Détruire()

Ici, vous ajoutez un appel à .create_menu () dans le constructeur de la classe. Puis dans .create_menu () lui-même, vous allez créer un wx.MenuBar exemple et un wx.Menu exemple.

Pour ajouter un élément de menu à un menu, appelez l’instance de menu suivante. .Ajouter() et passez le suivant:

  • Un identifiant unique
  • Le libellé du nouvel élément de menu
  • Une chaîne d'aide

Ensuite, vous devez ajouter le menu à la barre de menu. Vous devrez donc appeler le menu de la barre de menu. .Ajouter(). Il prend l'instance de menu et l'étiquette pour le menu. Cette étiquette est un peu étrange en ce que vous l'avez appelé &Fichier au lieu de Fichier. La perluète indique à wxPython de créer un raccourci clavier de Alt+F ouvrir le Fichier menu en utilisant seulement votre clavier.

Pour créer une liaison d'événement, vous devez appeler self.Bind (), qui lie le cadre à wx.EVT_MENU. Quand vous utilisez self.Bind () pour un événement de menu, vous devez non seulement indiquer à wxPython lequel gestionnaire à utiliser, mais aussi qui la source pour lier le gestionnaire à.

Enfin, vous devez appeler le .SetMenuBar () et passez l'instance de la barre de menu pour qu'elle soit montrée à l'utilisateur.

Maintenant que le menu est ajouté à votre cadre, passons au gestionnaire d’événements de l’élément de menu, qui est reproduit ci-dessous:

def on_open_folder(soi, un événement):
    Titre = "Choisissez un répertoire:"
    dlg = wx.DirDialog(soi, Titre, style=wx.DD_DEFAULT_STYLE)
    si dlg.ShowModal() == wx.ID_OK:
        soi.panneau.update_mp3_listing(dlg.GetPath())
    dlg.Détruire()

Puisque vous voulez que l’utilisateur choisisse un dossier contenant des fichiers MP3, vous voudrez utiliser les fichiers de wxPython. wx.DirDialog. le wx.DirDialog permet à l'utilisateur d'ouvrir uniquement des répertoires.

Vous pouvez définir le titre de la boîte de dialogue et divers indicateurs de style. Pour afficher la boîte de dialogue, vous devez appeler .ShowModal (). La boîte de dialogue s'affichera de manière modale, ce qui signifie que l'utilisateur ne pourra pas interagir avec votre application principale tant que la boîte de dialogue sera affichée.

Si l’utilisateur appuie sur le bouton D'accord bouton, vous pouvez obtenir le choix du chemin de l’utilisateur via le dialogue .GetPath (). Vous voudrez transmettre ce chemin à votre classe de panel, ce que vous pouvez faire ici en appelant le .update_mp3_listing ().

Enfin, vous devez fermer la boîte de dialogue. Pour fermer une boîte de dialogue, la méthode recommandée consiste à appeler son .Détruire().

Les dialogues ont un .Fermer() méthode, mais cela ne fait que masquer la boîte de dialogue et elle ne se détruira pas lorsque vous fermerez votre application, ce qui peut entraîner des problèmes étranges, tels que la fermeture de votre application. C’est plus simple d’appeler .Détruire() dans la boîte de dialogue pour éviter ce problème.

Maintenant, mettons à jour votre Mp3Panel classe. Vous pouvez commencer par mettre à jour .update_mp3_listing ():

def update_mp3_listing(soi, folder_path):
    soi.chemin_fichier_current = folder_path
    soi.list_ctrl.Tout effacer()

    soi.list_ctrl.InsertColumn(0, 'Artiste', largeur=140)
    soi.list_ctrl.InsertColumn(1, 'Album', largeur=140)
    soi.list_ctrl.InsertColumn(2, 'Titre', largeur=200)
    soi.list_ctrl.InsertColumn(3, 'Année', largeur=200)

    mp3s = glob.glob(folder_path + '/*.mp3')
    mp3_objects = []
    indice = 0
    pour mp3 dans mp3s:
        mp3_object = eyed3.charge(mp3)
        soi.list_ctrl.InsertItem(indice, 
            mp3_object.étiquette.artiste)
        soi.list_ctrl.SetItem(indice, 1, 
            mp3_object.étiquette.album)
        soi.list_ctrl.SetItem(indice, 2, 
            mp3_object.étiquette.Titre)
        mp3_objects.ajouter(mp3_object)
        soi.row_obj_dict[[[[indice] = mp3_object
        indice + = 1

Ici, vous définissez le répertoire actuel sur le dossier spécifié, puis vous effacez le contrôle de liste. Ceci garde le contrôle de liste frais et affiche uniquement les fichiers MP3 sur lesquels vous travaillez actuellement. Cela signifie également que vous devez réinsérer toutes les colonnes.

Ensuite, vous voudrez prendre le dossier qui a été transmis et utiliser le glob module pour rechercher des fichiers MP3.

Ensuite, vous pouvez passer en boucle sur les MP3 et les transformer en eyed3 objets. Vous pouvez le faire en appelant le .charge() de eyed3. En supposant que les MP3 possèdent déjà les balises appropriées, vous pouvez ensuite ajouter l'artiste, l'album et le titre du MP3 au contrôle de liste.

Fait intéressant, la méthode permettant d’ajouter une nouvelle ligne à un objet de contrôle de liste consiste à appeler .InsertItem () pour la première colonne et SetItem () pour toutes les colonnes suivantes.

La dernière étape consiste à enregistrer votre objet MP3 dans votre dictionnaire Python, row_obj_dict.

Maintenant, vous devez mettre à jour le .on_edit () gestionnaire d’événements pour que vous puissiez éditer les tags d’un MP3:

def on_edit(soi, un événement):
    sélection = soi.list_ctrl.GetFocusedItem()
    si sélection > = 0:
        mp3 = soi.row_obj_dict[[[[sélection]
        dlg = EditDialog(mp3)
        dlg.ShowModal()
        soi.update_mp3_listing(soi.chemin_fichier_current)
        dlg.Détruire()

La première chose à faire est d’obtenir la sélection de l’utilisateur en appelant le service de contrôle de liste. .GetFocusedItem ().

Si l'utilisateur n'a rien sélectionné dans le contrôle de liste, il retournera -1. En supposant que l'utilisateur ait sélectionné quelque chose, vous souhaiterez extraire l'objet MP3 de votre dictionnaire et ouvrir une boîte de dialogue de l'éditeur de balises MP3. Ce sera une boîte de dialogue personnalisée que vous utiliserez pour éditer les balises artiste, album et titre du fichier MP3.

Comme d'habitude, affichez le dialogue de manière modale. Lorsque la boîte de dialogue se ferme, les deux dernières lignes de .on_edit () va exécuter. Ces deux lignes mettront à jour le contrôle de liste pour qu'il affiche les informations de balise MP3 actuelles que l'utilisateur vient de modifier et détruira la boîte de dialogue.

Création d'un dialogue d'édition

La dernière pièce du puzzle consiste à créer une boîte de dialogue d’édition de balises MP3. Par souci de brièveté, nous allons ignorer l'esquisse de cette interface car il s'agit d'une série de lignes contenant des étiquettes et des contrôles de texte. Les commandes de texte doivent contenir les informations de balise existantes préremplies. Vous pouvez créer une étiquette pour les contrôles de texte en créant des instances de wx.StaticText.

Lorsque vous devez créer une boîte de dialogue personnalisée, le wx.Dialog la classe est votre ami. Vous pouvez l'utiliser pour concevoir l'éditeur:

classe EditDialog(wx.Dialogue):    
    def __init__(soi, mp3):
        Titre = F'Édition "mp3.tag.title"'
        super().__init__(parent=Aucun, Titre=Titre)        
        soi.mp3 = mp3        
        soi.main_sizer = wx.BoxSizer(wx.VERTICALE)        
        soi.artiste = wx.TextCtrl(
            soi, valeur=soi.mp3.étiquette.artiste)
        soi.add_widgets('Artiste', soi.artiste)        
        soi.album = wx.TextCtrl(
            soi, valeur=soi.mp3.étiquette.album)
        soi.add_widgets('Album', soi.album)        
        soi.Titre = wx.TextCtrl(
            soi, valeur=soi.mp3.étiquette.Titre)
        soi.add_widgets('Title', soi.Titre)        
        btn_sizer = wx.BoxSizer()
        save_btn = wx.Button(soi, étiquette='Save')
        save_btn.Bind(wx.EVT_BUTTON, soi.on_save)        
        btn_sizer.Ajouter(save_btn, 0, wx.ALL, 5)
        btn_sizer.Ajouter(wx.Button(
            soi, identifiant=wx.ID_CANCEL), 0, wx.ALL, 5)
        soi.main_sizer.Ajouter(btn_sizer, 0, wx.CENTER)        
        soi.SetSizer(soi.main_sizer)

Here you want to start off by sub-classing wx.Dialog and giving it a custom title based on the title of the MP3 that you are editing.

Next you can create the sizer you want to use and the widgets. To make things easier, you can create a helper method called .add_widgets() for adding the wx.StaticText widgets as rows with the text control instances. The only other widget here is the sauvegarder button.

Let’s write the add_widgets method next:

    def add_widgets(soi, label_text, text_ctrl):
        row_sizer = wx.BoxSizer(wx.HORIZONTAL)
        étiquette = wx.StaticText(soi, étiquette=label_text,
                              Taille=(50, -1))
        row_sizer.Ajouter(étiquette, 0, wx.ALL, 5)
        row_sizer.Ajouter(text_ctrl, 1, wx.ALL | wx.EXPAND, 5)
        soi.main_sizer.Ajouter(row_sizer, 0, wx.EXPAND)

add_widgets() takes the label’s text and the text control instance. It then creates a horizontally oriented BoxSizer.

Next you will create an instance of wx.StaticText using the passed-in text for its label parameter. You will also set its size to be 50 pixels wide and the default height is set with a -1. Since you want the label before the text control, you will add the StaticText widget to your BoxSizer first and then add the text control .

Finally, you want to add the horizontal sizer to the top level vertical sizer. By nesting the sizers in each other, you can design complex applications.

Now you will need to create the on_save() event handler so that you can save your changes:

    def on_save(soi, un événement):
        soi.mp3.étiquette.artiste = soi.artiste.GetValue()
        soi.mp3.étiquette.album = soi.album.GetValue()
        soi.mp3.étiquette.Titre = soi.Titre.GetValue()
        soi.mp3.étiquette.enregistrer()
        soi.Close()

Here you set the tags to the contents of the text controls and then call the eyed3 object’s .save(). Finally, you call the .Close() of the dialog. The reason you call .Close() here instead of .Destroy() is that you already call .Destroy() dans le .on_edit() of your panel subclass.

Now your application is complete!

Conclusion

You learned a lot about wxPython in this article. You became familiar with the basics of creating GUI applications using wxPython.

You now know more about the following:

  • How to work with some of wxPython’s widgets
  • How events work in wxPython
  • How absolute positioning compares with sizers
  • How to create a skeleton application

Finally you learned how to create a working application, an MP3 tag editor. You can use what you learned in this article to continue to enhance this application or perhaps create an amazing application on your own.

The wxPython GUI toolkit is robust and full of interesting widgets that you can use to build cross-platform applications. You are limited by only your imagination.

Lectures complémentaires

If you would like to learn more about wxPython, you can check out some of the following links:

For more information on what else you can do with Python, you might want to check out What Can I Do with Python? If you’d like to learn more about Python’s super(), then Supercharge Your Classes With Python super() may be just right for you.

You can also download the code for the MP3 tag editor application that you created in this article if you want to study it more in depth.