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 :

Formater les données avant affichage dans un QTableView


Sujet :

PyQt Python

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut Formater les données avant affichage dans un QTableView
    Hello,
    Comme l'indique le titre, je souhaite mettre en forme les données stockées dans une BDD SQLITE avant de les afficher dans un QTableView
    Dans la BDD, les dates sont stockées sous la forme yy-mm-dd et je veux les afficher au format français jj/mm/aa
    De même, pour les prix, j'ajouterai bien un "€" à chaque flottant
    j'ai également des valeurs binaires pour lesquelle j'afficherai bien des cases à cocher mais là j'ai vu qu'il fallait modifier le délégué -> ok, j'étudie ça à part

    Pour récupérer mes données, soit je fais appel à un QSqlTableModel ou un QSqlRelationalTableModel, soit à un QSqlQueryModel, ma question est la suivante :
    Pour formater mes données dans QTableView, faut-il modifier les délégués ou y-a-t-il une autre methode moins lourde ? ça me parait assez complexe pour quelques chaines de caractères.

    merci d'avance et bon dimanche
    Nicolas

  2. #2
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Je fais ça avec un QStyledItemDelegate sous-classé, dont je surcharge la méthode paint.C'est un delegate à brancher sur le QTableView avec sa méthode setItemDelegate

    Selon l'index de la colonne, on peut présenter les entiers, les flottants ou les chaines comme on veut. Par exemple: afficher les flottants avec un nb de décimales fixes, mettre les nb négatifs en rouge, centrer les chaines et les mettre en gras+italique, etc...

    Je n'ai pas le temps de te proposer du code maintenant. Si tu n'as pas résolu le pb ce soir ou demain, je pourrais te donner un exemple.

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    merci pour ta réponse, je vais creuser le sujet du délégué, de prime abord, ça me parait assez chronophage, je ne voulais pas m'aventurer là-dessus sans être assuré du résultat.

  4. #4
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    bonjour,
    Je poursuis la personnalisation de l'affichage de mes données dans un QTableView. Grace à un delegate perso, je peux afficher le nom des mois au lieu de leur numéro et j'affiche un "€" dans les colonnes concernées. Jusque là tout va bien.
    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
     
    NUMMOIS, TOTALCREDITMOIS, TOTALDEBITMOIS, SOLDEMOIS = range(4)
    ListeMois = ("janvier", "février","mars", "avril", "mai", "juin", "juillet", "aout", "septembre", "octobre", "novembre", "décembre")
     
    class BilanDelegate(QStyledItemDelegate):
        def __init__(self, parent=None):
            super(BilanDelegate, self).__init__(parent)
     
        def paint(self, painter, option, index):
            painter.save() #sauve le contexte
            #afficher " €" dans les colonnes credit debit et solde
            if index.column() == TOTALCREDITMOIS or index.column() == TOTALDEBITMOIS or index.column() == SOLDEMOIS:
                montant = index.model().data(index) #string représentant la somme brute
                montant = "{} €".format(montant)
                painter.drawText(option.rect, Qt.AlignRight|Qt.AlignVCenter, montant)
            elif index.column() == NUMMOIS:
                nummois = index.model().data(index)
                if nummois >= 1 and nummois <= 12:
                    mois = ListeMois[nummois - 1]
                    painter.drawText(option.rect,Qt.AlignVCenter, mois)
                painter.restore()
            else:
                QStyledItemDelegate.paint(self, painter, option, index)
    A l'affichage, je constate que le symbole "€" est collé à la ligne séparatrice de la colonne, ce n'est pas très esthétique et j'aimerai ajouter une marge entre le texte affiché dans la cellule et le cadre.
    Nom : tableaubilan.png
