QFileSystemModel - Completer Liste aktualisieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Nobuddy
User
Beiträge: 1018
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
zu meinem Code, habe ich Fragen.
Ich möchte
Wenn ich in QLineEdit eine virtuelle Tasteneingabe z.B. "self.setText(os.sep)" mache, werden nicht die Pfade von / root im Completer ausgegeben.
Ich habe schon versucht in Code-Zeile 35 einen Ansatz zu finden, leider ....
Gibt es eine Möglichkeit, dies umzusetzen?

Grüße Nobuddy

Code: Alles auswählen

import os
import sys
from PyQt5.QtCore import (
    Qt,
    QDir
)
from PyQt5.QtWidgets import (
    QApplication,
    QLineEdit,
    QCompleter,
    QFileSystemModel
)


class FileSystemCompleter(QCompleter):
    def __init__(self, parent=None):
        super(FileSystemCompleter, self).__init__(parent=parent)
        self.parent = parent
        # settings
        self.setCaseSensitivity(Qt.CaseInsensitive)
        self.setFilterMode(Qt.MatchStartsWith)
        self.setCompletionRole(QFileSystemModel.FileNameRole)
        # model
        self.model_ = QFileSystemModel(self)
        self.model_.setFilter(
            QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files
        )
        self.model_.setRootPath(QDir.rootPath())
        self.setModel(self.model_)

    def pathFromIndex(self, index):
        path = super().pathFromIndex(index)
        if os.path.isdir(path):
            # set os.sep on end of input field string
            #path = f'{path}{os.sep}'
            self.model_.setData(index, path, Qt.UserRole)
        self.parent.path = path
        # get the selected completion suggestion
        return os.sep.join([path])

    def splitPath(self, path):
        # output folders and files from text input
        # start parameter
        dirPath = startPath = path
        # check path
        self.parent.path = path
        if os.path.isdir(path):
            """it's a directory"""
        elif os.path.isfile(path):
            """it's a file"""
        else:
            """create directory path and check path"""
            self.parent.path = f'{os.sep.join(path.split(os.sep)[:-1])}'
            # get path from directory
            i = 0
            if not startPath.endswith(os.sep):
                for i in range(len(startPath)-1, 0, -1):
                    if startPath[i] == os.sep:
                        break
            dirPath = dirPath[:i]
            # get difference from prom path to dirPath
            pathValue = ''.join(path.split(dirPath))
            if not pathValue.startswith(os.sep):
                pathValue = f'{os.sep}{pathValue}'
            pathValue = pathValue.replace(os.sep, '')
            # check paths to startswith pathValue
            check = False
            for p in os.listdir(dirPath):
                if p.startswith(pathValue):
                    path = startPath
                    self.widget().setText(path)
                    sep = (os.sep if not dirPath.endswith(os.sep)
                        and not p.startswith(os.sep) else '')
                    check = True
                    break
            if not check:
                path = startPath[:-1]
                self.widget().setText(path)
        self.widget().setFocus()
        self.widget().setCursorPosition(len(self.widget().text())+1)
        return super(FileSystemCompleter, self).splitPath(path)


def main():
    App = QApplication(sys.argv)
    class start(QLineEdit):
        def __init__(self):
            super().__init__()
            self.textChanged[str].connect(self.searchFieldCallback)
            self.completer = FileSystemCompleter
            self.setCompleter(self.completer(parent=self))
            #self.searchFieldCallback(os.sep)

        def searchFieldCallback(self, text):
            #if text != self.text():
            #   self.setText(text)
            # get job on completer
            self.completer(parent=self)
            try:
                # Path selected, ready for further processing
                print('self.path: ', self.path)
                self.paths = []
                if os.path.isdir(self.path):
                    # get parent current paths
                    self.paths = sorted(os.listdir(self.path))
                    print('self.paths: ', self.paths)
                del self.path
            except AttributeError:
                pass

    win = start()
    win.show()
    sys.exit(App.exec())

if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 18245
Registriert: Sonntag 21. Oktober 2012, 17:20

