|
Seit der Version 3.0 wird in der GUI-Bibliothek Qt
von Trolltech auch die Kommunizierung mit Datenbanken unterstutzt. Auf der Suche nach geeigneter Literatur fand ich neben der Online-Referenz und einigen englischen Titeln keine deutschsprachige Einfürhrung in diese neuen Funktionen. Daher blieb mir nur noch der Click zu Amazon um das Buch “Programming withQtSecond Edition” zu
bestellen. Dieses englischsprachige Werk behandelt in einem Kapitel die Datenbankunterstützung mit Qt 3.0. Um anderen den Einstieg in das Thema leichter zu machen findet ihr hier eine Übersetzung des Kapitels. In dem Bereich Download findet ihr die Übersetzung im DOC-Format für den Drucker sowie alle Übungsbeispiele
Einfache Datenwiederherstellung
In diesem Abschnitt erklären wir Ihnen, wie Sie Daten aus Ihrer Datenbank in
Ihr Qt-Porgramm bekommen. In jeder relationalen Datenbank welche SQL benutzt, (momentan alle von Qt unterstützten Datenbanken) verwenden Sie die SQL-Befehl SELECT. Dieser Befehl hat sehr viele Optionen, es ist undenkbar Ihnen selbst einen kleinen Teil vorzustellen, deswegen verweisen wir sie auf die SQL Fachliteratur. Wie auch immer, einfache Wiederherstellungsvorgänge sind ebenso leicht und Sie werden hier einige sehen.
Um Ihnen diese Informationen in einem praktischen Beispiel zu zeigen, lassen
Sie uns eine Anwendung für eine Firma schreiben, die Computer-Kurse anbietet. Die Kunden können in der Liste nach Kursen suchen und sich für die Teilnahme der Kurse entscheiden, die Firma kann neue Kurse hinzufügen usw. Wir werden verschiedene Datenbanktabellen für die Darstellung von Studenten, Kursen und Kunden haben, die wir abfragen werden.
Bevor wir ein Qt-Programm zur Wiederherstellung und Anzeige dieser Daten
erstellen können, brauchen wir zunächst eine echte Datenbank, die wir mit Daten ergänzen. Dies wird durch die Eingabe von einigen SQL Anweisungen getan Beispiel 19-1 zeigt die SQL Befehle an, die benötigt werden um unsere Datenbank aufzubauen. Wenn Sie mit SQL vertraut sind sollte Ihnen dieser Vorgang bekannt sein. Das Wesentliche dieses Programmes besteht darin, dass es eine Datenbank namens Courses (Kurse) und eine gleichnamige Tabelle herstellt.Der SQL Code, der hier gezeigt wird funktioniert mit MySQL und auch mit anderen Datenbanken, für die nur geringe Änderungen notwendig wären. Diesen Code können Sie in Ihrer Datenbankkonsole eingeben wie zum Beispiel mysql für MySQL oder SQL*Plus für Oracle.
Beispiel 19-1. createdb.sql
### Create the table structure for the course registration
# Drop the database and create it anew
DROP DATABASE courses;
CREATE DATABASE courses;
USE courses;
# Create the courses table
CREATE TABLE courses
(
id INT NOT
NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
description TEXT,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
location VARCHAR(50) NOT NULL
);
CREATE INDEX courses_name_index ON courses (name);
Im ersten Schritt ist es unser Ziel, die Kurse von der Datenbank auszulesen und sie dem Benutzer anzuzeigen.
In diesem Abschnitt begnügen wir uns damit nur die Kursinformation in QListView anzuzeigen. In einem späteren Abschnitt werden wir spezielle Widgets
benutzen, die Qt für die Ansicht der Datenbank Daten zur Verfügung stellt. Um etwas anzeigen zu können verwenden
Sie den SQL Text in Beispiel 19-2 oder benutzen Sie ein graphisches Frontend das Ihre Datenbank zur Verfügung stellt, um die Daten einzugeben.
Beispiel 19-2. testdata.sql
USE courses;
INSERT INTO courses ( name, description, start_date, end_date, location )
values ( "HTML for Absolute Beginners", "A thorough introduction to HTML
programming", "2001-10-08", "2001-10-12", "Moscow, Russia" );
INSERT INTO courses ( name, description, start_date, end_date, location )
values ( "HTML for Top Cracks", "Everything you ever wanted to know about
HTML", "2002-05-03", "2002-05-07", "Stockholm, Sweden" );
Nun kommen wir zum Ablauf des Display-Programmes. Zuerst stellen wir die Verbindung zur Datenbank her, wie es im letzen Abschnitt beschrieben wurde; dann starten wir eine SELECT
Abfrage auf der Courses Tabelle, stellen die zurückgegebenen Zeilen eine nach der anderen wieder her und geben deren Werte in den QListViewItem
Objekten ein. Mehr Information über QListView und QListViewItem erhalten Sie im Abschnitt ”List Views”.
Den Code für das allererste Display-Programm finden Sie in Beispiel 19-3.
Beispiel 19-3. dblistview.cpp
#include <qapplication.h>
#include <qlistview.h>
#include <qsqldatabase.h>
#include <qsqlquery.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
// Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "courses" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// Create a QListView as the toplevel widget.
QListView courseLV;
app.setMainWidget( &courseLV );
courseLV.addColumn( "Name" );
courseLV.addColumn( "Location" );
courseLV.addColumn( "Start Date" );
courseLV.addColumn( "End Date" );
courseLV.show();
// Create a query and execute it
QSqlQuery query( "SELECT name, location, start_date, end_date FROM courses" );
if( query.isActive() ) {
while( query.next() ) {
new QListViewItem( &courseLV,
query.value( 0 ).toString(),
query.value( 1 ).toString(),
query.value( 2 ).toString(),
query.value( 3 ).toString() );
}
}
return app.exec();
} else
qDebug( "Could not open database" );
} else
qDebug( "Could not activate database driver" );
}
In den ersten paar Zeilen stellen wir einfach eine Verbindung, wie wir es im letzten Abschnitt getan haben, her. Wir nehmen an, dass der Datenbank-Benutzer
”courses” heisst und kein Passwort besitzt. Nachdem die Datenbank erfolgreich mit QSqlDatabase::open() geöffnet wurde, wir setzen ein QListView
als Toplevel-widget das die Daten anzeigen wird. Nun wird es interessant. Wir entwerfen ein QSqlQuery
Object, das den SQL SELECT Befehl beinhaltet. Beachten Sie, dass wir die Abfrage nicht ausführlich starten müssen, sie wird automatisch gestartet beim erstellen des QSqlQuery
Objects und übergibt die in Frage kommenden Textzeilen.
Mit QSqlQuery::isActive()
überprüfen wir dann ob die Abfrage aktiv ist. Eine Abfrage ist dann aktiv wenn sie erfolgreich gestartet wurde. Das bedeutet nicht, dass sie Daten enthält - das Ergebnis kann auch leer sein - aber es bedeutet, dass die SQL Abfrage syntaktisch korrekt war und durchgeführt werden konnte. In einer Schleife wiederholen wir die Anfrage.
QSqlQuery::next() geht zum nächsten Eintrag bis keine Einträge mehr vorhanden sind. Die Methoden first(), last(), prev()
, und seek() werden ebenfalls für die Navigation verwendet.
Beachten Sie, daß es nach dem Durchlauf der Abfrage keinen aktuellen Eintrag gibt auf den der interne Zeiger zeigt unmittelbar bevor der erste Eintrag zurückgegeben wird. Dies ist der
Grund weshalb Sie den Zeiger positioniern müssen wie wir es mit dem Aufruf von next()
getan haben. Dies unterscheidet sich von den Wiederholungen in den QT Container Klassen, die auf das erste Element in dem Container direkt nach der Erstellung zeigen.
Wir stellen die Werte der aktuellen Einträge wieder her, indem wir QSqlQuery::value() aufrufen und vergeben die Spaltennummer. Diese Nummer bezieht sich auf die
Spalten in der Abfrage, nicht aber auf die der betroffenen Tabelle. Deshalb ist es auch nicht ratsam SELECT in einem QSqlQuery
Objekt zu benutzen; der SQL Standard definiert in diesem Fall keine Reihenfolge der zurückgebenden Spalten (obwohl die meisten Datenbanken einfach die Reihenfolge der Tabellen verwenden - aber darauf können Sie sich nicht verlassen).
Der Rückgabewert von QSqlQery::value() ist ein QVariant Object.Die Klasse QVariant
ist QT’s Entsprechung einer C-style Union; sie kann Werte vieler verschiedener Qt Typen enthalten, aber nur jeweils einen Qt Typen zu einem bestimmten Zeitpunkt. Sie können abfragen welchen Qt Typen
QVariant enthält, aber das brauchen wir in diesem Fall nicht. Wir wissen jedenfalls was wir von der Datenbank bekommen. Wir konvertriern den QVariant
zu einem String den QListView anzeigen kann und fügen den String in QListViewItem für jeden Eintrag ein.
Wir starten die Eventschleife der Application nicht bevor wir das ListView
mit Daten gefüllt haben, es ist nicht hilfreich das Benutzerinterface zu starten solange wir keine Datenbankverbindung haben. Dieser Aufschub beweist auch, dass das Qt SQL Modul keine Eventschleife benötigt; die Datenbank Daten sind wiederhergestellt noch bevor die Eventschleife beginnt.
Darstellung 19-1 zeigt das Ergebnis dieses Programmes wenn es mit den Test-Daten, die wir vorher beschrieben haben, auf einer Datenbank läuft.
Nun haben Sie gesehen, wie man Daten einer Datenbank wiederherstellt und sie werden wahrscheinlich zustimmen, dass es nicht sehr schwierig ist. Dennoch mussten Sie in der Lage sein ein
SQL SELECT Statement formulieren zu können. Im nächsten Abschnitt werden wir uns eine einfachere Lösung ansehen, für die Sie keine SQL-Kenntnis benötigen.
Datenwiederherstellung mit Cursors
Das Qt Sql Modul unterstützt eine Klasse die leichter zu benutzen ist als QSqlQuery. Diese Klasse wird QSqlCursor
genannt und Sie benötigen kein Wissen über die Syntax des Sql-Befehls SELECT um Daten von ihrer Datenbank wiederherzustellen.
Bevor wir anfangen, hier noch ein paar Worte zu der Namensgebung der Klasse. Wenn Sie schon vorher Datenbanken programmiert haben, haben Sie wahrscheinlich gelernt, daß cursor eine
Programmkonstruktion ist die Ihnen den Zugriff auf Ihre Datenbank Eintrag für Eintrag erlaubt. Was sich von der internen Handhabung des Sql-Befehls SELECT unterscheidet.
Die Handhabung der Einträge ist mit QSqlQuery
abgedeckt. Wie auch immer, Sie haben gesehen wie Sie die Einträge einen nach dem anderen wieder herstellen und verarbeiten. In unserem Fall durch das Erstellen eines QListViewItem
Objekts für jedes Objekt. Deshalb erhalten Sie bei QSqlCursor
keine satzweise Verarbeitung, Sie haben sie bereits. Stattdessen ermöglicht er einen einfachen Weg um Einträge zu erhalten.
Während Sie einen SELECT Befehl benötigen um Daten aus Ihrer Datenbank mit QSqlQuery wiederherzustellen, benötigen Sie nur die Tabelle von der Sie die Daten mit QSqlCursor
möchten. QSqlCursor
wird dann alle Spalten von der Tabelle holen. Im einfachsten Fall wird er auch alle Zeilen holen. Sie können einfach eine Bedingung erstellen um nur eine Auswahl der Zeilen zu bekommen, es ist wesentlich schwieriger die zurückgegebenen Spalten einzuschränken um eine Auswahl von Spalten zu bekommen. Wenn Sie dies tun wollen verwenden sie stattdessen besser
QSqlQuery.
Die Art wie Sie ausdrücken welche Daten Sie möchten ist der eigentliche Unterschied zwischen QSqlQuery und QSqlCursor. Die Wiederherstellung der
individuellen Werte wird auf die exakt gleiche Art erledigt.
Zur Illustration dieses Konzepts und um es verständlich zu machen wurde das letzte Beispiel in Beispiel 19-4 mit dem QSqlCursor anstatt QSqlQuery
umgeschrieben.
dbcursor.cpp
#include <qapplication.h>
#include <qlistview.h>
#include <qsqldatabase.h>
#include <qsqlcursor.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
// Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "courses" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// Create a QListView as the toplevel widget.
QListView courseLV;
app.setMainWidget( &courseLV );
courseLV.show();
courseLV.addColumn( "Name" );
courseLV.addColumn( "Location" );
courseLV.addColumn( "Start Date" );
courseLV.addColumn( "End Date" );
// Create a cursor and execute it
QSqlCursor cursor( "courses" );
cursor.select();
if( cursor.isActive() ) {
while( cursor.next() ) {
new QListViewItem( &courseLV,
cursor.value( "name" ).toString(),
cursor.value( "location" ).toString(),
cursor.value( "start_date" ).toString(),
cursor.value( "end_date" ).toString() );
}
}
return app.exec();
} else
qDebug( "Could not open database" );
} else
qDebug( "Could not activate database driver" );
}
Der erste entscheidende Unterschied ist der Aufbau der Objekte welche die Datenbank abfragen. Im ersten Beispiel hatten wir:
QSqlQuery query ( "SELECT name, lacation, start_date, end_date FROM courses");
und hier haben wir:
QSqlCursor cursor ("courses");
cursor.select();
Wie Sie sehen können, benötigen Sie nur den Namen der Tabelle die Sie abfragen möchten, aber nun müssen Sie ausdrücklich Fragen um die Abfrage zu starten. Beachten Sie, daß Sie QSqlCursor
nicht benutzen können um über mehrere Tabellen Abfragen zu starten.
Es ist möglich die zurückgegebenen Einträge zu beschränken, auf nur die, die bestimmte Bedingungen erfüllen. Dies wird durch die Bestimmung der Bedingungen in den optionalen Parametern
der Methode QSqlCursor::select() getan. Die Syntax muss wie die der WHERE Klausel in einem SQL-Befehl sein. Und so sind wir wieder bei dem Schreiben von SQL
Anweisungen. Wenn sie zum Beispiel alle Kurse die in Schweden gehalten werden anzeigen möchten können Sie folgendes schreiben:
cursor.select ("location LIKE '%Sweden%'");
Sortieren ist auch möglich. Damit QSqlCursor seine Einträge in einer bestimmten Reihenfolge zurückgibt erzeugen Sie ein QSqlIndex
Objekt und übergeben dies der QSqlCursor::selcet()
Methode. Wenn Sie zum Beispiel alle Kurse die in Schweden gehalten werden sortiert nach dem Anfangsdatum bekommen wollen, dann schreiben Sie:
QSqlCursor.cursor ("courses");
QSqlIndex startIndex = cursor.index("start_date");
cursor.select("location LIKE '%Sweden%'", startIndex);
Beachten Sie, daß Sie das QSqlIndex Objekt nach dem QSqlCursor Object erstellen, aber vor dem select()
Aufruf. Sie brauchen das SqlCursor Objekt zum erstellen des QSqlIndex Objekts und sie brauchen das QSqlIndex
Objekt für den Aufruf der select() Methode. Natürlich ist es möglich ohne bestimmte Unterauswahl zu sortieren, in diesem Fall übergeben Sie der select()
Methode einfach nur das QSqlIndex Objekt.
Es ist auch möglich über mehrere Spalten zu sortieren. Sehen Sie sich hierzu die Dokumentation von QSqlIndex::append() an.
Daten Anzeige
In diesem Abschnitt sehen wir uns an wie wir Daten schöner darstellen können. Sie könnten der Meinung sein, daß wir dies mit der QListView
-Lösung getan haben. In der Tat, diese Lösung genügt für manche Anwendungen, aber QListView
ist oft nicht ausreichend und Sie benötigen des öffteren Geeigneteres um Daten aus einer Datenbank darzustellen. Dies ist vor allen Dingen der Fall wenn der Anwender die angezeigten Daten bearbeiten muss.
Wir sollten hinzufügen daß einige Dinge, die wir hier machen werden, einfacher mit dem Qt Designer zu erstellen sind. Wie es im 2. Kapitel des Buches beschrieben wurde. Wie auch immer,
es ist besser wenn man versteht was sich im Hintergrund abspielt, und manchmal benötigen Sie die Flexibilität die Ihnen nur Handprogrammierung bieten kann.
Der einfachste Weg um Daten anzuzeigen ist die Benutzung einer QDataTabel. Diese Klasse kann mit QSqlCursor
gefüttert werden und bekommt von sich aus die Daten. Im Grunde übergeben Sie ein QSqlCursor Object in den Konstruktor, fordern QDataTabel
auf die Daten zu lesen, und schon gehts los. Beispiel 19-5 zeigt eine einfache Möglichkeit wie Sie QDataTable benutzen.
#include <qapplication.h>
#include <qsqldatabase.h>
#include <qsqlcursor.h>
#include <qdatatable.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
// Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "courses" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// Create a cursor
QSqlCursor cursor( "courses" );
// Create a QDataTable as the toplevel widget
QDataTable* courseDT = new QDataTable( &cursor );
courseDT->addColumn( "name", "Name" );
courseDT->addColumn( "location", "Location" );
courseDT->addColumn( "start_date", "Start Date" );
courseDT->addColumn( "end_date", "End Date" );
courseDT->refresh();
courseDT->show();
app.setMainWidget( courseDT );
int ret = app.exec();
delete courseDT;
return ret;
} else
qDebug( "Could not open database" );
} else
qDebug( "Could not activate database driver" );
}
Als erstes sollten Sie feststellen daß wir keine Schleife haben in der wir die Wiederherstellung der Daten wiederholen, QDataTabel regelt die Wiederherstellung
intern. Wir müssen nur ein QSqlCursor Objekt übergeben (QSqlQuery ist hier nicht möglich) und QDataTable::refresh()
aufrufen um Daten einzulesen.
Sie können das ganze etwas kürzen indem Sie die addColumn() Aufrufe weglassen, die QDataTable
mitteilen wie die Überschriften der Spalten lauten und wo die Daten für die Spalten zu finden sind. Wenn Sie als zweites Argument true dem Konstruktor von QDataTable
übergeben , liest QDataTable
den Spaltenbezeichner von der Datenbank indem einfach der Spaltenname von der Datenbank genommen wird. Dies ist nur selten eine gute Idee. Die Spaltennamen in einer Datenbank folgen einer syntaktischen Regel für die Namensgebung die für die Datenbank nützlich ist und nicht für einen Anwender. Es ist gewiss angenehmer eine Spalte mit Start Date manuell zu bezeichnen als stillschweigend mit start_date bezeichnen zu lassen.
Der Programmcode beinhaltet noch ein anderes Feature das sie aufschrecken könnte weil es nicht aus dem Code ersichtlich ist. Sie können Werte in der Tabelle ändern und diese Änderung
wird in Datenbanktabelle geschrieben. Versuchen Sie es: Starten Sie das Programm, ändern Sie einige Werte, beenden Sie das Programm und starten Sie es erneut. Sie sehen die neuen
Werte die Sie eben eingetragen haben. Sie können genauso neue Einträge hinzufügen oder existierende löschen, indem Sie das built-in Kontextmenü benutzen das Einträge für Insert,
Update, und Delete beinhaltet.
Mit dem Aufruf QDataTable::setAutoEdit(false)
können Sie diese automatische Editierung abschalten. Vergessen Sie nicht, daß Sie auf ON voreingestellt ist. Sie können sogar QDataTable
abfragen, damit der Anwender seine Änderung bestätigt bevor sie in die Datenbank geschrieben werden. QDataTable:: setConfirmCancel(), QDataTable::setConfirmDelete(), QDataTable::setConfirmEdit()
kontrollieren diesen Schritt. Wir werden über die Manipulation von Daten im nächsten Abschnitt mehr erfahren.
Sie können einige Features programmtechnisch verändern. Zum Beispiel können Sie mit setNullText()
einen Text festlegen der als Platzhalter bei einem NULL-Wert erscheint, dasselbe geht mit Boolean-Werten mit setTrueText() and setFalseText()
. Es ist ebenso programmierbar Spalten hinzuzufügen, ändern oder verschieben mit addColumn(), setColumn() beziehungsweise removeColumn()
.
Natürlich sind Tabellen eine sehr komprimierte Form um Daten darzustellen und zu editieren. Anwender möchten oftmals etwas freundlicheres für das Auge welches einfacher zu betrachten
ist. Dies ist der Grund warum die meisten Datenzugriffsprogramme eine formbasierende Darstellung der Daten beinhalten, in der für jeden Eintrag ein bezeichnendes Eingabefeld
besteht, und nur ein Eintrag zu jedem Zeitpunkt angezeigt wird.
Nach unseren bisherigen Experimenten mit den Qt SQL Modul würden Sie erwarten, dass Sie nur einige Zeilen Programmcode zu schreiben haben und bekommen dann eine Form um Daten zu lesen
oder zu ändern. Ich befürchte, daß ich Sie hier enttäuschen muss. Um formbasierend Daten anzuzeigen und zu manipulieren müssen Sie viel selbst tun, wenn Sie in den Daten navigieren
wollen sind Sie vollständig auf sich selbst gestellt. Wir werden genügend Beispiele anbieten damit Sie Ihr eigenes Programm schreiben können.
Um Daten in einer Form wiederzugeben, müssen Sie folgende Schritte abarbeiten:
1. Erstellen Sie ein Label und ein Feld für jede Spalte die sie bearbeiten möchten. Um es einfach zu machen, werden wir mit LineEdit beginnen und zeigen Ihnen später wie man spezielle
Widgets benutzt.
2. Erstellen Sie ein QSqlCursor welcher die Daten empfängt die darzustellen sind.
3. Erstellen Sie ein QSqlFrom
Objekt welches die Beziehung zwischen den Feld-Widgets und den Einträgen in dem Cursor aufrecht erhält und die Felder von dem Cursor liest.
Beachten Sie bitte, daß wir nicht über die Navigation oder das Zurückschreiben von Daten gesprochen haben. Dieses Thema werden wir anschliessend behandeln. Sehen sie sich zuerst das
nächste Beispiel an, welches alle erwähnten Schritte beinhaltet.
#include <qapplication.h>
#include <qgrid.h>
#include <qlabel.h>
#include <qsqldatabase.h>
#include <qsqlcursor.h>
#include <qsqlform.h>
#include <qdataview.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
//Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "courses" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// Create a QGrid as the toplevel widget.
QGrid formGR( 2, Qt::Horizontal, 0 );
formGR.setSpacing( 10 );
app.setMainWidget( &formGR );
// Set up fields for displaying the data
new QLabel( "Name", &formGR );
QLabel* nameLA = new QLabel( &formGR );
nameLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
new QLabel( "Location", &formGR );
QLabel* locationLA = new QLabel( &formGR );
locationLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
new QLabel( "Start Date", &formGR );
QLabel* startDateLA = new QLabel( &formGR );
startDateLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
new QLabel( "End Date", &formGR );
QLabel* endDateLA = new QLabel( &formGR );
endDateLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
formGR.show();
// Create a cursor and execute it
QSqlCursor cursor( "courses" );
cursor.select();
// Go to the first record
cursor.next();
// Create a QSqlForm and configure it
QSqlForm form;
form.setRecord( cursor.primeUpdate() );
form.insert( nameLA, "name" );
form.insert( locationLA, "location" );
form.insert( startDateLA, "start_date" );
form.insert( endDateLA, "end_date" );
form.readFields();
return app.exec();
} else
qDebug( "Could not open database" );
} else
qDebug( "Could not activate database driver" );
}
In diesem Beispiel, nach dem wir die Datenbankverbindung wie gewöhnlich aufgebaut haben, erzeugen wir ein QGrid Objekt als Toplevel Widget welches QLabel
Objekte enthält die paarweise vorkommen. Eines von jedem Paar ist namenlos und bezeichnet das Andere, welches die Daten anzeigen wird. Danach erzeugen wir wie gewöhnlich ein QCursor
Objekt, lassen es seine Abfrage machen, und positionieren den Cursor auf den ersten Eintrag mit next(). Danach wird es interessant - wir erzeugen in QSqlForm
Object und informieren dies über zwei Dinge: den aktuellen Eintrag und wie die Spalten der Datenbank Einträge mit unseren Feldern verknüpft sind. Der erste Schritt ist mit der folgenden Zeile getan:
form.setRecord(cursor.primeUpdate());
Die Methode QSqlCursor::primeUpdate()
bereitet einen Eintrag für die Aktualisierung (sowie Anzeige) vor, indem er in einen sogenannten Edit-Buffer kopiert wird, der vom Typ QSqlRecord
ist. Dieser Zeiger wird der Methode QSqlForm::setRecord() übergeben, so daß das QSqlForm
Objekt über die Eintragsdaten Bescheid weis. Wie auch immer, es muss wissen wie man die Daten ausgibt was in den nächsten drei Zeilen mit QSqlForm::insert()
getan wird. Die insert()
Methode erwartet einen Zeiger auf das Widget welches als Feld benutzt wird und den Namen der Datenbankspalte. Abschliessend werden mit dem Aufruf QSqlForm::readField()
die Daten von dem Edit-Buffer in die Labels kopiert. Sie fragen sich wahrscheinlich, wie auf das jeweilige Widget zugegriffen wird. Es wird mit einer sogenannten Property Map zugegriffen, die für jedes Widget festlegt wie Werte eingetragen und wiederhergestellt werden.
Beim Ausführen des Programms werden Sie feststellen, daß es nicht viel zum bedienen gibt, so können Sie nicht in den Einträgen navigieren, Sie sehen nur den ersten Eintrag und können
nicht zum nächsten gehen. Dieses Problem werden wir gleich beheben.
Bevor wir uns ansehen wie wir Navigations Buttons bereitstellen, sollte ich darauf hinweisen, wenn Sie Qt Designer benutzen, können Sie einen Wizard verwenden der Navigations Buttons
erstellt (und automatisch Buttons für Eintragen, Aktualisieren, und Löschen der Einträge) und dementsprechend verbindet. Mit dieser Option haben Sie keine grosse Flexibilität. Zum
Beispiel bekommen Sie nur Navigations Buttons um einen Eintrag zurück, einen Eintrag vor oder zum Ende zu gehen. Andere Navigations Features wie sie bei anderen Systemen zu finden
sind, wie beispielsweise 10 Einträge vor oder zurück sind ohne Handarbeit nicht möglich.
Wenn wir in der Qt Reference Documentation nachsehen um etwas über diese Features zu lernen, könnten wir etwas verwirrt sein. Zwei Widgets QDataBrowser und QDataView
behaupten für Form-based Daten Unterstützung anzubieten. Dies ist nur im begrenzten Ausmass richtig. Sie müssen selber viele Dinge einstellen, die Feld Widgets und Navigations Buttons einbinden (ohne Qt Designer zu verwenden). Alles was Ihnen
QDataView gibt ist eine Methode mit der Sie die Anzeige auffrischen wenn ein Eintrag geändert wurde. Für alles andere sollten Sie QDataBrowser
oder etwas Eigenes benutzen. Die Qt Referenz vertritt die Ansicht, daß Sie QDataView für nur Lese-Aktionen und QDataBrowser
bei Lese- und Schreib-Aktionen verwenden sollten. Ich habe noch keine vernünftige Anwendung für QDataView
gefunden. Wenn alles was Sie wollen das Anzeigen von Einträgen ist, können Sie dies auch ohne QDataView tun.
QDataBrowser
ist es! Unsere Absicht ist es die Daten wie im letzten Beispiel anzuzeigen, aber unter hinzufügen von Buttons für Vor und Zurück (wir lassen die Buttons für den ersten und letzten Eintrag aus, sowie den schnellen Vorlauf und Rücklauf um den Code kurz zuhalten - sie werden genauso gehandhabt).
Weder QDataBrowser noch QDataView bieten von sich aus irgendeine visuelle Erscheinung, aber dafür brauchen wir auf jedenfall QDataBrowser
, wir könnten es als Container Widget für unsere Felder und Buttons ansehen.
#include <qapplication.h>
#include <qdatabrowser.h>
#include <qgrid.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qsqldatabase.h>
#include <qsqlcursor.h>
#include <qsqlform.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
//Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "courses" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// We need a QDataBrowser anyway, so we can use it as
// the toplevel widget. We give it some layout management
// as well.
QDataBrowser formDB;
app.setMainWidget( &formDB );
QVBoxLayout* toplevelVBL = new QVBoxLayout( &formDB, 10 );
QGridLayout* fieldGL = new QGridLayout( 4, 2, 10 );
toplevelVBL->addLayout( fieldGL );
// Set up fields for displaying the data
fieldGL->addWidget( new QLabel( "Name", &formDB ), 0, 0 );
QLabel* nameLA = new QLabel( &formDB );
nameLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
nameLA->setMinimumWidth( 100 );
fieldGL->addWidget( nameLA, 0, 1 );
fieldGL->addWidget( new QLabel( "Location", &formDB ), 1, 0 );
QLabel* locationLA = new QLabel( &formDB );
locationLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
locationLA->setMinimumWidth( 100 );
fieldGL->addWidget( locationLA, 1, 1 );
fieldGL->addWidget( new QLabel( "Start Date", &formDB ), 2, 0 );
QLabel* startDateLA = new QLabel( &formDB );
startDateLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
startDateLA->setMinimumWidth( 100 );
fieldGL->addWidget( startDateLA, 2, 1 );
fieldGL->addWidget( new QLabel( "End Date", &formDB ), 3, 0 );
QLabel* endDateLA = new QLabel( &formDB );
endDateLA->setFrameStyle( QFrame::Panel | QFrame::Sunken );
endDateLA->setMinimumWidth( 100 );
fieldGL->addWidget( endDateLA, 3, 1 );
// Set up the navigation buttons
QHBoxLayout* buttonsHBL = new QHBoxLayout( 5 );
toplevelVBL->addLayout( buttonsHBL );
QPushButton* prevPB = new QPushButton( "<", &formDB );
QObject::connect( prevPB, SIGNAL( clicked() ),
&formDB, SLOT( prev() ) );
QObject::connect( &formDB, SIGNAL( prevRecordAvailable( bool ) ),
prevPB, SLOT( setEnabled( bool ) ) );
buttonsHBL->addWidget( prevPB );
QPushButton* nextPB = new QPushButton( ">", &formDB );
QObject::connect( nextPB, SIGNAL( clicked() ),
&formDB, SLOT( next() ) );
QObject::connect( &formDB, SIGNAL( nextRecordAvailable( bool ) ),
Es gibt hier nichts spezielles wie die Widgets ausgerichtet werden, abgesehen davon daß wir ausdrücklich das Layoutmanagement mit QVBoxLayout, QHBoxLayout und QGridLayout benutzen. Wie auch immer, wir stellen ein paar interessante Dinge mit den Navigationsbuttons an. Zuerst
verbinden wir ihr clicked() Signal mit dem prev() und next() Slot von unserm QDataBrowser Objekt. Dies ist im allgemeinen ausreichend um in den Einträgen vor und zurückzugehen. Das QDataBrowser
Objekt benutzt die Form und den Cursor um dieses Ziel zu erreichen und füllt die Form dementsprechend. Wir verbinden ebenso die Signale prevRecordAvailable(bool)
und nextRecordAvailable(bool) von dem QDataBrowser Objekt mit dem setEnabled(bool)
Slot der Pushbuttons. Diese Verbindung aktiviert oder deaktiviert die Buttons automatisch, abhängig davon ob es einen vorherigen oder nächsten Eintrag gibt zu dem gegangen werden kann.
Es gibt hier nichts spezielles wie wir den Cursor erstellen, und sie haben gesehen wie das Form Object die Widget-Felder mit den Datenbank-Spalten verbindet. Wir tragen die Form und
den Cursor in dem QDataBrowser Object ein und diesem Object teilen wir mit setReadOnly(true)
mit, daß wir nichts bearbeiten möchten und nun die vorher erwähnten Signale prevRecordAvailable(bool) und nextRecordAvailable(bool)
mit setBoundaryChecking() senden wollen. Abschliessend benutzen wir einmal QDataBrowser::next()
um den ersten Eintrag zu bekommen.
QDataBrowser
hat noch eine Anzahl von anderen Methoden, welche meistens zum Bearbeiten verwendet werden. Diese werden im nächsten Kapitel behandelt. Es ist auch möglich mit setSort()
zu sortieren und eine Unterauswahl der Einträge mit setFilter
herauszufiltern. Es werden ebenso Signale gesendet wenn der Anwender Operationen wie aktualisieren und löschen startet, und Sie werden informiert, wenn die angezeigten Einträge verändert wurden.
Manipulation von Daten
Wir haben bis jetzt nur Daten angezeigt, aber nichts geändert. In diese Kapitel werfen wir einen Blick auf die Fragen, die aufkommen, wenn Sie Ihre Daten speichern wollen.
Der einfachste Weg um geänderte Daten zurückzuschreiben ist mit QDataBrowser. Hier haben Sie die 3 Slots insert(), update() und del(). Einfache Buttons bieten sich an um diese Aufgabe zu erledigen indem ihr clicked()
Signal mit den Slots verbunden wird. Sie können QDataBrowser den Andwender nach einer Bestätigung mit setConfirmEdits(),
setConfirmInsert(), setConfirmUpdate(), setConfirmDelete() und setConfirmCancel()
abfragen. Das folgende Listing zeigt das vorhergehende Beispiel mit dem Unterschied, daß Sie neue Einträge schreiben und vorhandene ändern oder löschen können.
#include <qapplication.h>
#include <qdatabrowser.h>
#include <qdatetimeedit.h>
#include <qgrid.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qsqldatabase.h>
#include <qsqlcursor.h>
#include <qsqlform.h>
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
//Connect to the database
QSqlDatabase* courseDB = QSqlDatabase::addDatabase( "QMYSQL3" );
if( courseDB ) {
courseDB->setHostName( "localhost" );
courseDB->setDatabaseName( "courses" );
courseDB->setUserName( "kalle" );
courseDB->setPassword( "" );
if( courseDB->open() ) {
// Database could be opened.
// We need a QDataBrowser anyway, so we can use it as
// the toplevel widget. We give it some layout management
// as well.
QDataBrowser formDB;
app.setMainWidget( &formDB );
QVBoxLayout* toplevelVBL = new QVBoxLayout( &formDB, 10 );
QGridLayout* fieldGL = new QGridLayout( 4, 2, 10 );
toplevelVBL->addLayout( fieldGL );
// Set up fields for displaying the data
fieldGL->addWidget( new QLabel( "Name", &formDB ), 0, 0 );
QLineEdit* nameED = new QLineEdit( &formDB );
nameED->setMinimumWidth( 100 );
fieldGL->addWidget( nameED, 0, 1 );
fieldGL->addWidget( new QLabel( "Location", &formDB ), 1, 0 );
QLineEdit* locationED = new QLineEdit( &formDB );
locationED->setMinimumWidth( 100 );
fieldGL->addWidget( locationED, 1, 1 );
fieldGL->addWidget( new QLabel( "Start Date", &formDB ), 2, 0 );
QDateEdit* startDateDE = new QDateEdit( &formDB );
startDateDE->setMinimumWidth( 100 );
fieldGL->addWidget( startDateDE, 2, 1 );
fieldGL->addWidget( new QLabel( "End Date", &formDB ), 3, 0 );
QDateEdit* endDateDE = new QDateEdit( &formDB );
endDateDE->setMinimumWidth( 100 );
fieldGL->addWidget( endDateDE, 3, 1 );
// Set up the navigation buttons
QHBoxLayout* buttonsHBL = new QHBoxLayout( 5 );
toplevelVBL->addLayout( buttonsHBL );
QPushButton* prevPB = new QPushButton( "<", &formDB );
QObject::connect( prevPB, SIGNAL( clicked() ),
&formDB, SLOT( prev() ) );
QObject::connect( &formDB, SIGNAL( prevRecordAvailable( bool ) ),
prevPB, SLOT( setEnabled( bool ) ) );
buttonsHBL->addWidget( prevPB );
QPushButton* nextPB = new QPushButton( ">", &formDB );
QObject::connect( nextPB, SIGNAL( clicked() ),
&formDB, SLOT( next() ) );
QObject::connect( &formDB, SIGNAL( nextRecordAvailable( bool ) ),
nextPB, SLOT( setEnabled( bool ) ) );
buttonsHBL->addWidget( nextPB );
// Set up the manipulations buttons
QHBoxLayout* manipbuttonsHBL = new QHBoxLayout( 5 );
toplevelVBL->addLayout( manipbuttonsHBL );
QPushButton* insertPB = new QPushButton( "Insert", &formDB );
QObject::connect( insertPB, SIGNAL( clicked() ),
&formDB, SLOT( insert() ) );
manipbuttonsHBL->addWidget( insertPB );
QPushButton* updatePB = new QPushButton( "Update", &formDB );
QObject::connect( updatePB, SIGNAL( clicked() ),
&formDB, SLOT( update() ) );
manipbuttonsHBL->addWidget( updatePB );
QPushButton* deletePB = new QPushButton( "Delete", &formDB );
QObject::connect( deletePB, SIGNAL( clicked() ),
&formDB, SLOT( del() ) );
manipbuttonsHBL->addWidget( deletePB );
// Show the form
formDB.show();
// Create a cursor and execute it
QSqlCursor* cursor = new QSqlCursor( "courses" );
cursor->select();
cursor->next();
// Create a QSqlForm and configure it
QSqlForm* form = new QSqlForm( &formDB );
form->insert( nameED, "name" );
form->insert( locationED, "location" );
form->insert( startDateDE, "start_date" );
form->insert( endDateDE, "end_date" );
// Some configuration of the browser.
// Set the cursor and the form
formDB.setForm( form );
formDB.setSqlCursor( cursor, true );
formDB.setBoundaryChecking( true );
formDB.refresh();
formDB.next(); // go to first record
return app.exec();
} else
qDebug( "Could not open database" );
} else
qDebug( "Could not activate database driver" );
}
Abgesehen vom Einfügen der Manipulation Buttons haben wir nur die QLabel
Widgets ausgetauscht, ab jetzt werden die Werte mit bearbeitbaren Widgets angezeigt. QLineEdit für die String-Wert und QDataEdit
für die Datums-Einträge.
Sie können die Manipulation auch auf der SQL-Command-Ebene ausführen, schliesslich es das was QDataBrowser und QSqlCursor
zu tun haben. Wenn Sie eine Verbindung haben sie mit QSqlQuery einfach SQL-Befehle zusammenstellen. Danach können Sie mit isActive
überprüfen ob die manipulierenden Befehle ausgeführt wurden. Ist dies der Fall können Sie mit QSqlQuery::numRowAffected()
überprüfen wieviele Zeilen von dem Befehl betroffen wurden. Hier sehen Sie wie Sie die Kurse von Stockholm nach Götheburg verschieben.
/*Verschiebt alle Kurse von Stockholm nach Gothenburg */
QSqlQuery updateQuery( “UPDATE courses SET location =
\”Gothenburg, Sweden\”
WHERE locatio = \”Stockholm, Sweden\”;
if ( query.isActive() )
qDebug(“%d rows changed”, query.numRowsAffected() );
Eine Alternative zu den zwei extremen Lösungen ist die Verwendung von QsqlCursor. Um einen Eintrag zu ändern, müssen Sie zuerst die Einträge auswählen die
Sie ändern möchten und wiederholen Sie dies für jeden ausgewählten Eintrag. Für jeden Eintrag müssen Sie sagen, daß Sie ihn mit QSqlCursor::primeUpdate()
ändern möchten. QSqlCurser kopiert die Daten in einen Editbuffer. Diese Methode gibt einen Zeiger auf ein QSqlRecord
Object zurück das den Editbuffer darstellt. Sie können nun Ihre Änderung mit dem Aufruf von QSqlRecord::setValue() ausführen. Abschliessend rufen Sie QSqlCursor::update() auf um die neuen Werte vom Editbuffer in die Datenbank zurückzurschreiben. Der Code sieht folgendermassen aus.
//Erstellt einen Cursor auf die SQL Datenbank
QSqlCursor cursor(“courses”);
//Wählt nur die Einträge für Stockholm aus
cursor.select(“WHERE location = \”Stockholm, Sweden\”);
//Wiederholung der Einträge
if (cursor.next() )
{
//kopiere Daten in den EditBuffer
QSqlRecord* editBuffer = cursor.primeUpdate();
//bearbeite den Buffer
editBuffer->setValue(“location”, “Gothenburg, Sweden”);
//schreibe die Daten zurück
cursor.update();
}
Einfügen und Löschen von Einträgen funktioniert ähnlich. Hier benutzen Sie QSqlCursor::primeInsert()
um einen frischen ungefüllten Editbuffer zu bekommen und zum eintragen QSqlCursor::insert() um den Buffer in die Datenbank einzufügen. Sie können QSqlCursor::primeDelete und QSqlCursor::del()
verwenden um die ausgewählten Einträge von der Datenbank zu löschen. Beachten Sie, daß primeDelete() ebenfalls einen Zeiger auf QSqlRecord
zurückliefert aber Sie sollten Ihn für gewöhnlich nicht benutzen, da Sie sowieso dabei sind die Daten zu löschen.
Einiges mehr
in diesem Abschnitt behandeln wir einige datenbankrelevanten Themen die wir noch nicht angesprochen haben.
DatenBanken beinhalten nicht nur Daten, sondern auch Metadaten. Metadaten sind beschreibende Daten die helfen die gespeicherten Daten zu organisieren. Namen der Tabellen, Eigenschaften
und Spalten einer bestimmten Tabelle sind Beispiele von Metadaten. Das Qt SQL Modul unterstützt Metadaten Informationen nicht so umfangreich wie andere Datenbankzugriffsbibliotheken,
aber wir können wenigstens an einige Informationen herankommen. Für den Anfang bekommen Sie mit QSqlDatabase::tables
die Namen der Tabellen. Dies setzt natürlich voraus daß Sie eine offene Verbindung zur Datenbank haben. Diese Methode liefert ein QStringList
das die Tabellennamen beinhaltet zurück.
Nachdem Sie die Tabelle, an der Sie interessiert sind, gefunden haben, können Sie mit QSqlDatabase::record()
und der Übergabe des Tabellennamens die Spalten abfragen. Diese Methode liefert einen Zeiger auf ein QSqlRecord Object
zurück, dies ist gewöhnlich nicht das was Sie wollen, aber Sie können einfach die Methode QSqlRecord::toStringList()
benutzen um eine Stringliste zu bekommen. Beachten Sie daß die Anordnung der Tabellen undefiniert ist , es muss nicht die selbe sein wie die Tabellen definiert wurden. Die Anordnung ist normalerweise nicht von Bedeutung da Sie typischerweise an bestimmte Namen von Tabellen interessiert sind und nicht wo sie physikalisch in der Datenbank angesiedelt sind.
Abschliessend, als letztes Teil der Metadaten, mit dem Aufruf von QSqlDatabase::primaryIndex()
und der Übergabe des Tabellennamens können Sie den Primary Index (den Index des Primary-Key) einer Tabelle bekommen. Diese Methode liefert ein QSqlIndex Object
zurück. Der Aufruf von name() des Objects gibt Ihnen den Namen sofern einer existiert.
Weitere Features
Es gibt ein paar Features, die wir in diesem Kapitel aus platzsparenden Gründen noch nicht angesprochen haben, da Sie sie sehr selten, wenn überhaupt, brauchen werden.
Hier sind ein paar Tipps, wie Sie diese Features benutzen können.
Wenn Sie Form benutzen und dafür ein Widget einsetzen, das Sie selbst geschrieben haben, dann müssen Sie Qt sagen, wie es dieses Widget mit Datenbankdaten füllen soll und wie es einen
möglicherweise veränderten Wert zurückholt. Tun Sie dies, indem Sie Ihr eigenes Beispiel der QSqlPropertyMap
installieren. Um zu lernen wie Sie das machen sehen Sie in der Referenzdokumentation unter QSqlPropertyMap
nach. Wenn Sie ihre eigenen Editoren in den Tabellen benutzen wollen sehen Sie unter QSqlEditorFactory nach.
Es ist möglich das Grundeinstellung des cursors zu ändern; zum Beispiel stellen Sie sich einen cursor vor, der automatisch Berechnungen über Felder vornimmt. Dies wird gemacht, indem
man QSqlCursor subklassifiziert. Die Beschreibung des SQL-Moduls in der Refernzdokumentation hat eine ausführliche Erklärung wie Sie dies bewerkstelligen.
|