2. Tabelle mit Datei Ein- und Ausgabe | |||||
In diesem Beispiel geht es darum eine einfache Standard-Tabelle zu erstellen, die ihren Inhalt aus einer Datei liest und bei Änderung des Inhalts die Möglichkeit einräumt, per Knopfdruck die veränderten Daten auch wieder in die Datei zurück zu schreiben. Eine Tabelle in Qt besteht aus zwei Elementen, einem QTableWidget und einem QTableWidgetItem. Während das QTableWidget quasi eine Art "Rahmen" darstellt und die Tabelle mit Spalten und Zeilen definiert, beschreibt das QTableWidgetItem den Inhalt einer einzelnen Zelle. TableItems werden dazu benutzt, den Inhalt einer Zelle zu verwalten, sei es nun Text, ein Icon oder eine Checkbox. Legen wir los: Nachdem wir einen Ordner mit dem Namen tabelle erstellt haben, starten wir den Qt-Designer und erstellen eine neue Dialog-Box mit den Standard-Buttons OK und Abbrechen. Dem Dialog geben wir den Namen tabelleWindow. Anschließend ziehen wir aus der Gruppe Item Widgets (Item-Based) das Widget Table Widget in unseren Dialog und ziehen die Ränder in eine angenehme Größe. Unserem TableWidget geben wir den Namen tabelleWidget. Als letztes ziehen wir noch zwei weitere Buttons in unseren Dialog. Einem geben wir die Aufschrift Load/Reload und den Namen reloadButton, dem anderem die Aufschrift Save und den Namen saveButton. Bei letzterem Button nehmen wir in den Eigenschaften bei enabled das Häckchen raus, setzen ihn also auf deaktiviert. Der saveButton soll sich erst aktivieren, wenn eine Zelle in unserer Tabelle verändert wurde. Das ganze Widget speichern wir unter der Bezeichnung tabelle.ui. Damit ist unsere Oberfläche schon fertig und sieht in etwa so aus:
Wie man sieht, ist unser TableWidget noch vollkommen leer, d.h. wir haben noch gar keine Zeilen und Spalten definiert. Müssen wir auch nicht, da wir die Anzahl besagter Zeilen und Spalten aus unserer Textdatei entnehmen können. Möchte man die Textdatei nicht von Hand schreiben, sondern gleich mit unserem Programm erstellen, selektiert man das TableWidget und kann ganz unten unter den Eigenschaften eine Zeilenanzahl (rowCount) und Spaltenanzahl (columnCount) voreinstellen. Nach dem Programmstart kann man dann die Spalten mit den gewünschten Einträgen füllen und ein Druck auf den saveButton erstellt die Textdatei. Allerdings wird bei diesem Vorgehen keine Kopfzeile geschrieben, da die Standard-Nummerierung im Layout keinen realen Wert darstellt, den man auslesen könnte. Anstelle der Nummerierung wird eine leere Zeile geschrieben. Bei einem Reload der Datei, wird sogar die Standard-Zahl im Spalten-Index durch die Leerzeile entfernt und man hat Spalten ganz ohne Index. Möchte man dem abhelfen und beim ersten Erstellen der Datei auch gleich die Kopfzeile haben, öffnet man mit einem Doppelklick das Bearbeitungsmenü des TableWidgets und kann per Doppelklick die Zahlen in Bezeichnungen umändern. Diese können dann als ganz normaler QString ausgelesen werden. Diesmal benötigen wir zusätzlich zu unseren vier Dateien noch eine fünfte Datei:
tabellendaten.txt
Wie man sieht, sind die einzelnen Einträge für die Spalten in Anführungsstriche eingefaßt, damit ein Eintrag auch Leerzeichen beinhalten kann. Die Anführungsstriche dienen somit als Trenner. Ich benutze hier denselben Algorithmus wie ich ihn schon für die String-Trennung in C++ verwendet habe. Nur für die Befehle in Qt in etwas abgeänderter Form. Die erste Zeile unserer Daten-Datei verwenden wir als Spalten-Index für unsere Tabelle. main.cpp
Die main.cpp enthält keine Besonderheiten und daher gibt es nichts weiteres dazu zu sagen. tabelle.h
Unter private slots: stehen die Definitionen für unsere 3 eigenen Slots. Einmal für das Laden der Datei in die Tabelle, für das Speichern und ein Slot der das Signal für eine Änderung in unserer Tabelle erhält. Dieser Slot hat später die einzige Aufgabe unseren saveButton zu aktivieren. tabelle.cpp
Der Code unserer tabelle.cpp sieht auf den ersten Blick nach sehr viel aus, aber keine Angst, es ist eigentlich halb so wild. Den Anfang machen unsere 3 Signale-Slots-Verbindungen. saveButton und reloadButton sind klar. Sie verbinden das clicked()-Signal der Knöpfe mit den entsprechenden Slots speichern und neuladen. Neu ist das Signal itemChanged der dritten Verbindung, welches von unserem TableWidget gesendet wird und mit dem Slot aenderung verbunden ist. Das Signal itemChanged wird immer dann ausgelöst, wenn in der Tabelle eine Zelle verändert wurde. Unser erster Slot neuladen dient dazu, die Daten aus unserer Textdatei tabellendaten.txt einzulesen. Mit QCoreApplication::applicationDirPath() kann man sich den momentanen Verzeichnispfad in dem unser Programm ausgeführt wird holen. An diesen Pfad hängen wir noch mit append den Namen unserer Datei an. Anschließend wird mittels QFile die Datei geöffnet und im Fall eines erfolgreichen Öffnens (if(datei.open)) der nachfolgende Code ausgeführt. QIODevice::ReadOnly öffnet die Datei nur zum lesen und QIODevice::Text definiert sie als Textdatei. Der Befehl QApplication::setOverrideCursor(Qt::WaitCursor); macht aus unserem Mauszeiger eine beschäftigte Sanduhr, damit man sieht, daß das Programm arbeitet, sollte das Laden der Tabelle länger dauern. Mittels eines QTextStream kann man die geöffnete Datei Zeile für Zeile auslesen lassen und einer QString-Variablen übergeben. In unserem Fall unser QString-Array eintrag welches 99 Spalten und 99 Zeilen ermöglicht. Durch die while-Schleife while(!in.atEnd()) wird solange in der Datei gelesen, bis das Ende erreicht ist. Es folgt mein Algorithmus um die einzelnen Spalten anhand der Anführungsstriche zu zerlegen. Durch die darauf folgende if-Schleife if(s>spalten){spalten=s;} kann die Textdatei in einer Zeile auch weniger Einträge enthalten. Trotzdem wird die Tabelle immer so groß gemacht, daß keine Spalten einer Zeile abgeschnitten werden. datei.close() schließt unser Textdokument wieder. Mittels tabelleWidget->clear(); wird die gesamte Tabelle geleert. Eventuell vorhandene Zeilen und Spalten bleiben jedoch bei diesem Vorgang erhalten. Mit tabelleWidget->setRowCount(zeilen-1); und tabelleWidget->setColumnCount(spalten); wird die Anzahl der Spalten und Zeilen festgelegt. Sozusagen wird unser "Tabellenrahmen" in den wir später unsere Daten eintragen können erstellt. Durch die vorige Zählung der Zeilen und Spalten wird die Tabelle immer nur so groß gemacht, wie die Anzahl der Daten in der Textdatei. Da wir die erste Zeile unserer Datei als Kopf-Layout verwenden wollen, müssen wir die Zeilen um 1 kürzen. Tabellenzeilen und -spalten beginnen immer bei 0, daher müssen wir später unsere rows um -1 wegen der fehlenden, ersten Zeile herabsetzen. Mit den ineinander verschachtelten for-Schleifen werden die einzelnen Spalten mit den Daten aus unserem QString-Array befüllt. Dabei sieht das Konstrukt zum befüllen schon wild aus, denn ein QTableWidget kann nur ein QTableWidgetItem an eine Zelle übergeben, während man dem QTableWidgetItem einen QString übergeben kann. Sozusagen kann man den QString nicht direkt in die Tabelle eintragen, sondern muß den Umweg über das Item gehen. Aus diesem Grund müssen wir mittels QTableWidgetItem *newItem = new QTableWidgetItem; ein neues Item mit dem Namen newItem erstellen. Diesem Item übergeben wir mit newItem->setText(eintrag[row][column]); unseren QString eintrag. Anschließend wird abgefragt ob die Zeile gleich 0 ist, damit die erste Zeile mittels setHorizontalHeaderItem in den Tabellenkopf eingetragen wird. Alle anderen Zeilen werden mittels setItem in unsere Tabellen-Zellen eingetragen. QApplication::restoreOverrideCursor(); stellt unseren Standard-Mauszeiger wieder her und saveButton->setEnabled(false); setzt den saveButton auf Disabled. Jetzt werdet ihr sagen, warum das denn, wir haben doch den saveButton schon im Qt-Designer auf Disabled gesetzt? Tja, das Problem ist, daß durch das Eintragen unserer Tabellendaten das Signal itemChanged ausgelöst wurde, da sich die Items ja verändert haben, welches den Slot aenderung() aufruft, in dem der saveButton aktiviert wird. Der saveButton soll aber erst aktiviert werden, wenn wir selbst was dran ändern und nicht schon beim Laden der Daten!
Der Slot speichern fängt ähnlich an wie der Slot neuladen, da ja auch hier eine Datei-Operation ansteht. Zuerst wird wieder das momentane Verzeichnis des Programms geholt und der Name unserer Datei angehängt. Wieder wird mit QFile die Datei geöffnet. Diesmal mittels QFile::WriteOnly zum schreiben. Der Zusatz QFile::Truncate löscht einen eventuell vorhandenen Inhalt gnadenlos. Da unsere Tabellenköpfe keinen Zeilenwert besitzen, müssen wir diese mit einer for-Schleife separat eintragen. Also macht die erste for-Schleife mit dem Befehl tabelleWidget->horizontalHeaderItem(i)->text(); nichts anderes als die Layout-Einträge unserer Tabelle als erste Zeile in die Textdatei zu schreiben. Man beachte wieder das wilde "Umwegs-Konstrukt" aus QTableWidget->QTableWidgetItem->text(). Gegen unsere beiden ersten Slots ist der letzte geradezu lachhaft langweilig, beinhaltet er doch lediglich, daß der saveButton aktiviert wird. Natürlich hat unser Programm so wie es jetzt ist einen kleinen Haken: Durch die festgelegte Größe des Arrays ist die maximale Zeilen- und Spaltenanzahl begrenzt. Daher ist es sinnvoller die Daten nicht über das Array einzutragen, sondern gleich direkt beim auslesen in die Tabelle zu schreiben. Da man vorher die erforderliche Zeilen- und Spaltenanzahl nicht kennt, muß man dynamisch mittels tabelleWidget->insertRow(row); und tabelleWidget->insertColumn(column); neue Zeilen und Spalten einfügen. Durch diese "Dynamik" ist es egal wie groß die Textdatei ist, solange der Arbeitsspeicher dafür ausreicht. Um die Daten auf direktem Weg in die Tabelle zu schreiben, müssen wir nur unseren Slot neuladen etwas abändern: tabelle.cpp (Slot neuladen ohne Array)
Wie man an der Variablen-Deklaration sieht, fallen zwei int-Variablen für die Zeilen und Spalten weg, welche wir vorher für die for-Schleifen zum Eintragen der Daten in die Tabelle benötigt haben. Aus unserer int-Variablen s wird ein Zähler für die maximale Spaltenanzahl. Dieser Zähler hilft später mit einer if-Schleife nur soviel neue Spalten hinzuzufügen, wie auch benötigt werden. Die benötigten Zeilen ergeben sich aus den Zeilen der Datei. Am Anfang des Auslesevorgangs löschen wir erst einmal die Tabelle mit tabelleWidget->clear(); und setzen die Spalten und Zeilen auf 0, damit auch wirklich nur die Spalten und Zeilen erstellt werden, für die Werte vorhanden sind. Das Erstellen einer Zeile übernimmt dabei tabelleWidget->insertRow(row);. Der int-Wert row bezieht sich auf die Position an der die Zeile eingefügt werden soll. Gleiches gilt für tabelleWidget->insertColumn(column); für die Spalten. Hierfür kommt die angesprochene if-Abfrage if(s<column) zum Einsatz, welche nur Spalten hinzufügt, wenn sie benötigt werden. Der Rest ist wie gehabt. Der benötigte QString eintrag wird ausgeschnitten, dem QTableWidgetItem übergeben und in die entsprechende Zelle eingetragen. Da wir die erste Zeile unserer Daten-Datei als Spalten-Index verwenden, rutschen natürlich alle nachfolgenden Zeilen um eins nach oben, daher müssen wir die letzte Zeile -da leer- wieder löschen. Dies erledigt tabelleWidget->removeRow(row-1);.
Noch zur Ergänzung falls jemand seine Tabelle optisch aufpeppen will: |
|||||
Zurück zum Auswahlmenü |