Aller au contenu

Programmation Python/wxFormBuilder

Un livre de Wikilivres.

wxpython avec wxFormBuilder (Python 3.13.2, wxPython 4.2.2 wxFormBuilder 4.2.1)

[modifier | modifier le wikicode]

wxFormBuilder est un constructeur d'interface graphique pour le framework wxWidgets, tournant sous Windows, Linux et macOS. Il supporte la génération de code pour C++, Python, Lua et PHP, l'importation et l'exportation de code XRC. Des extensions personnalisées peuvent être créées pour ajouter de nouveaux composants.

Notes (temporaires) de Musclor13

Page créé suite a un manque d'infos en francais et un bel échange sur https://discuss.afpy.org/t/probleme-wxformbuilder-wxpython-et-print/2544 Des erreurs possibles car je débute en GUI. Page en cours de création.


Après avoir créé plein de petits modules qui deviennent des grand modules puis des programmes en ligne de commande, on a parfois envie de rendre le tout plus joli et plus accessible avec des fenêtres, des barres d'outils et des menus et cases à cocher en tout genre... Malheureusement, il faut tout faire à la main et pour débuter devoir écrire plein de lignes de codes juste pour une boite de dialogue... Cela peut faire peur ou démotiver... Quel dommage qu'il n'y ait pas d'applications de dessin comme LibreOffice Draw, Inkscape ou Adobe Illustrator mais où on remplacerait les ellipses et autres flèches de diagrammes par des barres, boutons, champs de textes et palettes de couleurs...

Visiblement c'est ce que certains développeurs ont du se dire alors ils ont créé ces fameux outils de "dessin" (et oui, il y en a plusieurs!):

Ces outils sont généralement fait pour créer automatiquement le code correspondant a l'affichage des fenêtres dans des bibliothèques (QT, GTK wx...) et des langages de programmations divers (C++, Java, Python...). Il existe même des moteurs de jeux comme Godot qui permettent de dessiner ses fenêtre et boutons pour n'avoir plus qu'à coder le gameplay en Gdscript.

Une de ces applications s'appelle XxFormBuilder. Elle permet de dessiner ses interfaces graphique puis d'exporter le tout dans plusieurs langages y compris le C++... Vous suivez ? Bien sur le Python est supporté également et ici on code en python (enfin normalement).

Cette application ainsi que la bibliothèque sont compatibles avec Windows Linux et mac. Malheureusement pas de portage Android ou IOS à l'heure actuelle. Si vous souhaitez créer des applications pour votre smartphone, il faudra trouver une autre bibliothèque graphique mais si c'est pour ordinateurs allez-y ! Surtout que la bibliothèque fonctionne pareille sur l'OS du voisin même si c'est pas le même que le votre (enfin sauf si il a Windows98 et dans ce cas Python ne marchera même pas).

Bien sûr, il ne faudra pas jeter son éditeur de code dans les cachots de l'empire galactique. L'application ne fait pas tout à votre place. Ne jetez pas non plus votre machine à café car cette application ne vous servira pas d'expresso. Et surtout ne jetez pas votre chien ou votre chat car... Il aimerait pas (le chien ou le chat, pas l'application).

Il faut installer quoi ?

