Salut,
Rien de bien mirobolant, juste une appli python qui permet de créer des fichiers .desktop pour Linux (Ubuntu).
Le git est là :
https://github.com/diablo76600/Ubuntu-Desktop-File
Version imprimable
Salut,
Rien de bien mirobolant, juste une appli python qui permet de créer des fichiers .desktop pour Linux (Ubuntu).
Le git est là :
https://github.com/diablo76600/Ubuntu-Desktop-File
bonjour
coté "linux":
Pourquoi cette liste de catégorie pas standard ?
La liste officielle est ici et à noter que TextEditor n'est qu'une des nombreuses sous-catégories.
Pourquoi parler de ubuntu ?
Ces fichiers sont un standard freedesktop respecté par les bureaux.
Pourquoi écrire un outil en QT alors que ubuntu est gtk (normalement) ? Pour kde ok, mais pour gnome (comme son nom et l'icône l'indique), cela me parait absurde. Mais, ok si c'est pour tout bureau
Choix de l'icône :
Avoir la possibilité de piocher dans le thème actif me semble un minimum !
Lanceur de cette application:
Pourquoi créer un script bash alors qu'il suffit de mettre un shebang à ton fichier main.py ?
--------------
Coté python je n'ai pas regardé en profondeur, juste très surpris de trouver un fichier "model.py" sans le "modèle" des datas de ce fichier.
En fait, tu n'utilises même pas un modèle :weird: Du coup, le métier est complètement imbriqué dans la Gui et ce qui entraine que je n'ai même pas envie de lire le code.
Dans le dialogue de sauvegarde, tu pourrais ajouter filter = "Application file (*.desktop)"
Bonjour
Une critique (un conseil): par exemple sur cette fonction (au hasard)...
... pourquoi définir le type de façon textuelle ? C'est tout sauf efficace. Analyser une chaine c'est loooong (relativement aux comparaisons numériques je veux dire). Surtout qu'en plus Qt offre déjà les constantes existantes !!! Pourquoi en redéfinir d'autres qui seront au mieux inutiles ?Code:
1
2
3
4
5
6 def display_message(title: str, text: str, type_message: str) -> None: """Display a message box with the specified title, text, and type.""" if type_message == "warning": QMessageBox.warning(None, title, text) else: QMessageBox.information(None, title, text)
Et à l'appel, tu lui passes au choix QMessageBox.Warning ou QMessageBox.Information. Ou (à voir) un autre flag permettant d'afficher un autre type de messageBox (fonction alors à faire évoluer ce qui est assez facile dans la structure match...)Code:
1
2
3
4
5
6
7 def display_message(title: str, text: str, type_message=QMessageBox.Information: int) -> None: """Display a message box with the specified title, text, and type.""" match type_message: case QMessageBox.Warning: box=QMessageBox.warning case QMessageBox.Information: box=QMessageBox.information # match box(None, title, text)
Autre chose: hériter de Qt c'est bien... mais cela ne doit pas faire perdre les avantages des objets d'origine (autrement dit, on préserve les objets et tous leurs paramètres). Surtout...
...que ce n'est franchement pas super compliqué...Code:
1
2
3
4 class UbuntuDesktopFileCategoriesView(QDialog): """Manage the Ubuntu Desktop File Categories View.""" def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs)
Il ne faut pas exagérer ! :lol: L'application ne va certainement pas afficher 1000 erreurs en même temps.Citation:
Analyser une chaine c'est loooong
mais: je suis tout a fait raccord avec la modification !
@Diablo76
En programmation, il est classique de remplacer les constantes chaines (dupliquées) par des constantes numériques :
- moins de place en mémoire
- plus rapide ...
- surtout pas de risque d'erreur (en gros, tu as presque écrit : getattr(QMessageBox, type_message)(None, title, text))
ps: voir, parfois, si besoin, remplacer par des constantes de type enum
Hello,
Pour display_message,
ça ne fonctionnerait pas ? Car type_message ne semble pas optionnel... la doc me semble pas vraiment utile pour ce genre de méthode, son nom se suffit à elle même.Code:
1
2
3 def display_message(title: str, text: str, type_message: str) -> None: """Display a message box with the specified title, text, and type.""" getattr(QMessageBox, type_message)(None, title, text)
Mais au final, la question à se poser, c'est qui gagne-t-on comparé à l'utilisation directe de QMessageBox.warning ou QMessageBox.information ?
La structure du projet n'est pas très harmonieuse, mais au final on s'en fou un peu... parce-que c'est un petit projet, et que la maintenabilité est simple et qu'on s'y retrouve sans trop de difficulté.
Et je suis d'accord avec @papajoker, attention aux noms que l'on veut donner surtout quand on veut y apporter un sens, un modèle ou "domain" correspond généralement a la définition des entités métier...
L'esthétisme est bon, si c'est fonctionnel c'est top ! Mais on va pas se leurrer, ce code ne grossissant pas dans le futur, j'ai pas grand chose à en dire, sinon que l'essentiel est là, ça fonctionne !
EDIT :
Sve@r,
Je ne suis pas sûr de comprendre cette phrase, n'est-ce pas l'appel standard d'une fonction ou d'une méthode avec des arguments optionnels (sans les spécifier à nouveau) ?Citation:
Autre chose: hériter de Qt c'est bien... mais cela ne doit pas faire perdre les avantages des objets d'origine (autrement dit, on préserve les objets et tous leurs paramètres)
Pour démontrer,
Je conserve bien tous les attributs de ma classe mère...Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class Test: def __init__(self, parent=None, x=5): # Même configuration que l'initialisation de QDialog (2 args optionnels) self.parent = parent self.x = x class NewTest(Test): def __init__(self, *args, **kwargs): super().__init__() nt = NewTest(2, 3, a=5, d="z") print(nt.parent) # None print(nt.x) # 5
Mais peut-être n'ai je pas compris où tu voulais en venir avec cette remarque ?
En fait, il nous manque une info ! Quelle est la logique derrière ce code ?Citation:
pas grand chose à en dire, sinon que l'essentiel est là, ça fonctionne !
Si le but est juste qu'il fonctionne, alors ok pour le code
Si le but est de faire un "template" de bon code (je soupçonne avec les noms controleur et modele), alors c'est loupé.
- Qt a son propre système mvc (pour ce strict besoin, pas trop utile ici)
- Bloquer la taille des fenêtres :? et en plus, tu places les composants de façon statique :roll:
Et même si on fixe la taille de l'application/dialogues, dans tous les cas, on place/taille les composants relativement, par rapport au conteneur. On peut attribuer des marges si c'est cela qui te pose problème.Code:
1
2
3
4
5 pushButton_quit.setGeometry(QRect(758, 352, 68, 32)) utton_y = 168 self.gridLayoutWidget.setGeometry(QRect(8, 8, 585, 165)) QRect(int((self.width() / 2) - 34), 352, 68, 32) self.label_icon_application.setGeometry(QRect(758, 274, 68, 68))
Si tu crées un objet qui hérite d'un QDialog (ou de tout autre objet), ce que tu crées doit à minima offrir les mêmes possibilités que l'objet hérité (sinon on ne fait pas d'héritage). Prenons ton Test et NewTest, tu dois pouvoir utiliser NewTest au-moins exactement comme Test (ce qu'il fait en plus ne doit pas enlever ce que fait "Test").
Et justement ton NewTest tel que tu l'as écrit (avec *args et **kwargs) offre la possibilité de l'appeler comme Test et ça c'est bon. Mais si tu enlèves *args et **kwargs, tu es alors obligé de lui mettre des paramètres à l'image de ceux de Test (ce qui freine alors son évolutivité).
Evidemment (tout avantage amène souvent un inconvénient en retour) cela offre la possibilité d'appeler NewTest avec des paramètres non prévus comme dans ton exemple NewTest(2, 3, a=5, d="z"). Dans ce cas c'est Test qui doit checker et refuser "a" et "d" (comme le fera QDialog si tu l'appelles avec des paramètres non attendus).
Mais on est bien d'accord que QDialog ne prend pas comme argument *args et **kwargs comme paramètres d'initialisation mais deux paramètres nommés et optionnels qui sont parent et f (flag) ?Citation:
Et justement ton NewTest tel que tu l'as écrit (avec *args et **kwargs) offre la possibilité de l'appeler comme Test et ça c'est bon. Mais si tu enlèves *args et **kwargs, tu es alors obligé de lui mettre des paramètres à l'image de ceux de Test (ce qui freine alors son évolutivité).
Pour aller plus loin dans mon code, voilà ce que je peux être amené à faire sans perdre ni attribut ni méthode de Test.Citation:
Evidemment (tout avantage amène souvent un inconvénient en retour) cela offre la possibilité d'appeler NewTest avec des paramètres non prévus comme dans ton exemple NewTest(2, 3, a=5, d="z"). Dans ce cas c'est Test qui doit checker et refuser "a" et "d" (comme le fera QDialog si tu l'appelles avec des paramètres non attendus).
Code:
1
2
3
4
5
6
7
8
9
10
11
12 class NewTest(Test): def __init__(self, *args, **kwargs): super().__init__() self.arguments = args self.dictionary = kwargs nt = NewTest(2, 3, a=5, d="z") print(nt.parent) # None print(nt.x) # 5 print(nt.arguments) # (2, 3) print(nt.dictionary) # {'a': 5, 'd': 'z'}
Effectivement les termes sont mal choisis, et peut-être qu'il en ressort une incompréhension.Citation:
Si le but est de faire un "template" de bon code (je soupçonne avec les noms controleur et modele), alors c'est loupé.
Tout à fait d'accord. *args et **kwargs tu le mets dans ton objet (d'ailleurs tu ne les as pas mis dans "Test" mais dans "NewTest" et ma toute première remarque s'appliquait à l'objet "UbuntuDesktopFileCategoriesView" de Diablo76) afin que l'utilisateur de ton objet puisse l'appeler avec les paramètres attendus par QDialog (pour avoir depuis ton objet le comportement de QDialog qu'il est en droit d'espérer).
Et c'est la magie de Python (ou plutôt l'unpacking) qui fait que puisque QDialog attend un paramètre "parent", si tu appelles UbuntuDesktopFileCategoriesView avec un paramètre "parent" ce paramètre sera transmis tel quel à QDialog. Et si tu ne le mets pas (effectivement c'est optionnel) ben il n'y aura pas de paramètre "parent" transmis.
Ok je vois que tu es embêté avec le *args et **kwargs, je le retire
On est bien d'accord que NewTest a un intérêt en y ajoutant des attributs comme il a pu être fait sur la UbuntuDesktopFileCategoriesView ?Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 class NewTest(Test): def __init__(self): super().__init__() self.arguments = (2, 3) self.dictionary = {'a': 5, 'd': 'z'} nt = NewTest() print(nt.parent) # None print(nt.x) # 5 print(nt.arguments) # (2, 3) print(nt.dictionary) # {'a': 5, 'd': 'z'}
Note que Test prend la même configuration qu'un QDialog
je comprends Sve@r , c'est simplement le O du principe SOLID
Mais, ici dans cette appplication, je ne vois pas l'intéret :Citation:
une entité applicative (classe, fonction, module ...) doit être fermée à la modification directe mais ouverte à l'extension
- c'est une classe finale
- cette classe ne sera jamais surchargée
- surtout, cette classe n'est utilisée que par un seul appel (je suppose donc que le dev a déjà passé tous ces bons paramètres). Et à la limite, si il désire changer un truc, c'est exactement le même travail de modification dans main ou le fichier de cette classe.
Si la classe était utilisée plusieurs fois et peut-être dans un contexte différent, alors oui, il faut l'Ouvrir au maximum.
Perso ? dans ce code, je ne passerais pas kwargs à cette class CategoriesDialog
EDIT:
Nous sommes bien d'accord, c'est un super principe. Mais le problème ici, c'est que tu n'as pas trop donné de raison. Le faire machinalement peut en être une...
Ben non, c'est justement la solution la plus efficace (et que je plébiscite) pour conserver l'héritage !!!
Bien sûr !!! Faire un héritage c'est pour avoir le beurre et l'argent du beurre. Mais il faut tout de même avoir le beurre.
Prends cet exemple: un objet "vehicule" (qui est présumé provenir d'une lib quelconque donc qui n'est pas de toi)
Code:
1
2
3 class vehicule: def __init__(self, vMax): self.vMax=vMax
Toi ton job est de créer un objet qui va hériter de véhicule (donc rajouter un truc "en plus", on va dire la marque, sans perdre les propriétés du véhicule).
Solution 1
Code:
1
2
3
4 class voiture(vehicule): def __init__(self, marque, vMax): super().__init__(vMax) self.marque=marque
L'utilisateur: vvv=voiture("marqueX", 200)...
Solution 2
Code:
1
2
3
4 class voiture(vehicule): def __init__(self, marque, *args, **kwargs): super().__init__(*args, **kwargs) self.marque=marque
L'utilisateur: vvv=voiture("marqueX", 200)...
Les deux solutions fonctionnent et sont utilisables de la même façon (y compris si l'objet de base a des paramètres facultatifs).
Mais la première impose de connaitre l'objet hérité (savoir ce qu'il attend pour reproduire les paramètres attendus dans ton objet). Et n'oublions pas que cet objet peut lui-même hériter d'un objet de plus haut niveau => obligé de tout connaitre pour créer un objet hérité correct.
Alors que la seconde est souple. Tu n'as pas à te préoccuper de "vehicule", tu lui passes "*args" et "**kwargs" sans te faire de noeud au cerveau et roule (c'est le cas de le dire).
Et en plus la seconde permet de suivre l'évolution. Si "vehicule" évolue...
... tu n'as pas besoin de modifier ton objet et c'est ça qui est important.Code:
1
2
3
4 class vehicule: def __init__(self, vMin, vMax): self.vMin=vMin self.vMax=vMax
Je pars du principe que Diablo76 nous a demandé un avis, qu'il a envie d'apprendre des trucs qu'il ne sait pas forcément, et qu'il pourra les appliquer demain dans d'autres objets plus critiques. Pour résumer c'est juste "pour le principe" 8-)
Ton exemple ne coïncide pas avec QDialog et ses deux arguments optionnels. vehicule a son initialisation prend un argument obligatoire, on est donc obligé lors de l'héritage de l'utiliser avec super().__init__(vMax=...).
En appelant super().__init__() il appelle la classe mère avec ses deux arguments par défaut, il n'y a pas besoin de les spécifier. C'est comme exécuter une fonction avec des arguments optionnels.
Code:
1
2
3 def test(a=2, b=3): pass test() # pas d'erreur
J'ai précisé que ça fonctionne aussi avec les arguments optionnels. Remplace (dans l'objet d'origine) vMax par vMax=200 par exemple...
Toute la question est là: et s'il veut les spécifier justement ? S'il veut appeler son objet avec un parent (parce que la parenté sous Qt c'est super important et que là il ne s'en préoccupe pas du tout) ???
Le principe est de pouvoir tout offrir, et pas seulement offrir le cas par défaut.
Il me semble que ce choix est un choix délibéré, il veut utiliser les paramètres par défaut pour parent et f.Citation:
Toute la question est là: et s'il veut les spécifier justement ?
C'est important, mais dans son cas, il n'a pas ce besoin à mon sens. Si on met des valeurs par défaut, c'est justement prévu que si on ne les utilise pas, ça rend tout de même fonctionnel le code. Il semble que ce choix lui convienne...Citation:
S'il veut appeler son objet avec un parent (parce que la parenté sous Qt c'est super important et que là il ne s'en préoccupe pas du tout) ???
Eh bien je peux l'écrireCitation:
Remplace (dans l'objet d'origine) vMax par vMax=200 par exemple...
ou comme cela si je sais que ma renault roule à 200 km maxCode:
1
2
3
4
5
6
7
8
9
10
11
12 class vehicule: def __init__(self, vMax=200): self.vMax=vMax class voiture(vehicule): def __init__(self, marque): super().__init__(vMax=160) self.marque=marque v = voiture("renault") print(v.vMax) # 160
Code:
1
2
3
4
5
6
7
8
9
10
11 class vehicule: def __init__(self, vMax=200): self.vMax=vMax class voiture(vehicule): def __init__(self, marque): super().__init__() self.marque=marque v = voiture("renault") print(v.vMax) # 200
C'est un avis (qui suit celui de papajoker). Je ne peux que te répondre la même chose: je suis parti du postulat que Diablo76 voulait notre avis, je lui ai donné le mien. Toutefois les habitudes ont la vie dure et on peut aussi penser que s'il code ses objets Qt de cette façon ici il les code de la même façon de partout 8-)
Je t'invite à aller voir mon exemple Qt n° 6 Construire des widgets en vrac où je crée un objet Qt de mon cru contenant un slider et un lcd associé, puis une fois créé, quand il est devenu tout aussi manipulable et indépendant que tout autre widget Qt, je l'utilise à ma convenance et en place plusieurs dans un gridLayout.
Et donc ton objet "voiture" ne permet pas de lui spécifier une quelconque vMax. C'est aussi une façon de voir les choses mais qui n'est pas la mienne et qui (désolé de le faire remarquer), me semble aller à l'encontre de ta précédente remarque sur les avantages recherchés dans l'héritage (quand tu disais au post que créer un héritage est fait pour avoir des attributs en plus). C'est donc dommage de faire un héritier qui ne permet plus l'accès aux paramètres de l'ancètre.
Et puis (rappel: cette discussion est juste là parce que tu as demandé que je t'explique ma remarque du post 1 donc je te l'explique bien volontiers) moi j'aime bien la souplesse et il me semble que la solution n° 2 à base de *args et **kwargs offre toute la souplesse du monde. Permet de spécifier une vMax si on veut, ou prendre la vMax par défaut...
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class vehicule: def __init__(self, vMax=200): self.vMax=vMax class voiture(vehicule): def __init__(self, marque, *args, **kwargs): super().__init__(*args, **kwargs) self.marque=marque v1 = voiture("renault") print(v1.vMax) # 200 v2 = voiture("bmw", vMax=360) print(v2.vMax) # 360
Et si demain la lib "vehicule v2" rajoute une vMin je n'aurai absolument rien à toucher à mon objet "voiture", il s'y adaptera automatiquement. Seuls les utilisateurs de mon objet devront s'y adapter (mais ça c'est normal quand la lib change).
Elle a plus de souplesse, le problème n'est pas vraiment là, je comprend ton point de vue, et je pense que @papajoker aussi, je voulais surtout exprimer que l'on peut accepter et croire que le développeur a bien décidé cette manière de faire, c'est lui qui développe son projet. On peut se questionner sur la manière de faire, et proposer une autre manière d'écrire, mais il pourrait ne pas là comprendre, car ce n'est peut-être pas une décision prise à la légère (bénéfice du doute) et surtout comme ça fonctionne, qu'est-ce que ça lui apportera sur un projet qui n'évoluera pas ?Citation:
Et puis (rappel: cette discussion est juste là parce que tu as demandé que je t'explique ma remarque du post 1 donc je te l'explique bien volontiers) moi j'aime bien la souplesse et il me semble que la solution n° 2 à base de *args et **kwargs offre toute la souplesse du monde.
Bref je t'ai un peu titiller, mais j'aime bien parler POO, j'ai pas pu m'empêcher...:D
Salut,
Merci pour vos retours.
Effectivement, ici l'héritage de QDialog ne m'intéresse que pour le mode Modal (mais j'ai pris en compte des remarques et arguments).
Un template de bon code, non, un code qui fonctionne déjà, c'est pas mal. Après, je ne suis pas encore à l'aise avec la notion de MVC (Je vois de tout et de rien sur le net...)
Peux-tu développer : "Qt a son propre système mvc"
C'est une méthode de développement dans laquelle on sépare un projet en 3 grands domaines
- le Modèle (tout ce qui a trait à la sauvegarde des datas => fichier, bdd, ...)
- la Vue (tout ce qui a trait à la saisie et affichage => print, input, IHM, ..)
- le Contrôleur ou Calcul (tout ce qui permet de générer une info Y à partir d'une info X)
En séparant les 3 domaines et en les isolant, on peut généralement plus ou moins facilement en changer (remplacer les fichiers par une bdd, remplacer les print/input par des menus déroulants, ...)
En théorie, Qt c'est une GUI. C'est dans ce but que cet lib a été démarrée. Et GUI c'est juste la partie "Vue" de MVC.
Mais depuis elle s'est développée de façon tentaculaire pour offrir de la gestion de fichier ou de bdd (partie "M") et de la gestion de threads qui peuvent faire la partie "C", et plein d'autres choses encore. Et donc au final on peut faire tout le MVC par Qt.
Perso j'aime pas. Ce n'est jamais bon de mettre tous ses oeufs dans le même panier et l'informatique ne fait pas exception. J'aime Qt pour sa partie V mais moi pour le M et le C j'utilise d'autres libs.
Pour forcément à utiliser dans ton cas ...
Qt a son propre système (pas du mvc classique) et c'est dans la doc !
python ou C++ c'est la même doc
Note: pySide est la lib officielle python pour faire du Qt ;)
Puisque tu utilises un formulaire, la solution pour ton besoin spécifique serait le QDataWidgetMapper ( et est dispo un exemple)
---------
Si on désire une classe particulière pour le controleur/présentateur, rien n'est fait mais c'est possible, voir cet exemple mais, surtout cet article (en)
=========
Re-note: pour moi, c'est une aberration d'écrite un outil en Qt pour un bureau gnome !!!!
https://github.com/Taiko2k/GTK4PythonTutorial
https://github.com/yucefsourani/pyth...-gtk4-examples
https://discourse.gnome.org/tag/python
Je n'ai fais qu'un projet professionnel en PyQt, autant dire que je ne suis pas expérimenté avec... Je ne vais pas avoir une critique sur l'utilisation de Qt !
Une de mes erreurs (en ce qui me concerne, car l'entreprise ne voit pas le mal) avaient été de rendre ce projet totalement dépendant de PyQt.
Imaginons, que je veuille passer de PyQt à wxPython ou à Kivy ? Est-ce que tout mon code est bon à mettre à la poubelle ? Sur mon projet, oui !
C'est pour cela que maintenant dans tous mes projets je fais une fixette sur l'architecture du projet, c'est pour moi un temps que je ne négocie jamais avec mes chefs... L'objectif est de permettre d'ajouter du code, et de ne jamais en supprimer (évolution permanente).
Si ça vous intéresse, je vous propose de lire ce blog qui parle de la "clean architecture" d'Uncle Bob, tout est indépendant (si je veux passer de SQL à NoSQL par ex., ça doit être possible sans rien supprimer de l'existant SQL) !
J'ai bien évidemment le bouquin qui est je dois dire complexe à lire (pas parce-qu'il est en anglais, mais un peu vaste, on s'attend à plus de précision).
C'est très appréciable de se dire que ses projets persistent dans le temps, et à vrai dire aux nombres importants de projets que j'ai pu lire, très rarement je rencontre ce type d'architecture (rencontré 1x dans ma carrière).
Et perso, comme Sve@r, l'IHM n'est que Vue ! Mais cela implique de nombreux découpage de modules, une organisation plus complexe au départ, mais avec de nombreux avantages par la suite, comme par exemple la simplification des tests unitaires, une évolution rapide du projet, etc.
Et hélas c'est le cas de beaucoup. Parce qu'entre écrire directement son select truc from machin dans le widget Qt qui va afficher les datas ou créer la classe modele.select qui va se charger de récupérer les datas en cohérence avec le vrai MVC les sirènes de la tentation et de la facilité font malheureusement que...
Certes dans mes projets mes tables sont toutes gérées par un objet dédié qui contient les méthodes "update" et "delete" mais pour le select... Je pense que le vrai et pur MVC, celui qui permet de changer facilement et immédiatement X par Y est une utopie.
- est-ce que le côté obscur est le plus fort ?
- non ! seulement plus facile, plus rapide, plus séduisant le côté obscur est.
Le "côté obscur" n'est pas plus fort, mais il requiert une discipline et une vision à long terme pour résister à ses tentations.
Les bénéfices d'une architecture propre et d'une conception réfléchie surpassent largement les facilités de développement à court terme.
Le "côté obscur" (opter pour la facilité et la rapidité à court terme) peut sembler attrayant, mais il conduit souvent à une dette technique accrue et à des difficultés lorsqu'il s'agit d'adapter ou d'étendre l'application.
Note sur les model Qt : ce ne sont que des interfaces entre un widget et des datas ! Nous pouvons donc avoir des datas indépendantes de l'interface et suivre facilement le paradigme de fred1599 (à cette étape)
ps: ne pas chercher ici une logique métier ou pythonnesque à la classe Métier :mrgreen:Code:
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
36 @dataclass class Person: nom: str age: int class Metier: def __init__(self): self._datas = {} def retourne(self, index): return self._datas[index] def change(self, index, person): if person.age < 18: raise Exception("règle métier") self._datas[index] = person def count(self): return len(self._datas) class personnes_model(QtCore.QAbstractListModel, database_connecteur="'il vient de la gui ? non"): def __init__(self, parent=None): super().__init__(parent) self._datas = Metier(database_connecteur) def rowCount(self, parent=QtCore.QModelIndex()): return self._datas.count() def data(self, index, role): if role == QtCore.Qt.DisplayRole: return self._datas.retourne(index.row()).nom if role == QtCore.Qt.UserRole + 666: # vouloir l'objet dans la GUI ? c'est le diable (si il est mutable) return self._datas.retourne(index.row()) def addRow(self, name, age): self._datas.ajout(Person(name, age)) def removeRow(self, index): pass
Bonjour,
Pourquoi te limiter dans ta présentation à Ubuntu? Perso, j'aurai dit pour Debian et dérivés (ubuntu , mint, ...) ;)
Salut,
Oui, tu as raison, je dois être plus générique.
En quoi c'est aberrant ?? Qt, je l'utilise également sur MacOS et Windows
Edit: Mon environnement n'est pas basé sur Gnone mais Kde :
Citation:
Système d'exploitation*: Ubuntu Studio 22.04
Version de KDE Plasma*: 5.24.7
Version de KDE Frameworks*: 5.92.0
Version de Qt*: 5.15.3
Version de noyau*: 6.5.0-17-lowlatency (64-bit)
Plate-forme graphique*: X11
Voici un code presque fonctionnel.
Model python <-> model QT <-> GUI (QDataWidgetMapper <-> formulaire)
Particularité : utilise un QDataWidgetMapper pour lier le formulaire à un modelQT. Si on utilise une table ou treeview alors ce composant intermédiaire n'a pas lieu d'exister.
Le modèle "classique" est quasiment dépourvu de QT, sauf, que ici on modifie d'autres champs en fonction de la saisie de l'utilisateur. Pas une chose classique mais cela entraine un petit code supplémentaire (qt) qui peut parfois être utile. Notre modèle reste autonome (fonctionne avec -t passé en paramètre à l'application).
Le modèle ne gère ici pour démo que "Exec" et "Icone"
Note: ici, il y a des conversions dans les 2 sens entre les noms des champs et les index des variables, il y a moyen d'optimiser. Mais avec ce code, on peut par conséquent voir que le format des modèles est beaucoup plus libre (on descend et remonte toujours le numéro de la colonne et la clé associée à ce numéro).
Le modèle QT est classique, avec 2 méthodes pour lire une cellule et une autre pour écrire. Ici, il ne sert que d'interface avec notre modèle et la vue. Une grosse partie du travail est faite avec la transmission de messages.
La "vue" (fenêtre principale) : plus aucun code lié au métier (sauf création des champs du formulaire), on peut donc se focaliser ici entièrement à la GUI. En fait ici, il n'y a que __init__() :lol:
Donc ce n'est pas la ubuntu (classique) et bien sûr existe quelques dérivées de ubuntu avec kde (la tienne, kunbutu et neon).Code:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255 #!/usr/bin/env python from pathlib import PurePath from textwrap import dedent import sys try: from PySide6.QtWidgets import ( QApplication, QMainWindow, QDialogButtonBox, QGroupBox, QLineEdit, QFormLayout, QDataWidgetMapper, QVBoxLayout, QHBoxLayout, QWidget, QComboBox ) from PySide6.QtGui import QStandardItemModel, QIcon from PySide6.QtCore import Qt, QObject, Signal, QModelIndex, QCoreApplication, QLibraryInfo, QTranslator except ImportError: print("ERREUR: installez pyside 6 !") exit(13) """ https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#recognized-keys """ TEMPLATE = { "exec" : { "msg": "Le fichier à exécuter", "type": 1 }, "name" : { "msg": "", "type": 1 }, "icon": { "msg": "Fichier .png ou nom d'une icone dans notre thème", "type": 1 }, "comment": { "msg": "", "type": 1 }, "type" : { "msg": ("Application","Link","Directory"), "type": 2 }, "path" : { "msg": "", "type": 1 }, "categories" : { "msg": ["Development","System", "Utility"], "type": 3 }, } """ voir la fonction test() pour l'écrire/mini test de cette classe (app -t) """ class Work(QObject): onchange = Signal(int, str) # pour GUI refresh def __init__(self) -> None: super().__init__() self.error = "" self._exec = "" self.type = "" self.name = "" self.comment = "" self._icon = "" self.path = "" self.categories = [] self.type = "Application" @property def exec(self): return self._exec @exec.setter def exec(self, value): self.error = "" self._exec = value path = None if not self.name: path = PurePath(self.exec) self.name = path.stem self.onchange.emit(self.key_to_int("name"), "name") if not self.icon and self.name: self.icon = self.name @property def icon(self): return self._icon @icon.setter def icon(self, value): self.error = "" if value: ico = QIcon.fromTheme(value) if ico.isNull(): self.error = "icone non trouvée dans le theme courant" return self._icon = value self.onchange.emit(self.key_to_int("icon"), "icon") def __str__(self) -> str: return dedent( f""" [Desktop Entry] Type={self.type} Name={self.name} Comment={self.comment} Exec={self.exec} Icon={self.icon} # erreur: {self.error} """ ) @staticmethod def key_to_int(key): return list(TEMPLATE.keys()).index(key) class FormModel(QStandardItemModel): """ interface QT liée a mon métier """ cols = tuple(k for k in TEMPLATE) error = Signal(str) def __init__(self, parent): super().__init__(1, len(TEMPLATE), parent=parent) self._datas = Work() self._datas.onchange.connect(self.onUpdate) def data(self , index , role): if not index.isValid(): return if role in (Qt.EditRole, Qt.DisplayRole): """ texte par défaut à l'édition """ key = self.cols[index.column()] return getattr(self._datas, key) if role == Qt.UserRole: return self.cols[index.column()] return super().data(index, role) def setData(self, index, value, role) -> bool: if not index.isValid(): return if role == Qt.EditRole: self._datas.error = "" #puisque un seul setter existe dans la classe Work key = self.cols[index.column()] setattr(self._datas, key, value) self.error.emit(self._datas.error if self._datas.error else "") return True return False def onUpdate(self, col, key): self.beginResetModel() index = self.index(0, col, QModelIndex()) self.dataChanged.emit(index, index, Qt.DisplayRole) self.endResetModel() class MainWin(QMainWindow): """ fenetre principale """ def __init__(self): super().__init__() self.resize(790, 50) self.setWindowTitle("model Qt lié a model classique python") self.edits = {} for key in TEMPLATE.keys(): if TEMPLATE[key]["type"] != 2: self.edits[key] = QLineEdit() else: self.edits[key] = QComboBox() self.edits[key].addItems(TEMPLATE[key]["msg"]) self.edits[key].setPlaceholderText(str(TEMPLATE[key]["msg"])) btnWidget = QWidget() layout = QHBoxLayout() savetBtn = QDialogButtonBox(QDialogButtonBox.StandardButton.Save) exitBtn = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) savetBtn.clicked.connect(self.save_file) exitBtn.clicked.connect(self.close) layout.addWidget(savetBtn, stretch=1, alignment=Qt.AlignmentFlag.AlignRight) layout.addWidget(exitBtn) btnWidget.setLayout(layout) self.formGroupBox = QGroupBox("Données") self.form = QFormLayout() for key in TEMPLATE.keys(): self.form.addRow( f"{key.capitalize()} :", self.edits[key] ) self.formGroupBox.setLayout(self.form) mainLayout = QVBoxLayout() mainLayout.addWidget(self.formGroupBox) mainLayout.addWidget(btnWidget) widget = QWidget() widget.setLayout(mainLayout) self.setCentralWidget(widget) self.statusBar() self._setupModel() def _setupModel(self): self.model = FormModel(self) self.model.error.connect(self.onError) self.model.dataChanged.connect(self.onDataChanged) self.mapper = QDataWidgetMapper() self.mapper.setModel(self.model) self.model.modelReset.connect(self.mapper.toFirst) for i, key in enumerate(TEMPLATE.keys()): self.mapper.addMapping(self.edits[key], i) self.mapper.toFirst() def onError(self, msg): self.statusBar().showMessage(msg) def onDataChanged(self, item, _): print("maj de", item.data(Qt.UserRole)) if item.column() == 2: ico = item.data(Qt.DisplayRole) if ico: QApplication.setWindowIcon(QIcon.fromTheme(item.data(Qt.DisplayRole))) def save_file(self): print(self.model._datas) app = None def run(): """gui""" app = QApplication(sys.argv) app.setWindowIcon(QIcon.fromTheme("desktop")) trans = QTranslator() trans.load('qt_fr', QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)) QCoreApplication.installTranslator(trans) # dialogues/btn en fr win = MainWin() win.show() sys.exit(app.exec()) def test(): """ si on ne travaille que sur la classe métier """ work = Work() work.exec = "pacman" print(work) work.exec = "/usr/bin/pacman.sh" print(work) exit(0) if __name__ == "__main__": if "-t" in sys.argv: test() run()
ps: Du coup, tu aurais pu utiliser le logo de ta distribution (bien différent de celui que tu utilises dans ton code)
Salut !
@papajoker,
J'avoue ne pas avoir tout compris et savoir où tu voulais en venir avec ce code... est-ce que te concernant tu lies ce code a ce qui a été dit dans ma précédente conversation ?
Je l'ai testé avec PyQt6 n'ayant pas PySide6, cependant je ne vois pas concrètement ce que tu souhaites souligner, ni ce que techniquement on doit en retenir... Imaginons que tu expliques à un dev junior ce code, que doit-il en retenir et quelle expérience souhaite tu lui faire partager ? En plus, me concernant je suis une bille en Qt :mrgreen:
Ce code était uniquement la suite du message #25 : juste un code fonctionnel
ps: ne pas tenir compte que tout est dans le même fichier :mrgreen: c'est uniquement par facilité pour ce forum
Je viens de voir ton code Arborescence…
Et je me rends compte que nous avons une vue sur QT complètement différente (Et donc ton bon exemple sur l'autre sujet ne me convient pas du tout).
Les models QT :
Pour moi, il est incompréhensible de s'en passer (sauf GUI extrêmement simple)
Exemple:
si j'utilise un modelQt, tout mes widgets attachés a lui vont être actualisés en temps réel
- si j'ai dans ma barre d'outil générale un filtre, l'autocomplétion va s'adapter dès que l'utilisateur ajoute un enregistrement (par exemple un filtre sur "ville"). Même chose si j'ai un combobox
Donc, avec un modèle, toutes les vues attachés sont mises à jour automatiquement au moindre changement.
Exemple:
Si j'utilise un modelQt, nous avons les proxy (datas aussi en temps réel). Un proxy peut servir pour un filtre, mais aussi pour fournir un "nouveau" modèle qui lui aussi est lié à notre modelQT de base
Exemple:
- Parfois, pour la gestion fine de l'affichage d'une cellule, nous avons QStyledItemDelegate qui reçoit un item(gui) lié à notre modèleQt
- avec un QItemDelegate (.setModelData exemples) particulier, je vais pouvoir lier notre modelQT à ce délégé pour un comboBox; exemple: delégé ne retourne qu'une seule colonne et affichée différemment pour ce combo: un combo (temps-réel) avec "ville+cp" tirés de notre modèle
Puisque je dois absolument utiliser les modelQT, si je veux avoir quand même un modèle python plus classique (utilisable en tests), j'ai donné une recette ici. Et ce modèle (classique*) python à la particularité d'envoyer des messages à la gui lors de modification (pas un refresh de la vue mais uniquement de la cellule)
*"classique" : pas vraiment dans ce cas car le cahier des charges du départ n'est pas classique (un seul enregistrement), j'ai juste voulu faire simple (minimum de lignes pour ce fichier) et relativement général (manque ajout et suppression)
Comme indiqué plus haut #21, pyside est la lib officielle de la société qui fournit QT. Je ne vois pas l'intérêt d'utiliser une lib non officielle ;)
ps: ok, cela n'a que quelques années, mais en dev les choses évoluent, avant pyside était le vilain petit canard et pyqt la seule solution.
C'est juste parce-que j'ai un PyQt6 sous la main, et que je ne souhaitais pas créer un nouvel environnement juste pour installer PySide6.Citation:
Envoyé par papajoker
Non officielle ou pas, si elle existe c'est pour être utilisée... tout autant que PySide dans le temps où c'était le vilain petit canard.
Tu as beaucoup de termes où on sent un manque d'ouverture sur ce qui peut se faire dans le monde informatique ... "je ne vois pas l'intérêt, incompréhensible, je dois absolument"... si je te comprend bien, je jette PyQt6, la clean architecture n'a pas d'intérêt et je dois absolument utiliser les models Qt ?Citation:
Envoyé par papajoker
Mais je ne comprend pas en quoi cette architecture empêche l'utilisation des models Qt, j'ai dû manquer quelque chose ? Je ne l'ai peut-être pas utiliser dans mon exemple, mais ça ne veut pas dire que je fais le choix de m'en passer dans d'autres cas !
N'importe quoi !Citation:
Tu as beaucoup de termes où on sent un manque d'ouverture
je te dis que mon besoin est très différent du tien ! C'est pourtant clair ! Perso, j'ai besoin des modelQt dans 80% des cas (avec gtk c'est un autre système, les xxStore).
Donc, je persiste, pour moi, c'est un impératif d'utiliser les modèles natifs qt ou gtk.
Pour moi, renier une technologie est un manque d'ouverture ! moi, je dis, que je désire utiliser à mon maximum une techno. (c'est comme dire, je ne veux pas utiliser le css pour du web)Citation:
je dois absolument utiliser les models Qt ?
Toi, dans ton exemple "clean architecture", tu zappes une technologie, je vais en déduire que tu as un manque d'ouverture ? (bien sûr que non) je peux juste te dire que ton exemple dans cet état ne me convient pas. L'architecture de ton exemple est très très bonne, j'ai dit le contraire ? c'est encore du n'importe quoi.Citation:
la clean architecture n'a pas d'intérêt
Les 2 sont extrêmement proches en code, je souligne uniquement celle qui est officielle : beaucoup de personnes utilisent pyQt car c'est l'historique (sans connaitre pyside). Puisque tu disais ne pas trop bien connaitre Qt, c'était peut-être une chose que tu ne connaissais pas. ici aussi c'est du n'importe quoi, tu déformes une information en une demande/règle. Je développe encore parfois avec pyQt !Citation:
si je te comprend bien, je jette PyQt6
J'ai parlé plusieurs fois de qml sur ce forum (en bien), tu en déduits que je cherche à l'imposer au détriment de pyqt et pyside ? :ptdr: