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 :

Réglage ascenseur vertical d'un QTableView [QtGui]


Sujet :

PyQt Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut Réglage ascenseur vertical d'un QTableView
    Bonjour,

    J'ai une fenêtre qui affiche le contenu d'une table d'une base de données SQL avec un QTableView. Et ça marche bien!

    Sauf que, au lancement de la fenêtre, lorsque la table SQL à afficher est grande (plusieurs milliers de lignes), l'ascenseur vertical est réglé pour 255 lignes. Quand on descend le curseur de l'ascenseur, les 255 premières lignes s'affichent bien, et une fois arrivé tout en bas, le curseur remonte pour montrer les ...255 lignes suivantes. Etc... et on finit par voir toutes les lignes, 3000 lignes par exemple.

    Une fois cette "ajustement" fait, le curseur de l'ascenseur vertical adopte une bonne position relativement aux lignes affichées et au nombre total de lignes. En fait, tout se passe comme si le "branchement" du QTableView sur son "model" (avec setModel) ne mettait pas à jour le scrollbar vertical du nombre total de lignes.

    Ce n'est pas un problème grave, mais par rapport à la qualité des IHM qu'on fait avec PyQt4, je trouve ça moche...

    Ce problème ne se pose pas avec un QTableWidget.

    Quelqu'un aurait-il une idée pour que l'ascenseur vertical du QTableView sache dès le départ qu'il y a 3000 lignes à afficher issues du model, et pas seulement 255?

    Pour montrer le problème, voici un code autoporteur qui affiche une table SQL que ce code fabrique aussi lui-même (un simple copier-coller devrait suffire):

    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    from __future__ import division
    # Python 2.7
     
    import sys
    import sqlite3
    import random
    random.seed()
    from PyQt4 import QtCore, QtGui, QtSql
     
    #############################################################################
    def creabasesql(basesql):
        """créer une base sql composée de 3000 nombres entiers tirés au hasard"""
        cnx = sqlite3.connect(basesql)
        cur = cnx.cursor()
        cur.execute("""drop table if exists 'matable' """)
        cur.execute("""create table 'matable' (num integer)""")
        try:
            for i in xrange(1, 3001):
                cur.execute("""insert into 'matable' values(?)""", 
                                                    (random.randint(0, 100000),))
            cnx.commit()
        except sqlite3.Error, err:
            cnx.rollback()
            print "erreur: %s" % (unicode(err.args[0]),)
        cur.close()
        cnx.close()
     
    #############################################################################
    class Vuetable(QtGui.QWidget):
     
        def __init__(self, basesql, table, parent=None):
            super(Vuetable, self).__init__(parent)
     
            # stocker les arguments
            self.basesql = basesql
            self.table = table
     
            # instructions pour la fenêtre
            self.setWindowTitle(u"table: %s de la base: %s" % (self.table, 
                                                                    self.basesql))
            self.resize(800, 600)
     
            # ouvrir une connexion avec la base de données 
            self.cnx = QtSql.QSqlDatabase.addDatabase(u"QSQLITE")
            self.cnx.setDatabaseName(self.basesql)
            if not self.cnx.open():
                QtGui.QMessageBox.critical(self, 
                            u"Ouverture de la base de données",
                            "Erreur d'ouverture: %s" % self.cnx.lastError().text())
                self.cnx = None
                self.close() # fermeture de vuetable
     
            # créer et configurer le modèle
            self.model = QtSql.QSqlRelationalTableModel(self, self.cnx)
            self.model.setTable(self.table)
            self.model.setEditStrategy(QtSql.QSqlRelationalTableModel.\
                                                                   OnManualSubmit)
            self.model.select()
     
            # créer le QTableView d'affichage
            self.vue = QtGui.QTableView(self)
     
            # créer le lien entre la base sql et le QTableView grâce au modèle 
            self.vue.setModel(self.model)
            self.vue.setItemDelegate(QtSql.QSqlRelationalDelegate(self.vue))
     
            # tri ascendant du tableau affiché selon la 1ère colonne
            self.vue.setSortingEnabled(True)
            self.vue.sortByColumn(0, QtCore.Qt.AscendingOrder)
     
            # régler la largeur des colonnes en fonction de leur contenu
            self.vue.resizeColumnsToContents()
     
            # faire que le QTableView prenne toute la fenêtre et suive la redim.
            posit = QtGui.QGridLayout()
            posit.addWidget(self.vue, 0, 0)
            self.setLayout(posit)
     
     
    #############################################################################
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        basesql = u"basesql.db3"
        creabasesql(basesql)
        fen = Vuetable(basesql, u"matable")
        fen.show()
        sys.exit(app.exec_())

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    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 754
    Par défaut
    Salut,

    Pas testé mais QTableView hérite de méthodes de QAbstractScrollArea dont celle qui permet de récupérer la "verticalScrollBar" qui est un QScrollBar.

    Then QScrollBar a les méthodes setMinimum, setMaximum, setPageStep qui permettent de paramétrer la scrollbar en fonction du nombre de lignes.

    La doc recommande de préserver la relation suivante entre les 3 valeurs:
    The relationship between a document length, the range of values used in a scroll bar, and the page step is simple in many common situations. The scroll bar's range of values is determined by subtracting a chosen page step from some value representing the length of the document. In such cases, the following equation is useful: document length = maximum() - minimum() + pageStep()
    La question subsidiaire est "pourquoi ne s'ajuste-t-il pas automatiquement au nombre de rows"?

    Ma compréhension est qu'on affiche qu'un petit nombre de ligne de l'ensemble du jeu de données (car sinon on explose la mémoire). Ce qui est lu et chargé dans les items de la QTableView sera plus important mais de l'ordre de quelques centaines sur les milliers de lignes de la table.
    Si on veut que la scrollbar soit cohérente avec la taille du jeu de données, il va falloir lui donner explicitement le vrai nombre de lignes (et pas 100).

    Reste à gérer le rafraîchissement du contenu en fonction des actions sur les flèches et le slider de la scrollbar...
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut
    Bonsoir wiztricks,

    Merci pour les pistes!

    Effectivement, cela vient du fait que les données ne viennent en mémoire que dans un buffer. Aucune instruction de comptage de lignes comme rowCount du model ne donne le bon résultat mais seulement 256.

    Alors, j'ai trouvé une solution qui ne me rend pas très fier, mais qui a l'air de marcher sans inconvénient:

    self.vue étant le QTableView, on ajoute à la fin de l'__init__:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
            # calcul du nb de lignes
            m = self.vue.model()
            while m.canFetchMore():
                m.fetchMore()
            nblignes = m.rowCount()
     
            # mise à jour de l'ascenseur vertical du QTableView
            for i in xrange(0, nblignes, 256):
                self.vue.scrollToBottom()
            self.vue.scrollToTop()
    Avec ça, l'ascenseur est bien réglé dès l'affichage de la table, et ça ne semble pas provoquer d'attente visible.

    Mais je reste intéressé s'il y a une solution plus élégante!

    [edit] Je commence à comprendre pourquoi j'avais vu la même chose avec un programme similaire écrit en Delphi: c'est le même principe de bufferisation des données dans le model.

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

    Je complète ma solution précédente.

    En effet, si cette solution fonctionne bien au lancement de la fenêtre, chaque tri demandé ensuite en cliquant sur l'en-tête de l'une des colonnes, remet les données dans le buffer, et ça donne le même problème sur l'ascenseur vertical.

    J'ai trouvé une parade, qui consiste à intercepter le signal émis à chaque demande de tri, et à répéter ma solution après le tri.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
            self.hvue = self.vue.horizontalHeader()
            self.hvue.sectionClicked.connect(self.tricol)        
     
        def tricol(self, logindex):
            # mise à jour de l'ascenseur vertical
            mdl = self.vue.model()
            while mdl.canFetchMore():
                mdl.fetchMore()
            for i in xrange(0, mdl.rowCount(), 256): # nblignes=mdl.rowCount()
                self.vue.scrollToBottom()
            self.vue.scrollToTop()
    Toutes corrections faites, voilà le code initial complet avec les 2 corrections. Si vous avez Python 2.7 + PyQt4, un simple copier-coller devrait permettre l'exécution pour vérifier que ça marche:

    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    from __future__ import division
    # Python 2.7
     
    import sys
    import sqlite3
    import random
    random.seed()
    from PyQt4 import QtCore, QtGui, QtSql
     
    #############################################################################
    def creabasesql(basesql):
        """créer une base sql composée de 3000 nombres entiers tirés au hasard"""
        cnx = sqlite3.connect(basesql)
        cur = cnx.cursor()
        cur.execute("""drop table if exists 'matable' """)
        cur.execute("""create table 'matable' (num integer)""")
        try:
            for i in xrange(1, 3001):
                cur.execute("""insert into 'matable' values(?)""", 
                                                    (random.randint(0, 100000),))
            cnx.commit()
        except sqlite3.Error, err:
            cnx.rollback()
            print "erreur: %s" % (unicode(err.args[0]),)
        cur.close()
        cnx.close()
     
    #############################################################################
    class Vuetable(QtGui.QWidget):
     
        def __init__(self, basesql, table, parent=None):
            super(Vuetable, self).__init__(parent)
     
            # stocker les arguments
            self.basesql = basesql
            self.table = table
     
            # instructions pour la fenêtre
            self.setWindowTitle(u"table: %s de la base: %s" % (self.table, 
                                                                    self.basesql))
            self.resize(800, 600)
     
            # ouvrir une connexion avec la base de données 
            self.cnx = QtSql.QSqlDatabase.addDatabase(u"QSQLITE")
            self.cnx.setDatabaseName(self.basesql)
            if not self.cnx.open():
                QtGui.QMessageBox.critical(self, 
                            u"Ouverture de la base de données",
                            "Erreur d'ouverture: %s" % self.cnx.lastError().text())
                self.cnx = None
                self.close() # fermeture de vuetable
     
            # créer et configurer le modèle
            self.model = QtSql.QSqlRelationalTableModel(self, self.cnx)
            self.model.setTable(self.table)
            self.model.setEditStrategy(QtSql.QSqlRelationalTableModel.\
                                                                   OnManualSubmit)
            self.model.select()
     
            # créer le QTableView d'affichage
            self.vue = QtGui.QTableView(self)
     
            # créer le lien entre la base sql et le QTableView grâce au modèle 
            self.vue.setModel(self.model)
            self.vue.setItemDelegate(QtSql.QSqlRelationalDelegate(self.vue))
     
            # tri ascendant du tableau affiché selon la 1ère colonne
            self.vue.setSortingEnabled(True)
            self.vue.sortByColumn(0, QtCore.Qt.AscendingOrder)
     
            # régler la largeur des colonnes en fonction de leur contenu
            self.vue.resizeColumnsToContents()
     
            # faire que le QTableView prenne toute la fenêtre et suive la redim.
            posit = QtGui.QGridLayout()
            posit.addWidget(self.vue, 0, 0)
            self.setLayout(posit)
     
            # mise à jour de l'ascenseur vertical
            mdl = self.vue.model()
            while mdl.canFetchMore():
                mdl.fetchMore()
            for i in xrange(0, mdl.rowCount(), 256): # nblignes=mdl.rowCount()
                self.vue.scrollToBottom()
            self.vue.scrollToTop()
     
            # lien entre la dde de tri d'une col. et l'appel de la méthode tricol
            self.hvue = self.vue.horizontalHeader()
            self.hvue.sectionClicked.connect(self.tricol)        
     
        def tricol(self, logindex):
            """"mise à jour de l'ascenseur vertical après le tri d'une colonne"""
            mdl = self.vue.model()
            while mdl.canFetchMore():
                mdl.fetchMore()
            for i in xrange(0, mdl.rowCount(), 256): # nblignes=mdl.rowCount()
                self.vue.scrollToBottom()
            self.vue.scrollToTop()
     
    #############################################################################
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        basesql = u"basesql.db3"
        creabasesql(basesql)
        fen = Vuetable(basesql, u"matable")
        fen.show()
        sys.exit(app.exec_())
    Je pars là dessus, mais je suis toujours intéressé par une solution plus élégante!

    [edit] il faut faire la même mise à jour de l'ascenseur en cas de filtrage des données appliqué sur le "model": ça marche de la même façon!

  5. #5
    Membre expérimenté Avatar de ashren
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    101
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 101
    Par défaut
    Bonjour,

    je ne sais pas si c'est en rapport avec ton problème initial, mais je pencherai plus vers une approche de 'pages'. Genre 1-300 puis 301-600, etc ...

    Parce-que avec 3000 lignes ce n'est pas très visible (utilisation mémoire, cpu, taille de la scrollbar), mais imagine 300 000 lignes, ce serait un enfer pour scroll à la main avec précision (exemple avec certaines applications pour smartphones où il faut scroller 30s pour arriver là où on veut).

    PS: une solution sinon serait de sous-classer le modèle pour implémenter un signal 'rowCountChanged' par exemple que tu connecterais à une méthode métant à jour ta scrollbar.

    En espérant avoir été clair dans mes propos.

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

    Merci pour les idées!

    Heureusement, mon problème est plus simple que ça: le nombre d'article ne devrait pas dépasser 5000, et la base de données totale, malgré sa complexité interne (12 tables et de nombreuses contraintes), ne dépasse pas 5 Mo, ce qui est tout petit par rapport aux mémoires actuelles.

    Avec un nombre de ligne<5000, le déplacement du curseur de l'ascenseur vertical a une précision suffisante. Et si je fais une recherche plus complexe, je passe un filtre (setFilter) au modèle pour réduire le nombre de lignes.

    En fait, il me faudrait plutôt une simple instruction qui dise au "model" de tout charger en mémoire sans bufferiser!

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

Discussions similaires

  1. Div et ascenseur vertical
    Par shaun_the_sheep dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 29/05/2008, 15h13
  2. L'ascenseur vertical manque
    Par francoisch dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 19/02/2008, 11h36
  3. Réglage balayage vertical sous DOS
    Par startoof dans le forum Scripts/Batch
    Réponses: 1
    Dernier message: 02/02/2008, 19h48
  4. Calculer la largeur d'un ascenseur vertical
    Par PetitPapaNoël dans le forum MFC
    Réponses: 5
    Dernier message: 24/07/2007, 17h23
  5. [WD11] Positionner l'ascenseur vertical en bas
    Par fabpeden dans le forum WinDev
    Réponses: 1
    Dernier message: 13/04/2007, 16h19

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