QTableWidget erhält keine Werte

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Servus,

habe mal wieder ein kleine Frage.
Die Lösung ist vermutlich sehr einfach, aber nach 1,5 Std. Suche habe ich nun kapituliert.
Ich find die Lösung leider nicht alleine -.-

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import csv
import re
from collections import OrderedDict
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, \
                            QHBoxLayout, QLineEdit, QPushButton, QTextEdit, \
                            QCompleter, QTableWidget, QTableWidgetItem, \
                            QInputDialog, QMessageBox

class Lager():
    def __init__(self):
        self.lager = OrderedDict()
        self.artikelbezeichnungen = [] #Benötigt für Vorschläge in Autocomplete
        self.feldnamen = [] #Benötigt zur Erstellung der Feldnamen der Tabelle 

        
    def csv_einlesen(self, csvdatei):
        with open(csvdatei, "r") as datei:
            reader =  csv.DictReader(datei, delimiter=";")
            self.feldnamen = reader.fieldnames
            reader = sorted(reader, key=lambda d: d["Artikelbezeichnung"])
            vortlaufendenummer = 1
            
            print("-------------------------------------------------------")
            print("|folgende Artikel wurden aus der CSV Datei eingelesen:|")
            print("-------------------------------------------------------")
            
            for zeile in reader:
                self.lager[vortlaufendenummer] = zeile
                print(self.lager[vortlaufendenummer])
                self.artikelbezeichnungen.append(self.lager[vortlaufendenummer]\
                                                 ["Artikelbezeichnung"])        
                vortlaufendenummer += 1
            return (list(range(0,vortlaufendenummer)))
                
    