Affichages : 3005
Taille : 49,1 Ko
    Y-a-t-il un moyen technique et plus élégant que de simplement ajouter un espace après le "€" ?
    J'ai essayé de travailler sur les dimensions du option.rect mais j'obtiens des résultats aberrants
    merci

  5. #5
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Puisqu'il s'agit d'un traitement pour l'affichage, il devrait suffire d'ajouter un espace après le "€" dans la méthode paint du delegate.

    J'ai déjà fait un truc plus musclé qui consiste à placer le widget d'affichage de type QLineEdit dans un widget QWidget qui est lui-même inséré dans chaque case du QTableView, afin de pouvoir reconstruire des marges d'affichage qui ne sont pas prévues au départ. Mais c'est vraiment plus compliqué! Il faut construire à part ce widget composite et, dans le delegate, il faut en faire l'insertion en surchargeant la méthode createEditor. Si on veut en plus prendre en compte des modifications faites par l'utilisateur, il faut aussi surcharger les méthodes setEditorData (pour l'édition) et setModelData (pour l'enregistrement des modifications). Ceci en plus du paint pour l'affichage. Bref, ça devient une belle usine à gaz... Mais c'est possible!

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    merci pour ton retour détaillé,
    donc les marges n'existent pas dans les cellules d'un QTableView. Mais cette propriété existe pour un QLineEdit, c'est bon à savoir.
    Comme souvent, il faut faire simple, je reste sur l'ajout de l'espace, ça répond à mon besoin.
    bonne journée!

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut éditer les données dans un QTableView
    Hello,
    Maintenant que j'ai compris comment personnaliser l'affichage, je voudrais en personnaliser l'édition directement dans mon QTableView. Au moins pour une colonne contenant un booléen, je veux afficher une case à cocher et pouvoir cocher/décocher cette case.
    De la lecturre de la doc, je comprends que je dois créer mon propre modele à partir d'une des classes de base la plus adaptée étant QAbstractTableModel. Là où je suis un peu perdu et inquiet c'est qu'actuellement j'utilise un QSqlRelationnalTableModel et je ne me vois pas réimplémenter la fonction setRelation entre mes 2 tables.
    Je ne vois pas 36 solutions : repartir de QSqlRelationnalTableModel afin d'en dériver mon propre modèle. Quel est votre avis ? J 'ai loupé quelque chose ?
    merci d'avance pour votre aide
    Nicolas

  8. #8
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'étais en train de faire du code pour implanter un QCheckBox, mais c'est très compliqué, je ne suis pas sûr d'aboutir, et je me demande si c'est ça qu'il faut faire.

    En effet, le type booléen n'existe pas dans sqlite. Aussi, il faut choisir comment enregistrer dans la base ce qu'on souhaite pour représenter cette info. Par exemple un entier 0 ou 1. Ou encore une chaine "oui" ou "non". Ou, selon la signification du champ, "membre" et "non-membre". Ou n'importe quoi d'autre.

    Mais dans ce cas, il y a une autre solution que le QCheckBox! En effet, comme on est dans un mode relationnel, il suffit d'ajouter une table d'un seul champ avec 2 valeurs "oui" et "non" (ou "membre" et "non-membre", etc...), et d'ajouter à la création de la table normale une contrainte de clé étrangère. Quand on appelle l'affichage de la table avec le QTableView (et on l'appelle en mentionnant la contrainte), le champ "booléen" en question affiche le contenu du champ ( "oui" ou "non"), mais en cas d'édition, il s'affiche automatiquement un QComboBox présentant les 2 valeurs permises: "oui" et "non" et il n'y a aucune possibilité d'éditer autre chose. C'est la solution que j'utilise chez moi.

    Qu'est-ce que tu en penses?

  9. #9
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Je n'ai toujours pas trouvé comment utiliser un QCheckBox dans un QTableView, mais je ne désespère pas...

    En attendant, voilà un petit code qui dit comment changer une valeur dans une liste fermée affichée avec un combobox.

    Au départ, voilà la structure de la base de données sqlite3 utilisée pour l'essai:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE "appartenance" (
    "est_membre" TEXT NOT NULL PRIMARY KEY
    );
     
    CREATE TABLE "table1" (
    "id" INTEGER NOT NULL PRIMARY KEY, 
    "nom" TEXT DEFAULT "",
    "membre" TEXT REFERENCES appartenance("est_membre")
    );
    Dans cette structure:
    - le champ booléen est "membre" de la table "table1".
    - les valeurs permises sont dans le champ "est_membre" de la table "appartenance": "oui" et "non".

    A noter que la partie 'REFERENCES appartenance("est_membre")' n'est pas obligatoire puisque la relation de contrainte est donnée en plus sous PyQt. Mais elle est logique, et elle empêchera que dans un autre endroit du programme on puisse ajouter une autre valeur que "oui" ou "non": il s'agit du maintien de l'intégrité de la base!


    Et voilà le code:

    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
    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3, PyQt5
     
    import sys
    from PyQt5 import (QtWidgets, QtCore, QtGui, QtSql)
     
    #############################################################################
    def ouvrebaseqt(basesql, contrainte=True):
        """Ouvre la base 'basesql' avec le pilote sqlite3 de Qt5 et 
             retourne la connexion (None si échec)
           Active la prise en compte des contraintes de clés étrangères
        """
        driver = "QSQLITE"  # =pilote d'accès à la base sqlite3)
        db = QtSql.QSqlDatabase.addDatabase(driver)
        db.setDatabaseName(basesql)
        if not db.open():
            QtWidgets.QMessageBox.critical(None,
                        "Ouverture de la base de données",
                        "Erreur d'ouverture: {}".format(db.lastError().text()))
            db = None
        else:
            if contrainte:
                query = QtSql.QSqlQuery(db)
                if not query.exec_("PRAGMA foreign_keys=on;"):
                    query.finish()    # désactive le curseur
                    query = None
                    fermebaseqt(db)  # fermeture de la base
                    db = None
                else:
                    query.finish()    # désactive le curseur
                    query = None
     
        return db    
     
    #############################################################################
    def fermebaseqt(db):
        if db!=None:
            db.close()
     
    #############################################################################
    class VoirTableSql(QtWidgets.QMainWindow):
     
        def __init__(self, basesql, nomtable, parent=None):
            super().__init__(parent)
     
            self.setWindowTitle("Affichage de la table {}".format(nomtable))
            self.resize(800, 600)
     
            # base de données à exploiter
            self.basesql = basesql
            # table à afficher
            self.nomtable = nomtable
     
            # ouvre la base SQL avec contraintes
            self.db = ouvrebaseqt(self.basesql, True)
     
            # crée le modèle et sa liaison avec la base SQL
            self.model = QtSql.QSqlRelationalTableModel(self, self.db)
            # définit la stratégie en cas de modification de données par l'utilisateur
            self.model.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) #OnManualSubmit)
     
            # crée la table physique et son lien avec le modèle et le delegate
            self.vuetable = QtWidgets.QTableView(self)
            self.vuetable.setModel(self.model)
            self.vuetable.setItemDelegate(QtSql.QSqlRelationalDelegate(self.vuetable))
            # active le tri quand on clique sur les têtes des colonnes
            self.vuetable.setSortingEnabled(True)
            # ajuste la largeur des colonnes en fonction de leurs contenus
            self.vuetable.resizeColumnsToContents()
     
            # définit la table à afficher
            self.vuetable.model().setTable(self.nomtable)
            # définit une relation de contrainte de clés étrangères à respecter pour la table
            self.vuetable.model().setRelation(2, QtSql.QSqlRelation('appartenance', 'est_membre', 'est_membre'))
            # peuple le modèle avec les données de la table
            self.vuetable.model().select() 
            # option: trie par défaut selon la colonne 0
            self.vuetable.model().sort(0, QtCore.Qt.AscendingOrder) # ou DescendingOrder
     
            # place le QTableView dans la fenêtre QMainWindow
            self.setCentralWidget(QtWidgets.QFrame())
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.vuetable, 0, 0)
            self.centralWidget().setLayout(posit)
     
        #========================================================================
        def closeEvent(self, event=None):
            """Méthode appelée automatiquement à la fermeture de la fenêtre"""
            #ferme la base
            try:
                fermebaseqt(self.db)
            except Exception:
                pass    
            # et ferme le programme
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
     
        basesql = "mabasesql.db3"
        nomtable = "table1"
     
        fen = VoirTableSql(basesql, nomtable)
        fen.show()
        sys.exit(app.exec_())

  10. #10
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    Merci pour tes réponses Tyrtamos,
    Je n'aurais pas pensé qu'un QCheckBox pouvait poser tant de difficultés.
    j'aime bien ta proposition car elle permet d'éviter de toucher au modèle. Néanmoins, je ne suis pas sûr d'avoir tout compris. D'une manière générale, si je veux personnaliser l'édition des données d'un QSqlRelationnalTableModel, il faut bien toucher au modèle, non ?

  11. #11
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Non, parce que le delegate ne s'occupe que de l'édition. En dehors du mode d'édition, la case affiche "bêtement" le contenu de la base de données! Il n'y a donc pas à modifier le modèle.

    Et le QComboBox n'a même pas besoin d'être construit dans le delegate: il est déjà prévu quand on déclare la relation de contrainte de clés étrangères.

    Tu peux en connaître plus sur ce sujet en consultant les exemples fournis avec PyQt5.

    Et j'ai bientôt une solution avec QCheckBox, mais sur le même mode que le QComboBox: il ne s'affiche que dans le mode édition, c'est à dire après un double-clic.

  12. #12
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Et voilà une solution avec un QCheckBox.

    Comme dit plus haut, le QCheckBox ne s'affichera que dans le mode "édition".

    Le code est plus long parce qu'il construit en plus:

    - un QCheckBox personnalisé: la classe "CheckBox" héritant de QFrame qui contient le QCheckBox

    - un delegate personnalisé: la classe "MonSqlRelationalDelegate" héritant de "QSqlRelationalDelegate"

    En fait, la seule raison pour qu'on ait besoin d'un QCheckBox dans un QFrame est pour cacher les affichages venant du modèle sous le QCheckBox! Le fond du QFrame a été mis ici en jaune, et il est mis à la taille de la cellule en entrée dans le mode édition.

    Le code est copieusement commenté.

    Ce qui serait plus difficile et que je n'ai pas réussi à faire jusqu'à présent, c'est que le QCheckBox s'affiche tout le temps, même en dehors du mode édition.

    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
    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3, PyQt5
     
    import sys, os
    from PyQt5 import (QtWidgets, QtCore, QtGui, QtSql)
     
    #############################################################################
    def ouvrebaseqt(basesql, contrainte=True):
        """ouvre la base 'basesql' et renvoie la connexion (None si échec)
        """
     
        driver = "QSQLITE"  # =pilote d'accès à la base sqlite3)
        db = QtSql.QSqlDatabase.addDatabase(driver)
        db.setDatabaseName(basesql)
        if not db.open():
            QtWidgets.QMessageBox.critical(None,
                        "Ouverture de la base de données",
                        "Erreur d'ouverture: {}".format(db.lastError().text()))
            db = None
        else:
            if contrainte:
                query = QtSql.QSqlQuery(db)
                req = "PRAGMA foreign_keys=on;"
                if not query.exec_(req):
                    query.finish()    # désactive le curseur
                    query = None
                    fermebaseqt(db)  # fermeture de la base
                    db = None
                else:
                    query.finish()    # désactive le curseur
     
        return db    
     
    #############################################################################
    def fermebaseqt(db):
        if db!=None:
            db.close()
     
    #############################################################################
    class CheckBox(QtWidgets.QFrame):
        """QCheckBox placé dans un QFrame
        """
     
        def __init__(self, parent=None):
            super().__init__(parent)
     
            # met un fond de couleur
            self.setStyleSheet("background-color: yellow;") #lightgrey;")
     
            # crée le QChecBox à l'intérieur du QFrame
            self.box = QtWidgets.QCheckBox(self)
     
            # positionne le QCheckBox au milieu du QFrame
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.box, 0, 0)
            posit.setAlignment(QtCore.Qt.AlignCenter)
            posit.setContentsMargins(0, 0, 0, 0)
            self.setLayout(posit)
     
    #############################################################################
    class MonSqlRelationalDelegate(QtSql.QSqlRelationalDelegate):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
        #========================================================================
        def createEditor(self, parent, option, index):
            """Créer et initialise le widget d'édition
               aParent: type 'PyQt5.QtWidgets.QWidget'
               option: type 'PyQt5.QtWidgets.QStyleOptionViewItem'
               index: type 'PyQt5.QtCore.QModelIndex'
               Retourne l'éditeur utilisé
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # crée le QCheckBox personnalisé
                editor = CheckBox(parent)
     
                # donne la taille du QFrame = taille de la case 
                editor.resize(option.rect.size())           
     
                # retourne le QCheckBox personnalisé
                return editor        
     
            else:
                # cas d'une autre colonne: on repasse la main à l'ancêtre
                return QtSql.QSqlRelationalDelegate.createEditor(self, aParent, option, index)
     
        #========================================================================
        def setEditorData(self, editor, index):
            """Définit l'état du QCheckBox à l'entrée du mode édition
                 editor: QtWidgets.QWidget
                 index: QtCore.QModelIndex
               Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # récupère la valeur de la base de données, destinée à la case
                membre = index.model().data(index, QtCore.Qt.EditRole)
     
                # définit l'état coché ou non du QCheckBox
                editor.box.setChecked(membre=="oui")
     
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setEditorData(self, editor, index)
     
        #========================================================================
        def setModelData(self, editor, model, index):
            """ Enregistre la valeur modifiée dans la base de données 
                  editor: type 'QtWidgets.QWidget'
                  model: type 'QtCore.QAbstractItemModel'
                  index: type 'QtCore.QModelIndex'
                Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                #lit la valeur affichée dans le QCheckBox en fin d'édition
                coche = editor.box.isChecked()
                if coche:
                    membre = "oui"
                else:
                    membre = "non"    
     
                # on enregistre dans le modèle la valeur affichée du QLineEdit
                model.setData(index, membre, QtCore.Qt.EditRole)
     
                return
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setModelData(self, editor, model, index)
                return
     
    #############################################################################
    class VoirTableSql(QtWidgets.QMainWindow):
     
        def __init__(self, basesql, nomtable, parent=None):
            super().__init__(parent)
     
            self.setWindowTitle("Affichage de la table {}".format(nomtable))
            self.resize(800, 600)
     
            # base de données à exploiter
            self.basesql = basesql
            # table à afficher
            self.nomtable = nomtable
     
            # ouvre la base SQL avec contraintes
            self.db = ouvrebaseqt(self.basesql, True)
     
            # crée le modèle et sa liaison avec la base SQL
            self.model = QtSql.QSqlRelationalTableModel(self, self.db)
            # définit la stratégie en cas de modification de données par l'utilisateur
            self.model.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) #OnManualSubmit)
     
            # crée la table physique et son lien avec le modèle et le delegate
            self.vuetable = QtWidgets.QTableView(self)
            self.vuetable.setModel(self.model)
            self.vuetable.setItemDelegate(MonSqlRelationalDelegate(self.vuetable))
            # active le tri quand on clique sur les têtes des colonnes
            self.vuetable.setSortingEnabled(True)
            # ajuste la largeur des colonnes en fonction de leurs contenus
            self.vuetable.resizeColumnsToContents()
     
            # définit la table à afficher
            self.vuetable.model().setTable(self.nomtable)
            # peuple le modèle avec les données de la table
            self.vuetable.model().select() 
            # option: trie par défaut selon la colonne 0
            self.vuetable.model().sort(0, QtCore.Qt.AscendingOrder) # ou DescendingOrder
     
            # place le QTableView dans la fenêtre QMainWindow
            self.setCentralWidget(QtWidgets.QFrame())
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.vuetable, 0, 0)
            self.centralWidget().setLayout(posit)
     
        #========================================================================
        def closeEvent(self, event=None):
            """Méthode appelée automatiquement à la fermeture de la fenêtre"""
            #fermeture de la base
            try:
                fermebaseqt(self.db)
            except Exception:
                pass    
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
     
        basesql = "mabasesql.db3"
        nomtable = "table1"
     
        fen = VoirTableSql(basesql, nomtable)
        fen.show()
        sys.exit(app.exec_())

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    Mille mercis, pour toutes ces explications, je m'en vais tester tout ça.
    Pour revenir sur l'affichage du QCheckBox en lecture seule, ne pourrait-on pas afficher une icone ou une image de QCheckBox cochée ou décochée en fonction de l'état du "booléen"?

  14. #14
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Et voilà une solution complète avec un QCheckBox!

    La base de données contient maintenant un champs INTEGER dont le contenu est 1=True ou 0=False, ce qui est facile à convertir en booléen, même si le type booléen n'existe pas dans sqlite3.

    Voilà la structure de table ("table1") que j'ai utilisée:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    CREATE TABLE "table1" (
    "id" INTEGER NOT NULL PRIMARY KEY, 
    "nom" TEXT DEFAULT "",
    "membre" INTEGER
    );
    Et voilà le code. Par rapport à la version précédente, j'ai surtout ajouté la méthode paint au delegate, et je n'ai toujours pas modifié le modèle!

    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
    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3, PyQt5
     
    import sys, os
    from PyQt5 import (QtWidgets, QtCore, QtGui, QtSql)
     
    #############################################################################
    def ouvrebaseqt(basesql, contrainte=True):
        """ouvre la base 'basesql' et renvoie la connexion (None si échec)
        """
     
        driver = "QSQLITE"  # =pilote d'accès à la base sqlite3)
        db = QtSql.QSqlDatabase.addDatabase(driver)
        db.setDatabaseName(basesql)
        if not db.open():
            QtWidgets.QMessageBox.critical(None,
                        "Ouverture de la base de données",
                        "Erreur d'ouverture: {}".format(db.lastError().text()))
            db = None
        else:
            if contrainte:
                query = QtSql.QSqlQuery(db)
                req = "PRAGMA foreign_keys=on;"
                if not query.exec_(req):
                    query.finish()    # désactive le curseur
                    query = None
                    fermebaseqt(db)  # fermeture de la base
                    db = None
                else:
                    query.finish()    # désactive le curseur
     
        return db    
     
    #############################################################################
    def fermebaseqt(db):
        if db!=None:
            db.close()
     
    #############################################################################
    class CheckBox(QtWidgets.QFrame):
        """QCheckBox placé dans un QFrame
        """
     
        def __init__(self, parent=None):
            super().__init__(parent)
     
            # met un fond de couleur
            self.setStyleSheet("background-color: yellow;") #lightgrey;")
     
            # crée le QChecBox à l'intérieur du QFrame
            self.box = QtWidgets.QCheckBox(self)
     
            # positionne le QCheckBox au milieu du QFrame
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.box, 0, 0)
            posit.setAlignment(QtCore.Qt.AlignCenter)
            posit.setContentsMargins(0, 0, 0, 0)
            self.setLayout(posit)
     
    #############################################################################
    class MonSqlRelationalDelegate(QtSql.QSqlRelationalDelegate):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
        #========================================================================
        def createEditor(self, parent, option, index):
            """Créer et initialise le widget d'édition
               aParent: type 'PyQt5.QtWidgets.QWidget'
               option: type 'PyQt5.QtWidgets.QStyleOptionViewItem'
               index: type 'PyQt5.QtCore.QModelIndex'
               Retourne l'éditeur utilisé
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # crée le QCheckBox personnalisé
                editor = CheckBox(parent)
     
                # donne la taille du QFrame = taille de la case 
                editor.resize(option.rect.size())           
     
                # retourne le QCheckBox personnalisé
                return editor        
     
            else:
                # cas d'une autre colonne: on repasse la main à l'ancêtre
                return QtSql.QSqlRelationalDelegate.createEditor(self, parent, option, index)
     
        #========================================================================
        def setEditorData(self, editor, index):
            """Définit l'état du QCheckBox à l'entrée du mode édition
                 editor: QtWidgets.QWidget
                 index: QtCore.QModelIndex
               Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # récupère la valeur de la base de données, destinée à la case
                membre = index.model().data(index, QtCore.Qt.EditRole)
     
                # définit l'état coché ou non du QCheckBox
                editor.box.setChecked(membre!=0)
     
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setEditorData(self, editor, index)
     
        #========================================================================
        def setModelData(self, editor, model, index):
            """ Enregistre la valeur modifiée dans la base de données 
                  editor: type 'QtWidgets.QWidget'
                  model: type 'QtCore.QAbstractItemModel'
                  index: type 'QtCore.QModelIndex'
                Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                #lit la valeur affichée dans le QCheckBox en fin d'édition
                coche = editor.box.isChecked()
                if coche:
                    membre = 1
                else:
                    membre = 0    
     
                # on enregistre dans le modèle la valeur affichée du QLineEdit
                model.setData(index, membre, QtCore.Qt.EditRole)
     
                return
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setModelData(self, editor, model, index)
                return
     
        #========================================================================
        def paint (self, painter, option, index):
            """permet d'afficher un QCheckBox dans les colonnes booléennes
               en dehors du mode "édition" de la cellule 
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # lecture de la donnée de la base, et conversion en booléen (0=False, 1=True)
                data = bool(index.model().data(index, QtCore.Qt.EditRole))
     
                # création du style et position au centre de la cellule
                checkboxstyle = QtWidgets.QStyleOptionButton()
                #
                checkbox_rect = QtWidgets.QApplication.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, checkboxstyle)
                #
                checkboxstyle.rect = option.rect
                checkboxstyle.rect.setLeft(option.rect.x() + option.rect.width()//2 - checkbox_rect.width()//2)
     
                # mettre ou pas la coche selon la valeur de la donnée booléenne de la base
                if data:
                    checkboxstyle.state = QtWidgets.QStyle.State_On | QtWidgets.QStyle.State_Enabled
                else:
                    checkboxstyle.state = QtWidgets.QStyle.State_Off | QtWidgets.QStyle.State_Enabled
     
                # en cas de sélection de la case, le fond est coloré
                if option.state & QtWidgets.QStyle.State_Selected:
                    painter.fillRect(option.rect, option.palette.highlight())
     
                # dessin du QCheckBox au centre de la cellule
                QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_CheckBox, checkboxstyle, painter)            
     
            else:
                # traitement des autres colonnes
                super().paint(painter, option, index)    
     
    #############################################################################
    class VoirTableSql(QtWidgets.QMainWindow):
     
        def __init__(self, basesql, nomtable, parent=None):
            super().__init__(parent)
     
            self.setWindowTitle("Affichage de la table {}".format(nomtable))
            self.resize(800, 600)
     
            # base de données à exploiter
            self.basesql = basesql
            # table à afficher
            self.nomtable = nomtable
     
            # ouvre la base SQL avec contraintes
            self.db = ouvrebaseqt(self.basesql, True)
     
            # crée le modèle et sa liaison avec la base SQL
            self.model = QtSql.QSqlRelationalTableModel(self, self.db)
            # définit la stratégie en cas de modification de données par l'utilisateur
            self.model.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) #OnManualSubmit)
     
            # crée la table physique et son lien avec le modèle et le delegate
            self.vuetable = QtWidgets.QTableView(self)
            self.vuetable.setModel(self.model)
            self.vuetable.setItemDelegate(MonSqlRelationalDelegate(self.vuetable))
            # active le tri quand on clique sur les têtes des colonnes
            self.vuetable.setSortingEnabled(True)
            # ajuste la largeur des colonnes en fonction de leurs contenus
            self.vuetable.resizeColumnsToContents()
     
            # définit la table à afficher
            self.vuetable.model().setTable(self.nomtable)
            # peuple le modèle avec les données de la table
            self.vuetable.model().select() 
            # option: trie par défaut selon la colonne 0
            self.vuetable.model().sort(0, QtCore.Qt.AscendingOrder) # ou DescendingOrder
     
            # place le QTableView dans la fenêtre QMainWindow
            self.setCentralWidget(QtWidgets.QFrame())
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.vuetable, 0, 0)
            self.centralWidget().setLayout(posit)
     
        #========================================================================
        def closeEvent(self, event=None):
            """Méthode appelée automatiquement à la fermeture de la fenêtre"""
            #fermeture de la base
            try:
                fermebaseqt(self.db)
            except Exception:
                pass    
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
     
        basesql = "mabasesql.db3"
        nomtable = "table1"
     
        fen = VoirTableSql(basesql, nomtable)
        fen.show()
        sys.exit(app.exec_())
    Comme pour les autres cases, il faut double-cliquer pour rentrer dans le mode "édition" afin de changer la valeur du QCheckBox. et comme j'ai mis ici une stratégie "OnFieldChange", la modification est immédiatement enregistrée à la sortie du mode "édition".

    Pour se rappeler qu'il faut rentrer dans le mode "édition" pour modifier, j'ai laissé le fond jaune. Mais on peut bien sûr changer la couleur!


    Pour faciliter l'exécution, tu trouveras ci-dessous le code qui génère la base de données utilisée:

    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3
     
    import sys
    import sqlite3
    import random
    random.seed()
     
    #############################################################################
    # fabrique une chaine de n caractères pris au hasard dans alphanum
    genmot = lambda n=10, alphanum=u"ABCDEFGHIJKLMNOPQRSTUVWXYZ": \
                ''.join([alphanum[random.randint(0,len(alphanum)-1)] for i in range(n)])
     
    #############################################################################
    def creabasesql(basesql):
        """créer une base sql de test"""
        cnx = sqlite3.connect(basesql)
        cur = cnx.cursor()
        cur.execute("""DROP TABLE IF EXISTS 'table1' """)
        nmax = 100
        try:
            cur.execute("""
                CREATE TABLE "table1" (
                "id" INTEGER NOT NULL PRIMARY KEY, 
                "nom" TEXT DEFAULT "",
                "membre" INTEGER
                );
                """)
            noms = []
            for i in range(0, nmax):
                while True:
                    nom = genmot()
                    if nom not in noms:
                        break
                noms.append(nom)
                cur.execute("""INSERT INTO table1 VALUES (?, ?, ?);""", (i+1, nom, 0))
            cnx.commit()
        except sqlite3.Error as msgerr:
            cnx.rollback()
            print("erreur: {}".format(msgerr.args[0]))
            cur.close()
            cnx.close()   
            return
     
        cur.close()
        cnx.close()
     
    #############################################################################
    if __name__ == '__main__':
        basesql = "mabasesql.db3"
        creabasesql(basesql)
    Ouf...

  15. #15
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 362
    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 362
    Points : 36 894
    Points
    36 894
    Par défaut
    Salut,

    Joli boulot.

    Citation Envoyé par tyrtamos Voir le message
    La base de données contient maintenant un champs INTEGER dont le contenu est 1=True ou 0=False, ce qui est facile à convertir en booléen, même si le type booléen n'existe pas dans sqlite3.
    On peut les ajouter:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import sqlite3
     
    db = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
     
    sqlite3.register_adapter(bool, int)
    sqlite3.register_converter("BOOLEAN", lambda z: bool(int(z)))
     
     
    db.execute('CREATE TABLE test (bool BOOLEAN)')
    db.execute('INSERT INTO test VALUES (?)', (True,))
    db.execute('INSERT INTO test VALUES (?)', (False,))
    for row in db.execute('SELECT * FROM test'):
        print (row)
    - W

  16. #16
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Merci pour l'idée! Mais je me suis dit en même temps:
    - "c'est une bonne idée", j'ai déjà fait ça dans d'autres programmes, et je sais que ça marche!
    - "ça ne peut pas marcher", parce que ça ne concerne que le pilote sqlite3 livré avec Python, et moi j'utilise le pilote QSQLITE livré avec PyQt5!

    Mais comme je suis curieux, j'essaie quand même...

    Je crée donc une nouvelle base de données avec les 2 lignes "sqlite3.register_..." ce qui donne la structure de table suivante:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    CREATE TABLE "table1" (
    "id" INTEGER NOT NULL PRIMARY KEY, 
    "nom" TEXT DEFAULT "",
    "membre" BOOLEAN
    );
    Je reprends ensuite le code PyQt5 précédent, et je le simplifie en fonction du nouveau champ booléen, c'est à dire que je demande à PyQt de lire/écrire directement les booléens de la base sans conversion. Et... ça marche! La 3ème colonne du QTableView affiche bien une case à cocher, les modifications des coches sont bien enregistrées dans la base, et les sessions suivantes montrent bien la base ainsi modifiée.

    Bizarre, ça. Pourquoi ça marche? On dirait que le type BOOLEAN, bien que n'existant pas dans le pilote de Qt5, est reconnu quand même comme booléen...

    Ce type BOOLEAN est donc très intéressant!


    Cependant, avec INTEGER, et en prenant 1=True et 0=False, ce n'est pas très compliqué non plus de faire avec les conversions de type (int=>bool et bool=>int), car ça ne concerne que 3 lignes:

    Dans "setEditorData":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    # coche à mettre dans la case à cocher en fonction de la valeur de la base
    coche = bool(index.model().data(index, QtCore.Qt.EditRole))
    Dans "setModelData":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    # valeur à mettre dans la base (0 ou 1) en fonction de l'état de la case à cocher
    valeur = int(editor.box.isChecked())
    Et dans "paint":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    # coche à mettre dans la case à cocher en fonction de la valeur de la base
    coche = bool(index.model().data(index, QtCore.Qt.EditRole))
    Merci!

  17. #17
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut
    @Tyrtamos, bonsoir,
    un grand merci pour le temps que tu as consacré à mes questions. J'ai intégré ton proposition de code quasiment sans modif et ça marche du feu de dieu.
    J'ai pris le temps de le décortiquer quand même, c'est propre, facile à comprendre, mais j'aurai mis un temps fou à monter ça tout seul dans mon coin.
    que j'aime python!

  18. #18
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Je suis ravi d'avoir pu t'aider!

    Mais en plus, je me suis aidé moi-même, car si je savais que c'était possible, je ne l'avais pas encore fait jusqu'au bout: je m'en servirai maintenant dans mes propres projets!

    Et donc: merci d'avoir posé la question!

  19. #19
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 49
    Points : 51
    Points
    51
    Par défaut Formater les données avant affichage dans un QTableView --> PyQt6
    Bonjour,
    Je déterre un de mes vieux fils de discussion... sur l'affichage et l'édition d'un checkbox dans un tableview.
    j'ai voulu porter le code de Tyrtamos de pyqt5 à pyqt6. Après avoir corrigé toutes les erreurs, il m'en reste une sur laquelle je bloque (et ce n'est peut-être pas la dernière ) :
    la boite de dialogue et la table s'affiche bien

    Nom : qtableview_checkbox.png
Affichages : 243
Taille : 14,4 Ko
    mais quand je veux cocher ou décocher un checkbox, j'ai le message d'erreur suivant :
    AttributeError: 'QFrame' object has no attribute 'box' sur cette ligne 127
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    coche = editor.box.isChecked
    voici le code complet.
    merci pour votre aide.

    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
    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
    #
    !/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3, PyQt5
     
    import sys, os
    from PyQt6 import (QtWidgets, QtCore, QtGui, QtSql)
    from PyQt6.QtCore import *
    from PyQt6.QtSql import *
    from PyQt6.QtWidgets import QStyle
     
    #############################################################################
    def ouvrebaseqt(basesql, contrainte=True):
        """ouvre la base 'basesql' et renvoie la connexion (None si échec)
        """
     
        driver = "QSQLITE"  # =pilote d'accès à la base sqlite3)
        db = QtSql.QSqlDatabase.addDatabase(driver)
        db.setDatabaseName(basesql)
        if not db.open():
            QtWidgets.QMessageBox.critical(None,
                        "Ouverture de la base de données",
                        "Erreur d'ouverture: {}".format(db.lastError().text()))
            db = None
        else:
            if contrainte:
                query = QtSql.QSqlQuery(db)
                req = "PRAGMA foreign_keys=on;"
                if not query.exec(req):
                    query.finish()    # désactive le curseur
                    query = None
                    fermebaseqt(db)  # fermeture de la base
                    db = None
                else:
                    query.finish()    # désactive le curseur
     
        return db    
     
    #############################################################################
    def fermebaseqt(db):
        if db!=None:
            db.close()
     
    #############################################################################
    class CheckBox(QtWidgets.QFrame):
        """QCheckBox placé dans un QFrame
        """
     
        def __init__(self, parent=None):
            super().__init__(parent)
     
            # met un fond de couleur
            self.setStyleSheet("background-color: yellow;") #lightgrey;")
     
            # crée le QChecBox à l'intérieur du QFrame
            self.box = QtWidgets.QCheckBox(self)
     
            # positionne le QCheckBox au milieu du QFrame
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.box, 0, 0)
            posit.setAlignment(Qt.AlignmentFlag.AlignCenter)
            posit.setContentsMargins(0, 0, 0, 0)
            self.setLayout(posit)
     
    #############################################################################
    class MonSqlRelationalDelegate(QtSql.QSqlRelationalDelegate):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
        #========================================================================
        def createEditor(self, parent, option, index):
            """Créer et initialise le widget d'édition
               aParent: type 'PyQt5.QtWidgets.QWidget'
               option: type 'PyQt5.QtWidgets.QStyleOptionViewItem'
               index: type 'PyQt5.QtCore.QModelIndex'
               Retourne l'éditeur utilisé
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # crée le QCheckBox personnalisé
                editor = CheckBox(parent)
     
                # donne la taille du QFrame = taille de la case 
                editor.resize(option.rect.size())           
     
                # retourne le QCheckBox personnalisé
                return editor        
     
            else:
                # cas d'une autre colonne: on repasse la main à l'ancêtre
                return QtSql.QSqlRelationalDelegate.createEditor(self, parent, option, index)
     
        #========================================================================
        def setEditorData(self, editor, index):
            """Définit l'état du QCheckBox à l'entrée du mode édition
                 editor: QtWidgets.QWidget
                 index: QtCore.QModelIndex
               Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # récupère la valeur de la base de données, destinée à la case
                membre = index.model().data(index, Qt.ItemDataRole.EditRole)
     
                # définit l'état coché ou non du QCheckBox
                editor.box.setChecked(membre!=0)
     
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setEditorData(self, editor, index)
     
        #========================================================================
        def setModelData(self, editor, model, index):
            """ Enregistre la valeur modifiée dans la base de données 
                  editor: type 'QtWidgets.QWidget'
                  model: type 'QtCore.QAbstractItemModel'
                  index: type 'QtCore.QModelIndex'
                Retour: None
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                #lit la valeur affichée dans le QCheckBox en fin d'édition
                coche = editor.box.isChecked()
                if coche:
                    membre = 1
                else:
                    membre = 0    
     
                # on enregistre dans le modèle la valeur affichée du QLineEdit
                model.setData(index, membre,Qt.ItemDataRole.EditRole)
     
                return
            else:
                # traite les autres cas
                QtSql.QSqlRelationalDelegate.setModelData(self, editor, model, index)
                return
     
        #========================================================================
        def paint (self, painter, option, index):
            """permet d'afficher un QCheckBox dans les colonnes booléennes
               en dehors du mode "édition" de la cellule 
            """
            if index.column() == 2:
                # on est dans la colonne des booléens
     
                # lecture de la donnée de la base, et conversion en booléen (0=False, 1=True)
                data = bool(index.model().data(index, Qt.ItemDataRole.EditRole))
     
                # création du style et position au centre de la cellule
                checkboxstyle = QtWidgets.QStyleOptionButton()
                #
                checkbox_rect = QtWidgets.QApplication.style().subElementRect(QStyle.SubElement.SE_CheckBoxIndicator, checkboxstyle)
                #
                checkboxstyle.rect = option.rect
                checkboxstyle.rect.setLeft(option.rect.x() + option.rect.width()//2 - checkbox_rect.width()//2)
     
                # mettre ou pas la coche selon la valeur de la donnée booléenne de la base
                if data:
                    checkboxstyle.state = QStyle.StateFlag.State_On | QStyle.StateFlag.State_Enabled
                else:
                    checkboxstyle.state = QStyle.StateFlag.State_Off | QStyle.StateFlag.State_Enabled
     
                # en cas de sélection de la case, le fond est coloré
                if option.state & QStyle.StateFlag.State_Selected:
                    painter.fillRect(option.rect, option.palette.highlight())
     
                # dessin du QCheckBox au centre de la cellule
                QtWidgets.QApplication.style().drawControl(QStyle.ControlElement.CE_CheckBox, checkboxstyle, painter)            
     
            else:
                # traitement des autres colonnes
                super().paint(painter, option, index)    
     
    #############################################################################
    class VoirTableSql(QtWidgets.QMainWindow):
     
        def __init__(self, basesql, nomtable, parent=None):
            super().__init__(parent)
     
            self.setWindowTitle("Affichage de la table {}".format(nomtable))
            self.resize(800, 600)
     
            # base de données à exploiter
            self.basesql = basesql
            # table à afficher
            self.nomtable = nomtable
     
            # ouvre la base SQL avec contraintes
            self.db = ouvrebaseqt(self.basesql, True)
     
            # crée le modèle et sa liaison avec la base SQL
            self.model = QtSql.QSqlRelationalTableModel(self, self.db)
            # définit la stratégie en cas de modification de données par l'utilisateur
            self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnFieldChange) #OnManualSubmit)
     
            # crée la table physique et son lien avec le modèle et le delegate
            self.vuetable = QtWidgets.QTableView(self)
            self.vuetable.setModel(self.model)
            self.vuetable.setItemDelegate(MonSqlRelationalDelegate(self.vuetable))
            # active le tri quand on clique sur les têtes des colonnes
            self.vuetable.setSortingEnabled(True)
            # ajuste la largeur des colonnes en fonction de leurs contenus
            self.vuetable.resizeColumnsToContents()
     
            # définit la table à afficher
            self.vuetable.model().setTable(self.nomtable)
            # peuple le modèle avec les données de la table
            self.vuetable.model().select() 
            # option: trie par défaut selon la colonne 0
            self.vuetable.model().sort(0, Qt.SortOrder.AscendingOrder) # ou DescendingOrder
     
            # place le QTableView dans la fenêtre QMainWindow
            self.setCentralWidget(QtWidgets.QFrame())
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.vuetable, 0, 0)
            self.centralWidget().setLayout(posit)
     
        #========================================================================
        def closeEvent(self, event=None):
            """Méthode appelée automatiquement à la fermeture de la fenêtre"""
            #fermeture de la base
            try:
                fermebaseqt(self.db)
            except Exception:
                pass    
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
     
        basesql = "mabasesql.db3"
        nomtable = "table1"
     
        fen = VoirTableSql(basesql, nomtable)
        fen.show()
        sys.exit(app.exec())

  20. #20
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 480
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour

    Malheureusement, je ne suis pas (encore) passé à PyQt6, pour autant que j'y passe un jour... Je n'ai donc pas aujourd'hui les compétences pour te répondre.

    3 pistes pour t'aider dans tes recherches:

    - la doc de PyQt6:
    https://www.riverbankcomputing.com/static/Docs/PyQt6/

    - la doc de Qt6:
    https://doc.qt.io/qt-6/index.html

    - la "moulinette" de Sve@r pour passer de PyQt5 à PyQt6:
    https://www.developpez.net/forums/d2...t5-vers-pyqt6/

    Regarde d'abord les parties de ces documents qui donnent les différences entre PyQt5 et PyQt6. Regarde ensuite la doc de Qt6 qui décrit les possibilités des objets que tu manipules.

    Si tu ne trouves rien et si personne ne te répond, je regarderai un peu plus...

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [MySQL] Recuperer les champs avant insertion dans la base de données.
    Par jmtrivia dans le forum PHP & Base de données
    Réponses: 5
    Dernier message: 23/04/2014, 15h00
  2. Réponses: 2
    Dernier message: 24/05/2013, 16h04
  3. Réponses: 2
    Dernier message: 30/10/2006, 22h14
  4. Réponses: 4
    Dernier message: 04/12/2003, 08h12
  5. Réponses: 4
    Dernier message: 18/08/2003, 09h53

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