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 :

[QTableView] Comment récupérer une entrée absente de la table ?


Sujet :

PyQt Python

  1. #1
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut [QTableView] Comment récupérer une entrée absente de la table ?
    Hello,

    Ok l'intitulé est un peu surprenant mais je ne sais pas comment le dire autrement.
    Vous pourriez me répondre qu'il suffit de créer des colonnes cachées mais j'en aurais trop et le principe ne me satisfait pas vraiment.

    Avec le code ci-dessous, si on sélectionne puis clique sur le bouton, pas de soucis.
    Sauf si l'on tri les données dans un ordre qui n'est pas celui d'origine
    Bref, en quelques clics c'est facile à comprendre.

    Tout se passe à la ligne 47 car j'utilise la liste des données non triée avec l'index de celle provenant du modèle (triée donc).

    Merci
    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
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant, QSortFilterProxyModel
    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTableView, QAbstractItemView, QPushButton
    from operator import attrgetter
     
    class obj(object):
        def __init__(self, col0, park):
            self.col0, self.park= col0, park
     
    class ContentModel(QAbstractTableModel):
        def __init__(self, data, header, parent):
            QAbstractTableModel.__init__(self, parent)
            self.data, self.header= data, header
        def rowCount(self, content= None):
            return len(self.data)
        def columnCount(self, content):
            return len(self.header)
        def data(self, index, role):
            return getattr(self.data[index.row()], "col" + str(index.column())) if index.isValid() and role == Qt.DisplayRole else None
        def headerData(self, col, orientation, role):
            return QVariant(self.header[col]) if orientation == Qt.Horizontal and role == Qt.DisplayRole else None
        def sort(self, col, order):
            self.layoutAboutToBeChanged.emit()
            self.data.sort(key= attrgetter("col" + str(col)), reverse= True if order == Qt.DescendingOrder else False)
            self.layoutChanged.emit()
     
    class Content(QWidget):
        def __init__(self):
            QWidget.__init__(self)
            self.layout, self.tableView, self.proxy, self.table= QVBoxLayout(), QTableView(), QSortFilterProxyModel(), list()
            self.table.append(obj("Clio (1)", 1))
            self.table.append(obj("Megane (10)", 10))
            self.table.append(obj("Twingo (100)", 100))
            self.proxy.setSourceModel(ContentModel(self.table, ["Modèle"], self))
            self.tableView.setModel(self.proxy)
            self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.tableView.setSortingEnabled(True)
            self.tableView.sortByColumn(0, Qt.AscendingOrder)
            self.tableView.verticalHeader().setVisible(False)
            self.layout.addWidget(self.tableView)
            self.layout.addWidget(QPushButton("Libère", self, clicked= self.delete))
            self.setLayout(self.layout)
        def delete(self):
            for index in self.tableView.selectionModel().selectedRows():
                print(index.data(), "=", self.table[index.row()].park)
     
    if __name__ == "__main__":
        app, ui= QApplication([]), Content()
        ui.show()
        exit(app.exec_())

  2. #2
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
                print(index.data(), "=", self.table[self.proxy.mapToSource(index).row()].park)

  3. #3
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    Je pensais que j'avais réglé le problème mais je me suis fourvoyé
    Je vous soumet de nouveau un bout de 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
    #!/bin/env python3
     
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant, QSortFilterProxyModel, QModelIndex
    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTableView, QAbstractItemView, QPushButton
    from operator import attrgetter
     
    class obj(object):
        def __init__(self, col0, park):
            self.col0, self.park= col0, park
     
    class ContentModel(QAbstractTableModel):
        def __init__(self, data, header, parent):
            QAbstractTableModel.__init__(self, parent)
            self.data, self.header= data, header
        def rowCount(self, content= None):
            return len(self.data)
        def columnCount(self, content):
            return len(self.header)
        def data(self, index, role):
            return getattr(self.data[index.row()], "col" + str(index.column())) if index.isValid() and role == Qt.DisplayRole else None
        def headerData(self, col, orientation, role):
            return QVariant(self.header[col]) if orientation == Qt.Horizontal and role == Qt.DisplayRole else None
        def sort(self, col, order):
            self.layoutAboutToBeChanged.emit()
            self.data.sort(key= attrgetter("col" + str(col)), reverse= True if order == Qt.DescendingOrder else False)
            self.layoutChanged.emit()
        def removeRow(self, index, parent= QModelIndex()):
            self.beginRemoveRows(parent, index, index)
            self.data.pop(index)
            self.endRemoveRows()
            return True
     
    class Content(QWidget):
        def __init__(self):
            QWidget.__init__(self)
            self.layout, self.tableView, self.proxy, self.table= QVBoxLayout(), QTableView(), QSortFilterProxyModel(), list()
            self.table.append(obj("Clio   (10)", 10))
            self.table.append(obj("Megane (20)", 20))
            self.table.append(obj("Twingo (30)", 30))
            self.table.append(obj("Kangoo (40)", 40))
            self.table.append(obj("Scenic (50)", 50))
            self.proxy.setSourceModel(ContentModel(self.table, ["Modèle"], self))
            self.tableView.setModel(self.proxy)
            self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.tableView.setSortingEnabled(True)
            self.tableView.sortByColumn(0, Qt.AscendingOrder)
            self.tableView.verticalHeader().setVisible(False)
            self.layout.addWidget(self.tableView)
            self.layout.addWidget(QPushButton("Libère & Supprime", self, clicked= self.delete))
            self.setLayout(self.layout)
        def delete(self):
            for index in self.tableView.selectionModel().selectedRows():
                realRow= self.proxy.mapToSource(index).row()
                print(index.data(), "=", self.table[realRow].park)
                self.proxy.sourceModel().removeRow(realRow)
     
    if __name__ == "__main__":
        app, ui= QApplication([]), Content()
        ui.show()
        exit(app.exec_())
    J'ai ajouté quelques données pour l'exemple et j'ai implémenté removeRow dans le modèle pour pouvoir supprimer les lignes du tableau.
    Et ça devient n'importe quoi car ça ne supprime plus ce qu'il faudrait et parfois je me retrouve avec une ou plusieurs lignes vides dans le tableau.

  4. #4
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 484
    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 484
    Points : 9 286
    Points
    9 286
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'utilise aussi QTableView et QSortFilterProxyModel pour gérer des tables d'une base de données SQL, mais je ne suis qu'en PyQt4.

    Cependant, voilà ce que je fais pour effacer la ligne courante (celle sélectionnée) d'une table affichée:

    Rappel des liens entre les objets pour les notations que j'utilise:
    - self.model est le modèle qui fait le lien avec les données (pour moi, un QSqlRelationalTableModel)
    - self.proxy est l'instance de QSortFilterProxyModel
    - self.proxy est mis comme modèle du QTableView: self.tableview.setModel(self.proxy)
    - et self.model est mis comme source de self.proxy: self.proxy.setSourceModel(self.model)

    On a ainsi le lien entre l'affichage et les données QTableView <=> proxy <=> model <=> données

    Pour effacer la ligne courante:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    ligne = self.tableview.selectionModel().currentIndex().row()
    self.proxy.removeRow(ligne)
    La ligne est alors marquée par un '!' dans le titre à gauche, et sera effectivement supprimée de l'affichage et des données lors de l'application des modifs.

    Cette application dépend de ce qu'on a mis dans la stratégie d'édition du modèle.

    Avec:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
    Il faudra pour appliquer la modif (et d'autres en même temps s'il y en a en attente):

    En utilisant la même méthode, on peut insérer une nouvelle ligne:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    ligne = self.tableview.selectionModel().currentIndex().row()
    self.proxy.insertRow(ligne)
    Ces outils Qt, et en particulier QSortFilterProxyModel, sont vraiment très puissants! Et en sous-classant QSortFilterProxyModel, on arrive à trier et filtrer une table avec n'importe quelle méthode. Par exemple pour trier selon le dictionnaire français ou filtrer avec un regex ou pour trouver des mots similaires à un mot donné avec un ratio de similitude.

  5. #5
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    Bonjour tyrtamos,

    Très intéressant, je ne connaissais pas setEditStrategy().
    Il est clair que c'est ce que je devrait utiliser.
    Le problème est que ce n'est pas prévu par le QAbstractTableModel que j'utilise.

    Du coup je me demande si j'ai choisis le bon modèle?
    Je n'ai vraiment pas d'autre solution que d'implémenter un semblant de stratégie OnManualSubmit?

    En retirant la ligne 56 le code fonctionne comme il doit mais pas comme je voudrais.
    Dit autrement, si je supprime l'enregistrement de la table, au prochain for index, la liste des données a changé et tout va à vau l'eau.
    Le modèle ou proxy ne retrouve plus ses petits et dans certains cas je me retrouve avec des lignes vides.

    De plus,
    Je vais devoir dériver de QSortFilterProxyModel car je dois filtrer dans le tableau.
    Et la je me pose la question de qui doit supprimer les données? Le modèle ou le proxy?

    Dans ton exemple tu le fais via le proxy. As-tu réimplémenté removeRow() de ton proxy?
    Car dans mon cas, si je demande au proxy de supprimer il ne le fait pas!
    Je pensais que le proxy demandait au modèle de le faire mais ça n'a pas l'air d'être le cas.

    Les classes QAbstractTableModel et QSortFilterProxyModel possédent toutes les deux une méthode sort().
    Comment choisir où l'on doit implémenter la fonction pour trier?

  6. #6
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 484
    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 484
    Points : 9 286
    Points
    9 286
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Citation Envoyé par kimi31 Voir le message
    Très intéressant, je ne connaissais pas setEditStrategy().
    Le problème est que ce n'est pas prévu par le QAbstractTableModel que j'utilise.
    Ça veut dire que dans ce cas, on n'a pas le choix: les modifications des données affichées se répercutent immédiatement sur les données gérées, dès qu'on sort du mode d'édition (touche "Entrée").

    Citation Envoyé par kimi31 Voir le message
    Du coup je me demande si j'ai choisis le bon modèle?
    Pour gérer des données sous forme de listes, j'utilise plutôt QStandardItemModel.

    Pour tes autres questions, j'ai préféré te proposer un code de test assez complet et copieusement commenté.

    Une classe ProxyModel hérite de QSortFilterProxyModel, et possède 2 méthodes surchargées:

    - lessThan(self, left, right) qui permet un tri spécial en renvoyant True si left<right
    => pour exemple, on fera ici un tri selon le dictionnaire français

    si on trie selon les règles standards, on a par exemple dans l'ordre: "X", "a", "z", "é" puisque c'est l'ordre normal des polices de caractères type latin1.
    si on trie selon le dictionnaire français, on retrouve un ordre correct: "a", "é", "X", "z".

    - filterAcceptsRow(self, sourceRow, sourceParent) qui permet un filtrage personnalisé en renvoyant True si la ligne est acceptée dans le filtrage
    => pour exemple, on fera ici un filtre qui limitera les chaines de la colonne 0 à avoir une minuscule en 1er caractère

    Pour trier le tableau selon les valeurs d'une colonne, il suffit de cliquer sur le titre de cette colonne. Au lancement, le tri est neutralisé (self.proxy.sort(-1)), on a donc les données dans l'ordre d'insertion.

    On peut faite des tests grâce à l'ajout du keyPressEvent:

    Alt-I: insère une ligne vide au dessus de la ligne sélectionnée

    Alt-S: supprime la ligne sélectionnée

    Alt-T: annule un tri antérieur: on retrouve l'ordre initial d'insertion des données

    Alt-F: déclenche le filtrage prévu et affiche le résultat

    Alt-X: annule le filtrage en cours et affiche le tableau complet

    Alt-R: récupère dans une "liste de listes" les données affichées après modifications éventuelles et filtrage en cours. On peut avoir cette liste dans l'ordre affiché après tri, ou dans l'ordre initial d'insertion. Cette extraction est intéressante parce qu'on peut créer un fichier csv qui sera accepté par Excel.


    Voilà le code complet (Python 3), mais en PyQt4: j'espère que la conversion en PyQt5 sera facile...

    Je n'ai pas testé ce code très longtemps: s'il y a des anomalies de fonctionnement: dis-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
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3
     
    import sys, os
    import locale
     
    from PyQt4 import QtCore, QtGui
     
    #############################################################################
    class Compdicofr(object):
        """comparaison de 2 chaines (unicode) selon le dictionnaire français
        """
     
        def __init__(self):
            # stocke la locale courante
            self.loc = locale.getlocale()
            # espace insécable
            self.espinsec = '\xA0'
     
        def __call__(self, v1, v2):
     
            # on retire les tirets et les blancs insécables
            v1 = v1.replace('-', '')
            v1 = v1.replace(self.espinsec, '')
            v2 = v2.replace('-', '')
            v2 = v2.replace(self.espinsec, '')
     
            locale.setlocale(locale.LC_ALL, '')
            comp = locale.strcoll(v1, v2)
            # retour à la locale courante
            locale.setlocale(locale.LC_ALL, self.loc)
     
            # on retourne le résultat de la comparaison
            return comp
     
    compdicofr = Compdicofr()
     
    #############################################################################
    def cmp(x1, x2):
        """simulation de la fonction cmp de Python 2
        """
        return (x1 > x2) - (x1 < x2)
     
    #############################################################################
    class ProxyModel(QtGui.QSortFilterProxyModel):
        """ProxyModel pour ajouter des méthodes de tri et de filtrage particuliers
           s'interpose entre le modèle normal et le QTableView
        """
     
        #========================================================================
        def __init__(self, parent=None):
            super(ProxyModel, self).__init__(parent)
     
            self.filtreperso = False
     
        #========================================================================
        def lessThan(self, left, right):
            """fonction de comparaison particulière
               left et right: QModelIndex de 2 cases d'une même colonne à trier
               retourne un booléen (True si left<right)
            """
            role = self.sortRole()
            vleft = left.data(role)  # 1ère valeur à comparer
            vright = right.data(role)  # 2e valeur à comparer
     
            col = left.column()  # numéro de la colonne en cours de tri
            if col == 1:
                # la 2e colonne (indice 1) est composée de nombres entiers
     
                # au cas où on aurait ajouté des données chaine
                vleft, vright = int(vleft), int(vright)
     
                # comparaison standard (similaire à cmp de Python 2)
                return cmp(vleft, vright) < 0
            else:
                # les autres colonnes sont des chaines
     
                # pour éviter une erreur lors d'insertion d'une nouvelle ligne
                if vleft == None: vleft = ""
                if vright == None: vright = ""
     
                # comparaison des chaines selon le dictionnaire français
                return compdicofr(vleft, vright) < 0
     
        #========================================================================
        def filterAcceptsRow(self, sourceRow, sourceParent):
            """Surcharge de la méthode de filtrage des lignes de la table
               source_row: int, source_parent: QModelIndex
               retour: True si la ligne est retenue, False sinon
            """
     
            if self.filtreperso:
                # ici, les filtrages particuliers
     
                # role de filtrage (par défaut: QtCore.Qt.DisplayRole)
                role = self.filterRole()
     
                # index de la colonne sur laquelle s'exerce le filtrage (-1=toutes)
                col = self.filterKeyColumn()
     
                if col == 0:
                    # ici, le filtrage ne s'exerce que sur la colonne d'index col
     
                    # fonction d'index de la ligne et de la colonne en cours
                    index = self.sourceModel().index(sourceRow, col, sourceParent)
                    # valeur à filtrer
                    item = self.sourceModel().data(index, role)
     
                    if item == None: item = ""
     
                    " dit si la ligne doit être retenue dans le filtrage"
                    return item >= "a" and item <= "z"
                else:
                    return True
     
            else:
                # hors filtrage particuliers: exécution du filtrage prédéfini
                return QtGui.QSortFilterProxyModel.filterAcceptsRow(self, sourceRow, sourceParent)
     
    #############################################################################
    class Fenetre(QtGui.QWidget):
     
        def __init__(self, datas=[], parent=None):
            super(Fenetre, self).__init__(parent)
            self.resize(800, 600)
     
            headers = datas[0]  # titre des colonnes: 1ère ligne de datas
            self.datas = datas[1:]  # stocke les données hors headers
     
            #--------------------------------------------------------------------
            # initialise le modèle
            self.model = QtGui.QStandardItemModel()
     
            # met les titres des colonnes
            for j, header in enumerate(headers):
                titrecol = QtGui.QStandardItem(header)
                self.model.setHorizontalHeaderItem(j, titrecol)
     
            # peuple le modèle
            for i, row in enumerate(self.datas):
                for j, col in enumerate(row):
                    item = QtGui.QStandardItem()
                    # item.setTextAlignment(QtCore.Qt.AlignHCenter)
                    # item.setEditable(False)
                    item.setData(col, QtCore.Qt.DisplayRole)
                    self.model.setItem(i, j, item)
     
            #--------------------------------------------------------------------
            # initialise le proxy
            # self.proxy = QtGui.QSortFilterProxyModel() # cas du proxy standard
            self.proxy = ProxyModel()  # sous-classement d'un QSortFilterProxyModel
     
            # tri non automatique: il faudra cliquer en haut des colonnes
            self.proxy.setDynamicSortFilter(False)
     
            # connecte le proxy au modèle
            self.proxy.setSourceModel(self.model)
     
            #--------------------------------------------------------------------
            # initialise le QTableView
            self.tableview = QtGui.QTableView(self)
     
            # connecte le QTableView au proxy
            self.tableview.setModel(self.proxy)
            # ajuste la largeur des colonnes à leur contenu
            self.tableview.resizeColumnsToContents()
     
            # active la possibilité de tri des colonnes du QTableView
            self.tableview.setSortingEnabled(True)
            # pas de tri pour l'affichage intial (=ordre d'insertion)
            self.proxy.sort(-1)
     
            # positionne le QTableView dans la fenêtre
            posit = QtGui.QGridLayout()
            posit.addWidget(self.tableview, 0, 0)
            self.setLayout(posit)
     
        # =======================================================================
        def keyPressEvent(self, event):
     
            if self.tableview.hasFocus():
     
                # montrer les équivalences
                # print(self.tableview.model() == self.proxy)
                # print(self.tableview.model().sourceModel() == self.model)
     
                #----------------------------------------------------------------
                # Alt-I crée une nouvelle ligne à l'endroit du curseur
                if event.key() == QtCore.Qt.Key_I and  (event.modifiers() & QtCore.Qt.AltModifier):
                    ligne = self.tableview.selectionModel().currentIndex().row()
                    self.proxy.insertRows(ligne, 1)
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-S supprime la ligne à l'endoit du curseur
                elif event.key() == QtCore.Qt.Key_S and  (event.modifiers() & QtCore.Qt.AltModifier):
                    ligne = self.tableview.selectionModel().currentIndex().row()
                    self.proxy.removeRow(ligne)
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-T retrie le tableau dans l'ordre initial des données
                elif event.key() == QtCore.Qt.Key_T and  (event.modifiers() & QtCore.Qt.AltModifier):
                    self.proxy.sort(-1)
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-F déclenche le filtrage sur la colonne 0: mot>=a et mot<=z
                elif event.key() == QtCore.Qt.Key_F and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    # active le filtrage perso
                    self.proxy.filtreperso = True
     
                    # donne la colonne sur laquelle le filtrage s'exerce (-1=toutes)
                    self.proxy.setFilterKeyColumn(0)
                    # tenir compte ou non de la casse (majusc/minusc)
                    casse = QtCore.Qt.CaseSensitive
                    # crée et configure l'objet QRegExp (le motif n'a aucune importance ici)
                    rx = QtCore.QRegExp("", casse, QtCore.QRegExp.RegExp)
                    # et filtrer!
                    self.proxy.setFilterRegExp(rx)
     
                    # désactive le filtrage perso
                    self.proxy.filtreperso = True
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-X annule le filtrage en cours pour réafficher toutes les lignes
                elif event.key() == QtCore.Qt.Key_X and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    # donne la colonne sur laquelle le filtrage s'exerce (-1=toutes)
                    self.proxy.setFilterKeyColumn(-1)
                    # crée et configure un objet QRegExp qui attrape tout
                    rx = QtCore.QRegExp("^.*$", QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp)
                    # et filtrer pour tout retrouver!
                    self.proxy.setFilterRegExp(rx)
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-R récupére la liste de listes datas après modifications mais non triée
                elif event.key() == QtCore.Qt.Key_R and  (event.modifiers() & QtCore.Qt.AltModifier):
                    imax, jmax = self.tableview.model().rowCount(), self.tableview.model().columnCount()
                    data2 = []
                    for i in range(0, imax):
                        data2.append([])  # nouvelle ligne
                        for j in range(0, jmax):
     
                            # si on veut dans l'ordre des lignes
                            # item = self.model.item(i, j)
     
                            # si on veut dans l'ordre affiché trié
                            item = self.proxy.index(i, j)
     
                            elem = item.data(QtCore.Qt.DisplayRole)
                            data2[-1].append(elem)
                    print(data2)
                    event.accept()
                else:
                    event.ignore()
            else:
                event.ignore()
     
    #############################################################################
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
     
        # données à afficher. 1ère ligne => nom des colonnes
        datas = [['col1', 'col2', 'col3'], ["a", 2, "d"], ["e", 12, "g"], ["h", 6, "j"], ["X", 0, "Z"]]
     
        fen = Fenetre(datas)
        fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        fen.show()
        sys.exit(app.exec_())

  7. #7
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    re,

    Merci pour ton code qui m'a motivé pour changer de modèle.
    En suivant ton exemple, j'ai adopté QStandardItemModel qui est plus facile à utiliser et plus économe en code que QAbstractTableModel.
    J'en ai profité pour intégrer le filtrage dont j'avais besoin

    Mais le problème reste le même et j'ai peur que ton code souffre également du même soucis.
    Citation Envoyé par tyrtamos Voir le message
    Je n'ai pas testé ce code très longtemps: s'il y a des anomalies de fonctionnement: dis-le!
    Je t'invite, si tu le veux bien, à le modifier pour qu'il puisse supprimer toutes les lignes sélectionnées.
    Car dans l'état actuel, il n'en supprime qu'une.

    Plus je cherche plus je me dis que tu as bien fait de me parler de la stratégie OnSubmit.
    Mais dans mon vrai code, chaque obj hérite de ConfigParser et de QStandardItem. Au final ça pointe sur un fichier.
    Donc tu comprendras surement ma réticence à utiliser un modèle prévu pour des bases de données.

    Je vais donc continuer à chercher.

  8. #8
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 484
    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 484
    Points : 9 286
    Points
    9 286
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par kimi31 Voir le message
    Mais le problème reste le même et j'ai peur que ton code souffre également du même soucis.Je t'invite, si tu le veux bien, à le modifier pour qu'il puisse supprimer toutes les lignes sélectionnées.
    Car dans l'état actuel, il n'en supprime qu'une.
    Ce n'est un défaut, c'est bien ce que j'ai programmé. Je n'avais pas compris que tu voulais supprimer plusieurs lignes à la fois. Je regarde demain.

    Citation Envoyé par kimi31 Voir le message
    Plus je cherche plus je me dis que tu as bien fait de me parler de la stratégie OnSubmit.
    Mais dans mon vrai code, chaque obj est un fichier.
    Donc tu comprendras surement ma réticence à utiliser un modèle prévu pour des bases de données.
    Mais mon dernier code n'a plus rien à voir avec la base de données. Qu'est-ce que tu veux faire avec OnSubmit?

  9. #9
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    Sympa de te précoccuper de mon soucis.

    Si tu considères ce 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
    #!/bin/env python3
     
    from PyQt5.QtCore import Qt, QSortFilterProxyModel
    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTableView, QAbstractItemView, QPushButton
    from PyQt5.QtGui import QStandardItemModel, QStandardItem
     
    class Car(QStandardItem):
        def __init__(self, col0, col1, park):
            QStandardItem.__init__(self)
            self.col0, self.col1, self.park= col0, col1, park
     
    class ContentModel(QStandardItemModel):
        def __init__(self):
            QStandardItemModel.__init__(self, None)
        def data(self, index, role):
            if role == Qt.DisplayRole:
                return getattr(self.item(index.row()), "col%d" % index.column())
            return QStandardItemModel.data(self, index, role)
     
    class ContentProxy(QSortFilterProxyModel):
        def __init__(self):
            QSortFilterProxyModel.__init__(self, None)
        def lessThan(self, left, right):
            return left.data(self.sortRole()) < right.data(self.sortRole())
     
    class Content(QWidget):
        def __init__(self):
            QWidget.__init__(self)
            self.layout, self.tableView, self.model, self.proxy= QVBoxLayout(), QTableView(), ContentModel(), ContentProxy()
            self.model.setHorizontalHeaderLabels(["Modèle", "Type"])
            self.model.appendRow(Car("Clio   (10)", "Break", 10))
            self.model.appendRow(Car("Megane (20)", "Break", 20))
            self.model.appendRow(Car("Twingo (30)", "Break", 30))
            self.model.appendRow(Car("Kangoo (40)", "Cabriolet", 40))
            self.model.appendRow(Car("Scenic (50)", "Cabriolet", 50))
            self.proxy.setSourceModel(self.model)
            self.tableView.setModel(self.proxy)
            self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.tableView.setSortingEnabled(True)
            self.tableView.sortByColumn(0, Qt.AscendingOrder)
            self.tableView.verticalHeader().setVisible(False)
            self.layout.addWidget(self.tableView)
            self.layout.addWidget(QPushButton("Libère & Supprime", self, clicked= self.delete))
            self.setLayout(self.layout)
     
        def delete(self):
            for index in self.tableView.selectionModel().selectedRows():
                realRow= self.proxy.mapToSource(index).row()
                print(index.data(), "=", self.model.item(realRow).park)
    #            self.model.removeRow(realRow)
     
    if __name__ == "__main__":
        app, ui= QApplication([]), Content()
        ui.show()
        exit(app.exec_())
    Il fonctionne parfaitement. Quelques soit l'ordre du tri.
    En revanche, si je décommente la ligne 50, à la prochaine itération du for, ben c'est la cata.
    La première ligne supprimée est la bonne mais pas toujours les suivantes.

    Le modèle de sélection n'est pas informé de la suppression par le modèle. Et si je supprime via le proxy c'est pareil.

    Pour reproduire le problème il suffit de sélectionner Kangoo et Scenic et de cliquer sur le bouton.
    Suivant que la ligne est commentée ou pas, tu verra que le fonctionnement est différent.

    Précisions:
    J'ai en effet besoin de pouvoir supprimer plusieurs lignes.
    J'ai également besoin d'un "pointeur" vers l'objet. Ce qui est simulé par le print(self.model.item(realRow).park).
    Car en réalité, je dois appeler une méthode de l'objet concerné avant de valider la suppression de la ligne.
    J'ai vu que le proxy avait une méthode mapSelectionToSource() mais je ne sais pas encore comment l'utiliser.

    Citation Envoyé par tyrtamos Voir le message
    Mais mon dernier code n'a plus rien à voir avec la base de données. Qu'est-ce que tu veux faire avec OnSubmit?
    Ce que je voulais dire c'est que si le modèle utilisé possédait ce type de stratégie je n'aurais pas de problème. Je ferais un submitAll() en sortie de boucle.

  10. #10
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 484
    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 484
    Points : 9 286
    Points
    9 286
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Je crois avoir trouvé le code qui permet de supprimer correctement plusieurs lignes sélectionnées à la fois (y compris des lignes non contigües!).

    Voilà la fonction à ajouter et à appeler dans le keyPressEvent de mon code précédent:

    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
        def supprimeligne(self):
            """supprime la ligne sélectionnée entière
                 => une case sélectionnée ne suffit pas
               fonctionne avec ou sans tri et avec ou sans filtrage
            """
            # récupère la liste des QModelIndex des lignes sélectionnées
            listindex = self.tableview.selectionModel().selectedRows()
            # en extrait la liste des numéros de lignes sélectionnées
            # note: ce sont les numéros de lignes affichées y compris après tri
            # et non les numéros de lignes d'origine dans l'ordre d'insertion
            nlignes = [index.row() for index in listindex]
            # trie dans l'ordre inverse pour supprimer les grands numéros de ligne d'abord
            nlignes.sort(reverse=True)
            # et supprime les lignes en question une par une dans le bon ordre
            for nligne in nlignes:
                self.tableview.model().removeRow(nligne)
    Les commentaires expliquent comment on fait. Il y a 2 choses importantes:
    - pour que ça marche après tri (et éventuellement filtrage en + !), les numéros de lignes à trouver sont ceux des lignes réellement affichées, et non les numéros de lignes d'origine dans l'ordre d'insertion.
    - une fois ces numéros de lignes trouvés, il faut les supprimer dans l'ordre inverse: les + grands d'abord. Si on ne fait pas ça, à chaque suppression, les numéros de lignes suivantes ont changé!

    J'ai aussi travaillé sur le code d'insertion d'une ligne. Mais ça ne fonctionne correctement que sans tri ni filtrage. Ce n'est pas grave, parce que vouloir insérer une ligne à l'endroit du curseur n'a de sens que dans ce cas.

    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
        def insereligne(self, valeurs=None):
            """insere une nouvelle ligne  à l'endroit du curseur et décale les
               lignes suivantes. N'a de sens que sans tri ni filtrage
               => doit être désactivé en cas de tri et/ou filtrage
               valeurs: liste des valeurs à mettre dans la ligne vide
            """
            if self.tableview.model().sourceModel().rowCount() == 0:
                # insère la 1ère ligne d'un tableau vide
                nligne = 0
                self.tableview.model().insertRow(nligne)
            else:
                # insère une nouvelle ligne avant la ligne sélectionnée
                # calcule l'index de la ligne dans laquelle se trouve le curseur
                index = self.tableview.currentIndex()
                # calcule le numéro de ligne
                nligne = index.row()
                # insère la nouvelle ligne vide
                self.tableview.model().insertRow(nligne)
            # remplit la nouvelle ligne avec les valeurs si elles sont présentes
            if valeurs != None:
                for col, val in enumerate(valeurs):
                    index = self.tableview.model().sourceModel().index(nligne, col)
                    self.tableview.model().sourceModel().setData(index, val)
    Enfin, j'ai ajouté une fonction pour ajouter une ligne à la fin du tableau:

    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
        def ajouteligne(self, valeurs=None):
            """ajoute une nouvelle ligne en fin de tableau
               - si tri en cours, elle est affichée à la fin, mais pas triée
               - si filtrage en cours, elle est crée mais pas affichée
               valeurs: liste éventuelle des valeurs des colonnes à mettre
            """
            # nb total de lignes du tableau (affichées ou non!)
            nligne = self.tableview.model().sourceModel().rowCount()
            # ajoute une ligne vide en fin de tableau
            self.tableview.model().sourceModel().insertRow(nligne)
            # remplit la nouvelle ligne avec les valeurs si elles sont présentes
            if valeurs != None:
                for col, valeur in enumerate(valeurs):
                    index = self.tableview.model().sourceModel().index(nligne, col)
                    self.tableview.model().sourceModel().setData(index, valeur)
    Pour faire des essais, j'ai aussi un peu agrandi mon datas de test:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        datas = [['col1', 'col2', 'col3'],
                 ["a", 1, "d"], ["e", 2, "g"], ["h", 3, "j"], ["X", 4, "Z"],
                 ["M", 5, "h"], ["n", 6, "C"], ["g", 7, "p"], ["D", 8, "w"]]
    Toutes les autres fonctionnalités ont l'air de marcher: filtrage, annulation du filtrage, tri, annulation du tri, récupération des données affichées, etc...

    Ok?

  11. #11
    Futur Membre du Club
    Inscrit en
    Décembre 2010
    Messages
    21
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 21
    Points : 9
    Points
    9
    Par défaut
    Tout simplement excellent. Solution simple et radicale.

    Ton code fonctionne bien sauf si change l'ordre alors qu'on a pas encore renseigné les zones d'une ligne qu'on vient ajouter.
    Tu me diras que ce n'est pas logique mais tu sais que l'utilisateur ne l'est par forcément.

    Big thanks to you.

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

Discussions similaires

  1. Réponses: 0
    Dernier message: 31/05/2011, 20h13
  2. Comment ajouter une valeur absente aux entrées d'un TCD ?
    Par papadrago dans le forum Conception
    Réponses: 2
    Dernier message: 22/02/2011, 11h42
  3. Réponses: 3
    Dernier message: 03/11/2009, 08h19
  4. Réponses: 2
    Dernier message: 16/02/2009, 02h34
  5. Réponses: 1
    Dernier message: 19/06/2008, 11h32

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