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

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

PyQt Python Discussion :

[Python 3] Meilleure façon de récupérer les données d'un formulaire


Sujet :

PyQt Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre émérite

    Homme Profil pro
    Ingénieur
    Inscrit en
    Août 2010
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2010
    Messages : 662
    Par défaut [Python 3] Meilleure façon de récupérer les données d'un formulaire
    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à:
    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
    Pratique.

    Voici deux tentatives de ma part en PyQt. Je ne suis satisfait d'aucune des deux...

    La vue
    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)
    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
    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)
    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.

    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.
    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)
    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.

    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.

  2. #2
    Membre chevronné
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Par défaut
    Salut,

    ayant pas un super niveau, je fais comme toi, soit le 1 soit le 2 en fonction de mes besoins.

    Si c'est juste de la récup à la fin, ta 1ere méthode est pas mal et si j'ai besoin de travailler en direct, 2eme méthode mais tu peux mixer un peu les 2 si besoin...

    Mais pour le coup je ne vois pas de problème

    En tout cas tu as bien compris.

  3. #3
    Membre émérite

    Homme Profil pro
    Ingénieur
    Inscrit en
    Août 2010
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2010
    Messages : 662
    Par défaut
    Merci Hizoka.

    Pour le moment j'ai un penchant pour une variante du 2 car elle me permet de mettre en place un mécanisme de validation (QValidator), un exemple sympa d'un tel mécanisme: exemple.

    Je laisse ouvert ce thread. Si d'autres techniques intéressantes existent je suis intéressé.

    Ju

  4. #4
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 741
    Par défaut
    Salut,

    Citation Envoyé par Julien N Voir le message
    Comment faites vous pour gérer ce genre de cas?
    La méthode documentée pour ce faire est de passer par un QDataWidgetMapper et vous avez un tuto qui montre comment utiliser çà.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Membre émérite

    Homme Profil pro
    Ingénieur
    Inscrit en
    Août 2010
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2010
    Messages : 662
    Par défaut
    Intéressant. Assez complexe aussi. Je ne comprends pas encore très bien comment cela fonctionne ni les avantages et inconvénients du mapper.
    Si je ne dis pas de bêtises, on définit un un model et un mapper. Le model peut être un tableau (dans l'exemple du tuto un QstandardItemModel), on map un widget avec un index. Cet index est le numéro de la colonne correspondant dans le modèle. On peut changer de ligne dans le tableau à l'aide des méthodes toNext() ou toPrevious(). Le gros intérêt semble de pouvoir utiliser plusieurs fois les même widgets pour renseigner différents formulaires. Mais ce n'est pas vraiment mon cas de figure: je n'ai qu'un seul formulaire. C'est en réalité une fenêtre avec différents paramètres à renseigner par l'utilisateurs, paramètres utilisés pour lancer une simulation.

    Alors j'ai quand même bricolé un truc pour voir (bricolé est le bon terme je crois).

    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
    44
    45
    46
    47
    48
    class ModelForm3(QDialog):
     
        """Using a QDataWidgetMapper"""
     
        def __init__(self, parent=None):
            super(ModelForm3, self).__init__(parent)
     
            self.form = ViewForm(self)
            self.form.show()
     
            self._params = [
                ['entry', '0.0'],
                ['combobox', 'Item 1'],
                ['checkbox', True],
                ]
     
            self.setup_model()
     
            # Create a mapper to associate widgets to a model.
            self.mapper = QDataWidgetMapper(self)
            self.mapper.setModel(self.model)
            self.mapper.addMapping(self.form.entry, 0)
            self.mapper.addMapping(self.form.combobox, 1)
            self.mapper.addMapping(self.form.checkbox, 2)
     
            # Set to first index of the mapper. Mandatory.
            self.mapper.toFirst()
     
            # Connect the push button to the run method
            self.form.button.clicked.connect(self.run)
     
        def setup_model(self):
     
            self.model = QStandardItemModel(1, len(self._params), self)
     
            col = 0
            for key, val in self._params:
                item = QStandardItem(val)
                self.model.setItem(0, col, item)
                col += 1
     
        def _update_params(self):
            for col in range(self.model.columnCount()):
                self._params[col][1] = self.model.item(0, col).text()
     
        def run(self):
            self._update_params()
            print(self._params)
    ça marche, mais je ne vais pas partir sur ce principe sauf si simplifiable.

    J

  6. #6
    Membre chevronné
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Par défaut
    C'est en effet intéressant, mais je ne pense pas que ce soit adapté au cas présent.

    Car ce que tu nous montres permet de simplifier les liens entre les widgets de façon pratique.

    Mais dans le cas présent, c'est une simple récupération de valeurs qui est demandé.

    Enfin je dis ça, je dis rien...


    En tout cas je mets de coté le topic car ça me resservira sûrement.

  7. #7
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 741
    Par défaut
    Salut,

    Citation Envoyé par Julien N Voir le message
    Intéressant. Assez complexe aussi. Je ne comprends pas encore très bien comment cela fonctionne ni les avantages et inconvénients du mapper.
    Si je ne dis pas de bêtises, on définit un un model et un mapper. Le model peut être un tableau (dans l'exemple du tuto un QstandardItemModel), on map un widget avec un index.
    Si on veut faire du MVC, dans tout les cas, il faut un "modèle", une View et fabriquer des relations entre ce qui est affiché et son modèle. Le QDataWidgetMapper permet de mettre en correspondance chaque champ avec le widget qui va l'afficher (et optionnellement un Delegate)
    sans s'occuper de comment en récupérer le contenu ni devoir passer par déclarer N-connect pour mettre à jour le "modele".

    De toutes façons, View, Model et ControlleurDelegate, c'est déjà 3 rôles/responsabilités bien distinctes multiplié par tout ce qu'on veut pouvoir faire côté données (chaînes de caractères, entiers, flottants, adresses,...) et éventuellement "validation" des entrées...
    Un framework (comme Qt) qui permette d'utiliser cela sans trop coder sera nécessairement compliqué: il va falloir du temps pour comprendre comment çà fonctionne et s'en servir proprement (mais il faut savoir ce qu'on veut...).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre émérite

    Homme Profil pro
    Ingénieur
    Inscrit en
    Août 2010
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2010
    Messages : 662
    Par défaut
    Salut,

    Citation Envoyé par wiztricks Voir le message
    Si on veut faire du MVC, dans tout les cas, il faut un "modèle", une View et fabriquer des relations entre ce qui est affiché et son modèle [...] et comment en récupérer le contenu ni devoir passer par déclarer N-connect pour mettre à jour le "modele".
    Oui je perçois bien l'intérêt. Alors j'ai poussé un peu plus la réflexion. Je n'ai pas besoin de mettre en place un mécanisme très poussé (et que je ne maîtrise pas faut l'avouer), mais je peux quand même mettre en place un model. Le model que j'ai développé pour test est bourré de défauts et pas très élégant, mais je l'améliorerais plus tard lorsque je maîtriserais plus PyQt.

    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
    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
    class MyModel():
     
        def __init__(self, *args, **kwargs):
            super(MyModel, self).__init__(*args, **kwargs)
            self.widgets = {}
            self.values = {}
     
        def add_widget(self, widget, name):
     
            if name in self.widgets:
                raise Exception('Cannot bind several widgets with the same name')
     
            assert type(widget) == QLineEdit or type(widget) == QComboBox \
                or type(widget) == QCheckBox
     
            self.widgets[name] = widget
            self.values[name] = self.widget_value(widget)
     
            if type(widget) is QLineEdit:
                widget.textChanged.connect(partial(self.update_model, name))
            elif type(widget) is QComboBox:
                widget.currentIndexChanged.connect(partial(self.update_model, name))
            elif type(widget) is QCheckBox:
                widget.stateChanged.connect(partial(self.update_model, name))
     
        def widget_value(self, widget):
     
            # Get widget value
            if type(widget) is QLineEdit:
                value = widget.text()
            elif type(widget) is QComboBox:
                value = widget.currentText()
            elif type(widget) is QCheckBox:
                value = True if widget.checkState() == 2 else False
            else:
                raise Exception('Cannot read widget state')
     
            return value
     
        def update_model(self, name):
     
            sender = self.widgets[name]
            self.values[name] = self.widget_value(sender)
     
     
    class ModelForm4(QDialog):
     
        def __init__(self, parent=None):
            super(ModelForm4, self).__init__(parent)
     
            self.form = ViewForm(self)
            self.form.show()
     
            self.setup_model()
     
            # Connect the push button to the run method
            self.form.button.clicked.connect(self.run)
     
        def setup_model(self):
     
            self.model = MyModel()
            self.model.add_widget(self.form.entry, 'entry')
            self.model.add_widget(self.form.combobox, 'combobox')
            self.model.add_widget(self.form.checkbox, 'checkbox')
     
        def run(self):
            params = self.model.values
            print(params)
    Je retravaillerais le model pour être plus propre, mais je pense partir sur cette solution. Merci pour les conseils.

    Ju

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

Discussions similaires

  1. Réponses: 9
    Dernier message: 16/01/2020, 14h21
  2. [PHP 5.3] Meilleure façon de traiter les données transmises par formulaire
    Par AsKaiser dans le forum Langage
    Réponses: 2
    Dernier message: 06/02/2011, 22h00
  3. Réponses: 14
    Dernier message: 15/05/2007, 14h51
  4. Récupérer les données d'un formulaire
    Par rimeh dans le forum Langage
    Réponses: 12
    Dernier message: 05/02/2007, 14h58
  5. récupérer les données d'un formulaire un peu spécial
    Par Jim_Nastiq dans le forum Interfaces Graphiques
    Réponses: 12
    Dernier message: 10/07/2006, 10h53

Partager

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