Жасмин Бланшет - QT 4: программирование GUI на С++
Конструктор ArtistForm принимает имя артиста, который будет выбран при выводе на экран диалогового окна. Мы проходим по записям таблицы artist и выбираем этого артиста. Остальная часть программного кода конструктора используется для создания кнопок и подключения к ним слотов, а также для компоновки дочерних виджетов в диалоговом окне.
01 void ArtistForm::addArtist()
02 {
03 int row = model->rowCount();
04 model->insertRow(row);
05 QModelIndex index = model->index(row, Artist_Name);
06 tableView->setCurrentIndex(index);
07 tableView->edit(index);
08 }
Для добавления нового артиста мы вставляем одну пустую строку в конец табличного представления QTableView. Теперь пользователь может вводить имя нового артиста и его страну. Если пользователь подтверждает вставку, нажимая кнопку Enter, генерируется сигнал beforeInsert(), и после этого новая запись вставляется в базу данных.
01 void ArtistForm::beforeInsertArtist(QSqlRecord &record)
02 {
03 record.setValue("id", generateId("artist"));
04 }
В конструкторе мы связываем сигнал модели beforeInsert() с этим слотом. Мы передаем неконстантную ссылку на запись непосредственно перед ее вставкой в базу данных. Здесь мы устанавливаем значение поля id.
Поскольку нам потребуется вызывать функцию generateId() несколько раз, мы определяем ее как inline—функцию в заголовочном файле и включаем ее каждый раз по мере необходимости. Ниже дается простой (и неэффективный) способ ее реализации:
01 inline int generateId(const QString &table)
02 {
03 QSqlQuery query;
04 query.exec("SELECT MAX(id) FROM " + table);
05 int id = 0;
06 if (query.next())
07 id = query.value(0).tolnt() + 1;
08 return id;
09 }
Функция generateId() может гарантированно работать правильно, если она выполняется в рамках контекста одной транзакции соответствующей команды INSERT. Некоторые базы данных поддерживают средство автоматической генерации полей, и обычно значительно лучше использовать предусмотренные в базе данных специальные средства поддержки этой операции.
Удаление — это последняя операция, которую позволяет сделать диалоговое окно ArtistForm. Вместо каскадного удаления (вскоре будет рассмотрено) мы разрешаем удалять артистов только в том случае, если в коллекции нет их компакт-дисков.
01 void ArtistForm::deleteArtist()
02 {
03 tableView->setFocus();
04 QModelIndex index = tableView->currentIndex();
05 if (!index.isValid())
06 return;
07 QSqlRecord record = model->record(index.row());
08 QSqlTableModel cdModel;
09 cdModel.setTable("cd");
10 cdModel.setFilter("artistid = " + record.value("id").toString());
11 cdModel.select();
12 if (cdModel.rowCount() == 0) {
13 model->removeRow(tableView->currentIndex().row());
14 } else {
15 QMessageBox::information(this, tr("Delete Artist"),
16 tr("Cannot delete %1 because there are CDs associated "
17 "with this artist in the collection.")
18 .arg(record.value("name").toString()));
19 }
20 }
Если выделена какая-то запись, мы проверяем наличие компакт-дисков у данного артиста, и если они отсутствуют, мы сразу же удаляем эту запись артиста. В противном случае мы выводим на экран окно с сообщением о причине невыполнения удаления. Строго говоря, здесь следовало бы использовать транзакцию, потому что из программного кода видно, что между вызовами функций cdModel.select() и model->removeRow() у артиста может появиться свой компакт-диск. Транзакция будет рассмотрена в следующем разделе.
Создание форм по технологии «master—detail»
Теперь мы рассмотрим главную форму, которая реализует подход «master—detail». Главный вид представляет собой список компакт-дисков. Вид описания деталей представляет собой список дорожек текущего компакт-диска. Это диалоговое окно является главным окном приложения CD Collection (Коллекция компакт-дисков); оно показано на рис. 13.1.
01 class MainForm : public QWidget
02 {
03 Q_OBJECT
04 public:
05 MainForm();
06 private slots:
07 void addCd();
08 void deleteCd();
09 void addTrack();
10 void deleteTrack();
11 void editArtists();
12 void currentCdChanged(const QModelIndex &index);
13 void beforeInsertCd(QSqlRecord &record);
14 void beforeInsertTrack(QSqlRecord &record);
15 void refreshTrackViewHeader();
16 private:
17 enum {
18 Cd_Id = 0,
19 Cd_Title = 1,
20 Cd_ArtistId = 2,
21 Cd_Year = 3
22 };
23 enum {
24 Track_Id = 0,
25 Track_Title = 1,
26 Track_Duration = 2,
27 Track_CdId = 3
28 };
29 QSqlRelationalTableModel *cdModel;
30 QSqlTableModel *trackModel;
31 QTableView *cdTableView;
32 QTableView *trackTableView;
33 QPushButton *addCdButton;
34 QPushButton *deleteCdButton;
35 QPushButton *addTrackButton;
36 QPushButton *deleteTrackButton;
37 QPushButton *editArtistsButton;
38 QPushButton *quitButton;
39 };
Мы используем для таблицы компакт-дисков cd модель QSqlRelationalTableModel, а не простую модель QSqlTableModel, потому что нам придется работать с внешними ключами. Мы рассмотрим по очереди все функции, начиная с конструктора, который мы разобьем на несколько секций из-за его большого размера.
01 MainForm::MainForm()
02 {
03 cdModel = new QSqlRelationalTableModel(this);
04 cdModel->setTable("cd");
05 cdModel->setRelation(Cd_ArtistId,
06 QSqlRelation("artist", "id", "name"));
07 cdModel->setSort(Cd_Title, Qt::AscendingOrder);
08 cdModel->setHeaderData(Cd_Title, Qt::Horizontal, tr("Title"));
09 cdModel->setHeaderData(Cd_ArtistId, Qt::Horizontal, tr("Artist"));
10 cdModel->setHeaderData(Cd_Year, Qt::Horizontal, tr("Year"));
11 cdModel->select();
Конструктор начинается с настройки модели QSqlRelationalTableModel, которая управляет таблицей cd. Вызов setRelation() указывает модели на то, что ее поле artistid (индекс которого находится в переменной Cd_ArtistId) содержит идентификатор id внешнего ключа из таблицы артистов artist и что вместо идентификаторов необходимо выводить на экран содержимое соответствующего поля name. Если пользователь переходит в режим редактирования этого поля (например, нажимая клавишу F2), модель автоматически выведет на экран поле с выпадающим списком имен всех артистов, и если пользователь выбирает другого артиста, таблица cd будет обновлена.
12 cdTableView = new QTableView;
13 cdTableView->setModel(cdModel);
14 cdTableView->setItemDelegate(new QSqlRelationalDelegate(this));
15 cdTableView->setSelectionMode(QAbstractItemView::SingleSelection);
16 cdTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
17 cdTableView->setColumnHidden(Cd_Id, true);
18 cdTableView->resizeColumnsToContents();
Настройка представления таблицы cd выполняется аналогично тому, что мы уже делали. Единственным существенным отличием является применение QSqlRelationalDelegate вместо делегата по умолчанию. Именно этот делегат обеспечивает работу с внешними ключами.
19 trackModel = new QSqlTableModel(this);
20 trackModel->setTable("track");
21 trackModel->setHeaderData(Track_Title, Qt::Horizontal, tr("Title"));
22 trackModel->setHeaderData(Track_Duration, Qt::Horizontal,
23 tr("Duration"));
24 trackTableView = new QTableView;
25 trackTableView->setModel(trackModel);
26 trackTableView->setItemDelegate(
27 new TrackDelegate(Track_Duration, this));
28 trackTableView->setSelectionMode(QAbstractItemView::SingleSelection);
29 trackTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
Для дорожек мы собираемся выводить на экран только названия песен и их длительности, поэтому достаточно использовать модель QSqlTableModel. (Поля id и cdid, используемые в рассмотренном ниже слоте currentCdChanged(), не выводятся на экран.) Единственно, на что следует обратить внимание в этой части программного кода, — это использование разработанного в главе 10 класса TrackDelegate, показывающего времена дорожек в виде «минуты:секунды» и позволяющего их редактировать с помощью удобного класса QTimeEdit.
Создание представлений и кнопок, их компоновка и соединения сигнал—слот не содержат ничего особенного, поэтому из оставшейся части конструктора мы покажем только несколько не совсем очевидных соединений.
30 …
31 connect(cdTableView->selectionModel(),
32 SIGNAL(currentRowChanged(const QModelIndex &,
33 const QModelIndex &)),
34 this, SLOT(currentCdChanged(const QModelIndex &)));