Téléchargé 9 fois
Vote des utilisateurs
2
0
Détails
Licence : LGPL
Mise en ligne le 30 mars 2024
Plate-formes :
Linux, Mac, Windows
Langue : Français
Référencé dans
Navigation
Afficher le contenu d'un dossier
Afficher le contenu d'un dossier
Pour faire suite au reader CSV, ce programme affiche le contenu d'un dossier.
On lui donne un dossier et il affiche dans une zone de droite tous les fichiers du dossier. Et si on sélectionne un fichier, il affiche son contenu dans une zone de gauche.
On pourra y trouver une évolution intéressante apporté par papajoker et basée sur une délégation de style qui permet à tout fichier non lisible pour une raison ou une autre (problème de droit par exemple) d'être marqué en rouge dans la zone de droite (zone de listing). Et si on y revient alors qu'il est redevenu lisible (problème résolu) il est réaffiché en noir.
Cet exemple est disponible dans les versions PyQt5, PyQt6 et PySide6.
On lui donne un dossier et il affiche dans une zone de droite tous les fichiers du dossier. Et si on sélectionne un fichier, il affiche son contenu dans une zone de gauche.
On pourra y trouver une évolution intéressante apporté par papajoker et basée sur une délégation de style qui permet à tout fichier non lisible pour une raison ou une autre (problème de droit par exemple) d'être marqué en rouge dans la zone de droite (zone de listing). Et si on y revient alors qu'il est redevenu lisible (problème résolu) il est réaffiché en noir.
Cet exemple est disponible dans les versions PyQt5, PyQt6 et PySide6.
bonjour
Je n'aime pas l'utilisation systématique d'un attribut self.__ihm : code pas classique et énorme dépendance entre chaque objet :
Par exemple, pour moi, il est beaucoup plus intéressant d'avoir un widget (type myWork) réutilisable tel quel dans tous mes projets. Il suffit juste de supprimer cette dépendance "dure".
On utilise un code QT plus classique : c'est au "maitre" d'écouter le widget (si il le désire).
Pour myWork.__titre, Perso, je ne l'insère pas dans ce widget et émets aussi un signal (intercepté par myMainWindow qui a ça propre zone d'affichage), par conséquent, ce widget est encore plus réutilisable/souple.
Je n'aime pas l'utilisation systématique d'un attribut self.__ihm : code pas classique et énorme dépendance entre chaque objet :
Par exemple, pour moi, il est beaucoup plus intéressant d'avoir un widget (type myWork) réutilisable tel quel dans tous mes projets. Il suffit juste de supprimer cette dépendance "dure".
On utilise un code QT plus classique : c'est au "maitre" d'écouter le widget (si il le désire).
Code : | Sélectionner tout |
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 | class myMainWindow(QMainWindow): def __init__(self, ihm, *args, **kwargs): # La zone qui va afficher le fichier self.__affich=myWork(parent=self.centralWidget()) self.__affich.dossierChanged.connect(self.update_status_bar) # self.__affich.fichierChanged.connect(... def update_status_bar(self, text): self.statusBar().showMessage(text) class myWork(QWidget): dossierChanged = pyqtSignal(object) fichierChanged = pyqtSignal(object) def __init__(self, *args, **kwargs): """" on ne passe plus le dico ihm """ def getDir(self, dossier): ... if not dossier.is_dir(): self.dossierChanged.emit(f"{dossier} n'est pas un dossier") #self.__ihm["mainWid"].statusBar().showMessage("{0} n'est pas dossier".format(dossier)) ... self.dossierChanged.emit(f"Dossier: {dossier}") def __work(self, fic): ... self.fichierChanged.emit(f"Fichier: {fic}") |
Hello,
Merci pour le partage,
Tout fonctionne jusqu'au moment où je souhaite sélectionner un nouveau dossier,
Il faudrait aussi préciser que le module chardet doit être installé avant utilisation.
EDIT : J'utilise la version PyQt6
Merci pour le partage,
Tout fonctionne jusqu'au moment où je souhaite sélectionner un nouveau dossier,
Code : | Sélectionner tout |
1 2 3 | Traceback (most recent call last): File "/home/fred1599/.../.../test.py", line 296, in __slotLoadFile fic=current.data(Qt.ItemDataRole.UserRole) AttributeError: 'NoneType' object has no attribute 'data' |
EDIT : J'utilise la version PyQt6
Afficher en rouge les fichiers binaires
Il suffit de passer un paramètre au datas dans le widget : remplacer fichier par (fichier, status).
Ensuite, on utilise un QStyledItemDelegate que l'on attache à notre widget (valable pour tout type de widget) qui lui va interpréter ce nouveau paramètre.
La classe optionnelle qui va gérer une couleur de notre widget
Note: fichier original avec des tabulations et non espaces, résultat : pas top comme affichage dans ce forum
Il suffit de passer un paramètre au datas dans le widget : remplacer fichier par (fichier, status).
Ensuite, on utilise un QStyledItemDelegate que l'on attache à notre widget (valable pour tout type de widget) qui lui va interpréter ce nouveau paramètre.
Code : | Sélectionner tout |
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 | class myWork(QWidget): # Chargement dossier def getDir(self, dossier): ... item=QListWidgetItem(str(p), parent=self.__listFile) item.setData(Qt.ItemDataRole.UserRole, (p, 1)) # on passe un (des) parametre supplémentaire self.__listFile.addItem(item) # si on ne désire pas attendre le clic utilisateur (plus logique?) # on peut déjà passer un paramètre en fonction de ... (type fichier, dossier ou non, erreur, ....) # Slot demandant le chargement d'un fichier @pyqtSlot(QListWidgetItem, QListWidgetItem) def __slotLoadFile(self, current, prev): fic, status =current.data(Qt.ItemDataRole.UserRole) # changement, ici on récupère un tuple ... except (PermissionError, UnicodeDecodeError) as e: current.setData(Qt.ItemDataRole.UserRole, (fic, -1)) # On change l'état : pour démo : -1 == erreur # Constructeur def __init__(self, *args, **kwargs): # La zone d'affichage des fichiers du dossier self.__listFile=QListWidget(parent=self) try: self.__listFile.setItemDelegate(FileDelegate(self.__listFile)) # on delège une partie de l'affichage except NameError: pass # indépendance !!! c'est une option si on réutilise ce widget |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | class FileDelegate(QStyledItemDelegate): ERRORVALUE = QColor(240, 80, 60, 220) # va être calculée qu'une fois def initStyleOption(self, option, index): if not index.isValid(): return None super().initStyleOption(option, index) file, status = index.data(Qt.ItemDataRole.UserRole) if status < 0: option.palette.setBrush(QPalette.ColorRole.Text, self.ERRORVALUE) # QPalette.ColorRole.Text ce n'est que pour qt6 ? # if file.isdir(): ... ATTENTION appel au "délégé" fait a chaque affichage, donc pas bon |
Envoyé par Sve@r
lien PyPi chardet
Par contre je ne vois pas où tu l'utilises...
Envoyé par Sve@r
J'ai un peu plus de temps pour visionner ton code, quelques observations (ce qui est dit par @papajoker est raccord avec ce que je pense aussi).
Envoyé par Sve@r
Code : | Sélectionner tout |
self.__ihm=dict(ihm)
Dans ton code j'ai trouvé difficile de faire le lien entre le nom de tes méthodes et le nom de chaque classe, il y a de l'anglais et du français, perso, la syntaxe devrait toujours être écrite en anglais et évidemment le texte se décide par le développeur selon le besoin de l'application (français ou international).
Je pense que tu as voulu raccourcir le code au maximum, je peux me tromper... (n'hésite pas à le dire) mais en ce qui me concerne, il serait bien plus long, car j'aurai préféré plus séparé les responsabilités et leurs actions. C'est un choix technique qui ne regarde que moi . Il n'y aurait pas qu'un seul module et ce nombre de classes.
Bonjour,
Par curiosité j'ai voulu voir le code, mais bon lorsque j'essaie de télécharger :
. Je suis pourtant bien connecté et avec noscript mis en veilleuse
Comme c'était juste pour voir je ne vais pas contacter un administrateur...
Ce message juste pour te remercier, car tu m'inspires l'envie de faire de même avec kivy.
Par curiosité j'ai voulu voir le code, mais bon lorsque j'essaie de télécharger :
Vous devez vous connecter pour accéder à cette partie du site
Comme c'était juste pour voir je ne vais pas contacter un administrateur...
Ce message juste pour te remercier, car tu m'inspires l'envie de faire de même avec kivy.
Ce sont 2 sites différents developpez.net et developpez.com, il suffit de se re-connecter avec les mêmes identifiants.
---------------------
Note :
Existe déjà le modèle QT QFileSystemModel qui permet l'affichage d'un répertoire dans un treeview, listview ou tableview.
Exemple (prévisualise fichiers texte et certaines images):
Pour changer de répertoire : clic droit
pySyde/pyQt sont légèrement différents !
Par exemple, PyQt6.QtGui.QFileSystemModel, PySide6.QtWidgets.QFileSystemModel
---------------------
Note :
Existe déjà le modèle QT QFileSystemModel qui permet l'affichage d'un répertoire dans un treeview, listview ou tableview.
Exemple (prévisualise fichiers texte et certaines images):
Code : | Sélectionner tout |
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 | #!/usr/bin/env python from pathlib import Path import sys try: from PySide6 import QtCore, QtGui, QtWidgets except ImportError: print("ERREUR: installez pyside 6 !") exit(13) class WinMain(QtWidgets.QMainWindow): """ Application simple """ def __init__(self, directory): super(WinMain, self).__init__() self.model = QtWidgets.QFileSystemModel() self.model.setFilter(QtCore.QDir.Files | QtCore.QDir.NoDotAndDotDot) self.resize(600, 520) if layout := QtWidgets.QVBoxLayout(): # décalage juste pour mon visuel splitter = QtWidgets.QSplitter() tmp = QtWidgets.QHBoxLayout(splitter) self.content = QtWidgets.QLabel() self.content.setText("Voir image") self.content.setGeometry(QtCore.QRect(0, 0, 300, 300)) tmp.addWidget(self.content) self.content.setMaximumWidth(600) self.list = QtWidgets.QListView(splitter) self.list.setModel(self.model) self.list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.list.customContextMenuRequested.connect(self.onContextMenu) self.list.doubleClicked.connect(self.afficher) layout.addWidget(splitter) widget = QtWidgets.QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.statusBar() self.directory = directory @property def directory(self): return self._directory @directory.setter def directory(self, value): """on change de répertoire""" self._directory = value self.content.clear() self.content.setText("Voir image/fichier texte") self.content.setWordWrap(True) self.model.setRootPath(self.directory) self.list.setRootIndex(self.model.index(self.directory)) self.setWindowTitle(self.directory) self.statusBar().showMessage(self.directory) print("root dir:", self.model.rootDirectory()) def changeDir(self): choix=QtWidgets.QFileDialog.getExistingDirectory( self, "Choisissez votre dossier", self.directory, QtWidgets.QFileDialog.Option.ShowDirsOnly | QtWidgets.QFileDialog.Option.ReadOnly, ) if choix: self.directory = choix def afficher(self, item): #file_ = self.model.filePath(item) # même chose que ligne suivante file_ =item.data(QtWidgets.QFileSystemModel.Roles.FilePathRole) print(file_) pix = QtGui.QPixmap(file_) # ("png", "jpeg", "jpg", "svg", "bmp", "gif", 'ico', "pdf") if not pix.isNull(): self.content.setPixmap(pix) sizem = min(self.content.width(), self.content.height()) size = QtCore.QSize(sizem, sizem) myScaledPixmap = pix.scaled(size, QtCore.Qt.KeepAspectRatio) self.content.setPixmap(myScaledPixmap) else: try: # que appercu car pas de scrolling self.content.setText(Path(file_).read_text()[0:412]) except UnicodeDecodeError: self.content.setText("fichier incompatible •`_´•") self.statusBar().showMessage("") return self.statusBar().showMessage(file_) def onContextMenu(self, point): menu = QtWidgets.QMenu() notUsed = menu.addAction('-') notUsed.setEnabled(False) menu.addSeparator() aInfo = menu.addAction("Changer de dossier...") aRefresh = menu.addAction("Actualiser") action = menu.exec(self.list.mapToGlobal(point)) if action == aInfo: self.changeDir() if action == aRefresh: self.directory = self.directory def run(directory): app = QtWidgets.QApplication(sys.argv) ico = QtGui.QIcon.fromTheme("folder") app.setWindowIcon(ico) trans = QtCore.QTranslator() trans.load('qt_fr', QtCore.QLibraryInfo.path(QtCore.QLibraryInfo.LibraryPath.TranslationsPath)) QtCore.QCoreApplication.installTranslator(trans) # pour avoir les dialogues en fr win = WinMain(directory) win.show() sys.exit(app.exec()) if __name__ == "__main__": run(QtCore.QDir.currentPath()) |
pySyde/pyQt sont légèrement différents !
Par exemple, PyQt6.QtGui.QFileSystemModel, PySide6.QtWidgets.QFileSystemModel
Super extra, j'ai de quoi m'amuser durant quelques jours.
Maintenant quelques réponses plus précises...
Zut je n'arrive pas à retrouver l'idée qui m'a fait penser que c'était installé d'office. Ceci dit effectivement il n'est pas utile. J'avais un moment pensé à afficher l'encoding du fichier traité (comme je fais pour le readerCSV) et là il aurait servi puis ai abandonné l'idée et ai oublié de l'enlever (history code)...
En fait si la copie est nécessaire (enfin dans ma tête) car la fenêtre qui récupère ce dictionnaire modifie certaines clefs ("self" et "parent" notamment). Or il ne faut pas modifier le dico de l'appelant.
C'était le truc que j'avais trouvé à mes débuts pour que les widgets Qt puissent connaître leur hiérarchie (chaque widget connait ainsi son parent). Mais suite à vos très sympatiques remarques et conseils j'ai revu le concept (et ai corrigé).
Là je pense que tu préfèrerais que je remplace from PyQt5.QtWidgets import * par from PyQt5.QtWidgets import QAppli, QMainWindow, QWidget, QMenuBar, QListWidget, QListWidgetItem, etc. c'est ça ?
Ca doit être énormément de boulot à faire et à maintenir (regarde ce pauvre chardet que j'ai même pas pensé à enlever). Est-ce vraiment nécessaire ? Par exemple pour les autres import tu le fais aussi ???
Oui je suis d'accord. L'anglais (ex "open" est plus pratique que le français (ex "ouvre", "ouvrir" ???) mais bon il y a les bonnes résolutions et la motivation de s'y tenir...
Oui là aussi je suis d'accord. Dans un vrai projet chaque objet a son propre source. Mais là je n'avais pas envie de demander à télécharger un zip donc j'ai tout mis dans un seul code unique un peu long mais qui se télécharge en une fois. A regarder comme un "proof of concept" ou "comment utiliser telle ou telle techno" (à l'image de mes autres exemples plus simples).
Maintenant quelques réponses plus précises...
Zut je n'arrive pas à retrouver l'idée qui m'a fait penser que c'était installé d'office. Ceci dit effectivement il n'est pas utile. J'avais un moment pensé à afficher l'encoding du fichier traité (comme je fais pour le readerCSV) et là il aurait servi puis ai abandonné l'idée et ai oublié de l'enlever (history code)...
En fait si la copie est nécessaire (enfin dans ma tête) car la fenêtre qui récupère ce dictionnaire modifie certaines clefs ("self" et "parent" notamment). Or il ne faut pas modifier le dico de l'appelant.
C'était le truc que j'avais trouvé à mes débuts pour que les widgets Qt puissent connaître leur hiérarchie (chaque widget connait ainsi son parent). Mais suite à vos très sympatiques remarques et conseils j'ai revu le concept (et ai corrigé).
Là je pense que tu préfèrerais que je remplace from PyQt5.QtWidgets import * par from PyQt5.QtWidgets import QAppli, QMainWindow, QWidget, QMenuBar, QListWidget, QListWidgetItem, etc. c'est ça ?
Ca doit être énormément de boulot à faire et à maintenir (regarde ce pauvre chardet que j'ai même pas pensé à enlever). Est-ce vraiment nécessaire ? Par exemple pour les autres import tu le fais aussi ???
Oui je suis d'accord. L'anglais (ex "open" est plus pratique que le français (ex "ouvre", "ouvrir" ???) mais bon il y a les bonnes résolutions et la motivation de s'y tenir...
Oui là aussi je suis d'accord. Dans un vrai projet chaque objet a son propre source. Mais là je n'avais pas envie de demander à télécharger un zip donc j'ai tout mis dans un seul code unique un peu long mais qui se télécharge en une fois. A regarder comme un "proof of concept" ou "comment utiliser telle ou telle techno" (à l'image de mes autres exemples plus simples).
Bonjour Sve@r
Curieusement, quand j'ai vu le titre de ton post, j'ai pensé que tu utilisais un QTreeView avec le modèle QFileSystemModel, et j'ai été surpris que ce ne soit pas le cas.
C'est pourtant ce qu'il y a de plus pratique. Voilà un petit code de principe en PyQt5:
La navigation est facile, et chaque clic sur une ligne, lance la méthode "clicligne" (j'ai été très inspiré pour son nom... ), qui se contente d'afficher le nom cliqué en bas de fenêtre avec son chemin. Mais cette méthode peut faire n'importe quoi d'autre, y compris afficher le contenu du fichier cliqué dans une autre fenêtre avec un QTextEdit si c'est un fichier texte, ou même lancer VLC si c'est une vidéo.
Curieusement, quand j'ai vu le titre de ton post, j'ai pensé que tu utilisais un QTreeView avec le modèle QFileSystemModel, et j'ai été surpris que ce ne soit pas le cas.
C'est pourtant ce qu'il y a de plus pratique. Voilà un petit code de principe en PyQt5:
Code : | Sélectionner tout |
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 | # -*- coding: utf-8 -*- import sys import os from PyQt5.QtCore import QLocale, QTranslator, QLibraryInfo from PyQt5.QtGui import QFont#, QIcon from PyQt5.QtWidgets import (QApplication, QFileSystemModel, QTreeView, QFrame, QGridLayout, QMainWindow, QStyleFactory) ############################################################################## class NavigFichiers(QMainWindow): #========================================================================= def __init__(self, repertoire, parent=None): super().__init__(parent) self.resize(800, 600) self.setWindowTitle("Navigateur de fichiers") # crée le modèle model = QFileSystemModel() model.setRootPath(repertoire) #Crée le QTreeView et intégre le modèle self.view = QTreeView() self.view.setModel(model) self.view.setRootIndex(model.index(repertoire)) # Police de caractères à utiliser font = QFont() font.setStyleHint(QFont.Monospace) self.view.setFont(font) # largeur de la colonne 0 self.view.setColumnWidth(0, 350) # place le QTreeView dans la fenêtre self.setCentralWidget(QFrame()) layout = QGridLayout() layout.addWidget(self.view, 0, 0) self.centralWidget().setLayout(layout) # Etablit le lien entre signal et méthode self.view.clicked.connect(self.clicligne) # initialise la ligne de status pour les messages self.statusbar = self.statusBar() self.statusbar.showMessage("") #========================================================================= def clicligne(self, qindex): """affiche en bas de page le chemin cliqué """ chemin = self.view.model().filePath(qindex) if os.path.isdir(chemin): message = "Répertoire: " + chemin else: message = "Fichier: " + chemin self.statusbar.showMessage(message) ############################################################################## if __name__ == '__main__': #======================================================================== # Répertoire d'exécution avec ou sans pyinstaller (onedir ou onefile) if getattr(sys, 'frozen', False): REPEXE = sys._MEIPASS # programme traité par pyinstaller else: REPEXE = os.path.dirname(os.path.abspath(__file__)) # prog. normal py #======================================================================== # initialise la bibliothèque graphique app = QApplication(sys.argv) #======================================================================== #met la même icône pour toutes les fenêtres du programme #app.setWindowIcon(QIcon(os.path.join(REPEXE, "navigfichiers.png"))) #======================================================================== # met les tooltips à fond jaune clair dans toute l'application #app.setStyleSheet("QToolTip {background-color: #ffff99; border: 1px solid black}") #======================================================================== # style pour toute l'application if "Fusion" in [st for st in QStyleFactory.keys()]: app.setStyle(QStyleFactory.create("Fusion")) elif sys.platform=="win32": app.setStyle(QStyleFactory.create("WindowsVista")) elif sys.platform=="linux": app.setStyle(QStyleFactory.create("gtk")) elif sys.platform=="darwin": app.setStyle(QStyleFactory.create("macintosh")) # pour trouver les styles disponibles: # print([st for st in QStyleFactory.keys()]) # Windows 11: ['WindowsVista', 'Windows', 'Fusion'] app.setPalette(QApplication.style().standardPalette()) #======================================================================== # assure la traduction automatique du conversationnel à la locale locale = QLocale.system().name() translator = QTranslator() reptrad = QLibraryInfo.location(QLibraryInfo.TranslationsPath) translator.load("qtbase_" + locale, reptrad) # qtbase_fr.qm app.installTranslator(translator) #======================================================================== repertoire = "C:\\" navigfichiers = NavigFichiers(repertoire) navigfichiers.show() sys.exit(app.exec_()) |
Hello,
Tant qu'on est à montrer certaines techniques, voici un exemple simple de ce à quoi peut ressembler la clean architecture avec ton projet.
Repo GitHub
L'objectif est que quelque soit le module que vous souhaitez utiliser pour votre interface graphique, vous ayez un minimum de changement ou ajout à faire. L'autre objectif est de ne rien supprimer et de rendre modulable graphiquement le projet.
Chaque interface graphique nouvellement créée est dépendante des méthodes obligatoires de la classe Abstraite UIPort.
On sépare la partie métier de l'interface, même si quelque fois cela peut paraître inutile, sa modification sera simple et sa recherche aussi (évite d'aller dans les méandres d'un code mélangé avec Qt par ex.)
L'ajout d'une interface tkinter peut se faire dans infrastructure/tkadapter.py avec stricto les mêmes méthodes d'affichage
Tant qu'on est à montrer certaines techniques, voici un exemple simple de ce à quoi peut ressembler la clean architecture avec ton projet.
Repo GitHub
L'objectif est que quelque soit le module que vous souhaitez utiliser pour votre interface graphique, vous ayez un minimum de changement ou ajout à faire. L'autre objectif est de ne rien supprimer et de rendre modulable graphiquement le projet.
Chaque interface graphique nouvellement créée est dépendante des méthodes obligatoires de la classe Abstraite UIPort.
On sépare la partie métier de l'interface, même si quelque fois cela peut paraître inutile, sa modification sera simple et sa recherche aussi (évite d'aller dans les méandres d'un code mélangé avec Qt par ex.)
L'ajout d'une interface tkinter peut se faire dans infrastructure/tkadapter.py avec stricto les mêmes méthodes d'affichage
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.