class Lieferanten():
    def __init__(self):
        self.lieferanten = {}


        
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.fenstereinstellungen()
        self.warenbestand = Lager()
        
        self.erzeugeWidgets()
        self.erzeugeLayout()

        self.warenbestand.csv_einlesen("Artikelliste-oop.csv")
        self.tabelle_erzeugen(tuple(self.warenbestand.lager.keys()))
        self.autovervollstaendigung()
        self.show()

        
    # Setzt die allgemeinen Eistellungen des Hauptfensters    
    def fenstereinstellungen(self):
        self.resize(1500, 600)
        self.setWindowTitle("Bestellung V1.0")

        
    # Definition der benötigten Windows    
    def erzeugeWidgets(self):
        self.labeltreffer = QLabel()
        self.tablewidget = QTableWidget()
        self.suchleiste = QLineEdit(self)
        self.suchleiste.returnPressed.connect(self.artikel_suchen_und_anzeigen)
        self.suchleiste.setToolTip("Artikelbezeichung, oder Artikelnummer "\
                                   "eingeben")
        self.sfsuche = QPushButton("Suchen", self)
        self.sfsuche.clicked.connect(self.artikel_suchen_und_anzeigen)
        self.sfbestellen = QPushButton("Bestellen", self)
        self.sfbestellen.clicked.connect(self.bestellmenge_auswaehlen)
        self.sfsenden = QPushButton("Senden", self)
        self.sfsenden.clicked.connect(self.lieferant_auswaehlen)
        self.sfbeenden = QPushButton("Beenden", self)
        self.sfbeenden.clicked.connect(self.programm_beenden)

        
    # Setzen des Layouts    
    def erzeugeLayout(self):
        hBoxLay1 = QHBoxLayout()
        hBoxLay1.addWidget(self.suchleiste)
        hBoxLay1.addWidget(self.sfsuche)
            
        vBoxLay2 = QVBoxLayout()
        vBoxLay2.addWidget(self.labeltreffer)
        vBoxLay2.addWidget(self.tablewidget)
        
        hBoxLay3 = QHBoxLayout()
        hBoxLay3.addWidget(self.sfbestellen)
        hBoxLay3.addWidget(self.sfsenden)
        hBoxLay3.addWidget(self.sfbeenden)
        
        masterbox = QVBoxLayout()
        masterbox.addLayout(hBoxLay1)
        masterbox.addLayout(vBoxLay2)
        masterbox.addLayout(hBoxLay3)
        self.setLayout(masterbox)
         
        
    # Autovervollständigung des Suchen-feldes oben am Fensterrand
    def autovervollstaendigung(self):
        vorschlaege = self.warenbestand.artikelbezeichnungen
        autocomplete = QCompleter(vorschlaege)
        self.suchleiste.setCompleter(autocomplete)


    # Erzeugen der Tabelle mit den übergebenen Artikelnummern aus der
    # übergebenen Liste
    def tabelle_erzeugen(self, artikelnummernliste):
        self.tablewidget.setColumnCount(len(self.warenbestand.feldnamen))
        self.tablewidget.setHorizontalHeaderLabels(self.warenbestand.feldnamen)

        zeile = 0

        print("----------------------------------------------------")
        print("|folgende Artikel wurden in die Tabelle geschrieben|")
        print("----------------------------------------------------")
        
        for artikelnummer in artikelnummernliste:
            zeile += 1
            self.tablewidget.setRowCount(zeile)
            spalte = 0
            print(self.warenbestand.lager[artikelnummer])
            print(artikelnummer)
            for schluessel in self.warenbestand.lager[artikelnummer]:
                print(zeile, spalte, str(self.warenbestand.lager[artikelnummer][schluessel])) 
                self.tablewidget.setItem(zeile, spalte, \
                                         QTableWidgetItem(str(self.warenbestand.lager[artikelnummer][schluessel])))
                spalte += 1
        self.labeltreffer.setText("Es wurden " + str(zeile) +\
                                  " Artikel gefunden")
        """
        # Aufbau und Anwendung wie Werte in eine Zelle geschrieben werden
        #self.tablewidget.setItem(0,0, QTableWidgetItem("Zellenname0"))
        #self.tablewidget.setItem(0,1, QTableWidgetItem("Zellenname1"))
        #self.tablewidget.setItem(0,2, QTableWidgetItem("Zellenname2"))
        """

        
    # Such nach den eingegebenen Textteilen in allen Artikelbezeichungen und
    # und gibt diese dann durch aufruf der tabelle_erzeugen funktion aus
    def artikel_suchen_und_anzeigen(self):
        durchlaufzaehler = 0

        suchbegriff = "".join(["[" + str(zeichen).upper() +\
                               str(zeichen).lower() + "]"\
                               for zeichen in self.suchleiste.text()])

        print("------------------------------------------------")
        print("|Die Suche nach Artikel ergab folgende Nummern:|")
        print("------------------------------------------------")
        print("Gesuchte Zeichenkette: " + suchbegriff)

        gefundene_artikelnummern = []
        for artikelnummer in self.warenbestand.lager:
            if re.findall(suchbegriff, self.warenbestand.lager[artikelnummer]\
                          ["Artikelbezeichnung"]):
                gefundene_artikelnummern.append(artikelnummer)       
            durchlaufzaehler += 1
        print("passende Artikelnummern: " + str(gefundene_artikelnummern))
            
        if durchlaufzaehler == 0:
            self.labeltreffer.setText("Es konnte kein Treffer ermittelt werden")

        self.tabelle_erzeugen(gefundene_artikelnummern)
    
        
    # Auswahl der zu bestellenden Menge die dem Artikel hinterlegt werden soll  
    def bestellmenge_auswaehlen(self):
        menge, check = QInputDialog.getInt(self, "Bestellmenge",
                                           "Zu bestellenden Menge eingeben:",
                                           value=1)
        #if check:
            
        

    # Auswahl des Lieferanten dem die Bestellung gesendet werden soll
    def lieferant_auswaehlen(self):
        lieferanten = ("Innstolz", "optima", "Hofmark")
        lieferant, check = QInputDialog.getItem(self, "Lieferant",
                                                   "Lieferanten auswählen:",\
                                                lieferanten)
        #if check:

        
    # Beenden des Programmes    
    def programm_beenden(self):
        reply = QMessageBox.question(self, "Achtung", "Möchten Sie die " \
                                     "Anwendung wirklich schließen?", \
                                     QMessageBox.Yes | QMessageBox.No, \
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            QCoreApplication.quit()

        
def main():
    app = QApplication(sys.argv)
    w = Window()
    sys.exit(app.exec_())  
        
        
if __name__ == "__main__":
    main()
Problem:
Das bei mir auftauchende Problem erscheint beim Aufruf der Funktion "tabelle_erzeugen".
Es sollte anhand der übergeben Liste die jeweiligen Elemente in die QTableWidgets übergeben werden.
Es wird eine Tabelle erzeugt, diese hat auch so viele Zeilen wie sich in der Csv Datei befinden, bzw.
so viele wie durch die Suchfunktion Elemente gefunden werden. Nur bleiben die Felder leer, also es steht kein Text darin.
Ich bin neu in der GUI Programmierung und kann es mir nicht erklären.

Das Programm ist noch NICHT fertig!
Es wurde bisher nur der Aufbau, Import und Anzeige fertig gestellt.
Die Funktionen "bestellmenge_auswaehlen" und "lieferant_auswaehlen" sind nur ganz grob notiert.
Ich würde mich daher sehr über Hilfe freuen.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hartmannsgruber: Der Fehler ist recht trivial: `QTableWidget.setItem()` ignoriert einfach Elemente die damit ausserhalb der aktuell gesetzten Tabellengrösse gesetzt werden, und Qt fängt bei Indexzugriffen genau wie Python bei 0 an zu zählen und nicht bei 1. Daneben macht es auch wenig Sinn für jede neue Zeile die Zeilenanzahl des Widgets um eins höher zu setzen, wenn die doch vor der Schleife bereits bekannt ist, und man sie dort *einmal* auf den endgültigen Wert setzen kann.

Weitere Anmerkungen:

`QTextEdit` wird importiert, aber nicht verwendet.

Warum `QCoreApplication` importiert wird, zusätzlich zu `QApplication` verstehe ich nicht. `QApplication` ist ja (indirekt) von `QCoreApplication` abgeleitet, hat also auch die statische `quit()`-Methode.

`w` ist kein guter Name. Wenn man `window` meint, sollte man das auch schreiben.

Der `show()`-Aufruf gehört nicht in die `__init__()` des Widgets. Kein anderes Widget zeigt sich selbst an, nur weil es erzeugt wird. Das ist Aufgabe und Entscheidung des Codes der das Widget erstellt.

Die Erzeugung der Widgets und das Layout gehören nicht in zwei verschiedene Methoden. Dadurch müssen mehr Widgets an das `Window`-Objekt gebunden werden als tatsächlich dort hingehören, es ist schwieriger sich am Quelltext vorzustellen wie denn am Ende die GUI aussieht, und wenn man einen Teil der GUI in eine eigene Klasse herausziehen will, muss man sich den Quelltext dafür erst aus verschiedenen Methoden zusammen suchen, statt einfach den bereits zusammen stehenden, weil zusammengehörigen Quelltext an einer Stelle auszuschneiden und am Stück in eine andere `__init__()` zu kopieren.

``\`` um Zeilen fortzusetzen ist fehleranfällig. Danach darf zum Beispiel kein Whitespace stehen. Das kann man eigentlich immer anders lösen. Entweder in dem man sowieso schon vorhandene Klammern nutzt, oder eben mit zusätzlichen Klammern.

Namen sollten weder durchnummeriert werden, noch irgendwelche kryptischen Pre- oder Suffixe enthalten. Bei den Layouts erreicht man das beispielsweise durch Umstellung des Codes, denn man braucht ja gar nicht für jedes Layout einen eigenen Namen. Und was der `sf`-Präfix bei den Buttons bedeuten soll, habe ich auch nach intensivem Nachdenken nicht rausbekommen. Namen sollen dem Leser verraten was der Wert dahinter bedeutet und nicht zum rätselraten zwingen.

`tabelle_erzeugen()` sollte wohl eher `tabelle_fuellen()` heissen.

Da sich die Spaltenanzahl und Beschriftung eigentlich nicht ändert, kann man die *einmal* ausserhalb der Methode setzen.

Wenn man zusätzlich zum iterieren über ein Objekt noch eine laufende Zahl benötigt, dann verwendet man die `enumerate()`-Funktion statt das manuell mit initialisieren vor der Schleife und hochzählen in der Schleife zu lösen. Das betrifft sowohl `zeile` als auch `spalte`.

``self.warenbestand.lager[artikelnummer][schluessel]`` ist auch viel zu lang. Wenn man die `artikelnummer` nummer hat, kann man schon weit vorher das Ergebnis von `self.warenbestand.lager[artikelnummer]` an einen Namen binden, beispielsweise `artikel` und schon muss man dafür nicht immer `self.warenbestand.lager[artikelnummer]` schreiben. Und `schluessel` sollte man nicht aus dem Artikelwörterbuch holen, sondern aus Feldnamen, denn sonst ist bis Python 3.7 gar nicht garantiert in welcher Reihenfolge die Schlüssel geliefert werden.

Literale Zeichenketten und Werte per ``+`` und `str()` zusammenstückeln ist eher BASIC denn Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale.

Literale Zeichenketten sind keine Kommentare, die sollten also auch nicht zum ”auskommentieren” von Quelltext verwendet werden.

In `artikel_suchen_und_anzeigen()` macht der `durchlaufzaehler` keinen Sinn und ist sogar falsch. Statt alle Durchläufe der Schleife zu zählen, willst Du da ja eigentlich die Anzahl der Treffer zählen. Das brauchst Du aber gar nicht machen, denn das ist ja die Länge er Trefferliste die man jederzeit mit `len()` abfragen kann.

Das Suchen mit regulärem Ausdruck ist kaputt. Du kannst nicht einfach so Benutzereingaben in einen regulären Ausdruck hineinbasteln, denn wenn der Benutzer Zeichen eingibt, die an der Stelle im regulären Ausdruck eine besondere Bedeutung haben, ist der Ausdruck ganz schnell auch mal syntaktisch falsch. `findall()` ist unnötig aufwändig wenn die einzelnen Treffer gar nicht interessieren, sondern man nur wissen will ob es überhaupt Treffer gibt. Dafür würde `search()` vollkommen ausreichen.

Du machst das ja anscheinend sowieso nur um Gross-/Kleinschreibung bei der Suche zu ignorieren. Dafür kann man aber auch einfach beide Texte, die Eingabe und die Artikelbezeichnung in Kleinbuchstaben wandeln und ``in`` als Test verwenden. Da braucht man keine regulären Ausdrücke für.

Bei der `Lager`-Klasse ist das Attribut `lager` komisch benannt. Ein Lager hat als Bestandteil etwas das `lager` heisst? Das klingt doch komisch. Wenn Du das Exemplar nicht `warenbestand` genannt hättest, würde da im Code sehr oft `self.lager.lager` stehen, was nicht wirklich sinnvoll erscheint. Wobei das auch mit `self.warenbestand.lager` oder mit einem anderen Namen für das Attribut nicht so wirklich sinnvoll ist wenn da Code von aussen immer soweit in dieses `Lager`-Objekt durchgreift. Das sollte nicht sein. Zudem ist das ein `OrderedDict` das als Schlüssel fortlaufende ganze Zahlen enthält. *Das* ist der Job für eine Liste, nicht für ein Wörterbuch.

Bei `csv_einlesen()` ist der Argumentname falsch. Da wird keine `csvdatei` übergeben, sondern ein Datei*name*. `csvdatei` ist ein Name für das was die `open()`-Funktion zurück gibt, denn das ist tatsächlich ein Datei-Objekt.

Beim Öffnen von Textdateien sollte man immer die Kodierung angeben. Und bei CSV-Dateien *muss* man ``newline=""`` angeben wenn das nicht nur zufällig funktionieren soll, oder nur so aussehen soll als würde es funktionieren.

Das Ergebnis von `sorted` sollte man nicht `reader` nennen, denn das ist keiner. Das sind alle Datensätze in einer sortierten Liste. Grunddatentypen gehören nicht in Namen, also nur `datensaetze`. Zudem kann man nach der Zeile den ``with``-Block schon verlassen, denn da ist der gesamte Dateiinhalt bereits eingelesen. Und wenn man für die Artikel kein `OrderedDict` mit fortlaufenden Nummern, sondern eine Liste verwendet, dann ist das Einlesen der Daten an der Stelle auch schon fertig, dass heisst man braucht da keinen lokalen Namen mehr, sondern kann das Ergebnis von `sorted()` bereits an das Attribut vom `Lager`-Objekt binden.

Der Rückgabewert von `csv_einlesen()` wird nirgends verwendet.

Im Grund enthält die Klasse aber viel zu wenig Substanz. Mit einer Funktion die eine CSV-Datei einliest und die Feldnamen + die Datensätze als nach Artikelbezeichnung sortierte Liste liefert, käme man auch ganz prima zurecht. Das erstellen der Liste für die Autovervollständigung kann man auch direkt dort als einfache „list comprehension“ in den Code schreiben. Als Ladefunktion bleibt dann das hier von der `Lager`-Klasse übrig:

Code: Alles auswählen

def csv_einlesen(dateiname):
    with open(dateiname, "r", encoding="utf-8", newline="") as datei:
        reader = csv.DictReader(datei, delimiter=";")
        return (
            reader.fieldnames,
            sorted(reader, key=itemgetter("Artikelbezeichnung")),
        )
Einmal überarbeitet:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
import sys
from operator import itemgetter

from PyQt5.QtWidgets import (
    QApplication,
    QCompleter,
    QHBoxLayout,
    QInputDialog,
    QLabel,
    QLineEdit,
    QMessageBox,
    QPushButton,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QWidget,
)


get_artikelbezeichnung = itemgetter("Artikelbezeichnung")


def csv_einlesen(dateiname):
    with open(dateiname, "r", encoding="utf-8", newline="") as datei:
        reader = csv.DictReader(datei, delimiter=";")
        return (reader.fieldnames, sorted(reader, key=get_artikelbezeichnung))


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Bestellung V1.0")
        self.resize(1500, 600)

        main_layout = QVBoxLayout()

        self.sucheingabe = QLineEdit(self)
        self.sucheingabe.setToolTip(
            "Artikelbezeichung oder Artikelnummer eingeben"
        )
        self.suche_button = QPushButton("Suchen", self)
        layout = QHBoxLayout()
        layout.addWidget(self.sucheingabe)
        layout.addWidget(self.suche_button)
        main_layout.addLayout(layout)

        self.treffer_label = QLabel()
        self.table_widget = QTableWidget()
        layout = QVBoxLayout()
        layout.addWidget(self.treffer_label)
        layout.addWidget(self.table_widget)
        main_layout.addLayout(layout)

        self.bestellen_button = QPushButton("Bestellen", self)
        self.senden_button = QPushButton("Senden", self)
        self.beenden_button = QPushButton("Beenden", self)
        layout = QHBoxLayout()
        layout.addWidget(self.bestellen_button)
        layout.addWidget(self.senden_button)
        layout.addWidget(self.beenden_button)
        main_layout.addLayout(layout)

        self.setLayout(main_layout)

        self.sucheingabe.returnPressed.connect(
            self.artikel_suchen_und_anzeigen
        )
        self.suche_button.clicked.connect(self.artikel_suchen_und_anzeigen)
        self.bestellen_button.clicked.connect(self.bestellmenge_auswaehlen)
        self.senden_button.clicked.connect(self.lieferant_auswaehlen)
        self.beenden_button.clicked.connect(self.programm_beenden)

        self.feldnamen, self.warenbestand = csv_einlesen("test.csv")
        self.table_widget.setColumnCount(len(self.feldnamen))
        self.table_widget.setHorizontalHeaderLabels(self.feldnamen)
        self.tabelle_aktualisieren()

        self.sucheingabe.setCompleter(
            QCompleter(list(map(get_artikelbezeichnung, self.warenbestand)))
        )

    def tabelle_aktualisieren(self, artikelindizes=None):
        if artikelindizes is None:
            artikelindizes = range(len(self.warenbestand))

        treffer_anzahl = len(artikelindizes)
        self.table_widget.setRowCount(treffer_anzahl)

        for zeile, index in enumerate(artikelindizes):
            artikel = self.warenbestand[index]
            for spalte, schluessel in enumerate(self.feldnamen):
                self.table_widget.setItem(
                    zeile, spalte, QTableWidgetItem(str(artikel[schluessel]))
                )

        self.treffer_label.setText(
            f"Es wurden {treffer_anzahl} Artikel gefunden"
        )

    def artikel_suchen_und_anzeigen(self):
        suchbegriff = self.sucheingabe.text().lower()
        self.tabelle_aktualisieren(
            [
                index
                for index, artikelbezeichnung in enumerate(
                    map(get_artikelbezeichnung, self.warenbestand)
                )
                if suchbegriff in artikelbezeichnung.lower()
            ]
        )

    def bestellmenge_auswaehlen(self):
        menge, check = QInputDialog.getInt(
            self, "Bestellmenge", "Zu bestellenden Menge eingeben:", value=1
        )
        ...

    def lieferant_auswaehlen(self):
        lieferanten = ["Innstolz", "optima", "Hofmark"]
        lieferant, check = QInputDialog.getItem(
            self, "Lieferant", "Lieferanten auswählen:", lieferanten
        )
        ...

    def programm_beenden(self):
        reply = QMessageBox.question(
            self,
            "Achtung",
            "Möchten Sie die Anwendung wirklich schließen?",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )
        if reply == QMessageBox.Yes:
            QApplication.quit()


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
Man könnte natürlich überlegen ob man nicht lieber `QTableView` verwenden möchte, eine Model-Klasse für die Daten erstellt, und dann ein `QSortFilerProxyModel` dazwischen hängt.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Antworten