[modifier | modifier le wikicode]
  • Python (oui-oui) en version 3 (la version 3.13.2 est utilisé ici)
  • wxPython (ici c'est la version 4.2.2)
  • wxFormBuilder (les tests pour ce tutoriel ont été réalisés avec la version 4.2.1)

L'installation

[modifier | modifier le wikicode]

Normalement python est déjà installé sur votre ordinateur donc on ne s'attarde pas là-dessus.

Si on regarde le site officiel à la page des téléchargements...

https://wxpython.org/pages/downloads/

...On a toutes les informations qu'il faut.

Sous Windows et mac, il suffit d'utiliser PIP (pas de tuto sur PIP sur ce livre ?) :

 pip install -U wxPython 

Sous Linux, c'est différent donc regardez la page en lien ci-dessus

Là, c'est comme pour n'importe quelles applications (sous Windows en tous cas).

Il faut télécharger la version qui correspond à son système sur

https://github.com/wxFormBuilder/wxFormBuilder/releases ATTENTION: le projet a encore les restes d'une page sur Sourceforge mais il a été déplacé sur GitHub vers 2015 donc si vous ne trouvez pas la version 4.2.1 ou plus, c'est que vous vous êtes trompé de site.

Les bases sans codage ni scripts

[modifier | modifier le wikicode]

Une fois l'application installée, lancez-là. En plus, elle a été faite en wxWidgets donc en théorie vous pourriez la recopier.

L'interface graphique

[modifier | modifier le wikicode]

Quand on ouvre l'application, on a :

  • Une barre de menu et d'outils en haut
  • Un panneau "Object Tree" à gauche avec une racine "MyProject1 : Project"
  • Un "éditeur" au milieu avec deux barres d'outils juste au dessus : "Editor" avec Designer sélectionné et "Component Palette" Normalement, c'est un gros rectangle gris.
  • A droite, on a le panneau "Object Properties" avec 2 boutons tout en haut : Properties qui est normalement déjà sélectionné et Event. Au milieu, on a tout un tas de réglages que l'on peut faire avec une souris et en bas un espace vide qui affiche une aide en anglais dès qu'on clique sur un de ces réglages.

Vous pouvez toujours échanger l'emplacement de "'éditeur" et la barre "Object propretés" dans le menu View.

Du coté de "Component Palette", si vous cliquez sur un des boutons de la deuxième ligne, vous pouvez dessiner votre fenêtre... Ou pas car bizarrement y a un message d'erreur. Si vous avez déjà utilisé Tkinter ou une autre bibliothèque d'interface graphique, vous savez déjà pourquoi. Mais si vous débutez alors...

Apprenez les base du dessin d'interfaces graphiques

[modifier | modifier le wikicode]

Le premier conseil que je donnerais c'est de s'amuser à "dessiner" des trucs sans chercher à faire des trucs sérieux avec du code. Il y a quand même une logique qui fait par exemple qu'on ne peut pas mettre une barre d'outils ou une fenêtre dans une case à cocher (logique).

Une fenêtre bien ordonnée

[modifier | modifier le wikicode]

Dans le menu "Components" et la barre "Component palette", on peut voir que chaque composant de fenêtres est trié par catégorie.

Voici un tri non exhaustif par "niveaux"

- "Forms" pour créer la base des fenêtres ("frame", "dialog" et "panels" sont couramment utilisés)

- "Layout" permet de créer des positionnement de futurs objets. "wxWrapSizer" est bien pour créer des trucs vite fait sans se soucier de l'ordre ni de la mise en page. Il vaut mieux toutefois tester et utiliser les autres "Layout" et faire quelque chose de propre dès le départ

- "Conteners" contient de quoi créer des onglets entre autres (faudra remettre un "Layout" dedans).

- "Common" contient les boutons, textes et autres cases à cocher souvent utilisés

- "Menu/Toolbars" est un peu à part car il permet d'ajouter des menus et des barres d'outils ou de statuts sans nécessiter de "Layout".

Dans le panneau "Object Tree", un clic droit sur la fenêtre créée ouvre un menus permettant entre autres d’accéder au "Menu Editor..." qui permet de créer simplement ses menus et sous-menus.

Et maintenant...

[modifier | modifier le wikicode]

Vous en savez assez pour vous amuser sans codage. Ne cherchez pas à créer une interface graphique complexe ou pro mais testez juste des petits trucs pour voir ce que ça peut donner. Jouez avec les propriétés "properties" et voyez ce que ça peut donner. Le résultat sera inutile sans code mais peut être beau à regarder tout de même.

Le retour du code

[modifier | modifier le wikicode]

Vous ne vous y attendez pas ? Pourtant il reviens tapis dans l'ombre: Le code Python :D .

Sachez que dans "Object Tree" vous pouvez cliquer sur le noeud racine (par défaut "MyProject1 :project" ) et dans "Object Properties" (onglet "Properties") vous pouvez double-cliquer sur code-generator pour par exemple désactiver C++ et activer Python (éventuellement XRC peut aussi être activé en même temps mais n'aura d'intérêt qu'en cas de gros projets). En faisant "File/Generate Code", vous aurez les scripts dans tous les langages que vous avez activés. Malheureusement les scripts en Python générés ne semblent pas s'exécuter et quand on ouvre ceux-ci dans un bloc-notes ou un IDE ba... Il y a un long commentaire

PLEASE DO *NOT* EDIT THIS FILE! 

Traduction très approximative : "ne modifiez pas ce fichier sinon ca fera BOUM et un serpent viendra vous manger" (Si vous avez un niveau d'anglais catastrophique alors bienvenue au club).

On continue?...

[modifier | modifier le wikicode]

Pour tester les scripts on part sur 3 fichiers:

[modifier | modifier le wikicode]

- Un fichier wxformbuilder qu'on appellera "Sourcewxform.fbp" qui contient ce qu'on crée avec wxformbuilder

- le fichier "Monwxform.py" que le logiciel a généré et qu'on se contentera de regarder (on le regénèrera si on modifie le fichier wxformbuilder)

- Le fichier "run.py" dans lequel on va écrire notre code

Évidemment vous pouvez donner d'autre noms à vos fichiers mais gardez les extension tel quel et adaptez vos codes en conséquence...

Dans le fichier "run.py", on part du principe que vous importerez toujours les modules en écrivant ces lignes dans vos scripts:

import wx import Monwxform 

En gros, on importe wx qui permet d'afficher les fenêtres et on importe le script python "Monwxform.py" que wxformbuilder nous a générés (sans l'extension et si toutefois vous avez gardé les mêmes noms de fichiers que dans ce tutoriel sinon adaptez en conséquence).

Afficher sa création

[modifier | modifier le wikicode]

Ça y est ! Vous avez créé une œuvre d'art et vous souhaitez la montrer au monde entier sur votre merveilleuse machine ? Ou alors vous voulez simplement faire un poisson d'avril à un ami ou un collègue en lui disant "Tiens, ton logiciel préféré ne marche pas comme d'habitude..." ?

Codes rapide en 2 versions sans explications

[modifier | modifier le wikicode]

Une fois les modules importés, il suffirait d'écrire :

Monwxform.MyFrame1(None).Show() wx.App().MainLoop() 

Mais pour plus de lisibilité et flexibilité, on préfèrera écrire :

app = wx.App() fenetre=Monwxform.MyFrame1(None) fenetre.Show() app.MainLoop() 

Et voila... Mais peut-être qu'il serait bien d'expliquer un peu comment ça fonctionne, non ?

Comment marche le code minimal vs code amélioré

[modifier | modifier le wikicode]

Voici comment fonctionne les 2 versions du code et leurs avantages et inconvénients.

La différence principale c'est que la version plus longue du code utilise des variables. La première ligne de cette version permet de créer une variable vide wx.App.

app = wx.App() 

La première ligne de la version courte

Monwxform.MyFrame1(None).Show() 

correspond a peu près à:

fenetre=Monwxform.MyFrame1(None) fenetre.Show() 

Cette partie des scripts indique que la fenêtre sera affichée (d'ou le "Show()" en fin de chaque bout de scripts).

Rappel: il s'agit de la fenêtre appelée MyFrame1 présent dans le fichier Monwxform.py mais il aurait fallu adapter en fonction des noms donnés aux fichiers et fenêtres.

Malheureusement rien ne s'affiche mais c'est tout à fait normal car il manque :

wx.App().MainLoop() 

ou

app.MainLoop() 

Si on travail dans une variable appellé "app"

Ça sert à quoi de passer par une variable ?

[modifier | modifier le wikicode]

Simplement à retrouver la fenêtre quelque part dans la mémoire et ainsi à changer des infos pendant l’exécution du script (par exemple récupérer un texte écrit par l'utilisateur ou avoir un label qui afficherait un texte différent si il se passe quelque chose comme par exemple le clic sur un bouton).

Le résultat ? La fenêtre s'affiche, on peut cliquer sur les éventuels boutons, dérouler les menus, taper du texte et jouer avec les barres de défilement... Mais ça sert à rien vu qu'il n'y a pas d'event.

Les evennements ou "event"

[modifier | modifier le wikicode]

Vous l'avez surement remarqué: dans le panneau "Object Properties" il y a un onglet Events avec des tas de textes en anglais qui diffèrent suivant le type de composent sélectionner.

Exemple: les fenêtres ont comme evenement OnClose (si la fenêtre est fermée).

Les boutons ont quand a eut un événement OnButtonClick (quand l'utilisateur actionne le bouton en cliquant dessus par exemple)

Créer un event dans wxFormBuilder et l'utiliser dans Python

[modifier | modifier le wikicode]

Si on a créé un bouton, qu'on l'a sélectionné et que dans l'onglet "event" on renseigne un évènement par exemple en écrivant "clicsurlebouton" dans le champ de texte à coté de "OnButtonClick" le code Python généré se terminera par:

# Virtual event handlers, override them in your derived class def clicsurlebouton( self, event ): event.Skip() 

On aurait pu se dire qu'il faut l'éditer mais si on régénère le code (après une petite modification de taille de fenêtre par exemple faudrait tout recommencer (d'où le "PLEASE DO *NOT* EDIT THIS FILE!" au début du script).

Une base pour bien commencer

[modifier | modifier le wikicode]

Créez un nouveau fichier, assurez-vous que la propriété "code_generator" indique "Python", dans la propriété "Path" mettez "." et dans la propriété "file" indiquez "genwx" (dans le panneau "Object Properties", bouton "properties")

Ajoutez une Frame (via le menu Components/Form//Frame) et nommez-la "Fenetre" (dans la propriété "Name" entrez le nom l'objet créé).

Créez un wxGridSizer (via le menu Components/layout/wxGridSizer) et laissez le nom par défaut (probablement "gSizer1").

Créez un wxButton (un bouton créable via le menu Components/Common/wxButton) et nommez-le "bouton". Vous avez aussi quelques lignes plus bas une propriété "Label". Écrivez ce que vous voulez afficher, ça n'a pas d'importance ici.


Si tout va bien, vous aurez dans Object Tree:

<Votreprojet> - Project Fenetre - Frame gSizer1 - wxGridSizer bouton - wxBytton

Assurez-vous que "bouton - wxBytton" soit bien sélectionné et dans le panneau Object Properties cliquez sur "Event"

Dans l'event OnButtonClic entrez "clicsurlebouton"

Et sauvegardez le fichier dans "Sourcewxform.fbp" (choisissez un dossier vide)

Naturellement, rien ne vous empêche de tout nommer autrement mais faudra adapter le code donc gardez les miens si vous débutez.

Et générez votre code Python via le menu File/Generate Code !

Remplacement (override) d'evenement (event)
[modifier | modifier le wikicode]

Pour remplacer un évènement, le code est assez spécial quand on débute...

Partons du principe que vous avez bien généré votre code python après avoir créé un event "clicsurlebouton". Il faudra créer un script à coté de "Monwxform.py" qu'on appellera "run.py".

Il faudra évidemment importer wx et peut-être wx.xrc et importer le script qu'on aura généré et renommé en "genwx.py" si ce n'est pas le cas.

import genwx class MaFenetre(genwx.Fenetre):  def __init__(self, *args, **kwargs):  super().__init__(*args, **kwargs)  # self.mavariable = mavaleur  def clicsurlebouton(self, event):  event.Skip() app = wx.App() fenetre = MaFenetre(None) fenetre.Show() app.MainLoop() 
Explication du code
[modifier | modifier le wikicode]

Voici comment fonctionne tout ça :

import genwx 

On importe le fichier "genwx.py"

class MaFenetre(genwx.Fenetre): 

On crée une class qu'on appellera MaFenetre et qui pointe vers la "Frame" qu'on a créé et qui est devenu la class "Fenetre" dans le fichier "genwx.py" (si "Fenetre" est bien le nom qu'on a donné a sa "Frame" dans wxFormBuilder).

    def __init__(self, *args, **kwargs): 

On définit la fonction __init__ qui se charge a l'exécution de la class ATTENTION A L'INDENTATION!

        super().__init__(*args, **kwargs) 

Ce truc bizarre n'est pas la pour rien car si on jette on œil sur le fichier "genwx.py" on peut voire que la class "Fenetre" contiens sa fonction __init__ qu'il faut garder car elle contient tout ce qu'il faut pour afficher notre "wxButton" (entre autre). Un super() sans rien derrière serais assez moyen car ca voudrais peut-être dire qu'on crée une fonction init dans notre fonction init et ca génèrerais une erreur car les éléments nécéssaires se trouvent dans __init__ donc faudrait pas chercher dans __init__.__init__ . N'hésiter pas a vous rensaigner sur cette super() fonction!

        # self.mavariable = mavaleur 

Ce code est commenté car il sert d'exemple. On pourrait faire autre chose à la création de la fenêtre. Mais en général on crée des variables.

    def clicsurlebouton(self, event): 

Vous vous souvenez des dernières lignes du code de "genwx.py" ? On la remet ici en faisant toujours attention à l'indentation.

        event.Skip() 

Cette ligne était dans le script  genwx.py" et sera exécutée dès que l'utilisateur cliquera sur le bouton mais actuellement il se contente de passer l'évènement et il ne se passera rien (j'ai fait un bête copier/coller).

app = wx.App() fenetre = MaFenetre(None) fenetre.Show() app.MainLoop() 

Relisez le chapitre "Afficher sa création". Ici on utilise entre autre la variable fenetre qui "charge" la class MaFenetre avec "None" comme parent car elle est toute seule.

Remplaçons ce "event.Skip()"
[modifier | modifier le wikicode]

A la place de cette fameuse ligne "event.Skip()" du fichier "run.py" on peut mettre notre code.

        print("clic sur le bouton") 

Attention à l'indentation. Elle doit rester la même ! Et pour que le script "marche", il faut qu'un terminal (une console, une invite de commande...) s'ouvre quand on lance le script. Et si on s'amuse à cliquer plein de fois sur le bouton le terminal affichera

clic sur le bouton clic sur le bouton clic sur le bouton ... 

Et oui. J'avoue. J'ai quintuple-cliqué comme un bourrin mais je nous ai épargné toutes les autres lignes de "clic sur le bouton" car je ne veux pas trop alourdir le texte !

Amusons-nous un peu du coté de app.MainLoop()
[modifier | modifier le wikicode]

Et si on rajoutais un

print("avant MainLoop") 

juste avant le "app.MainLoop()" et un

print("apres MainLoop") 

à la fin de notre code ?

En lançant le programme, la sortie pourrait être :

avant MainLoop clic sur le bouton clic sur le bouton apres MainLoop 

J'ai cliqué que deux fois donc je mets tout. Comme quoi de temps en temps je ne bourrine pas :D .

Malheureusement si vous lancez votre programme en double-cliquant dessus vous n'aurez pas le temps de voir le "apres MainLoop" qui s'affiche bel et bien mais qui part avec la fenêtre avant même qu'on puisse lire. Utilisez une ligne de commande ou une autre astuce pour laisser le terminal ouvert.

Et si on comptait ?
[modifier | modifier le wikicode]

Là on va faire encore plus fort. On va compter combien de fois l'utilisateur clique sur le bouton et on ne l'affichera qu'une fois la fenêtre fermée (sinon ce serait trop simple). Dans le code plus haut, j'avais mis :

        # self.mavariable = mavaleur 

C'est un commentaire qui ne sert à rien... Ou presque car on peut le remplacer par le vrai code :

        self.combien_de_clics = 0 

Ne mettez pas de # et indentez le code comme il faut. Et juste en dessous de notre

 print("clic sur le bouton") 

on rajoute un

        self.combien_de_clics += 1 

toujours bien indenté.

Et pour faire plus joli, on remplacera notre dernière ligne

print("apres MainLoop") 

par

print(f"apres MainLoop et {fenetre.combien_de_clics} clics") 

Si vous n'êtes pas à l'aise avec les F-string, vous pouvez préférez mettre:

print("apres MainLoop et ", fenetre.combien_de_clics, "clics") 
A quoi ressemble tout ça ?
[modifier | modifier le wikicode]
Le code Run.py qu'on a écrit nous-même:
[modifier | modifier le wikicode]
# -*- coding=utf-8 -*- #/usr/bin/env python3  import wx import wx.xrc  import genwx   compteclic= 0  class MaFenetre(genwx.Fenetre):  def clicsurlebouton(self, event):  print("clic sur le bouton")  global compteclic  compteclic+=1  app = wx.App() MaFenetre(None).Show() print("avant MainLoop") app.MainLoop() print("apres MainLoop et", compteclic, "clics") 
Le genwx.py qu'on a généré :
[modifier | modifier le wikicode]
# -*- coding: utf-8 -*-  ########################################################################### ## Python code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) ## http://www.wxformbuilder.org/ ## ## PLEASE DO *NOT* EDIT THIS FILE! ###########################################################################  import wx import wx.xrc  import gettext _ = gettext.gettext  ########################################################################### ## Class Fenetre ###########################################################################  class Fenetre ( wx.Frame ):   def __init__( self, parent ):  wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )   self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )   gSizer1 = wx.GridSizer( 0, 2, 0, 0 )   self.bouton = wx.Button( self, wx.ID_ANY, _(u"Afficher texte dans console"), wx.DefaultPosition, wx.DefaultSize, 0 )  gSizer1.Add( self.bouton, 0, wx.ALL, 5 )    self.SetSizer( gSizer1 )  self.Layout()   self.Centre( wx.BOTH )   # Connect Events  self.bouton.Bind( wx.EVT_BUTTON, self.clicsurlebouton )   def __del__( self ):  pass    # Virtual event handlers, override them in your derived class  def clicsurlebouton( self, event ):  event.Skip() 

Quelques astuces et corrections de bugs de wxFormBuilder

[modifier | modifier le wikicode]

Les cases a cocher font n'importe quoi ("forward_declare")?

[modifier | modifier le wikicode]

C'est apparemment un problème qui peu arriver... Mais même si ca "casse" le fichier wx et son code c'est très simple à résoudre.

J'ignore si ce problème concerne seulement "forward_declare" ou si il concerne aussi d'autres choses mais étant donné que wxFoemBuilder est opensource et que n'importe quel développeur peu proposer des changements/correctifs peut-être qu'un jour ce soucis sera corrigé par vous ou quelqu'un d'autre mais en attendant faut faire avec.

Dans mon cas quand je lançais le code généré j'avais droit a ces deux dernières lignes:

from forward_declare import getbar ModuleNotFoundError: No module named 'forward_declare' 

Qui étaient provoqués par une bêtise:

J'avais activé "forward_declare" qui marche apparement en C++ mais pas ici. Cette petite case a coché est dans la propriété "subclass" (peut-être que j'ai testé des trucs dans cette propriété car désolé de vous décevoir mais je fait des erreurs comme tout le monde :) ).

La solution? Désactiver la case? Pas seulement...

La solution se trouve ici mais en anglais : BUG wxformbuilder Python - NameError: name 'forward_declare' is not defined · Issue #893 · wxFormBuilder/wxFormBuilder

Si vous ne savez pas ou vous avez coché la case
[modifier | modifier le wikicode]

Ouvrez le fichier enregistré avec wxFormBuilder (le ".fbp" et non le ou les fichiers générés) avec un logiciel type BlocNote Windows si vous ne savez pas sur quel item cette case est coché.

Recherchez (edition/rechercher la plupars du temps) le terme "forward_declare".

Il y a de fortes chance qu'il vous le trouve dans une ligne du genre:

<property name="subclass">; ; forward_declare</property> 

Vous trouverez plusieurs lignes au-dessus un truc du genre:

<property name="name">LeNomDeMonItem</property> 

Ainsi que d'autres propriétés qui peuvent vous aider identifier de quel objet il s'agis.

Même si vous n'êtes pas sur a 100% vous pouvez malgré tout faire l'une des solutions ci-dessous.

Vous savez ou se trouve votre objet: méthode simple et sure:
[modifier | modifier le wikicode]

Ouvrez le fichier enregistré avec wxFormBuilder (le ".fbp" et non le ou les fichiers générés) normalement (avec wxFormBuilder donc).

selectionnez l'objet ou sous objet qui ipose problème (dans la section "Objects Tree").

Regardez du coté des propriétés de cet objet a la section "subclass"

- Case "forward_declare" coché et "name" et/ou "subclass" indique "forward_declare"? BUG!

- Case "forward_declare" coché mais les autres champs sont vides? Tout va bien. C'est juste un problème graphique

- Case "forward_declare" décoché et autres champs vides? Vous n'avez pas de problème de ce coté.

Alors bug? Si oui alors...
[modifier | modifier le wikicode]

Effacez la valeur de name et désélectionnez forward_declare.

Sélectionnez un autre contrôle dans l'arborescence des objets puis revenez au contrôle précédent, name devrait toujours être vide mais forward_declare devrait afficher à nouveau une coche.

Dans cet état, le fichier problème est résolut. Ne cliquez pas encore sur cette case à cocher. Elle restera coché mais c'est un problème graphique. La fonction lié a cette case est bien désactivé.

Répétez toutes ces étapes pour les éventuels autre contrôle. Ca va si il y en a 2 ou 3 mais si il y en a plus de 20 alors ca peu être long... Sauf si vous employez...

La méthode bourrine et très risquée
[modifier | modifier le wikicode]

Cette solution vous permet de régler le problème en peu de temps même avec plusieurs milliers d'objets avec ce problème... Mais elle peut aussi créer d'autres problèmes surtout si vous avez mal suivit les instructions qui suivent.

D'abord et avant tout sauvegardez votre fichier (le ".fbp" mais aussi pourquoi pas les autres fichiers au cas ou)

Ensuite assurez-vous bien de l'avoir sauvegardé si vous ne l'avez pas sauvegardé ou que vous avec sauvegardé un autre fichier alors vous risquerez de devoir recommencer la partie graphique de votre programme cote wxformbuilder.

Maintenant c'est l'heure du bourrinage!
[modifier | modifier le wikicode]

Ouvrez le fichier enregistré avec wxFormBuilder (le ".fbp" et non le ou les fichiers générés) avec un logiciel type BlocNote Windows.

Recherchez (edition/rechercher la plupars du temps) le terme "forward_declare".

Il y a de fortes chance qu'il vous le trouve dans une ligne du genre:

<property name="subclass">getbar; forward_declare</property>

ou du genre: 
<property name="subclass">; ; forward_declare</property> 

Selectionnez sette ligne en veillant bien a ce que le premier caractère soit bien le "<" et le dernier ">" (on peut facilement selectionner un espace sans faire expres.

Faites remplacer (ou rechercher et remplacer en général dans le menu Edition).

Remplacez cette ligne par:

 <property name="subclass"></property> 

Et cliquez sur "remplacer tout" (je vous avez bien dit que c'était bourrin)!

Recherchez a nouveau le terme "forward_declare".

Si il y a encore des résultats...
[modifier | modifier le wikicode]

Refaites la sélection de la ligne concerné (qui commance par "<property name="subclass">" et se termine par "</property>").

Il se peu qu'elle quelques différences a cause de ":" ou d'espaces en plus ou en moins dedans mais remplacez-les comme au-dessus par:

 <property name="subclass"></property> 

et refaite votre recherche de "forward_declare" pour pouvoir remplacer ensuite.

Si il y a aucune occurences de "forward_declare"...
[modifier | modifier le wikicode]

Bravo. Vous n'avez plus qu'a enregistrer votre fichier, le fermer et l''ouvrir normalement (avec wxformbuilder).

Si il y a des grosses erreurs ba... J'ai bien insisté sur le fait que vous devez bien fait une copie de sauvegarde. Vous pouvez ainsi le restaurer tel qu'il était avant le bourrinage et réessayez en faisant attention aux espaces et autre dans vos recherches et remplacements. Ou alors vous pouvez faire la méthode moins bourrine.

Si il n'y a pas d'erreurs et que tout semble normal vous pouvez générer votre code et retester. Normalement vous n'aurez plus cette erreur.

Gardez votre sauvegarde quand même car rien ne dit que vous n'aurez pas des soucis de mises en page ou autre...

Vous pensez avoir bien nommés vos objets et vous avez des soucis pour les retrouver ou pour les utiliser?

[modifier | modifier le wikicode]

Parfois vous créez des longs codes avec pleins de fenêtres et vous êtes perdus? Nommez bien vos objets. Comment? Vous l'avez déjà fait? Seulement en remplissant la propriété "name" ou vous avez aussi remplis la propriété "windows_name" qui se trouve sur chaque objets?

windows_name? Ca ne concerne pas que les fenêtres?

[modifier | modifier le wikicode]

Cette propriété "windows_name" est présente sur pleins de trucs: des boutons, des cases a cocher, des labels et... des fenêtres!

En fait elle est presque partout donc si vous voyez cette propriété quelque part alors servez-vous en.

L'utiliser dans wxFormBuilder
[modifier | modifier le wikicode]

Cherchez cette propriété et donnez-lui un nom. Pas besoin de guillemets. Dans mon exemple ce sera:

boutonquitter 

Vous pouvez mêttre un nom a rallonge mais il y a de fortes chance que la convention de nommage soit la même que pour des variables. A tester donc.

Et dans votre code
[modifier | modifier le wikicode]

Dans mon exemple vous n'aurez qu'a écrire:

wx.FindWindowByName("boutonquitter") 

Mettez bien les guillemets car c'est une chaine de caractère. Pratique par exemple si vous avez créés une liste et que vous cherchez la présence d'un item de celle-ci dans une boucle "for i in liste:"

Ici ca m'a retourné:

<wx._core.Button object at 0x000002D5E26B97F0> 

mais ca ne retourne rien si rien n'a été trouvé.

rien ne vous empèche par la suite de faire un

actionquitter= wx.FindWindowByName("boutonquitter")

Pour utiliser votre bouton grace a cette variable pour par exemple:

actionquitter.Label

Vous ne serez ainsi pas bloqué a cause d'une fonction/class qui ne marche pas car la variable n'existe pas (si vous codez après votre classe d'interface utilisateur) ou car c'est votre bouton qui n'existe pas (si vous codez en haut de votre fichier et avant la classe d'interface utilisateur).

Mais... Et "name" dans tout ca?
[modifier | modifier le wikicode]

Quand vous générez votre code et que vous l'importez (par exemple en faisant import monfichier) vous aurez une class mafenetre. Si vous y avez mis un bouton appellé monbouton alors vous pouvez y accéder dans la classe que vous écrivez dans votre code et afficher son label comme dans l'exemple du dessus en faisant:

self.monbouton.Label 

qui équivaux a:

monfichier.mafenetre.monbouton.Label 

Mais dés que vous codez en dehors de cette classe ce sera plus hasardeux. D'ou la solution du FindWindowByName() qui est bien plus simple pratique car il a tout les avantages des chaines de caractères sans les inconvenants des variables.

Et les ID dans tout ça?
[modifier | modifier le wikicode]

Les Id n'ont pas besoin d'être modifiés. C'est un chiffre qui est auto généré et on n'y touche pas en général.

Veillez donc a ce que la propriété "Id" soit réglé sur "wxID_ANY" sauf si vraiment vous savez ce que vous faites.

Changer l'Id à très facilement tendance à générer des erreurs.

Bien sur vous pouvez toujours faire un:

wx.FindWindowById(-31826)

Mais si vous n'avez pas définis d'Id spécifiques vous ne saurez pas si vous allez tomber sur quelque chose et si oui sur quoi donc plutôt a éviter.

En expérimentant et en lisant la documentation, vous pouvez faire plein de trucs. Voici des exemples qui pourraient s'appliquer a pas mal de codes dont le run.py du tutoriel:

Les programmes ont besoin de récupérer ce que l'utilisateur écrit ou fait et ils ont aussi besoin de remplacer des textes ou d'autres paramètres comme des noms de boutons ou autre en fonction de ce qu'il se passe par exemple pour afficher un message d'erreur.

Dans wxFormBuilder, on peut modifier des propriétés comme le "label" qui est entre autre le nom d'un bouton.

On peut modifier les propriétés d'un composent wx. Par exemple:

SetLabel("un texte") remplace le "Label" qu'on a indiqué par "Mon Texte"

Si on reprend le code Run.py du tutoriel, on peut rajouter à la fonction "clicsurlebouton" un

        self.bouton.SetLabel("Vous savez vous servir d'une souris") 

On pourrait aussi changer une couleur, une police, une taille ou un état (grisé, affiché, éditable) ou écrire la date et l'heure à la seconde près...

A noter que cela concerne seulement le label (le nom) d'un composent. Si vous avez une case a cocher vous modifirez le nom visible par l'utilisateur.

Un SetValue() permet de modifier l'état d'une case a cocher ou le texte éditable que l'utilisateur aurais entré.

On veut parfois pouvoir récupérer des infos par exemple le label d'un bouton (ou son nom).

Dans notre exemple de code bien plus haut, on pourrait mettre :

        print(self.bouton.GetLabel()) 

ou:

        print(self.bouton.Label) 

Et ca afficherait dans la console "Afficher texte dans console" ou "Vous savez vous servir d'une souris" si on utilise ce code après que l'utilisateur ait cliqué (si on a bien utilisé l'exemple du 'GetLabel()"

On pourrait aussi récupérer l'état d'une case à cocher, si une barre de défilement est au début ou au milieu, un texte écrit dans un champ prévu, le choix dans une liste mais ca se fait via GetValue() ou GetSelection().

Ouvrir et fermer des fenêtres

[modifier | modifier le wikicode]

Il est possible de dessiner plusieurs fenêtres dans un même fichier avec wxFormBuilder mais après il peut être utile de ne pas utiliser seulement la fenêtre principale...

Une belle ouverture

[modifier | modifier le wikicode]

Si par exemple vois avez créé une fenêtre "paramètres" qui permet a l'utilisateur de modifier la configuration de l'application il faut bien qu'elle s'affiche si il clique sur le bouton "paramêtre d'une barre d'outils ou si il ouvre un menu (généralement "Fichier", "Edition", "Outils" ou parfois "Options" et qu'il séléctionne "Paramêtres" (et je ne parle pas des racourcis claviers).

En réalité nous l'avons déja fait:

Monwxform.MyFrame1(None).Show() 

Et sa version qui passe par une variable (qu'on utilisera ici):

fenetre=Monwxform.MyFrame1(None) fenetre.Show() 

Évidemment ici on adaptera le code mais pas seulement...

Il faut créer un event comme le fameux "clicsurlebouton", et l'ajouter dans la class de la fenêtre pfincipale

def ouvre(self, event):         options = options(parent=self)         options.Show() 

On pourrais rajouter un self.Disable() pour que la fenêtre principale soit inutilisable tent qu'on utilise celle des options mais il ne faudra pas oublier de faire un self.GetParent().Enable() dans la classe de la fenêtre des options.

A noter que self est la classe ou l'on écrit le code donc self.GetParent() permettra de retrouver la fenêtre parent de celle des options. Bien sur on peu aussi faire un parent=none lors de l'ouverture mais c'est généralement le meilleur moyen de se compliquer la vie pour pas grand chose...

Pour fermer une fenêtre on pourrais panser qu'un "self.Close()" dans sa classe serait la solution... Mais pas toujours.

Oui, un self.Close() fonctionne bien mais en réalité il s'agis d'une action qui simule le fait de cliquer sur la croix en haut de votre fenêtre (la barre de titre) et cela lance l'action OnClose() qui ferme la fenêtre si laissé par défaut. C'est bien la plupart du temps mais on peut préférer détruire la fenêtre comme les monstres dans les films!

self.Destroy() 

ferme (ou détruit) la fenêtre concerné. Ici c'est self donc la fenêtre de la classe ou se trouve cette ligne de code mais ca pourrait aussi être une autre fenêtre voir peut-être une barre d'outil ou un onglet (a vérifier).

On peu toujours utiliser "self.Close()" si on veut faire la même action que celle de fermer la fenêtre a coup de alt+F4 et ca permettra de n'écrire qu'une seule ligne ici mais parfois détruire la fenêtre ne suffit pas... Il faut réactiver la fenêtre parent.

 def OnClose(self, event):         self.GetParent().Enable()         self.Destroy() 

Ici ce script réactivera la fenêtre parent et fermera notre fenêtre... Car si on oublis le self.destroy() la fenêtre ne se fermera pas même si on clique frénétiquement sur le bouton. Simplement car on remplace l'action par défaut qui indiquais de fermer la fenêtre. Parfois on ne veut pas fermer la fenêtre mais en ouvrir une autre qui demandera si on veut vraiment quitter ou enregistrer.

Sachez que détruire une fenêtre détruit aussi ses enfants. Pratique pour fermer son programme si on répond "oui" a "Voulez-vous vraiment quitter?" mais n'essayez pas de vous débarrasser de vos voisin de cette façon.

Pour résumer:

[modifier | modifier le wikicode]

Dans votre script vous pouvez indiquer le nom que vous avez donnés a vos composent de fenêtre (ou parfois self oula fenêtre elle-même) suivit d'un .quelquechose() qui peut être (entre autre)

récupère le nom visible de votre composent

Exemple:

GetLabel() 

peut renvoyer

"Annuler" 
"Ok" 
"Edition" 
"Ne plus afficher ce message" 

Mais je ne sais pas quel est le texte de votre bouton, menu ou case a cocher donc faites des tests de votre coté et vous verrez.

Permet de renommer un label (sur un bouton par exemple)

SetLabel("Vous venez de cliquer sur le bouton!") 

Idéal pour afficher une variable, la date et l'heure ou le texte d'un message dans une boite de dialogue qui serait différent suivant les actions de l'utilisateur (erreur, information...)

Permet de récupérer l'état d'une case a cocher ou le texte d'un champ éditable. A VERIFIER si cela retourne aussi le texte d'une liste déroulante ou de boutons radios a choix multiple

GetValue() 

Cela retourne le texte entré ou l'état d'une case a cocher comme ceci:

True 
False 
"Chapitre 10: La vengeance du python en tutu de ballerine" 

Pratique par exemple pour verifier si la case "ne plus afficher ce message" et activé ou non afin de ne pas bombarder l'utilisateur de popups alors qu'il a bien activer la case... Ou pour enregistrer des réglages dans un fichier de configuration quand l'utilisateur cliquera sur Ok. Peu aussi permettre d'avoir le nom d'un joueur dans une variable ou la sauvegarde de sa partie ou récupérer l'info comme quoi il a défini 1 en force et 9 en intelligence (ou l'inverse).

Permet de changer l'état d'une case a cocher ou d'un texte.*
[modifier | modifier le wikicode]

SetValue(True)

SetValue("Je viens d'effacer tout votre texte!")

Idéal pour charger des options préalablement sauvegardés et afficher l'état de chaque cases a cocher et champs de textes dans les options comme laissé par l'utilisateur. Peu aussi permettre de recharger la partie et le nom d'un joueur ou effacer tout son texte quand il clique sur un bouton.

GetSelection()
[modifier | modifier le wikicode]
SetSelection()
[modifier | modifier le wikicode]

SetEditable(True)

SetEditable(False)


Enable() et Disable()
[modifier | modifier le wikicode]

Enable(False)

Disable(True)

Enable(True)

Disable(False)

Pour aller pluis loins

[modifier | modifier le wikicode]

wxPython API Documentation — wxPython Phoenix 4.2.3 documentationLa documentation officielle en anglais

DEMO/wxFormBuilder at main · Musclorman/DEMODes bouts de codes non officiels et des sortes de "démos" avec commentaires en Français.

Astuce a vérifier

[modifier | modifier le wikicode]

Que retou_rne getvalue sur une liste déroulante ou a boutons radios.

GetSelection dans champ de texte, liste déroulante...

SetEditable et une sorte de GetEditable: Fonctionne sur quoi exactement? Seulement champ de texte?

Enable/disable avec et sans True/False sur tout un tas de trucs (y compris menus et barres d'outils)

Encore en cours d'écriture...