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 :

QTableWidget reproduisant des fonctionnalités Excel


Sujet :

PyQt Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    DAF
    Inscrit en
    Août 2020
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : DAF
    Secteur : Distribution

    Informations forums :
    Inscription : Août 2020
    Messages : 12
    Par défaut QTableWidget reproduisant des fonctionnalités Excel
    Bonjour à tous,

    Avant tout je précise que je débute avec PyQt. Je publie cette demande d'aide car je tourne en rond et je n'arrive pas à trouver de solution malgré mes recherches sur le net.

    J'ai créé un QTableWidget. Celui-ci est alimenté par des données stockées dans une pandas Dataframe. Dans la colonne 7 de ce tableau j'ai implémenté la possibilité de saisir des formules de calcul comme dans Excel (formules simples qui sont résolues grâce à la fonction eval() de python).
    Ces formules sont gardées en mémoire dans un autre pandas Dataframe.

    Jusque là ça fonctionne bien. Maintenant, là où je bloque, c'est quand je cherche à reproduire 3 fonctionnalités que l'on trouve dans Excel.
    1/ Lorsque je double-clique ou que j'appuie sur F2 la formule qui a été saisie auparavant apparait à l'écran en mode édition tant qu'on n'a pas validé.
    2/ Lors de la saisie de la formule, lorsque l'on clique sur une cellule, ses coordonnées apparaissent en mode édition, puis le contenu est utilisé au moment de la validation pour faire le calcul
    3/ Le cliquer-tirer qui permet d'étirer une formule vers une cellule adjacente.

    J'ai pensé au début que les delegate pourraient m'aider mais j'avoue que je bloque. Finalement je ne sais même pas si ce que je cherche à faire est réalisable avec PyQt car je n'ai rien trouvé qui corresponde à ce que je cherche dans tout ce que j'ai pu lire jusqu'à présent. Si quelqu'un a une idée ou au moins une piste, je suis preneur.

    Voici le code que j'ai écrit:
    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
    class TableFAR(Qt.QTableWidget):
    
        def __init__(self, data, my_date):
            Qt.QTableWidget.__init__(self)
    
            if data is None:
                self.data = pd.DataFrame()
            else:
                self.data = data
     
            self.formula = ""
            self.copy = ""
            self.df_formulas = pd.DataFrame(columns=("row", "col", "formula"))
    
            # Dimensionnement du tableau
            n_rows, n_columns = self.data.shape
            self.setColumnCount(n_columns)
            self.setRowCount(n_rows)
    
            # Remplissage du tableau
            for i in range(self.rowCount()):
                for j in range(self.columnCount()):
                    self.setItem(i, j, Qt.QTableWidgetItem(str(self.data.iloc[i, j])))
    
            self.cellChanged[int, int].connect(self.update_df)
    
            self.itemChanged.connect(self.on_item_changed)
            self.itemDoubleClicked.connect(self.on_item_doubleclicked)
        
        def update_df(self, row, column):
            text = self.item(row, column).text()
            self.data.iloc[row, column] = text
    
        def on_item_changed(self, item):
            list_signs = ["=", "+", "-", "*", "/"]
            row = item.row()
            col = item.column()
            if item.text() == "":
                pass
            elif col == 7 and item.text()[1:] != "" and item.text()[0] in list_signs:
                if item.text()[0] == "=":
                    self.formula = item.text()[1:]
                else:
                    self.formula = item.text()
                if "%" in self.formula:
                    self.formula = self.formula.replace("%", "/100")
                self.save_formula(row, col, self.formula)
                self.calculate(row, col, self.formula)
    
        def calculate(self, row, col, item):
            """ Test si la formule est calculable par la fonction eval.
            Signaux bloqués pour éviter les boucles infinies lors de l'écriture d'un résultat négatif par setitem
            Réactivation des signaux à la fin de l'opération."""
            try:
                self.blockSignals(True)
                calc = round(eval_calc(item), 2) <- Fonction eval_calc définie à la fin du code
                res = Qt.QTableWidgetItem(str(calc))
                self.setItem(row, 7, res)
            except TypeError or SyntaxError:
                print("La cellule ne contient pas une formule de calcul valide.")
            except ZeroDivisionError:
                print("Depuis quand est-ce qu'on peut diviser par 0???.")
    
            self.blockSignals(False)
    
        def save_formula(self, row, col, formula):
            """Sauvegarde dans un Pandas Dataframe des formules saisies en colonne 7 du Qtablewidget"""
            df_formula = pd.DataFrame({"row": [row], "col": [col], "formula": [formula]})
            if self.df_formulas.empty:
                self.df_formulas = self.df_formulas.append(df_formula)
            else:
                if not self.df_formulas[self.df_formulas["row"] == row].empty:
                    idx = self.df_formulas[self.df_formulas["row"] == row].index
                    if self.df_formulas.at[idx[0], "col"] == 7:
                        self.df_formulas.at[idx[0], "formula"] = formula
                else:
                    self.df_formulas = self.df_formulas.append(df_formula)
                self.df_formulas.reset_index(inplace=True)
    
        def find_if_formula(self, row, col):
            """Vérifie si trouve une formule enregistrée dans le self.df_formulas"""
            if self.df_formulas.empty or col != 7 or self.df_formulas[self.df_formulas["row"] == row].empty:
                pass
            else:
                idx = self.df_formulas[self.df_formulas["row"] == row].index
                return self.df_formulas.at[idx[0], "formula"]
    
        def on_item_doubleclicked(self, item): <- c'est là que se situe mon 1er problème
            """Affichage de la formule en mémoire si double-clique sur cellule de la colonne 7"""
            row = item.row()
            col = item.column()
            if self.df_formulas.empty or col !=7 or self.df_formulas[self.df_formulas["row"] == row].empty:
                pass
            else:
                formula = self.find_if_formula(row, col)
                return formula
    
    def eval_calc(formula):
        accepted = re.compile("[\s0-9()+*/-]+")
        if accepted.match(formula):
            return eval(formula)
    En vous remerciant par avance.

  2. #2
    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,

    Pour un débutant en PyQt5, il s'agit déjà d'un projet bien compliqué... Je ne sais pas si on peut "copier" exactement Excel, mais on doit pouvoir reproduire ses fonctionnalités, même si ce n'est pas de façon identique.

    Voilà quelques idées si j'avais à entreprendre un tel projet.

    - il me semble qu'il faille définir un widget composite qui représente pour chaque case:
    => la donnée de la case à afficher (un QLineEdit)
    => le type de donnée (puisque ce qui est dans le QLineEdit est toujours un str)
    => le format d'affichage
    => la formule de calcul
    => d'autres?
    Et créer les méthodes pour accéder et manipuler ces données (un des principes de la POO)

    - il faut créer une classe qui hérite d'un delegate, par exemple un QStyledItemDelegate. Celui-ci comporte plusieurs méthodes importantes:
    => createEditor qui permet de construire la case comme on le veut. Ici, il faudrait simplement installer le widget composite avec son QLineEdit d'affichage, et préciser son affichage hors du mode édition.
    => setEditorData qui permet de définir ce qui se passe quand on double-clique pour accéder au mode d'édition pour modifier la donnée.
    => setModelData qui permet de définir ce qui se passe à la sortie du mode édition. Ici, probablement le déclenchement des calculs.
    => paint qui permet quand c'est utile de mieux définir la présentation de l'affichage.

    Reste à définir comment on accède à l'édition de la formule puisque le double-clic est déjà pris pour accéder au mode édition de la donnée. Peut-être un QLineEdit hors QTableWidget qui reçoit automatiquement la formule de la case courante? ou au clavier (type Alt-C)? on par un menu contextuel?. A voir.

    Pour bien comprendre comment on développe un bon delegate pour un QTableWidget, je suggère de télécharger les exemples de riverbank: ces exemple se trouvent avec les sources de PyQt5:
    https://riverbankcomputing.com/software/pyqt/download

    Il y a en particulier plusieurs codes du répertoire "itemviews" qui parlent du sujet, par exemple "stardelegate.py". Il faudrait commenter par écrire un code test simple, par exemple l'implantation d'un QLineEdit ou d'un QSpinBox dans chaque case.

    Bref, un beau projet potentiellement faisable, mais plein d'embuches. Courage!

  3. #3
    Membre averti
    Homme Profil pro
    DAF
    Inscrit en
    Août 2020
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : DAF
    Secteur : Distribution

    Informations forums :
    Inscription : Août 2020
    Messages : 12
    Par défaut
    Bonjour et merci beaucoup pour les pistes tyrtamos.

    Pour donner un peu plus de détail, en fait dans mon Qtablewidget j'ai une colonne Montant HT qui est alimentée par la base de donnée que je charge dedans.
    J'ai besoin également d'avoir un montant TTC (variable en fonction du taux de TVA, d'où l'idée d'implémenter la possibilité de saisir des formules), donc pour faciliter la saisie par le futur utilisateur et la rendre plus rapide je voulais m'inspirer des fonctionnalités Excel dont je parlais.
    C'est vrai que je n'avais pas imaginé que ce serait si complexe à mettre en place.

    Je vais me pencher sur tes pistes pour voir si j'arrive à m'en sortir, mais effectivement c'est parti pour du triturage de méninge .

    En tout cas encore merci du coup de main.

  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
    Ma réponse convergeait vers un tableur type Excel capable de traiter n'importe quelle application de type tableur. Mais si tu restreins ton projet à une application précise, ça peut être plus simple!

Discussions similaires

  1. Réponses: 1
    Dernier message: 24/07/2005, 22h25
  2. Comment remplir un ComboBox avec le nom des feuilles Excel ?
    Par libracom dans le forum API, COM et SDKs
    Réponses: 2
    Dernier message: 27/06/2005, 15h14
  3. Insérer des données Excel dans une base Access ?
    Par MaTHieU_ dans le forum Access
    Réponses: 3
    Dernier message: 22/06/2005, 15h11
  4. Extraction de données sur des fichiers excel
    Par iupgeii dans le forum MFC
    Réponses: 3
    Dernier message: 23/01/2004, 13h53
  5. Récupérer des données Excel vers Interbase ...
    Par Djedjeridoo dans le forum InterBase
    Réponses: 2
    Dernier message: 20/07/2003, 18h16

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