Salut,
Je suis passé récemment à Qt, mais j'ai conservé mes réflexes de Tkinter. J'ai suivi pas mal de tutos et dans l'ensemble PyQt est bien sympa mais il y a des choses qui me manquent ou que je ne comprends pas encore. L'une d'elles c'est comment récupérer efficacement les variables/états renseignés dans un formulaire par un utilisateur. La très grosse majorité des tutos se contentent de détailler l'aspect "View" et pas tant que ça l'aspect "Model" du MVC. Et lorsque c'est le cas il y a en général 2/3 widgets.
Mon problème est le suivant: j'ai une fenêtre avec une petite vingtaine de widgets (des QLineEdit, des QComboBox et des QCheckBox pour l'essentiel), les paramètres renseignés via ces widgets doivent être stockés dans un dictionnaire pour être ensuite passé au cœur du programme (le "Controller", si je comprends bien). Comment faire ça proprement? Avec Tkinter j'utilisais des StringVar() pour garder la trace de l'état de mes widgets, donc j'avais quelque chose dans ce gout là:
Pratique.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 self._params = { # variable_1 est de type StringVar() 'param_1': variable_1, } def get_params(self): params = {k: v.get() for k, v in self._params.items()} return params
Voici deux tentatives de ma part en PyQt. Je ne suis satisfait d'aucune des deux...
La vue
Essai 1: On récupère le contenu des widgets une fois, à la demande.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 class ViewForm(QDialog): def __init__(self, parent=None): super(ViewForm, self).__init__(parent) self.entry = QLineEdit() self.combobox = QComboBox() self.combobox.addItems(('Item 1', 'Item 2')) self.checkbox = QCheckBox('check') self.button = QPushButton('Run') layout = QGridLayout() layout.addWidget(self.entry, 0, 0) layout.addWidget(self.combobox, 0, 1) layout.addWidget(self.checkbox, 0, 2) layout.addWidget(self.button, 1, 0, 1, 3) self.setLayout(layout)
Comme chaque type de widget a sa propre méthode pour récupérer son contenu, on est obligé de bricoler un test. Je ne trouve pas ça super élégant.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 class ModelForm1(QDialog): def __init__(self, parent=None): super(ModelForm1, self).__init__(parent) self.form = ViewForm(self) self.form.show() self.params = { 'entry': [self.form.entry, '0.0'], 'combobox': [self.form.combobox, float('nan')], 'checkbox': [self.form.checkbox, False], } self.form.button.clicked.connect(self.run) def update_params(self): for key, data in self.params.items(): widget, _ = data if type(widget) is QLineEdit: data[1] = widget.text() elif type(widget) is QComboBox: data[1] = widget.currentText() elif type(widget) is QCheckBox: data[1] = widget.checkState() else: data[1] = 'fuck' def run(self): self.update_params() print(self.params)
Essai 2: On lie le dictionnaire avec les widgets de sorte que les paramètres sont updatés au fur à mesure que l'utilisateur modifie l'état des widgets. Donc profusion de signaux.
Une méthode par type de widget ou alors comme précédemment une unique méthode avec un test sur le type (une sorte de proxy). Pour 20 widgets j'ai 20 connections de définies... Et j'utilise partial pour pouvoir passer des arguments aux méthodes, je ne comprends pas comment le faire sans.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 from functools import partial class ModelForm2(QDialog): def __init__(self, parent=None): super(ModelForm2, self).__init__(parent) self.form = ViewForm(self) self.form.show() # Store parameters (user input) by a name and default value. self.params = { 'entry': '0.0', 'combobox': 'nan', 'checkbox': False, } self.form.button.clicked.connect(self.run) self.form.entry.textChanged.connect( partial(self._update_params_lineedit, 'entry') ) self.form.combobox.currentIndexChanged.connect( partial(self._update_params_combobox, 'combobox') ) self.form.checkbox.stateChanged.connect( partial(self._update_params_checkbox, 'checkbox') ) def _update_params_lineedit(self, name): widget = self.sender() self.params[name] = widget.text() def _update_params_combobox(self, name): widget = self.sender() self.params[name] = widget.currentText() def _update_params_checkbox(self, name): widget = self.sender() self.params[name] = widget.checkState() def run(self): print(self.params)
J'ai aussi une troisième méthode que je vous épargne basée sur la création de widgets custom partageant tous un unique dictionnaire et ré-implémentant les méthodes comme .setText() de QLineEdit. ça me semble un peu...trop.
Comment faites vous pour gérer ce genre de cas?
Pur l'affichage:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 if __name__ == '__main__': app = QApplication(sys.argv) form = ModelForm1() #form = ModelForm2() app.exec_()
Julien
[EDIT]
Simplification méthode 2.
Partager