Pfade sind nicht normale Strings, zum Arbeiten damit gibt es das pathlib-Modul.
os.path ist entsprechend veraltet.
`str.join` auf eine Liste mit exakt einem Element aufzurufen ist ziemlicher Unsinn.
In `splitPath` erzeugst Du literale Strings, die Du an keine Variable bindest und auch sonst nicht weiter nutzt. Das macht keinen Sinn.
Einen f-String, der nur aus einem Ausdruck besteht, ist unsinnig. Wie schon geschrieben, gibt es pathlib, man arbeitet nicht mit split und join.
Über einen Index iteriert man in Python nicht. Das vermischen der Nutzung von path, dirPath und startPath macht den Code zusätzlich schwer zu verstehen.
Der Sinn von pathValue erschließt sich mir auch nicht. dirPath scheint wohl dem Parent-Path zu entsprechen, pathValue ist dann entweder eine leerer String, falls der Letzte Teil dem ersten Teil entspricht, also '/root/root' oder es ist der letzte Teil des Pfads. Warum dann erst os.sep an pathValue rangepackt wird, um das dann im nächsten Schritt wieder zu entfernen, erschließt sich mir auch nicht.
Programme schreibt man nicht, indem man irgendwelche Zeichen aneinander reiht und dann hofft, dass was sinnvolles dabei herauskommt, sondern man entwickelt ein Programm Schritt für Schritt und testet dann jeden Teil, dass er auch wirklich das tut, was man erwartet. Dazu benutzt man Funktionen, die genau eine Sache tun, damit das alles testbar bleibt.

Klassen schreibt man immer mit großem Anfangsbuchstaben, Klassen werden nicht innerhalb von Funktionen definiert und von normale Widgets ergbt man nicht, weil QT kennt ja das Signal-Konzept. Alle Attribute werden in __init__ angelegt, und vor allem erzeugt man keine neuen Attribute irgendwo anders im Code. Wenn es einen AttributeError gibt, dann ist das ein Programmierfehler, del benutzt man normalerweise nicht. Warum erzeugst Du in searchFieldCallback eine neue Instanz von FileSystemCompleter und machst dann nichts damit?
Nobuddy
User
Beiträge: 1018
Registriert: Montag 30. Januar 2012, 16:38

Danke Sirius3 für Deine Überprüfing!
Werde mich genauer über das pathlib-Modul informieren und versuchen die betreffenden Stellen zu ersetzen.
`str.join` auf eine Liste mit exakt einem Element aufzurufen ist ziemlicher Unsinn.
Du meinst die Code-Zeile 40 mit: "return os.sep.join([path])".
Wie würde Deine Lösung aussehen?

Mit f-String kann ich einfach einen neuen Wert weiter verarbeiten.
Andere Lösungen, kenne ich nicht.

Über weitere Ünterstützung würde ich mich freuen!
Grüße Nobuddy
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nobuddy: Lösung für was? Einen überflüssigen Aufruf? Na den überflüssigen Aufruf weglassen. :-) Und bei dem f-String verstehe ich nicht was Du damit überhaupt sagen willst. Auch hier: einfach weg lassen und den Wert so nehmen wie er ist, denn in beiden Fällen wird ja der Wert nicht verändert, man könnte den also gleich so nehmen.

Worin unterscheidet sich denn ``os.path.join([path])`` von einfach nur ``path``? Oder ``f"{text}"`` von einfach nur `text` wenn `text` eine Zeichenkette ist? Gib mal Beispiele wo das Sinn macht!?
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Nobuddy
User
Beiträge: 1018
Registriert: Montag 30. Januar 2012, 16:38

Habe mich mit pathlib auseinander gesetzt und bin positiv überrascht, aber unter der Haube steckt auch os.
Ich habe meinen Code abgeändert, so dass er für mich jetzt gut funktioniert.

Code: Alles auswählen

from pathlib import Path, PurePath
from PyQt5.QtCore import (
    Qt,
    QDir
)
from PyQt5.QtWidgets import (
    QApplication,
    QLineEdit,
    QCompleter,
    QFileSystemModel
)

