QT и Базы данных
Эта статья является незавершенной! Эта статья нуждается в доработке. Если Вы можете помочь, сделайте это в соответствии с руководством по оформлению. |
Содержание
Работа с базами данных в Qt4
В библиотеке Qt4 имеются драйверы для работы со следующими СУБД:
- QDB2 -- IBM DB2 версии не ниже 7.1;
- QIBASE -- Borland InterBase;
- QMYSQL -- MySQL;
- QOCI -- Oracle;
- QODBC -- ODBC (в том числе Microsoft SQL Server);
- QPSQL -- PostgreSQL;
- QSQLITE -- SQLite версии не ниже 3;
- QSQLITE2 -- SQLite версии 2;
- QTDS -- Sybase Adaptive Server.
В Qt4 Open Source Edition отсутствует поддержка коммерческих СУБД Oracle, Sybase и DB2, т.к. драйверы для них распространяются под лицензией, не совместимой с GPL.
Компиляция SQL-драйверов
При компиляции Qt4 по умолчанию устанавливается только драйвер QSQLITE. Чтобы подключить поддержку остальных СУБД, надо при запуске configure указать параметры -qt-sql-mysql, -qt-sql-psql, -qt-sql-odbc, -qt-sql-ibase для компиляции соответствующих статических библиотек или -plugin-sql-mysql, -plugin-sql-psql, -plugin-sql-odbc, -plugin-sql-ibase для компиляции динамических библиотек. Компилятору и компоновщику потребуются заголовочные файлы и библиотеки, поставляемые вместе с соответствующими СУБД. Поэтому при запуске configure обычно требуется указать параметр -Iпуть_к_h-файлам и -Lпуть_к_lib. В результате компиляции в каталоге lib появится нужная динамическая библиотека. Чтобы узнать, драйверы каких СУБД уже установлены, можно открыть демонстрационную программу demos/sqlbrowser.
Если библиотека Qt уже скомпилирована, то можно просто зайти в каталог qt/src/plugins/sqldrivers/НужныйДрайвер и выполнить компиляцию находящегося там проекта *.pro.
Например, для сборки драйвера ODBC в Windows:
cd %QTDIR%\src\plugins\sqldrivers\odbc qmake -o Makefile odbc.pro mingw32-make
При использовании Visual C++ последняя команда, разумеется, nmake. В Linux:
cd $QTDIR/src/plugins/sqldrivers/odbc qmake "INCLUDEPATH+=/usr/local/unixODBC/include" "LIBS+=-L/usr/local/unixODBC/lib -lodbc" make
Для сборки драйвера PostgreSQL в Windows:
cd %QTDIR%\src\plugins\sqldrivers\psql qmake -o Makefile "INCLUDEPATH+=C:\psql\include" "LIBS+=C:\psql\lib\libpq.a" psql.pro mingw32-make
(все пути не должны содержать пробелов). Для компилятора Microsoft Visual C++ указывается библиотека C:\psql\lib\ms\libpq.lib. В Linux:
cd $QTDIR/src/plugins/sqldrivers/psql qmake -o Makefile "INCLUDEPATH+=/usr/include/pgsql" "LIBS+=-L/usr/lib -lpq" psql.pro make
Заметим, что драйвер QPSQL может работать только с кодировкой utf-8. Для сборки драйвера MySQL в Windows:
cd %QTDIR%\src\plugins\sqldrivers\mysql qmake -o Makefile "INCLUDEPATH+=C:\MySQL\include" "LIBS+=C:\MySQL\lib\opt\libmysql.a" mysql.pro mingw32-make
(все пути не должны содержать пробелов). Для компиляции с помощью Microsoft Visual C++ вместо libmysql.a надо указать libmysql.lib. В Linux:
cd $QTDIR/src/plugins/sqldrivers/mysql qmake -o Makefile "INCLUDEPATH+=/usr/local/include" "LIBS+=-L/usr/local/lib -lmysqlclient_r" mysql.pro make
Результат компиляции (динамические библиотеки) помещается в каталог plugins/sqldrivers.
Обычно для Windows поставляются библиотеки *.lib, рассчитанные на использование компилятора Microsoft. Поскольку свободная версия Qt4 для Windows поддерживает только MinGW, то перед сборкой SQL-драйвера придётся сначала сгенерировать def-файл, например:
cd c:\mysql\lib\opt reimp -d libmysql.lib
(утилита reimp поставляется вместе с MSYS), а затем сформировать библиотеку *.a:
dlltool -d libmysql.def -l libmysql.a libmysql.dll -k
Перед выполнением приложений Qt, если используются SQL-драйверы, скомпилированные в виде динамических библиотек, требуется позаботиться о том, чтобы сами эти библиотеки, а также, в свою очередь, используемые ими библиотеки, поставляемые с соответствующими СУБД, были доступны по стандартным путям поиска.
Если пути к библиотекам были добавлены в системную переменную PATH уже после того, как любое приложение Qt попыталось загрузить некоторый SQL-драйвер, то в Windows придётся очистить ветку реестра HKEY_CURRENT_USER\Software\Trolltech\OrganizationDefaults.
В окончательных релизах, поставляемых пользователю, Qt-драйверы qsql* лучше всего размещать в подкаталоге ./sqldrivers, а используемые ими библиотеки -- либо в каталоге самой программы, либо в стандартном каталоге Windows/system32 (для системы Windows) или usr/local/lib (для Linux).
Подключение в базе данных и выполнение SQL-запросов
Для подключения к базе данных надо указать название SQL-драйвера, например:
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "MyDB1");
Второй необязательный параметр позволяет задать имя соединения. Затем указывается имя сервера, название базы данных, имя пользователя и пароль:
db.setHostName("localhost"); // или, например, "my1.server.ru" db.setDatabaseName("mydb1"); db.setUserName("root"); db.setPassword("mypassword");
Если сервер использует нестандартный порт, то придётся задать и его:
db.setPort(НомерПорта);
В случае использования QODBC имя сервера не требуется, а вместо названия базы данных указывается ODBC-псевдоним (алиас). SQLite не поддерживает авторизацию пользователей, поэтому ему требуется указать только имя файла данных. Предопределённое имя ":memory:" позволяет размещать временную базу данных в оперативной памяти.
После того, как все параметры подключения заданы, можно открыть соединение:
bool connected = db.open();
Если подключение установить не удалось, то не плохо бы узнать описание ошибки и сообщить его пользователю:
if (!connected) { QMessageBox::critical( // Диалог с сообщением об ошибке. parent, // Родительский виджет. QObject::tr("Database Error"), // Заголовок. db.lastError().text()); // Текст сообщения. return false; // Вернуть признак неудачного подключения. }
Если подключение установлено, то можно выполнить любой SQL-запрос, например:
QSqlQuery query; query.exec("SELECT id, name, salary FROM empl WHERE salary>=1000");
или
QSqlQuery query("SELECT id, name, salary FROM empl WHERE salary>=1000");
Здесь запрашиваются номера id, имена name и оклады salary всех работников из таблицы empl, у которых оклад не ниже 1 000. Обратите внимание, что если при создании объекта QSqlQuery указан текст запроса на языке SQL, то этот запрос сразу выполняется. В обоих случаях в конструкторе запроса можно указать базу данных QSqlDatabase, с которой он будет работать. Но как правило, приложение открывает только одно соединение с единственной базой данных, поэтому этот параметр принимает значение по умолчанию.
Если при выполнении запроса возникла ошибка, то метод lastError() позволяет вывести на экран её описание:
if ( ! query.isActive() ) QMessageBox::warning( this, tr("Database Error"), query.lastError().text() );
Иначе можно получить данные, которые сервер вернул в качестве результата:
while ( query.next() ) { qint64 id = query.value(0).toLongLong(); QString name = query.value(1).toString(); double salary = query.value(2).toDouble(); // ....... }
Метод QSqlQuery::next() переводит курсор на очередную запись результирующего набора данных или возвращает false, если достигнут его конец. Метод value(номер_столбца) возвращает значение типа QVariant, которое надо преобразовать к нужному типу с помощью методов QVariant::toInt, QVariant::toLongLong, QVariant::toString, QVariant::toDouble, QVariant::toDate, QVariant::toDateTime и т.д. Кроме next(), для навигации по набору данных можно использовать методы first(), last(), previous(), seek(int index, bool relative=false). Для увеличения быстродействия набор данных лучше сделать однонаправленным, вызвав метод QSqlQuery::setForwardOnly(true) до выполнения запроса, после этого можно использовать только next().
Метод QSqlQuery::size() возвращает число записей, полученных в результате выполнения запроса SELECT (-1, если была ошибка или если драйвер данной СУБД не поддерживает эту функцию). При выполнении SQL-запросов INSERT, UPDATE или DELETE вместо size() надо использовать метод QSqlQuery::numRowsAffected(). Чтобы узнать, возникла ли ошибка при последнем выполнении запроса, используется метод QSqlQuery::lastError(). Аналогичный метод имеет и класс QSqlDatabase. В обоих случаях возвращается экземпляр класса QSqlError. Тип ошибки можно выяснить, вызвав метод QSqlError::type(). Возможные типы ошибок: QSqlError::NoError (ошибок не было), QSqlError::ConnectionError (ошибка соединения), QSqlError::StatementError (синтаксическая ошибка в SQL-запросе), QSqlError::TransactionError (ошибка транзакции) и QSqlError::UnknownError (неизвестная ошибка).
Если требуется выполнить большое количество однотипных SQL-операторов, то эффективнее использовать запрос с параметрами:
QSqlQuery query; query.prepare("INSERT INTO empl (id, name, salary) " "VALUES (:id, :name, :salary)"); for (int i=0; i<N; i++) { query.bindValue(":id", arr_id[i]); query.bindValue(":name", arr_name[i]); query.bindValue(":salary", arr_salary[i]); query.exec(); }
или, что же самое (только параметры безымянные):
QSqlQuery query; query.prepare("INSERT INTO empl (id, name, salary) " "VALUES (?, ?, ?)"); for (int i=0; i<N; i++) { query.addBindValue(arr_id[i]); query.addBindValue(arr_name[i]); query.addBindValue(arr_salary[i]); query.exec(); }
Здесь выполняется вставка N записей, данные берутся из массивов arr_id, arr_name и arr_salary. Если СУБД поддерживает механизм транзакций, то для начала новой транзакции используется метод
bool QSqlDatabase::transaction()
для её подтверждения надо вызвать
bool QSqlDatabase::commit()
а для отмены:
bool QSqlDatabase::rollback()
Если СУБД не поддерживает транзакций, то вызовы transaction, commit и rollback ничего не делают. С помощью метода QSqlDriver::hasFeature() можно узнать, поддерживается ли данным драйвером и СУБД та или иная функция, в том числе и транзакции:
QSqlDriver *driver = QSqlDatabase::database().driver(); if (driver->hasFeature(QSqlDriver::Transactions)) .......
Каждое соединение с базой данных может иметь только одну активную транзакцию. Если этого недостаточно, всегда можно открыть ещё несколько соединений с той же базой данных. Ссылку на соединение с базой данных можно получить, вызвав функцию QSqlDatabase::database(connectionName). Необязательный параметр connectionName -- это имя соединения, которое было задано при его создании с помощью QSqlDatabase::addDatabase().
По окончании работы с базой данных соединение надо закрыть: QSqlDatabase::close(). Затем можно либо открыть его снова с помощью метода open(), либо удалить из списка соединений, вызвав статический метод QSqlDatabase::removeDatabase(connectionName).
Выполнение SQL-запросов (система Windows, драйвер QODBC)
Выполнение SQL-запросов (система Linux, драйвер QMYSQL)
В листингах приведён пример программы, работающей с базой данных, а на рис. показан результат её работы в Windows и Linux.
Листинг. Выполнение SQL-запросов (файл examples-qt/db00/db00.h)
1 #include <QtGui> 2 3 class MyDialog : public QDialog { 4 Q_OBJECT 5 public: 6 MyDialog(QWidget *parent=0); 7 protected: 8 virtual void closeEvent(QCloseEvent *event); 9 private slots: 10 bool start(); 11 private: 12 QComboBox *mode; // Режим (драйвер). 13 QLineEdit *host; // Хост. 14 QLineEdit *dbname; // Имя БД. 15 QLineEdit *user; // Пользователь. 16 QLineEdit *password; // Пароль. 17 QTextEdit *scr; // Для вывода сообщений. 18 QPushButton *btnStart; // Кнопка 'Старт'. 19 ;
Листинг. Выполнение SQL-запросов (файл examples-qt/db00/db00.cpp)
1 #include <QtGui> 2 #include <QtSql> 3 #include <QtTest/QtTest> // Для qWait(). 4 5 #include "db00.h" 6 7 MyDialog::MyDialog(QWidget *parent) 8 : QDialog(parent) { 9 QTextCodec *codec = QTextCodec::codecForName("CP1251"); 10 QTextCodec::setCodecForTr(codec); 11 QTextCodec::setCodecForCStrings(codec); 12 QTextCodec::setCodecForLocale(codec); 13 14 setWindowFlags(Qt::Window); 15 16 mode = new QComboBox(this); 17 QStringList drivers = QSqlDatabase::drivers(); 18 drivers.removeAll("QMYSQL3"); 19 drivers.removeAll("QOCI8"); 20 drivers.removeAll("QODBC3"); 21 drivers.removeAll("QPSQL7"); 22 drivers.removeAll("QTDS7"); 23 mode->addItems(drivers); 24 25 host = new QLineEdit(tr("localhost"), this); 26 dbname = new QLineEdit(this); 27 user = new QLineEdit(this); 28 password = new QLineEdit(this); 29 password->setEchoMode(QLineEdit::Password); 30 31 btnStart = new QPushButton(tr("Старт"), this); 32 33 scr = new QTextEdit(this); 34 scr->setReadOnly(true); 35 36 QGridLayout *layout = new QGridLayout(this); 37 layout->addWidget(new QLabel(tr("Режим:"), this), 38 0, 0, Qt::AlignRight); 39 layout->addWidget(mode, 0, 1, 1, 3); 40 41 layout->addWidget(new QLabel(tr("Хост:"), this), 42 1, 0, Qt::AlignRight); 43 layout->addWidget(host, 1, 1); 44 45 layout->addWidget(new QLabel(tr("База данных:"), this), 46 1, 2, Qt::AlignRight); 47 layout->addWidget(dbname, 1, 3); 48 49 layout->addWidget(new QLabel(tr("Пользователь:"), this), 50 2, 0, Qt::AlignRight); 51 layout->addWidget(user, 2, 1); 52 53 layout->addWidget(new QLabel(tr("Пароль:"), this), 54 2, 2, Qt::AlignRight); 55 layout->addWidget(password, 2, 3); 56 57 layout->addWidget(btnStart, 3, 1, 1, 2); 58 layout->addWidget(scr, 4, 0, 1, 4); 59 60 layout->setMargin(6); 61 layout->setSpacing(5); 62 layout->setColumnStretch(1, 1); 63 layout->setColumnStretch(3, 1); 64 layout->setRowStretch(4, 1); 65 setLayout(layout); 66 67 connect(btnStart, SIGNAL(clicked()), this, SLOT(start())); 68 } 69 70 bool MyDialog::start() { 71 scr->append(tr("Соединяюсь с базой данных...")); 72 QSqlDatabase db = QSqlDatabase::addDatabase( 73 mode->currentText() ); 74 db.setHostName(host->text()); 75 db.setDatabaseName(dbname->text()); 76 db.setUserName(user->text()); 77 db.setPassword(password->text()); 78 if (db.open()) { 79 mode->setEnabled(false); 80 host->setEnabled(false); 81 dbname->setEnabled(false); 82 user->setEnabled(false); 83 password->setEnabled(false); 84 btnStart->setEnabled(false); 85 scr->append(tr("Соединение установлено!")); 86 }else{ 87 scr->append(tr("Не могу соединиться: ")); 88 scr->append(db.lastError().text()); 89 return false; 90 } 91 92 QSqlQuery sql = QSqlQuery(); 93 //sql.exec(tr("SET NAMES 'cp1251'")); 94 QStringList dbtables = db.tables(QSql::Tables); 95 if (dbtables.contains( tr("employee"), 96 Qt::CaseInsensitive)) { 97 scr->append( tr( 98 "Таблица \"employee\" уже существует.")); 99 sql.exec(tr("DROP TABLE employee")); 100 if ( sql.lastError().type() == QSqlError::NoError ) { 101 scr->append( tr( 102 "Удалили таблицу \"employee\" ")); 103 }else{ 104 scr->append( tr( 105 "Не могу удалить таблицу \"employee\":")); 106 scr->append(sql.lastError().text()); 107 return false; 108 } 109 } 110 111 sql.exec( tr( 112 "create table employee ( " 113 " id integer PRIMARY KEY, " 114 " name char(30) not null, " 115 " born date null, " 116 " salary numeric(12,2), " 117 " married boolean NULL ) " ) ); 118 if ( sql.lastError().type() == QSqlError::NoError ) { 119 scr->append( tr( 120 "Создали таблицу \"employee\".")); 121 }else{ 122 scr->append( tr( 123 "Не могу создать таблицу \"employee\":")); 124 scr->append(sql.lastError().text()); 125 return false; 126 } 127 128 if (sql.prepare( tr( 129 "INSERT INTO employee " 130 " VALUES (?, ?, ?, ?, ?)") ) ) { 131 int arr_id[] = {123, 345, 501}; 132 QString arr_name[] = {tr("Винни-Пух"), 133 tr("Ослик Иа"), 134 tr("Поросёнок")}; 135 QDate arr_born[] = {QDate(1971, 12, 31), 136 QDate(1965, 2, 23), 137 QDate(1982, 6, 14)}; 138 float arr_salary[] = {1234.56f, 2345.67f, 871}; 139 int arr_married[] = {1, 0, 0}; 140 141 for (unsigned int i=0; i < 3; i++) { 142 sql.bindValue(0, arr_id[i]); 143 sql.bindValue(1, arr_name[i]); 144 sql.bindValue(2, arr_born[i]); 145 sql.bindValue(3, arr_salary[i]); 146 sql.bindValue(4, arr_married[i]); 147 sql.exec(); 148 if ( sql.lastError().type() == QSqlError::NoError ) { 149 scr->append( tr( 150 "Вставили новую запись.")); 151 }else{ 152 scr->append( tr( 153 "Не могу вставить новую запись:")); 154 scr->append(sql.lastError().text()); 155 return false; 156 } 157 } 158 }else{ 159 scr->append( tr( 160 "Не могу подготовить запрос:")); 161 scr->append(sql.lastError().text()); 162 return false; 163 } 164 165 sql.exec( tr("SELECT * FROM employee ") ); 166 if ( sql.isActive() ) { 167 QSqlRecord rec = sql.record(); 168 scr->append( tr( 169 "В таблице \"employee\" %1 столбцов: ") 170 .arg(rec.count() ) ); 171 172 QString fields; 173 for(int j=0; j<rec.count(); j++) 174 fields += rec.fieldName(j) + ", "; 175 176 scr->append(fields); 177 178 scr->append( tr( 179 "В таблице \"employee\" %1 записей: ") 180 .arg(sql.size() ) ); 181 182 while ( sql.next() ) { 183 int id = sql.value(0).toInt(); 184 QString name = sql.value(1).toString(); 185 QDate born = sql.value(2).toDate(); 186 double salary = sql.value(3).toDouble(); 187 bool married = sql.value(4).toBool(); 188 scr->append( tr( 189 "%1\t %2\t %3\t %4\t %5") 190 .arg(id) 191 .arg(name) 192 .arg(born.toString(tr("dd/MM/yyyy"))) 193 .arg(salary) 194 .arg(married) ); 195 } 196 }else{ 197 scr->append( tr( 198 "Не могу получить данные:")); 199 scr->append(sql.lastError().text()); 200 return false; 201 } 202 203 scr->append( tr( 204 "При закрытии окна соединение с БД будет завершено.")); 205 return true; 206 } 207 208 void MyDialog::closeEvent(QCloseEvent *event) { 209 QSqlDatabase db = QSqlDatabase::database(); 210 if (db.isOpen()) { 211 db.close(); 212 scr->append("--------------------------"); 213 scr->append(tr("Соединение с базой данных закрыто!")); 214 QTest::qWait(1000); // Ждать 1 сек. 215 } 216 } 217 218 int main(int argc, char *argv[]) { 219 QApplication app(argc, argv); 220 221 MyDialog *mainWin = new MyDialog(); 222 mainWin->show(); 223 return app.exec(); 224 }
- (14) Установили для диалога флаг Qt::Windows, чтобы в заголовке окна появились кнопки сворачивания и восстановления.
- (16-23) Создали поле со списком и заполнили его названиями всех доступных драйверов SQL, кроме устаревших (17-21).
- (25-29) Создали однострочные поля для ввода параметров соединения.
- (31) Кнопка Старт.
- (33-34) Многострочное поле для вывода сообщений.
- (36-65) Расположили все элементы с помощью сеточного менеджера размещения.
- (67) Связали кнопку Старт с функцией start().
- (69) Функция, выполняемая при нажатии на кнопку.
- (71) Вывели сообщение.
- (72-78) Создали соединение с базой данных.
- (79-84) Если соединение установлено, то менять параметры уже нельзя.
- (93) При необходимости сообщили серверу кодовую таблицу клиента.
- (94-95) Выяснили, существует ли в базе данных таблица employee (работники).
- (99) Если да, то удалили её.
- (111) Создали новую таблицу.
- (128-162) Выполнили серию SQL-запросов с параметрами для вставки новых записей в созданную таблицу.
- (165) Получили все записи таблицы.
- (166-174) Получили информацию о полях (столбцах).
- (178-194) Вывели на экран все записи (строки) таблицы.
- (208) Виртуальная функция closeEvent выполняется при закрытии окна.
- (209-211) Если соединение с базой данных открыто, то закрываем его.
- (212-214) Выводим сообщение и ждём секунду перед закрытием окна. Для компиляции проектов, использующих модуль QtTest, в файл проекта *.pro надо добавить строку CONFIG += qtestlib.
Перед компиляцией проекта, в котором используется модуль QtSql, надо добавить в pro-файл строку QT += sql.
Перед выполнением этой программы требуется создать какую-нибудь базу данных:
create database db1 character set utf8;
(вместо utf8 можно использовать кодировки cp1251 или koi8r); а перед использованием QODBC -- настроить ODBC-псевдоним.
Работа с таблицами баз данных
Самый простой способ отображения информации базы данных в виде таблицы заключается в использовании классов QSqlQueryModel и QTableView:
QSqlQueryModel model; model.setQuery("select * from employee"); QTableView view; view.setModel(&model); view.show();
Но вместо QsqlQueryModel можно использовать класс QSqlTableModel, позволяющий работать с таблицами баз данных на более высоком уровне, чем выполнение SQL-запросов. Тогда приведённый выше фрагмент кода запишется следующим образом:
QSqlTableModel model; model.setTable("employee"); model.select(); QTableView view; view.setModel(&model); view.show();
Более сложный пример: вместо выполнения SQL-запроса
SELECT * FROM employee WHERE salary >= 1000 ORDER BY id DESC
для модели QSqlTableModel надо задать фильтр и условие сортировки:
QSqlTableModel model = QSqlTableModel(parent, db); model.setTable("employee"); // Имя таблицы базы данных. model.setFilter("salary >= 1000"); // Условие WHERE. model.setSort(0, Qt::DescendingOrder); // Сортировка по убыванию id. model.select(); // Получить данные.
После определения модели можно узнать значение любого поля любой записи, например:
QString name = model.record(i).value("name").toString();
или
int salary = model.data(model.index(i, 3)).toInt();
Для перебора всех записей набора данных:
for (int i = 0; i < model.rowCount(); ++i) { QSqlRecord record = model.record(i); QString name = record.value("name").toString(); double salary = record.value("salary").toDouble(); ....... }
В листинге приведён пример работы с таблицей работников, созданной в результате выполнения предыдущей программы, а на рис. показан внешний вид таблицы в системе Windows.
Листинг. Таблица базы данных (файл examples-qt/db01/db01.cpp)
1 // Таблица базы данных 2 3 #include <QtGui> 4 #include <QtSql> 5 6 int main(int argc, char *argv[]) { 7 8 QApplication app(argc, argv); 9 10 QTextCodec *codec = QTextCodec::codecForName("CP1251"); 11 QTextCodec::setCodecForTr(codec); 12 QTextCodec::setCodecForCStrings(codec); 13 QTextCodec::setCodecForLocale(codec); 14 15 QSqlDatabase db = QSqlDatabase::addDatabase("QODBC"); 16 db.setDatabaseName("mysql_db1"); 17 db.setUserName(""); 18 db.setPassword(""); 19 db.open(); 20 21 // QSqlQuery q; 22 // Для корректного отображения символов кириллицы, 23 // возможно, придётся установить кодировку: 24 // q.exec(QObject::tr("SET NAMES 'cp1251'")); 25 26 QSqlTableModel *model = new QSqlTableModel(); 27 model->setTable("employee"); 28 29 model->insertRows(0, 1); 30 model->setData(model->index(0, 0), 159); 31 model->setData(model->index(0, 1), QObject::tr("Сова")); 32 model->setData(model->index(0, 2), QDate(1985, 12, 31)); 33 model->setData(model->index(0, 3), 12.34); 34 model->setData(model->index(0, 4), 1); 35 model->submitAll(); 36 37 model->setEditStrategy(QSqlTableModel::OnFieldChange); 38 39 model->select(); 40 model->setHeaderData(0, Qt::Horizontal, 41 QObject::tr("Табельн.\nномер")); 42 model->setHeaderData(1, Qt::Horizontal, 43 QObject::tr("Имя")); 44 model->setHeaderData(2, Qt::Horizontal, 45 QObject::tr("День рождения")); 46 model->setHeaderData(3, Qt::Horizontal, 47 QObject::tr("Зарплата")); 48 model->setHeaderData(4, Qt::Horizontal, 49 QObject::tr("Женат/\nзамужем")); 50 51 QTableView *view = new QTableView(); 52 view->setModel(model); 53 54 view->setAlternatingRowColors(true); 55 view->resizeRowsToContents(); 56 view->resizeColumnsToContents(); 57 view->show(); 58 59 return app.exec(); 60 }
- (15-19) Установили параметры соединения и подключились к базе данных.
- (21-24) При необходимости сообщили серверу кодировку строковых данных, которую использует наша программа.
- (26-27) Создали модель QSqlTableModel и задали имя таблицы БД.
- (29-35) Добавили к таблице новую строку, задали значения всех пяти ячеек этой строки и подтвердили запись изменений в базу данных (диагностика возможных ошибок для простоты опущена).
- (37) Задали режим обновления данных в БД при редактировании таблицы: параметр QSqlTableModel::OnFieldChange означает, что запись изменений в базу данных будет выполняться автоматически после окончания редактирования очередной ячейки. Другие возможные режимы: QSqlTableModel::OnRowChange (при переходе к другой строке) и QSqlTableModel::OnManualSubmit (при выполнении метода submitAll, подтверждающего все изменения, или revertAll, отменяющего их).
- (39) Выбрали данные из таблицы БД.
- (40-49) Определили заголовки столбцов, которые будут отображаться в окне.
- (51-52) Создали представление и задали для него модель.
- (54) Установили чередующийся цвет фона для строк таблицы.
- (55-56) Выполнили автоподстройку высоты строк и ширины столбцов.
- (57) Вывели представление таблицы на экран.
Проверьте, как работает эта програма: можно изменять данные в ячейках, при редактировании чисел и дат автоматически отображаются кнопки инкремента/декремента. Но поскольку для элемента QDoubleSpinBox по умолчанию задано максимальное значение 99.99, то при попытке изменить, например, величину зарплаты работника, любое значение, большее этого максимального, автоматически усекается. Разумеется, попытка ввести число больше допустимого, оканчивается неудачей. Кроме того, после редактирования первой же ячейки таблицы, когда происходит автоматическое обновление данных, ширина столбцов и высота строк изменяется, т.к. размеры ячеек по умолчанию отличаются от тех, что установились в результате однократного выполнения методов resizeRowsToContents и resizeColumnsToContents.
Если мы хотим получать большую зарплату, выводить разные столбцы различным цветом, отображать галочки для полей логического типа, использовать календарик для ввода дат, в общем, как-то изменять заданные по умолчанию параметры отображения и редактирования ячеек, то у нас имеются две возможности: разрабатывать свой класс модели и/или представления таблицы или использовать специальные объекты-делегаты для её ячеек. Рассмотрим оба варианта по очереди.
Разработка модели и представления таблицы БД
Решим сначала простую задачу: в ячейках последнего столбца таблицы, где хранится только два возможных значения, будем отображать элемент QCheckBox и текст "Да" или "Нет" (рис.). Кроме того, запретим редактирование первого столбца, изменим цвет фона ячеек первого и последнего столбцов, а также параметры шрифта во втором столбце.
Для этого определим свою модель таблицы, использовав в качестве базового класс QSqlQueryModel. А чтобы управлять размерами ячеек таблицы, определим свой класс представления на основе стандартного QTableView. В листингах . и . приведён текст программы.
Листинг. Модель и представление таблицы БД (файл examples-qt/db02/db02.h)
1 #include <QSqlQueryModel> 2 #include <QTableView> 3 4 class MyModel : public QSqlQueryModel { 5 Q_OBJECT 6 public: 7 MyModel(QObject *parent = 0); 8 Qt::ItemFlags flags(const QModelIndex &index) const; 9 QVariant data(const QModelIndex &index, 10 int role = Qt::DisplayRole) const; 11 bool setData(const QModelIndex &index, 12 const QVariant &value, int role); 13 private: 14 void refresh(); 15 }; 16 17 class MyView : public QTableView { 18 Q_OBJECT 19 public: 20 MyView(QWidget *parent = 0); 21 private: 22 virtual void resizeEvent(QResizeEvent *event); 23 };
Листинг. Модель и представление таблицы БД (файл examples-qt/db02/db02.cpp)
1 // Таблица базы данных: пользовательская модель и представление 2 3 #include <QtGui> 4 #include <QtSql> 5 6 #include "db02.h" 7 8 MyModel::MyModel(QObject *parent) 9 : QSqlQueryModel(parent) { 10 refresh(); 11 } 12 13 Qt::ItemFlags MyModel::flags( 14 const QModelIndex &index) const { 15 16 Qt::ItemFlags flags = QSqlQueryModel::flags(index); 17 if (index.column() >= 1 && index.column() < 4) 18 flags |= Qt::ItemIsEditable; 19 if (index.column() == 4) 20 flags |= Qt::ItemIsUserCheckable; 21 return flags; 22 } 23 24 QVariant MyModel::data( 25 const QModelIndex &index, 26 int role) const { 27 28 QVariant value = QSqlQueryModel::data(index, role); 29 30 switch (role) { 31 32 case Qt::DisplayRole: // Данные для отображения 33 case Qt::EditRole: // Данные для редактирования 34 if (index.column() == 0) 35 return value.toString().prepend(tr("№")); 36 else if (index.column() == 2 && role == Qt::DisplayRole) 37 return value.toDate().toString("dd.MM.yyyy"); 38 else if (index.column() == 3 && role == Qt::DisplayRole) 39 return tr("%1") 40 .arg(value.toDouble(), 0, 'f', 2); 41 else if (index.column() == 4) 42 return value.toInt() != 0 ? tr("Да") : tr("Нет"); 43 else 44 return value; 45 46 case Qt::TextColorRole: // Цвет текста 47 if(index.column() == 1) 48 return qVariantFromValue(QColor(Qt::blue)); 49 else 50 return value; 51 52 case Qt::TextAlignmentRole: // Выравнивание 53 if(index.column() == 3) 54 return int(Qt::AlignRight | Qt::AlignVCenter); 55 else if(index.column() == 2 || index.column() == 4) 56 return int(Qt::AlignHCenter | Qt::AlignVCenter); 57 else 58 return int(Qt::AlignLeft | Qt::AlignVCenter); 59 60 case Qt::FontRole: // Шрифт 61 if(index.column() == 1) { 62 QFont font = QFont("Helvetica", 10, QFont::Bold); 63 return qVariantFromValue(font); 64 }else 65 return value; 66 67 case Qt::BackgroundColorRole: { // Цвет фона 68 int a = (index.row() % 2) ? 14 : 0; 69 if(index.column() == 0) 70 return qVariantFromValue(QColor(220,240-a,230-a)); 71 else if(index.column() == 4) 72 return qVariantFromValue(QColor(200,220-a,255-a)); 73 else 74 return value; 75 } 76 case Qt::CheckStateRole: // Галочка 77 if (index.column() == 4) 78 return (QSqlQueryModel::data(index).toInt() != 0) ? 79 Qt::Checked : Qt::Unchecked; 80 else 81 return value; 82 83 case Qt::SizeHintRole: // Размер ячейки 84 if (index.column() == 0) 85 return QSize(70, 10); 86 if (index.column() == 4) 87 return QSize(60, 10); 88 else 89 return QSize(110, 10); 90 } 91 return value; 92 } 93 94 bool MyModel::setData( 95 const QModelIndex &index, 96 const QVariant &value, 97 int /* role */) { 98 if (index.column() < 1 || index.column() > 4) 99 return false; 100 101 QModelIndex primaryKeyIndex = QSqlQueryModel::index( 102 index.row(), 0); 103 int id = QSqlQueryModel::data(primaryKeyIndex).toInt(); 104 105 //clear(); // Если надо полностью перерисовать таблицу. 106 107 bool ok; 108 QSqlQuery query; 109 if (index.column() == 1) { 110 query.prepare("update employee set name = ? where id = ?"); 111 query.addBindValue(value.toString()); 112 query.addBindValue(id); 113 }else if(index.column() == 2) { 114 query.prepare("update employee set born = ? where id = ?"); 115 query.addBindValue(value.toDate()); 116 query.addBindValue(id); 117 }else if(index.column() == 3) { 118 query.prepare("update employee set salary = ? where id = ?"); 119 query.addBindValue(value.toDouble()); 120 query.addBindValue(id); 121 }else if(index.column() == 4) { 122 query.prepare("update employee set married = ? where id = ?"); 123 query.addBindValue(value.toBool()); 124 query.addBindValue(id); 125 } 126 ok = query.exec(); 127 refresh(); 128 return ok; 129 } 130 131 void MyModel::refresh() { 132 setQuery("select * from employee ORDER BY id"); 133 134 setHeaderData(0, Qt::Horizontal, 135 tr("Табельн.\nномер")); 136 setHeaderData(1, Qt::Horizontal, 137 tr("Имя")); 138 setHeaderData(2, Qt::Horizontal, 139 tr("День рождения")); 140 setHeaderData(3, Qt::Horizontal, 141 tr("Зарплата")); 142 setHeaderData(4, Qt::Horizontal, 143 tr("Женат/\nзамужем")); 144 } 145 146 //------------------------------------ 147 MyView::MyView(QWidget *parent) 148 : QTableView(parent) { 149 150 } 151 152 void MyView::resizeEvent(QResizeEvent *event) { 153 resizeRowsToContents(); 154 resizeColumnsToContents(); 155 QTableView::resizeEvent(event); 156 } 157 158 //------------------------------------ 159 int main(int argc, char *argv[]) { 160 161 QApplication app(argc, argv); 162 163 QTextCodec *codec = QTextCodec::codecForName("CP1251"); 164 QTextCodec::setCodecForTr(codec); 165 QTextCodec::setCodecForCStrings(codec); 166 167 QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); 168 db.setDatabaseName("db1"); 169 db.setUserName("root"); 170 db.setPassword("password"); 171 db.open(); 172 173 // QSqlQuery q; 174 // Для корректного отображения кириллицы, возможно, 175 // придётся установить кодировку: 176 //q.exec(QObject::tr("SET NAMES 'cp1251'")); 177 178 MyModel *model = new MyModel(); 179 180 MyView *view = new MyView(); 181 view->setModel(model); 182 183 view->setAlternatingRowColors(true); 184 view->resizeRowsToContents(); 185 view->resizeColumnsToContents(); 186 view->show(); 187 188 return app.exec(); 189 }
- (8-10) В конструкторе модели таблицы вызвали конструктор базового класса и определённый ниже метод refresh (131), в котором указаны заголовки столбцов и текст SQL-запроса, выполняемый для получения данных из БД.
- (13-22) В методе flags переопределили свойства ячеек таблицы.
- (16) Получили значения флагов-свойств, которые определены для данного столбца в базовом классе.
- (17-18) Для всех столбцов, кроме первого (вернее, нулевого) и последнего, выставили флаг разрешения редактирования Qt::ItemIsEditable.
- (19-20) Для последнего столбца установили признак Qt::ItemIsUserCheckable, в результате во всех его ячейках перед текстовой меткой будет отображаться элемент QCheckBox.
- (24-92) В методе data переопределяются данные, "хранящиеся" в ячейках таблицы. В зависимости от параметра role (роль), это либо сами данные, читаемые из БД, либо параметры их отображения.
- (28) Получили значение, определённое в базовом классе.
- (30-90) Определяем параметры ячейки таблицы, в зависимости от значения параметра role: Qt::DisplayRole (данные для отображения ячейки таблицы), Qt::EditRole (данные для режима редактирования), Qt::TextColorRole (цвет текста), Qt::TextAlignmentRole (выравнивание текста), Qt::FontRole (параметры шрифта), Qt::BackgroundColorRole (цвет фона ячейки), Qt::CheckStateRole (надо ли отображать элемент QCheckBox), Qt::SizeHintRole (предпочитаемые размеры ячейки).
- (34-35) Для самого первого столбца перед числом добавили символ "№".
- (36-37) Для столбца с датами рождения в режиме отображения задали формат "число.месяц.год" (по умолчанию действует формат "год-месяц-число").
- (38-40) Для столбца зарплат в режиме отображения установили точность два знака после запятой.
- (41-42) В последнем столбце отображаем текст "Да" или "Нет".
- (43-44) Для всех остальных стрлбцов (остался только столбец с именами работников) ничего не изменяем.
- (46-50) Для столбца с именами установили голубой цвет символов, для всех остальных -- цвет по умолчанию.
- (52-58) Числа выравниваем по правому краю, даты и надписи "Да/Нет" -- по центру, остальные ячейки -- по левому краю. Во всех случаях центрируем по вертикали.
- (60-65) Для столбца имён задали жирный шрифт "Helvetica" размером 10 пунктов, для всех остальных ячеек -- шрифт по умолчанию.
- (67-74) Установили цвет фона для ячеек самого первого и последнего столбцов (для чётных строк -- немного светлее, чем для нечётных). Для всех остальных столбцов -- цвет фона по умолчанию.
- (76-81) Для ячеек последнего столбца устанавливаем галочку или сбрасываем её, в зависимости от значения, хранящегося в БД.
- (83-90) Установили предпочтительные размеры ячеек.
- (94-129) В методе setData переопределяются данные, которые будут записываться в БД.
- (98-99) Могут изменяться только ячейки из столбцов с 1 по 4.
- (101-103) Выяснили значение поля id (целочисленный идентификатор) изменяемой записи.
- (107-128) Определили текст SQL-запроса для обновления данных каждого столбца таблицы.
- (131-144) Задали текст SQL-запроса для чтения данных из БД и отображаемые заголовки столбцов таблицы. Заметим, что если не указать критерий сортировки, то для некоторых драйверов (например, QPSQL) после редактирования строки таблицы будут "прыгать" (только что изменённая строка будет перемещаться на последнее место).
- (147-150) В конструкторе представления таблицы не забыли вызвать конструктор базового класса.
- (152-156) Определяем действия, выполняемые при изменении размеров таблицы (в данном случае делается автоматическая подгонка размеров по содержимому ячеек).
- (159-189) Процедура main повторяет текст предыдущей программы, только в (178-181) используется наш класс модели и класс для представления таблицы.
Для своей модели мы использовали базовый класс QSqlQueryModel, работающий с произвольным набором SQL-запросов для чтения и записи данных в БД. Но за всё приходится платить: нам пришлось подробно расписывать реализацию методов data и setData. В данном случае мы имели дело с единственной таблицей базы данных, поэтому можно было в качестве базового класса взять QSqlTableModel.
Проверьте, как работает эта программа. Намного лучше, чем предыдущая, не правда ли? Размеры ячеек теперь не "прыгают" при редактировании, а при щелчке левой кнопкой мыши по элементу QCheckBox в ячейках последнего столбца автоматически изменяется текстовая метка "Да/Нет". Но ввести трёхзначную зарплату всё ещё не получается. Мы исправим данный недостаток в следующем разделе.
Делегаты для ячеек таблицы
Делегаты -- это специальные классы, которые могут использоваться для управления режимами отображения или редактирования ячеек таблицы QTableView, равно как и элементов других представлений (QListView, QTreeView). В данном разделе мы определим делегатов для редактирования дат и чисел в ячейках таблицы.
В листингах . и . приведён текст программы, а на рис. показан внешний вид окна в момент ввода даты рождения.
Листинг. Делегаты (файл examples-qt/db03/db03.h)
1 #include <QtGui> 2 #include <QtSql> 3 4 class MyModel : public QSqlQueryModel { 5 Q_OBJECT 6 public: 7 MyModel(QObject *parent = 0); 8 Qt::ItemFlags flags(const QModelIndex &index) const; 9 QVariant data(const QModelIndex &index, 10 int role = Qt::DisplayRole) const; 11 bool setData(const QModelIndex &index, 12 const QVariant &value, int role); 13 private: 14 void refresh(); 15 }; 16 17 //----------------------------------------------- 18 class MyView : public QTableView { 19 Q_OBJECT 20 public: 21 MyView(QWidget *parent = 0); 22 private: 23 virtual void resizeEvent(QResizeEvent *event); 24 }; 25 26 //----------------------------------------------- 27 class MyDSBDelegate : public QItemDelegate { 28 Q_OBJECT 29 public: 30 MyDSBDelegate(double min=0.00, 31 double max=999999999.99, 32 double step=0.1, 33 int precision=2, 34 QObject *parent = 0); 35 QWidget *createEditor( 36 QWidget *parent, 37 const QStyleOptionViewItem &option, 38 const QModelIndex &index) const; 39 void setEditorData(QWidget *editor, 40 const QModelIndex &index) const; 41 void setModelData(QWidget *editor, 42 QAbstractItemModel *model, 43 const QModelIndex &index) const; 44 void updateEditorGeometry( 45 QWidget *editor, 46 const QStyleOptionViewItem &option, 47 const QModelIndex &index) const; 48 private: 49 double m_min; 50 double m_max; 51 double m_step; 52 int m_precision; 53 }; 54 55 //--------------------------------------------- 56 class MyDEDelegate : public QItemDelegate { 57 Q_OBJECT 58 public: 59 MyDEDelegate(bool calpopup = true, 60 QObject *parent = 0); 61 QWidget *createEditor( 62 QWidget *parent, 63 const QStyleOptionViewItem &option, 64 const QModelIndex &index) const; 65 void setEditorData(QWidget *editor, 66 const QModelIndex &index) const; 67 void setModelData(QWidget *editor, 68 QAbstractItemModel *model, 69 const QModelIndex &index) const; 70 void updateEditorGeometry( 71 QWidget *editor, 72 const QStyleOptionViewItem &option, 73 const QModelIndex &index) const; 74 private: 75 bool m_calpopup; 76 };
- (4-15) Модель таблицы.
- (18-24) Представление таблицы.
- (27-53) Класс-делегат для вещественных чисел (на основе элемента QDoubleSpinBox).
- (56-76) Класс-делегат для ввода даты (на основе элемента QDateEdit).
Листинг. Делегаты (файл examples-qt/db03/db03.cpp)
1 // Таблица базы данных: делегаты 2 3 #include <QtGui> 4 #include <QtSql> 5 6 #include "db03.h" 7 8 MyModel::MyModel(QObject *parent) 9 : QSqlQueryModel(parent) { 10 refresh(); 11 } 12 13 Qt::ItemFlags MyModel::flags( 14 const QModelIndex &index) const { 15 16 Qt::ItemFlags flags = QSqlQueryModel::flags(index); 17 if (index.column() >= 1 && index.column() < 4) 18 flags |= Qt::ItemIsEditable; 19 if (index.column() == 4) 20 flags |= Qt::ItemIsUserCheckable; 21 return flags; 22 } 23 24 QVariant MyModel::data( 25 const QModelIndex &index, 26 int role) const { 27 28 QVariant value = QSqlQueryModel::data(index, role); 29 30 switch (role) { 31 32 case Qt::DisplayRole: 33 case Qt::EditRole: 34 if (index.column() == 0) 35 return value.toString().prepend(tr("№")); 36 else if (index.column() == 2 && role == Qt::DisplayRole) 37 return value.toDate().toString("dd.MM.yyyy"); 38 else if (index.column() == 3 && role == Qt::DisplayRole) 39 return tr("%1") 40 .arg(value.toDouble(), 0, 'f', 2); 41 else if (index.column() == 4 && role == Qt::DisplayRole) 42 return value.toInt() != 0 ? tr("Да") : tr("Нет"); 43 else 44 return value; 45 46 case Qt::TextColorRole: 47 if(index.column() == 1) 48 return qVariantFromValue(QColor(Qt::blue)); 49 else 50 return value; 51 52 case Qt::TextAlignmentRole: 53 if(index.column() == 3) 54 return int(Qt::AlignRight | Qt::AlignVCenter); 55 else if(index.column() == 2 || index.column() == 4) 56 return int(Qt::AlignHCenter | Qt::AlignVCenter); 57 else 58 return int(Qt::AlignLeft | Qt::AlignVCenter); 59 60 case Qt::FontRole: 61 if(index.column() == 1) { 62 QFont font = QFont("Helvetica", 10, QFont::Bold); 63 return qVariantFromValue(font); 64 }else 65 return value; 66 67 case Qt::BackgroundColorRole: { 68 int a = (index.row() % 2) ? 14 : 0; 69 if(index.column() == 0) 70 return qVariantFromValue(QColor(220,240-a,230-a)); 71 else if(index.column() == 4) 72 return qVariantFromValue(QColor(200,220-a,255-a)); 73 else 74 return value; 75 } 76 case Qt::CheckStateRole: 77 if (index.column() == 4) 78 return (QSqlQueryModel::data(index).toInt() != 0) ? 79 Qt::Checked : Qt::Unchecked; 80 else 81 return value; 82 83 case Qt::SizeHintRole: 84 if (index.column() == 0) 85 return QSize(70, 10); 86 if (index.column() == 4) 87 return QSize(60, 10); 88 else 89 return QSize(110, 10); 90 } 91 return value; 92 } 93 94 bool MyModel::setData( 95 const QModelIndex &index, 96 const QVariant &value, 97 int /* role */) { 98 if (index.column() < 1 || index.column() > 4) 99 return false; 100 101 QModelIndex primaryKeyIndex = QSqlQueryModel::index( 102 index.row(), 0); 103 int id = QSqlQueryModel::data(primaryKeyIndex).toInt(); 104 105 //clear(); // Если надо полностью перерисовать таблицу. 106 107 bool ok; 108 QSqlQuery query; 109 if (index.column() == 1) { 110 query.prepare("update employee set name = ? where id = ?"); 111 query.addBindValue(value.toString()); 112 query.addBindValue(id); 113 }else if(index.column() == 2) { 114 query.prepare("update employee set born = ? where id = ?"); 115 query.addBindValue(value.toDate()); 116 query.addBindValue(id); 117 }else if(index.column() == 3) { 118 query.prepare("update employee set salary = ? where id = ?"); 119 query.addBindValue(value.toDouble()); 120 query.addBindValue(id); 121 }else if(index.column() == 4) { 122 query.prepare("update employee set married = ? where id = ?"); 123 query.addBindValue(value.toBool()); 124 query.addBindValue(id); 125 } 126 ok = query.exec(); 127 refresh(); 128 return ok; 129 } 130 131 void MyModel::refresh() { 132 setQuery("select * from employee ORDER BY id"); 133 134 setHeaderData(0, Qt::Horizontal, 135 tr("Табельн.\nномер")); 136 setHeaderData(1, Qt::Horizontal, 137 tr("Имя")); 138 setHeaderData(2, Qt::Horizontal, 139 tr("День рождения")); 140 setHeaderData(3, Qt::Horizontal, 141 tr("Зарплата")); 142 setHeaderData(4, Qt::Horizontal, 143 tr("Женат/\nзамужем")); 144 } 145 146 //------------------------------------ 147 MyView::MyView(QWidget *parent) 148 : QTableView(parent) { 149 150 MyDSBDelegate *dsbd = new MyDSBDelegate( 151 0.0, 999999.99, 0.05, 2, this); 152 setItemDelegateForColumn(3, dsbd); 153 154 MyDEDelegate *ded = new MyDEDelegate( 155 true, this); 156 setItemDelegateForColumn(2, ded); 157 } 158 159 void MyView::resizeEvent(QResizeEvent *event) { 160 resizeRowsToContents(); 161 // resizeColumnsToContents(); 162 QTableView::resizeEvent(event); 163 } 164 165 //------------------------------------ 166 MyDSBDelegate::MyDSBDelegate( 167 double min, 168 double max, 169 double step, 170 int precision, 171 QObject *parent) 172 : QItemDelegate(parent), 173 m_min(min), 174 m_max(max), 175 m_step(step), 176 m_precision(precision) { 177 } 178 179 QWidget *MyDSBDelegate::createEditor( 180 QWidget *parent, 181 const QStyleOptionViewItem& /* option */, 182 const QModelIndex& /* index */) const { 183 QDoubleSpinBox *editor = new QDoubleSpinBox(parent); 184 editor->setMinimum(m_min); 185 editor->setMaximum(m_max); 186 editor->setDecimals(m_precision); 187 editor->setSingleStep(m_step); 188 editor->installEventFilter(const_cast<MyDSBDelegate*>(this)); 189 return editor; 190 } 191 192 void MyDSBDelegate::setEditorData( 193 QWidget *editor, 194 const QModelIndex &index) const { 195 double value = index.model()->data( 196 index, Qt::EditRole).toDouble(); 197 QDoubleSpinBox *dsb = static_cast<QDoubleSpinBox*>(editor); 198 dsb->setValue(value); 199 } 200 201 void MyDSBDelegate::setModelData( 202 QWidget *editor, 203 QAbstractItemModel *model, 204 const QModelIndex& index) const { 205 QDoubleSpinBox *dsb = static_cast<QDoubleSpinBox*>(editor); 206 dsb->interpretText(); 207 double value = dsb->value(); 208 model->setData(index, value); 209 } 210 211 void MyDSBDelegate::updateEditorGeometry( 212 QWidget *editor, 213 const QStyleOptionViewItem &option, 214 const QModelIndex& /* index */) const { 215 editor->setGeometry(option.rect); 216 } 217 218 //------------------------------------ 219 MyDEDelegate::MyDEDelegate( 220 bool calpopup, 221 QObject *parent) 222 : QItemDelegate(parent), 223 m_calpopup(calpopup) { 224 } 225 226 QWidget *MyDEDelegate::createEditor( 227 QWidget *parent, 228 const QStyleOptionViewItem& /* option */, 229 const QModelIndex& /* index */) const { 230 QDateEdit *editor = new QDateEdit(parent); 231 editor->setCalendarPopup(m_calpopup); 232 editor->installEventFilter(const_cast<MyDEDelegate*>(this)); 233 return editor; 234 } 235 236 void MyDEDelegate::setEditorData( 237 QWidget *editor, 238 const QModelIndex &index) const { 239 QDate value = index.model()->data( 240 index, Qt::EditRole).toDate(); 241 QDateEdit *de = static_cast<QDateEdit*>(editor); 242 de->setDate(value); 243 } 244 245 void MyDEDelegate::setModelData( 246 QWidget *editor, 247 QAbstractItemModel *model, 248 const QModelIndex& index) const { 249 QDateEdit *de = static_cast<QDateEdit*>(editor); 250 de->interpretText(); 251 QDate value = de->date(); 252 model->setData(index, value); 253 } 254 255 void MyDEDelegate::updateEditorGeometry( 256 QWidget *editor, 257 const QStyleOptionViewItem &option, 258 const QModelIndex& /* index */) const { 259 editor->setGeometry(option.rect); 260 } 261 262 //------------------------------------ 263 int main(int argc, char *argv[]) { 264 265 QApplication app(argc, argv); 266 267 QTextCodec *codec = QTextCodec::codecForName("CP1251"); 268 QTextCodec::setCodecForTr(codec); 269 QTextCodec::setCodecForCStrings(codec); 270 271 QSqlDatabase db = QSqlDatabase::addDatabase("QODBC"); 272 db.setDatabaseName("mysql_db1"); 273 db.setUserName(""); 274 db.setPassword(""); 275 db.open(); 276 277 // QSqlQuery q; 278 // q.exec(QObject::tr("SET NAMES 'cp1251'")); 279 280 MyModel *model = new MyModel(); 281 282 MyView *view = new MyView(); 283 view->setModel(model); 284 285 view->setAlternatingRowColors(true); 286 view->resizeRowsToContents(); 287 view->resizeColumnsToContents(); 288 view->show(); 289 290 return app.exec(); 291 }
- (1-144) Начало листинга повторяет текст предыдущей программы.
- (150-151) Создаём экземпляр делегата для редактирования вещественных чисел; первые два параметра конструктора -- разрешённый диапазон, третий параметр -- шаг изменения, четвёртый -- количество знаков после запятой, пятый -- родительский виджет.
- (152) Связали только что созданный экземпляр делегата с третим (считая с нуля) столбцом таблицы.
- (154-156) Создали экземпляр делегата для редактирования даты (первый параметр конструктора определяет, будет ли при вводе даты использоваться всплывающее окно календаря) и связали его со вторым столбцом таблицы.
- (159-163) Реализация метода resizeEvent взята из предыдущей программы.
- (166-177) Конструктор делегата для редактирования вещественных чисел. Параметры: диапазон, шаг, точность и указатель на родительский виджет.
- (179-190) Переопределяем виртуальный метод createEditor, который выполняется при входе в режим редактирования ячейки таблицы. В нашем случае создаётся элемент QDoubleSpinBox и задаются его параметры.
- (185) Наконец-то наша зарплата стала больше, чем 99.99.
- (188) Установили фильтр для событий: теперь наш экземпляр MyDSBDelegate (параметр this) будет получать все сообщения, посылаемые элементу редактирования edit.
- (192-199) Переопределили виртуальный метод setEditorData, в котором задаются данные, отображаемые элементом редактирования при его создании.
- (201-209) Переопределили виртуальный метод setModelData, в котором определяются данные, передаваемые элементом редактирования в модель.
- (211-216) Переопределили виртуальный метод updateEditorGeometry, в котором осуществляется прорисовка элемента редактирования.
- (219-260) Реализовали класс MyDEDelegate для редактирования даты. Свойство m_calpopup определяет режим редактирования: будет ли использоваться всплывающий календарик или кнопки инкремента/декремента.
- (263-291) Функция main (как и в предыдущем примере).
Таким образом, создание пользовательского делегата для редактирования данных включает следующие шаги:
- Описание класса-делегата на основе QItemDelegate.
- Реализация конструктора и виртуальных методов createEditor, setEditorData, setModelData и updateEditorGeometry.
- Создание экземпляра делегата (например, в конструкторе объекта-вида) и связывание всех или некоторых элементов вида (в случае таблицы -- всех ячеек или определённых строк и/или колонок) с этим экземпляром путём вызова метода setItemDelegate, setItemDelegateForRow или setItemDelegateForColumn.
Если требуется изменить вид элемента не только в режиме редактирования, но и в обычном состоянии, то в классе делегата надо переопределить виртуальный метод paint. Например, для MyDSBDelegate::paint:
void MyDSBDelegate::paint ( QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QString text; QRect rect; QVariant value; QStyleOptionViewItemV2 opt = setOptions(index, option); value = index.data(Qt::DisplayRole); text = QLocale().toString(value.toDouble(), 'f', 2); opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; drawDisplay(painter, opt, opt.rect, text); }
Здесь задано отображение чисел с точностью до двух знаков после десятичной точки, с выравниванием по правому краю и центрированием по вертикали. Получается, что всю информацию, связанную с внешним видом ячеек таблицы (шрифт, цвет, выравнивание), можно перенести из модели в классы делегатов. Хотя идеологически этот вариант кажется более предпочтительным (модель -- для данных, вид -- для их отображения), но на самом деле вопрос сводится к тому, что считать данными: перейдя на более высокий уровень абстракции, мы понимаем, что шрифт, цвет, параметры выравнивания и точность представления чисел -- это тоже особый вид данных (вернее, метаданных), которые могут храниться в БД и изменяться пользователем.