class FileSystemCompleter(QCompleter):
    def __init__(self, parent=None):
        super(FileSystemCompleter, self).__init__(parent=parent)
        self.parent = parent
        # settings
        self.setCaseSensitivity(Qt.CaseInsensitive)
        self.setFilterMode(Qt.MatchStartsWith)
        self.setCompletionRole(QFileSystemModel.FileNameRole)
        # model
        self.model_ = QFileSystemModel(self)
        self.model_.setFilter(
            QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files
        )
        self.model_.setRootPath(QDir.rootPath())
        self.setModel(self.model_)

    def pathFromIndex(self, index):
        path = super().pathFromIndex(index)
        path = self.checkPath(path)
        if path != self.widget().text():
            self.widget().setText(path)
        # get the selected completion suggestion
        return path

    def splitPath(self, path):
        path = self.checkPath(path)
        self.widget().setFocus()
        self.widget().setCursorPosition(len(self.widget().text())+1)
        return super().splitPath(path)

    def checkPath(self, path):
        completePath = False
        pathsTo = []
        if Path(path).exists() and Path(path).is_dir():
            try:
                paths = sorted([str(x) for x in Path(path).iterdir()])
            except PermissionError:
                return self.path
            completePath = path
        elif Path(path).is_file():
            paths = []
        else:
            paths = []
            if not Path(self.path).exists():
                self.path = Path(self.path).parent
            pathsTo = []
            s = string = str(PurePath(Path(path).stem, '.', Path(path).suffix))
            for i in range(len(s)):
                for x in sorted(Path(self.path).iterdir()):
                    if str(PurePath(x.stem, '.', x.suffix)).startswith(string):
                        pathsTo.append(str(PurePath(self.path, x)))
                if pathsTo == []:
                    string = string[:-1]
                    path = path[:-1]
                    self.widget().setText(path)
            path = path if pathsTo != [] else self.path
        if pathsTo != []:
            completePath = ''
            if len(pathsTo) == 1:
                completePath = pathsTo[0]
                if Path(completePath).is_dir():
                    paths = [
                        str(x) for x in sorted(Path(completePath).iterdir())
                    ]
            elif len(pathsTo) > 1:
                paths = pathsTo
        # attributes for outsite
        self.parent.path = self.path = path
        self.parent.paths = self.paths = paths
        self.parent.pathsTo = self.pathsTo = pathsTo
        return path


def main():
    App = QApplication(sys.argv)
    class start(QLineEdit):
        def __init__(self):
            super().__init__()
            self.textChanged[str].connect(self.searchFieldCallback)
            self.completer = FileSystemCompleter
            self.setCompleter(self.completer(parent=self))

        def searchFieldCallback(self, text):
            self.completer(parent=self)
            try:
                # Path selected, ready for further processing
                print('self.path: ', self.path)
                print('self.paths: ', self.paths)
                del self.path, self.paths
            except AttributeError:
                pass

    win = start()
    win.show()
    sys.exit(App.exec())

if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 18245
Registriert: Sonntag 21. Oktober 2012, 17:20

Die eigentlichen Probleme hast Du nicht beseitigt: Man erzeugt nicht einfach so neue Attribute in Instanzen.

Auch bei der ganzen Pfadbehandlung in checkPath passieren seltsame Dinge, die Du so sicher nicht getestet hast.
is_dir impliziert exists, was Du ja bei der is_file-Prüfung ja auch implizit annimmst.
Du übergibst checkPath ein `path`-Argument, hast aber gleichzeitig auch self.path, was anscheinend eine alter Wert von path ist.
Warum arbeitest Du mit dem? Es ist nicht einmal garantiert, dass das Attribut existiert.
Die Zeile

Code: Alles auswählen

s = string = str(PurePath(Path(path).stem, '.', Path(path).suffix))
macht so keinen Sinn. Warum spaltest Du den Namen an der Erweiterung auf, um ihn dann als Pfadteile wieder zusammenzusetzen? . als Pfad bedeutet aktuelles Verzeichnis, und kann hier genausogut weggelassen werden.
Dann haben wir wieder eine for-Schleife über einen Index, mit einem sehr schlechten Variablennamen `s`. Das schlechte ist, dass mit i innerhalb der for-Schleife gar nichts gemacht wird.
Pfade sollte man nicht als einfache Strings behandeln. Und die Stringvermurksung zeigen deutlich, dass Du den Teil des Codes niemals getestet hast.
Meist schreibe ich ja, dass die Leute Code zeigen sollen, weil es da deutlicher wird, wo das Problem liegt, in Deinem Fall würde ich Dich bitten, mal in Worten zu beschreiben, was checkPath eigentlich machen soll, denn aus dem Code wird man nicht schlau.
Antworten