Программирование на C++ с использованием библиотеки Qt4
Компиляция Qt4 в Linux
Поскольку библиотека Qt является основой для KDE, то, скорее всего, она уже установлена в вашей системе. Но даже в этом случае могут отсутствовать некоторые средства разработки, т.к. в KDevelop используются свои собственные инструменты. Кроме того, обычно вместе с дистрибутивом Linux распространяются не самые свежие версии Qt.Порядок установки:
После компиляции библиотеки Qt4 в каталоге bin появятся следующие исполняемые файлы:
При необходимости разрешается задать параметры сборки, список которых можно узнать, если при запуске configure указать ключ -help;
Предварительные условия:
Build.cmd
. Например, в системе Windows: 1 qmake -project -o 00.pro 00.cpp 2 qmake -makefile 00.pro 3 4 rem mingw32-make -f Makefile.Debug 5 mingw32-make -f Makefile.Release 6 7 pauseКодеков
(специальных объектов для перекодировки строк). Соответствующий класс QTextCodec определён в заголовочном файле с тем же именем (без расширения! Заголовочные файлы с расширением .h используются только в старых проектах Qt3). При создании кодека надо указать название используемой кодовой таблицы, например: QTextCodec *codec = QTextCodec::codecForName("CP1251"); Затем надо связать этот кодек со всеми строками C++: QTextCodec::setCodecForCStrings(codec); Поскольку при создании кодека указана конкретная кодовая таблица, то исход компиляции исходных текстов не зависит от системной кодировки той платформы, на которой производится сборка программы: результат будет везде одним и тем же. Но файлы с исходными текстами программ в этом случае нельзя подвергать перекодировке (или после перекодировки требуется изменить название кодовой таблицы, заданной при создании кодека). В листинге 3 приведён новый вариант нашей программы.Простейшее приложение Qt (файл examples-qt/00/00.cpp)
1 // Простейшее приложение Qt4 (пустое окно) 2 3 #include4 #include
5 6 int main(int argc, char *argv[]) { 7 8 QApplication app(argc, argv);
9 10 QMainWindow *mw = new QMainWindow(0, Qt::Window);
11 mw->
setWindowTitle("Hello Qt4");
12 mw->
resize(400, 300);
13 mw->
show();
14 15 return app.exec();
16 } Пояснения к программе:

Перед компиляцией программы надо сначала создать проект Qt (файл с расширением .pro), для этого требуется войти в тот каталог, в котором находится cpp-файл с исходным текстом, и запустить утилиту
Простейшее приложение Qt4
1 // Простейшее приложение Qt4 (пустой фрейм) 2 // Вариант с кириллицей в заголовке 3 4 #include5 #include
6 7 int main(int argc, char *argv[]) { 8 9 QApplication app(argc, argv);
10 11 QMainWindow *mw = new QMainWindow(0, Qt::Window);
12 mw->
setWindowTitle(QString::fromLocal8Bit("Пустое окно Qt4"));
13 mw->
resize(400, 300);
14 mw->
show();
15 16 return app.exec();
17 }

Второй вариант работы с символами кириллицы -- явное использование
с символами национальных алфавитов связан
1 // Простейшее приложение Qt4 (пустой фрейм) 2 // Кодеки 3 4 #include5 #include
6 #include
7 8 int main(int argc, char *argv[]) { 9 10 QApplication app(argc, argv);
11 12 QTextCodec *codec = QTextCodec::codecForName("CP1251");
13 QTextCodec::setCodecForCStrings(codec);
14 15 QMainWindow *mw = new QMainWindow(0, Qt::Window);
16 mw->
setWindowTitle("Пустое окно Qt4");
17 mw->
resize(400, 300);
18 mw->
show();
19 20 return app.exec();
21 } Наконец, третий (наиболее предпочтительный) метод работы с символами национальных алфавитов связан с использованием специальной функции перевода tr, с помощью которой осуществляется интернационализация приложений. Подробнее этот вопрос мы обсудим позже, а пока договоримся все строковые константы, указанные в тексте программы, передавать в качестве параметра функции tr. Эта статическая функция является членом всех классов Qt, порождённых от базового класса QObject, но если, как сейчас, мы собираемся вызвать её в главной программе, а не в каком-либо методе класса, то приходится указывать какой-нибудь подходящий объект, например, QObject::tr. Для указания кодировки, используемой функцией перевода, надо создать соответствующий кодек и передать его в качестве аргумента методу setCodecForTr. Окончательный вариант нашей программы показан в листинге4.
Простейшее приложение Qt4
1 // Простейшее приложение Qt4 (пустой фрейм) 2 // Кодеки и функция tr() 3 4 #include5 #include
6 #include
7 8 int main(int argc, char *argv[]) { 9 10 QApplication app(argc, argv);
11 12 QTextCodec *codec = QTextCodec::codecForName("CP1251");
13 QTextCodec::setCodecForTr(codec);
14 15 QMainWindow *mw = new QMainWindow(0, Qt::Window);
16 mw->
setWindowTitle(QMainWindow::tr("Пустое окно Qt4"));
17 18 mw->
resize(400, 300);
19 mw->
show();
20 21 return app.exec();
22 }
Make
(для g++ в Linux) и т.п. При этом можно явно указать имя make-файла, для этого все перечисленные компиляторы распознают ключ -f. Поскольку в процессе разработки программу приходится много раз перекомпилировать, вышеописанный процесс лучше всего проводить с помощью командного файлаPro
следующего содержания: TEMPLATE = app TARGET = 00 DEPENDPATH += . INCLUDEPATH += .# Input SOURCES += 00.cpp После создания pro-файла можно сгенерировать make-файл, для чего достаточно запустить утилиту
.
Теперь, как и обещали, обсудим вопрос о символах кириллицы. Библиотека Qt предлагает несколько решений для национальных алфавитов, отличных от стандартной латиницы. Первый вариант -- использовать функцию QString::fromLocal8Bit. Дело в том, что для работы со строковыми значениями в Qt имеется класс QString, который оперирует двухбайтными символами Unicode. Для преобразования обычных строк с однобайтными символами к типу QString как раз и предназначен метод fromLocal8Bit. При этом используется системная кодировка (в Windows --- cp1251, в Linux -- обычно koi8-r).
Недостаток данного варианта заключается в том, что если программа, например, разрабатывалась в системе Windows, а затем мы захотели скомпилировать её исходные тексты в Linux, где используется другая кодовая таблица для символов кириллицы, то предварительно придётся осуществить перекодировку исходных текстов, иначе после компиляции все русские сообщения окажутся нечитаемыми.
Изменённый текст нашей первой программы приведён в листинге2, а внешний вид окна в системе Linux для двух разных тем рабочего стола показан на рис.
Попробуем создать пустое окно и вывести его на экран.
Qmake
с параметром -project. Имя cpp-файла можно не указывать. По умолчанию имя файла проекта будет совпадать с названием каталога, в котором происходит сборка программы. Для задания другого имени pro-файла при вызовенадо указать параметр -o. Например, для нашего первого проекта будет создан файл
ещё раз, но уже без указания каких-либо ключей (или задать параметр -makefileФайлПроекта.pro). Только после этого можно начинать компиляцию с помощью обычной команды
Ctags
, её можно скачать с сайта http://ctags.sourceforge.net/.После компиляции программы QDevelop (или после её инсталляции, если вы не захотели возиться с исходными текстами) требуется указать пути к инструментам библиотеки Qt, компилятору и отладчику. Для этого надо выполнить команду меню
Инструменты | Инструменты
(рис.) .
Рис. Настройка параметров QDevelop
Если читатель является счастливым обладателем коммерческой версии Qt, то в системе Windows, разумеется, будет использовать Microsoft Visual Studio 6.0 или .NET 2003/2005. На рис. показан внешний вид окна интегрированной среды Microsoft Visual Studio .NET 2003 с установленной поддержкой Qt.

Рис. Проект Qt в Microsoft Visual Studio .NET 2003
Интегрированная среда разработки
Конечно, с исходным текстом программ можно работать, используя любой текстовый редактор, а компиляцию проводить с помощью командной строки. Но более удобно (во всяком случае, так считают те, кто мало знаком с миром Unix/Linux-систем) вести разработку программ в какой-нибудь интегрированной среде (IDE). В состав библиотеки Qt входит утилита QtDesigner, с помощью которой можно в диалоговом режиме вести проектирование графического интерфейса приложений. Но она не является интегрированной средой разработки, т.к. в ней нет текстового редактора и отладчика (как было объявлено, в 4-й версии команда Qt отказалась от идеи превратить Qt Designer в полноценную среду разработки, а сосредоточилась на вопросах интеграции его со стандартными инструментами).В Linux, где Qt стала де-факто стандартом разработки программ, с этим особых проблем не возникает, поскольку практически любая IDE (например, KDevelop) поддерживает работу с Qt.
С системой Windows всё, как обычно, сложнее. В коммерческой версии Qt для Windows предусмотрена интеграция с Microsoft Visual Studio, но в Open Source Edition данная возможность отсутствует. Поэтому в рамках направления Open Source в настоящее время разрабатываются несколько похожих проектов, способных претендовать на звание "IDE для Qt". Среди них стоит отметить QDevelop (http://qdevelop.org/, рис.).

Рис. Интегрированная среда разработки QDevelop
Разумеется, сама программа QDevelop написана с использованием Qt, поэтому её исходные тексты можно скомпилировать на любой платформе, поддерживаемой библиотекой Qt. Хотя к настоящему времени (январь 2007 года) пока выпущен только предварительный релиз 0.21, но программа уже вполне работоспособна: имеется русский перевод интерфейса, система контекстной помощи, работает отладчик, подсветка синтаксиса, а также автодополнение кода (для последнего требуется утилита
Assistant
) находим, что в классе QAbstractSlider, потомком которого является QScrollBar, определён сигнал void QAbstractSlider::valueChanged ( int value ) оповещающий об изменении положения ползунка полосы прокрутки. Далее, в описании класса QLabel находим, что изменение текста надписи производится с помощью функции setText(строка) или setNum(число). Тогда вызов метода connect должен выглядеть следующим образом: QObject::connect( scrollBar, // Источник события. SIGNAL(valueChanged(int)), // Сигнал. label, // Объект-приёмник сигнала. SLOT( setNum(int) ) ); // Функция-обработчик. Заметим, что параметрами сигнала и слота являются типы, а не переменные.Количество параметров слота всегда не больше количества параметров сигнала. Соответствие между ними, как обычно, позиционное: при выполнении программы значением i-го параметра слота становится значение i-го параметра сигнала.
В объявлении класса методы-слоты необходимо указывать в разделе publicslots или private slots. Обычно в программе используются стандартные сигналы, но если требуется определить собственные, то их надо объявить в разделе signals. Например: class MainWondow : public QMainWindow { Q_OBJECT ............. signals: void mySignal(); private slots: void onMySignal(); .............
Сигнал может быть "соединён" с другим сигналом, например: connect( myButton, SIGNAL( clicked() ), this, SIGNAL( buttonClicked() ) );
Один и тот же сигнал можно связать с несколькими слотами и/или другими сигналами. С одним и тем же слотом можно связать несколько сигналов. Можно указать несколько одинаковых "соединений": тогда одно событие вызовет генерацию нескольких сигналов.
Чтобы разорвать связь между сигналом и слотом, используется метод disconnect: bool QObject::disconnect ( const QObject *sender, const char *signal, const QObject *receiver, const char *method ) [static]
В листинге 5 приведён текст небольшой программы, иллюстрирующей принцип обработки нажатия на кнопку (экземпляр класса QPushButton), а на рис. показано, как выглядит окно программы в системе Windows.

файл examples-qt/01/01.cpp)
1 // Сигналы и слоты: кнопка в окне 2 3 #include4 #include
5 6 int main(int argc, char *argv[]) { 7 8 QApplication app(argc, argv);
9 10 QPushButton *button = new QPushButton( 11 QString::fromLocal8Bit("&Выход") );
// Кнопка. 12 button->
setFont(QFont("Times", 16, QFont::Bold));
13 QObject::connect( 14 button, // Источник сигнала. 15 SIGNAL(clicked()), // Сигнал о нажатии кнопки. 16 &app, // Приёмник сигнала. 17 SLOT( quit() ) );
// Функция-слот (обработчик события). 18 button->
show();
19 20 return app.exec();
21 } Здесь мы разместили в окне обычную кнопку (10-11) с надписью "Выход" и связали её нажатие -- сигнал clicked (15) -- с функцией-обработчиком quit (17), которая завершает приложение app. Заметим, что мы не создаём главное окно для кнопки, это будет сделано автоматически (рис.).
Символ "&" перед буквой "В" в тексте надписи на кнопке (11) позволяет активировать её не только по щелчку левой кнопкой мыши или нажатием клавиши Enter, но также с помощью комбинации клавиш Alt+в (к сожалению, только в режиме ввода кириллицы).
Обработка событий
Для связывания событий, происходящий с объектами, и функций, предназначенных для обработки этих событий, в библиотеке Qt используется интересный механизмСлотов
. Сигнал -- это сообщение о том, что произошло какое-либо событие, например, нажатие на кнопку или выбор пункта меню. Вся информация о событии сохраняется в полях экземпляра соответствующего класса. У сигнала есть источник (например, кнопка) и приёмник (объект, метод которого будет обрабатывать это событие). Слот -- это сама функция-обработчик события. Связь между всеми четырьмя перечисленными элементами задаётся с помощью метода connect (соединить): bool QObject::connect ( const QObject *sender, // Источник события. const char *signal, // Сигнал. const QObject *receiver, // Объект-приёмник сигнала. const char *method, // Функция-обработчик. Qt::ConnectionType type = Qt::AutoConnection ) const Последний параметр определяет режим обработки: Qt::DirectConnection -- событие обрабатывается сразу; Qt::QueuedConnection -- событие ставится в общую очередь и будет обработано только после всех сообщений, уже имеющихся в этой очереди; Qt::AutoConnection -- если источник события находится в том же потоке, что и приёмник, то будет использован режим Qt::DirectConnection, в противном случае -- Qt::QueuedConnection.Для определения сигнала и слота используются макросы SIGNAL и SLOT. Например, мы хотим, чтобы текстовая метка label (экземпляр класса QLabel) отображала позицию полосы прокрутки scrollBar (экземпляр класса QScrollBar). В документации на библиотеку Qt (открыв doc/html/index.html или программу
Действие
(action).При создании действия указывается родительский элемент и, при необходимости, пиктограмма и/или текстовая метка: QAction::QAction(QObject *parent) QAction::QAction(const QString& text, QObject *parent) QAction::QAction(const QIcon& icon, const QString& text, QObject *parent)
Метод QAction::setStatusTip(текст) определяет текст подсказки по данному действию, выводимую в строке состояния, а QAction::setShortcut(QKeySequence& shortcut) -- привязывает к действию некоторую комбинацию клавиш.
С каждым действием связывается функция-обработчик, для этого используется всё тот же метод connect, например: QAction *exitAction = new QAction(tr("В&ыход"), this); exitAction->setStatusTip(tr("Выход из программы")); exitAction->setShortcut(tr("Ctrl+Q")); connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
Действия
Пользователь может влиять на процесс выполнения программы с помощью указателя и кнопок мыши, выбирая нужный пункт меню или кнопку на панели инструментов. Кроме того, в его распоряжении имеются клавиши со стрелками, а также различные клавиатурные комбинации. Все эти события объединены в библиотеке Qt под общим названиемКомпилятора метаобъектов
moc, который автоматически формирует на языке C++ необходимый код (в папке проекта после компиляции можно найти файл moc_*.cpp).Меню и строка состояния (файл examples-qt/02/02.h)
1 #include2 3 class MainWindow : public QMainWindow { 4 Q_OBJECT 5 6 public: 7 MainWindow();
8 9 protected: 10 virtual void resizeEvent(QResizeEvent *event);
11 virtual void mouseMoveEvent(QMouseEvent *event);
12 virtual void closeEvent(QCloseEvent *event);
13 14 private slots: 15 void about();
16 17 private: 18 QAction *aboutAction; 19 QAction *exitAction; 20 QMenu *fileMenu; 21 QLabel *sb1; 22 QLabel *sb2; 23 QLabel *sb3; 24 25 bool askOnClose();
26 };
Меню и строка состояния (файл examples-qt/02/02.cpp)
1 #include2 #include "02.h" 3 4 MainWindow::MainWindow() { 5 6 QTextCodec *codec = QTextCodec::codecForName("CP1251");
7 QTextCodec::setCodecForTr(codec);
8 9 aboutAction = new QAction(tr("&О программе"), this);
10 aboutAction->
setStatusTip(tr("Сведения о программе"));
11 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
12 13 exitAction = new QAction(tr("В&ыход"), this);
14 exitAction->
setStatusTip(tr("Выход из программы"));
15 exitAction->
setShortcut(tr("Ctrl+Q"));
16 connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
17 18 fileMenu = menuBar()->
addMenu(tr("&Файл"));
19 fileMenu->
addAction(aboutAction);
20 fileMenu->
addSeparator();
21 fileMenu->
addAction(exitAction);
22 23 sb1 = new QLabel(statusBar());
24 sb2 = new QLabel(statusBar());
25 sb3 = new QLabel(statusBar());
26 27 statusBar()->
setSizeGripEnabled(false);
28 statusBar()->
addWidget(sb1, 2);
29 statusBar()->
addWidget(sb2, 1);
30 statusBar()->
addWidget(sb3, 1);
31 32 sb1->
setText(tr("Привет!"));
33 34 setMouseTracking(true);
35 } 36 37 38 void MainWindow::resizeEvent(QResizeEvent *event) { 39 QSize sz = event->
size();
40 sb2->
setText( 41 QString( "(%1, %2)" ).arg( sz.width() ).arg( sz.height() ) );
42 } 43 44 void MainWindow::mouseMoveEvent(QMouseEvent *event) { 45 QPoint pos = event->
pos();
46 sb3->
setText( 47 QString( "%1, %2" ).arg( pos.x() ).arg( pos.y() ) );
48 } 49 50 void MainWindow::about() { 51 QMessageBox::about( 52 this, tr("О программе"), 53 tr("
Простое приложение Qt4
" 54 "
Окно с меню и строкой состояния"));
55 } 56 57 bool MainWindow::askOnClose() { 58 int r = QMessageBox::question( 59 this, tr("Подтвердите"), 60 tr("Выйти из программы?"), 61 QMessageBox::Yes | QMessageBox::No, 62 QMessageBox::Yes, 63 QMessageBox::Cancel | QMessageBox::Escape);
64 return (r == QMessageBox::Yes);
65 } 66 67 void MainWindow::closeEvent(QCloseEvent *event) { 68 if (askOnClose()) { 69 event->
accept();
70 } else { 71 event->
ignore();
72 } 73 } 74 75 int main(int argc, char *argv[]) { 76 QApplication app(argc, argv);
77 MainWindow mainWin; 78 mainWin.show();
79 return app.exec();
80 }
Чуть позже мы узнаем, как использовать программу
Меню
Горизонтальная панель меню QMenuBar создаётся автоматически, если мы обращаемся к ней для добавления хотя бы одного вертикального меню QMenu. Например: // Определяем действия: QAction *aboutAction = new QAction(tr("&О программе"), this); QAction *exitAction = new QAction(tr("В&ыход"), this);// Создаём вертикальное меню и добавляем // его на автоматически созданную панель QMenuBar: QMenu *fileMenu = menuBar()->addMenu(tr("&Файл")); fileMenu->addAction(aboutAction); // Добавили действие 'О программе'. fileMenu->addSeparator(); // Разделитель пунктов меню. fileMenu->addAction(exitAction); // Добавили действие 'Выход'. Здесь мы создали меню "Файл" с двумя пунктами "Опрограмме" и "Выход" и горизонтальной полосой-разделителем между ними.
Наследники класса QObject и метакомпилятор
Любые пользовательские классы, разрабатываемые программистом, обычно являются наследниками базового класса QObject (или какого-нибудь из его потомков). Только в этом случае для них можно использовать механизм сигналов и слотов, а также некоторые другие возможности, реализованные в библиотеке Qt (в частности, информацию о типах во время выполнения программы и динамические свойства объектов). При этом необходимо соблюдать следующие правила:Утилита qmake просматривает заголовочные файлы проекта, и если в описании какого-нибудь класса встречается макрос Q_OBJECT, то в make-файл вставляется вызов так называемого
Обработка событий с помощью виртуальных методов
Кроме механизма сигналов и слотов, в Qt используется также старый метод виртуальных функций. Так, например, базовый класс QWidget (элемент интерфейса, виджет) и все его многочисленные потомки имеют виртуальный метод mouseMoveEvent void QWidget::mouseMoveEvent ( QMouseEvent * event ) который вызывается при перемещении указателя мыши над данным элементом. Если требуется определить какую-либо реакцию на это событие, то достаточно в собственном классе переопределить метод mouseMoveEvent. Текущие координаты мыши в системе отсчёта данного виджета можно узнать с помощью методов QMouseEvent::x() и QMouseEvent::y(). Следует иметь в виду, что по умолчанию метод mouseMoveEvent вызывается, только если при перемещении указателя мыши удерживается любая из её кнопок. Чтобы это происходило независимо от состояния кнопок, надо для данного виджета вызвать метод setMouseTracking(true).Аналогично можно обрабатывать событие изменения размеров любого элемента, только в этом случае для него придётся переопределить метод resizeEvent.
Пример приложения с меню и строкой состояния
В листингах 6 и 7 приведён текст небольшой программы, в которой иллюстрируются все описанные в данном разделе элементы: меню, строка состояния с несколькими текстовыми полями, обработка перемещения указателя мыши и изменения размеров окна с помощью виртуальных функций, а также определение собственного класса на основе QObject. На рис. показано, как выглядит окно программы в Windows и Linux.
Рис. Меню и строка состояния: внешний вид окна программы в системе Windows (верхний скриншот) и Linux (для двух различных тем рабочего стола). В строке состояния отображаются размеры окна и координаты указателя мыши
QtDesigner
для определения действий, создания меню и других элементов интерфейса.Строка состояния
Строка состояния QStatusBar создаётся автоматически в нижней части главного окна приложения, если в программе хоть раз вызвается метод MainWindow::statusBar. При наведении указателя мыши на кнопку панели инструментов или пункт меню в строке состояния на время появляется текст подсказки, если этот текст определён для данной кнопки или данного пункта.Указатель на экземпляр QStatusBar можно получить с помощью метода QMainWindow::statusBar(). Чтобы вывести в строке состояния произвольный текст (и затереть предыдущий), используется функция showMessage: void QStatusBar::showMessage( const QString &message, // Выводимая строка. int timeout = 0 ) // Кол-во миллисекунд. Если timeout>0, то сообщение автоматически исчезает через указанный промежуток времени, а на его место возвращается текст предыдущей надписи.
По умолчанию строка состояния представляется в виде одной панели, располагаемой по всей ширине родительского окна. Но её можно разбить по ширине на отдельные поля, если вставить в неё другие элементы, например, QLabel. Для этого предназначены методы addWidget, addPermanentWidget и insertWidget: void QStatusBar::addWidget ( QWidget *widget, // Вставляемый элемент. int stretch = 0 ) // Коэффициент растяжимости.
void QStatusBar::addPermanentWidget ( QWidget *widget, int stretch = 0 )
int QStatusBar::insertWidget ( int index, // Позиция. QWidget *widget, // Вставляемый элемент. int stretch = 0 ) // Коэффициент растяжимости.
Элементы, добавляемые с помощью метода addPermanentWidget, располагаются в правой части строки состояния и не затираются сообщениями, выводимыми с помощью showMessage.
В нижней правой части строки состояния по умолчанию отображается специальный маркер, который можно "зацепить" указателем мыши для изменения размеров окна. Его показ можно запретить, вызвав QStatusBar::setSizeGripEnabled(false). При этом возможность изменять размеры окна по-прежнему остаётся.
Менеджеры размещения (файл examples-qt/04/04.cpp)
// Создаём менеджер размещения: 52 QGridLayout *mainlay = new QGridLayout();// Размер полей вокруг сетки элементов и // интервалы между ячейками сетки: 53 mainlay->
setMargin(2);
54 mainlay->
setSpacing(3);
55 // Размещаем элементы: 56 mainlay->
addWidget(lb, 0, 0);
57 mainlay->
addWidget(le, 0, 1, 1, 2);
58 59 mainlay->
addWidget(cb, 1, 0, 1, 2);
60 mainlay->
addWidget(sb, 1, 2);
61 62 mainlay->
addWidget(chb, 2, 0);
63 mainlay->
addWidget(dsb, 2, 1, 1, 2);
64 65 mainlay->
addWidget(dte, 3, 0, 1, 3);
66 // Менеджер размещения для радиокнопок: 67 QHBoxLayout *hbl = new QHBoxLayout();
68 hbl->
addWidget(rb1, 1);
69 hbl->
addWidget(rb2, 1);
70 hbl->
addWidget(rb3, 1);
71 gb->
setLayout(hbl);
72 mainlay->
addWidget(gb, 4, 0, 1, 3);
73 // Менеджер размещения для кнопок диалога: 74 QHBoxLayout *btns = new QHBoxLayout();
75 btns->
addStretch(1);
76 btns->
addWidget(btn1, 2);
77 btns->
addWidget(btn2, 2);
78 btns->
addStretch(1);
79 mainlay->
addLayout(btns, 6, 0, 1, 3);
80 // Растяжимость колонок и строк: 81 mainlay->
setColumnStretch(1, 1);
82 mainlay->
setColumnStretch(2, 1);
83 mainlay->
setRowStretch(5, 1);
84 setLayout(mainlay);
85 }
Для вставки любого виджета в нужную ячейку QGridLayout предназначен метод addWidget, при вызове которого указываются номер строки и столбца (нумерация начинается с нуля), а также, если требуется, количество соседних строк и столбцов, которые будет занимать элемент. Растяжимостью строк и столбцов сетки управляют методы setRowStretch и setColumnStretch, первый параметр которых указывает номер строки (столбца), а второй -- коэффициент растяжения. Чем он больше, тем сильнее будет растягиваться/сжиматься данная строка по вертикали (или столбец по горизонтали) по сравнению с остальными строками (столбцами) при изменении размеров всего окна.
Разумеется, в данном случае можно предложить и другие варианты, например, на внешнем уровне использовать QVBoxLayout и заполнять его менеджерами QHBoxLayout, в которые вставлять элементы каждой горизонтальной строки диалога, подобно тому, как это сделано с радиокнопками в нашем примере.
Если нас не удовлетворяет то, как ведут себя какие-либо элементы диалога при изменении размеров окна, то можно попытаться вызвать для них метод setSizePolicy, первый параметр которого задаёт политику изменения ширины элемента, а второй -- его высоты. Каждый параметр может принимать одно из значений:
Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.h)
1 // Перерасчёт геометрии всех элементов при изменении размеров окна 2 3 #include4 5 class MyDialog : public QDialog { 6 Q_OBJECT 7 8 public: 9 MyDialog();
10 11 protected: 12 virtual void resizeEvent(QResizeEvent *event);
13 14 private: 15 QLabel *lb; // Текстовая метка. 16 QLineEdit *le; // Строковое поле ввода. 17 QComboBox *cb; // Поле ввода с раскрывающимся списком. 18 QSpinBox *sb; // Целочисленное поле ввода с кнопками 19 // инкремента/декремента. 20 QCheckBox *chb; // Независимый переключатель 21 // с двумя состояниями. 22 QDoubleSpinBox *dsb; // Поле ввода вещественного значения 23 // с кнопками инкремента/декремента. 24 QDateTimeEdit *dte; // Поле ввода даты и времени. 25 QGroupBox *gb; // Рамка с надписью вокруг группы элементов. 26 QRadioButton *rb1; // Три 27 QRadioButton *rb2; // зависимых 28 QRadioButton *rb3; // переключателя. 29 QPushButton *btn1; // Кнопка "Сохранить". 30 QPushButton *btn2; // Кнопка "Отменить". 31 };
Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.cpp)
1 // Перерасчёт геометрии всех элементов при изменении размеров окна 2 3 #include "03.h" 4 5 MyDialog::MyDialog() { 6 7 QTextCodec *codec = QTextCodec::codecForName("CP1251");8 QTextCodec::setCodecForTr(codec);
9 10 lb = new QLabel(tr("Метка:"), this);
11 12 le = new QLineEdit(tr("Строка"), this);
13 14 cb = new QComboBox(this);
15 cb->
addItem(tr("Первый"));
16 cb->
addItem(tr("Второй"));
17 cb->
addItem(tr("Третий"));
18 cb->
setCurrentIndex(2);
19 cb->
setEditable(true);
20 cb->
setInsertPolicy(QComboBox::InsertAtBottom);
21 22 sb = new QSpinBox(this);
23 sb->
setValue(5);
24 25 chb = new QCheckBox(tr("Пометка"), this);
26 chb->
setCheckState(Qt::Checked);
27 28 dsb = new QDoubleSpinBox(this);
29 dsb->
setMaximum(200.0);
30 dsb->
setDecimals(2);
31 dsb->
setSingleStep(0.25);
32 dsb->
setValue(100.25);
33 34 dte = new QDateTimeEdit( 35 QDateTime(QDate(2007, 2, 5), QTime(13, 35, 55, 10)), 36 this);
37 38 gb = new QGroupBox(tr("Выбрать одно из трёх:"), this);
39 40 rb1 = new QRadioButton(tr("Один"), gb);
41 42 rb2 = new QRadioButton(tr("Два"), gb);
43 44 rb3 = new QRadioButton(tr("Три"), gb);
45 46 rb2->
setChecked(true);
47 48 btn1 = new QPushButton(tr("Сохранить"), this);
49 50 btn2 = new QPushButton(tr("Отменить"), this);
51 52 setMinimumSize(160, 205);
53 } 54 55 void MyDialog::resizeEvent(QResizeEvent* /* event */ ) { 56 int dw = width() - minimumWidth();
57 int dh = height() - minimumHeight();
58 59 lb->
setGeometry(5, 6, 45, 24);
60 le->
setGeometry(55, 6, 100+dw, 24);
61 62 cb->
setGeometry(5, 36, 80+dw*2/3, 24);
63 sb->
setGeometry(95+dw*2/3, 36, 60+dw/3, 24);
64 65 chb->
setGeometry(5, 65, 70, 24);
66 dsb->
setGeometry(95, 65, 60+dw, 24);
67 68 dte->
setGeometry(5, 95, 150+dw, 24);
69 70 gb->
setGeometry(5, 120, 150+dw, 40);
71 rb1->
setGeometry(5, 15, 45+dw/3, 24);
72 rb2->
setGeometry(55+dw/3, 15, 45+dw/3, 24);
73 rb3->
setGeometry(105+dw*2/3, 15, 45+dw/3, 24);
74 75 btn1->
setGeometry(5+dw/4, 170+dh, 70+dw/4, 29);
76 btn2->
setGeometry(85+dw/2, 170+dh, 70+dw/4, 29);
77 } 78 79 int main(int argc, char *argv[]) { 80 81 QApplication app(argc, argv);
82 83 MyDialog *dlg = new MyDialog();
84 dlg->
show();
85 86 return app.exec();
87 }

Рис. Внешний вид окна и элементов управления в системе Windows при запуске программы с параметром
-style=Windows, WindowsXP, Motif, CDE, Plastique и Cleanlooks
На рис. показано, как изменяется внешний вид элементов диалога, если при запуске программы, текст которой приведён в листингах 8 и 9, указать в командной строке параметр -style=ИмяСтиля. Обратите внимание, что при одних и тех же размерах окна, устанавливаемых сразу после запуска программы, в режиме WindowsXP текстовая метка "Один" оказалась обрезанной на последней букве, а для стиля Motif высота всех полей ввода и ширина кнопок оказалась на грани критической: текстовые надписи еле "влазят" в отведённые для них границы из-за увеличенной толщины декоративных элементов. В системе Linux, где размер шрифта по умолчанию выбирается обычно больше, чем в Windows (из-за традиционно худшего качества отображения шрифтов), это различие компоновки элементов диалога для различных стилей ещё более заметно.
Менеджеры размещения
В Qt имеются классы QHBoxLayout, QVBoxLayout и QGridLayout, которые специально предназначены для управления положением и размерами элементов в окне. Первый позволяет располагать элементы друг за другом по горизонтали, второй -- по вертикали, а третий размещает виджеты в ячейках воображаемой таблицы, причём каждый элемент может занимать несколько смежных ячеек по вертикали и/или горизонтали.В листинге 10 показаны строки, которые потребуется дописать к тексту конструктора диалога (см. листинг 9), если мы захотим использовать менеджеры размещения. Здесь все элементы располагаются в сетке QGridLayout, а для зависимых переключателей и кнопок создаются отдельные менеджеры QHBoxLayout. Разумеется, метод resizeEvent теперь не нужен.
"Ручное" размещение
С помощью метода setGeometry(int x, int y, int w, int h) или setGeometry(const QRect&) можно задать положение и размер любого визуального элемента в пикселах. Для установки размеров без изменения положения может использоваться метод resize(int w, int h) или resize(const QSize&). Наоборот, для перемещения элемента в нужную позицию с сохранением прежних размеров служит метод move(int x, int y) или move(const QPoint&).Недостатком жёсткого варианта размещения элементов интерфейса является то, что пользователь не может изменить размер окна диалога (или изменение размеров окна не влияет на взаимное положение и размеры всех его элементов). В результате при низком разрешении монитора всё выглядит слишком крупно, а то и вовсе не помещается на экран, при высоком-- наоборот, слишком мелко. Кроме того, в различных операционных системах используются разные шрифты, поэтому надписи и поля ввода, прекрасно смотревшиеся в одной системе, при переносе на другую платформу могут не уместиться в прежних границах. К тому же в солидных программных продуктах принято давать пользователю возможность настраивать интерфейс программы по своему вкусу, в частности, изменять гарнитуру и размер шрифта. А при локализации (переводе интерфейса программы на другой язык) всё ещё больше усложняется.
Очевидный (но не лучший) способ решить хотя бы некоторые из перечисленных проблем заключается в том, чтобы в виртуальном методе resizeEvent, автоматически выполняемом при изменении размеров окна, пересчитывать размеры и положение всех элементов. Пример программирования такого диалога приведён в листингах 8 и 9, а внешний вид окна -- на рис.

Рис. Перерасчёт геометрии в методе resizeEvent
Edit| Edit Signals/Slots
или просто нажав клавишу F4. С помощью левой кнопки мыши "зацепим" поле sb -- источник сигнала и "бросим" его на элемент le -- приёмник сигнала (рис.).
В открывшемся диалоге (рис.) поставим галочку
Edit Tab Order
. На редактируемой форме появятся квадратики с числами (рис.). Будем щёлкать по ним левой кнопкой мыши в нужном порядке. После этого можно опять войти в режим предпросмотраForm| Preview
).
Разумеется, для реализации более сложных действий (например, изменение доступности одних элементов диалога в зависимости от значений, вводимых пользователем в другие элементы) без настоящего программирования обойтись не получится.
Сохраним описание диалога под любым именем (например, dialog) и с расширением ui.
В листинге 11 приведён фрагмент получившегося в результате файла. Как видим, описание диалога хранится в формате XML.
Label
(рис.) и с помощью левой кнопки мыши перетащим его на форму. В правой части дизайнера, в окнеLay Out Horizontally
(расположить по горизонтали). В результате автоматически будет создан менеджер размещения QHBoxLayout и оба выделенных виджета окажутся внутри него. Останется только задать единичное значение параметра horizontalStretch (растяжение по горизонтали) из раздела sizePolicy для однострочного поля ввода.
Рис. Внешний вид окна Qt Designer
Аналогично создадим два горизонтальных менеджера размещения для двух следующих строк нашего диалога. Поле для ввода даты/времени пропустим, т.к. оно единственное в своей строке. Далее выделим три зависимых переключателя и создадим для них всё тот же горизонтальный менеджер размещения. Для самой рамки Group Box, внутри которой расположены три зависимых переключателя, также создадим свой QHBoxLayout (хотя эта рамка и единственная в своей строке, но она является контейнером для других элементов и без менеджера размещения её высота не может быть вычислена правильно).
Кнопки диалога трогать не будем, потому что при создании формы мастер уже разместил их в элементе Button Box, который является аналогом менеджера размещения, только используется специально для кнопок. Можно изменить состав кнопок с помощью свойства standardButtons и их выравнивание по горизонтали (свойство centerButtons=true).
Наконец, нажмём правой кнопкой мышки по пустому фону диалога и в появившемся контекстном меню выберем команду
Lay Out Vertically
(расположить по вертикали). В результате будет создан вертикальный менеджер QVBoxLayout и внутри него будут размещены все имеющиеся на форме строки QHBoxLayout вместе с полем ввода даты/времени и вертикальным промежутком (для которых мы так и не создали горизонтального менеджера размещения).Можно проверить, как будет выглядеть наш диалог при различном оформлении (команда меню
Файл *.ui
Dialog
0
0
274
259
.......
В листинге 12 показано, как использовать в программе диалог, разработанный с помощью QtDesigner.
Загрузка ui-ресурса (examples-qt/05/05.cpp)
#include#include
#include "ui_dialog.h"
int main(int argc, char *argv[]) { QApplication app(argc, argv);
Ui::Dialog ui; QDialog *dialog = new QDialog; ui.setupUi(dialog);
dialog->
show();
return app.exec();
}
Мы подключили заголовочный файл, имя которого начинается префиксом ui_, после которого записывается имя файла, созданного в дизайнере. Откуда берётся ui_dialog.h? Этот файл формируется с помощью утилиты uic, которая автоматически вызывается при компиляции программы.
Затем объявили экземпляр класса Ui::Dialog (этот класс описан в заголовочном файле) и экземпляр стандартного класса QDialog, после чего для первого вызвали метод setupUi, указав второй в качестве параметра.
Компиляция программы проводится обычным порядком: сначала команда qmake -project, затем просто qmake и, наконец, mingw32-make или nmake.
Можете запустить программу на выполнение: на экран выводится окно диалога, доступность поля ввода вещественного числа зависит от состояния переключателя, изменяемое значение sb тут же отображается в однострочном поле ввода le, а при нажатии любой из двух кнопок диалог закрывается.
Для выполнения более сложных действий придётся описать новый класс, например, MyDialog, указав в качестве базового стандартный класс QDialog. При этом относительно разработанного в дизайнере класса Ui::Dialog имеется две возможности: либо определить экземпляр этого класса внутри MyDialog, либо сделать класс Ui::Dialog вторым родителем класса MyDialog.
Первый вариант (с одним базовым классом) приведён в листинге 13.
Работа с ui-ресурсом
// В заголовочном файле *.h: #include#include "ui_dialog.h"
class MyDialog : public QDialog { Q_OBJECT public: MyDialog(QWidget *parent = 0);
private: Ui::Dialog ui; };
// В файле *.cpp: MyDialog::MyDialog(QWidget *parent) : QDialog(parent) {
ui.setupUi(this);
ui.cb->
addItem(tr("Первый"));
ui.cb->
addItem(tr("Второй"));
ui.cb->
addItem(tr("Третий"));
ui.cb->
setCurrentIndex(2);
// ....... } Второй вариант (с двумя базовыми классами) показан в листинге 14.
Работа с ui-ресурсом (файлы examples-qt/06/06.h и 06.cpp)
// В заголовочном файле *.h: #include#include "ui_dialog.h"
class MyDialog : public QDialog, private Ui::Dialog { Q_OBJECT public: MyDialog(QWidget *parent=0);
};
// В файле *.cpp: MyDialog::MyDialog(QWidget *parent) : QDialog(parent) {
setupUi(this);
cb->
addItem(tr("Первый"));
cb->
addItem(tr("Второй"));
cb->
addItem(tr("Третий"));
cb->
setCurrentIndex(2);
// ........ }
int main(int argc, char *argv[]) { QApplication app(argc, argv);
QTextCodec *codec = QTextCodec::codecForName("CP1251");
QTextCodec::setCodecForTr(codec);
MyDialog *dlg = new MyDialog();
dlg->
show();
return app.exec();
}
Как видим, к элементам диалога можно обращаться по тем именам, которые были указаны для них в QtDesigner.
Соединения между сигналами и слотами для класса MyDialog можно определять как обычно, с помощью метода connect (например, в конструкторе класса MyDialog), при этом пользовательские слоты должны быть перечислены в объявлении класса или унаследованы от базовых классов. Но можно воспользоваться особым правилом именования слотов: private slots: on_ИмяВиджета_ИмяСигнала(ПараметрыСигнала);
В этом случае соединение между сигналом и слотом будет выполнено автоматически (в методе setupUi(), который генерируется утилитой uic). Например: // В заголовочном файле *.h: #include
#include "ui_dialog.h"
class MyDialog : public QDialog, public Ui::Dialog { Q_OBJECT public: MyDialog(QWidget *parent=0);
private slots: void on_rb3_toggled(bool s);
};
// В файле *.cpp: void MyDialog::on_rb3_toggled(bool s) { dte->
setHidden(s);
} В результате при выборе радиокнопки rb3 поле ввода даты/времени будет спрятано, а при выборе любой другой радиокнопки -- снова показано.
Оба описанных варианта компоновки внешнего ресурса с основной программой являются статическими: после компиляции программы ui-файл можно удалить. Существует и вариант динамической загрузки во время выполнения программы (листинг 15). Здесь используется класс QUiLoader. При этом требуется подключить заголовочный файл QtUiTools, а в файле проекта *.pro -- добавить строку CONFIG += uitools
После редактирования pro-файла не следует выполнять qmake с параметром -project, иначе все изменения в нём будут потеряны.
Динамическая загрузка ui-ресурса (examples-qt/07/07.cpp)
#include#include
int main(int argc, char *argv[]) { QApplication app(argc, argv);
QTextCodec *codec = QTextCodec::codecForName("CP1251");
QTextCodec::setCodecForTr(codec);
QUiLoader uiLoader; QFile file("dialog.ui");
file.open(QFile::ReadOnly);
QWidget *dlg = uiLoader.load(&file);
file.close();
if (dlg) { QComboBox *cb = dlg->
findChild
("cb");
cb->
addItem(QObject::tr("Первый"));
cb->
addItem(QObject::tr("Второй"));
cb->
addItem(QObject::tr("Третий"));
cb->
setCurrentIndex(2);
cb->
setEditable(true);
cb->
setInsertPolicy(QComboBox::InsertAtBottom);
QSpinBox *sb = dlg->
findChild
("sb");
sb->
setValue(5);
dlg->
findChild
("chb")->
setCheckState(Qt::Checked);
// .......
dlg->
findChild
("rb2")->
setChecked(true);
dlg->
show();
return app.exec();
} else return 1; }
Для обращения к элементам диалога при динамической загрузке используется метод parent->
findChild<Тип *>
("ИмяЭлемента") или функция qFindChild<Тип *>
(parent, "ИмяЭлемента")
Второй вариант предназначен для компиляторов, которые не поддерживают шаблоны методов (как, например, Microsoft Visual C++ 6.0).
NewForm
. Из представленных вариантов заготовок (диалоги с горизонтальным или вертикальным размещением кнопок, главное окно приложения или произвольный виджет) выберем первый и нажмём кнопкуOk
. После этого можно снова проверить, как всё работает (+
", после чего последовательно укажите все четыре параметра соединения (рис.). После этого в свойствах элемента chb надо задать начальное значение checked=false, а для элемента dsb -- значение enabled=false. Теперь можно в режиме предварительного просмотра проверить, как изменяется доступность поля ввода числа при изменении состояния переключателя.
Аналогично определим ещё два соединения, соответствующие нажатию кнопок "Сохранить" и "Отмена" (см. рис.). Стандартные для модального диалога слоты accept и reject закрывают диалог и запоминают результат Accepted (принят) или Rejected (отклонён), который затем может быть прочитан с помощью метода result().
Теперь предположим, что при изменении числа в поле sb необходимо тут же выводить это же значение в поле le. Воспользуемся вторым способом определения соединений. Войдём в режим визуального редактирования сигналов и слотов, выполнив пункт меню
Preview in
).
Рис. Порядок обхода элементов
Теперь надо задать порядок обхода элементов при нажатии клавиши Tab. Для этого выполним команду меню
Previewin
и с помощью клавиши Tab убедиться, что порядок обхода элементов задан правильно.Допустим, нам требуется сделать недоступным поле ввода вещественного числа dsb при сбросе переключателя chb. Другими словами, надо связать сигнал toggled(bool) элемента chb и слот enabled(bool) элемента dsb. Если в нижнем правом углу окна Qt Designer вы не видите панель
Qt
. Покажем, как пользоваться дизайнером на примере нашего диалога (см.рис.).Откроем Qt Designer и (если диалог создания новой формы не появится автоматически) выполним команду меню
Редактор свойств
) укажем идентификатор элемента (значение свойстваShow all signals and slots
(показывать все сигналы и слоты), в левом окне выберем сигнал valueChanged(QString), а в правом -- слот setText(QString). Нажмём| Signal/Slot Editor
. Для добавления нового соединения нажмите кнопку "Создание диалогов с помощью QtDesigner
Для разработки интерфейса программы в Qt имеется специальный инструмент -- Qt Designer. Он используется либо как самостоятельное приложение, либо как компонент, встроенный в интегрированную среду Microsoft Visual Studio. Во втором случае доступ к его функциям производится с помощью пункта менюText
).Аналогично разместим на форме однострочное поле ввода Line Edit (идентификатор le), поле со списком Combo Box (идентификатор cb), поле для ввода целого числа с кнопками инкремента/декремента Spin Box (идентификатор sb), независимый переключатель Check Box (идентификатор chb), поле для ввода вещественного числа с кнопками инкремента/декремента Double Spin Box (идентификатор dsb), поле для выбора даты/времени Date/Time Edit (идентификатор dte), контейнер Group Box (идентификатор groupBox) с тремя зависимыми переключателями Radio Button (идентификаторы rb1, rb2 и rb3), как показано на рис.
Пока не следует сильно заботиться о красивом размещении элементов друг относительно друга и об их точных размерах. Для первой радиокнопки зададим значение свойства checked=true. Между рамкой с зависимыми переключателями и кнопками диалога вставим элемент Vertical Spacer (вертикальный промежуток, он находится в группе Spacers). Для элемента Button Box проверим значение свойства standardButtons: галочки должны быть установлены только для кнопок Cancel и Save.
Теперь выделим мышкой первые два элемента (Label и Line Edit), находящиеся в первой горизонтальной строке диалога. Для выделения нескольких элементов можно использовать клавишу Shift. Щёлкнем по выделенной группе правой кнопкой мыши и в появившемся контекстном меню выберем команду
Компиляция 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\ms\libpq.a" psql.pro mingw32-make (все пути не должны содержать пробелов).
В 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-драйверы, скомпилированные в виде динамических библиотек, требуется позаботиться о том, чтобы сами эти библиотеки, а также, в свою очредь, используемые ими библиотеки, поставляемые с соответствующими СУБД, были доступны по стандартным путям поиска. В окончательных релизах, поставляемых пользователю, Qt-драйверы qsql* лучше всего размещать в подкаталоге ./plugins/sqldrivers, а используемые ими библиотеки -- либо в каталоге самой программы, либо в стандартном каталоге Windows/system32 (для системы Windows) или usr/lib (для Linux).
В библиотеке Qt4 имеются драйверы для работы со следующими СУБД:
В Qt4 Open Source Edition отсутствует поддержка коммерческих СУБД Oracle, Sybase и DB2, т.к. драйверы для них распространяются под лицензией, не совместимой с GPL.
Выполнение SQL-запросов (файл examples-qt/db00/db00.h)
1 #include2 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 ;
1 #include
2 #include
3 #include
// Для 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
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 }
Перед компиляцией проекта, в котором используется модуль QtSql, надо добавить в pro-файл строку QT += sql.
Перед выполнением этой программы требуется создать какую-нибудь базу данных: create database db1 character set utf8;
(вместо utf8 можно использовать кодировки cp1251 или koi8r);
а перед использованием QODBC -- настроить ODBC-псевдоним.
Подключение с базе данных и выполнение 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 sql; sql.exec("SELECT id, name, salary FROM empl WHERE salary>=1000"); или QSqlQuery sql("SELECT id, name, salary FROM empl WHERE salary>=1000"); Здесь запрашиваются номера id, имена name и оклады salary всех работников из таблицы empl, у которых оклад не ниже 1000. Обратите внимание, что если при создании объекта QSqlQuery указан текст запроса на языке SQL, то этот запрос сразу выполняется.
В обоих случаях в конструкторе запроса можно указать базу данных QSqlDatabase, с которой он будет работать. Но как правило, приложение открывает только одно соединение с единственной базой данных, поэтому этот параметр принимает значение по умолчанию.
Если при выполнении запроса возникла ошибка, то метод lastError() позволяет вывести на экран её описание: if ( ! query.isActive() ) QMessageBox::warning( this, tr("Database Error"), query.lastError().text() ); Иначе можно получить данные, которые сервер вернул в качестве результата: while ( sql.next() ) { qint64 id = sql.value(0).toLongLong(); QString name = sql.value(1).toString(); double salary = sql.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; sql.prepare("INSERT INTO empl (id, name, salary) " "VALUES (:id, :name, :salary)"); for (int i=0; i
Ссылку на соединение с базой данных можно получить, вызвав функцию QSqlDatabase::database(connectionName). Необязательный параметр connectionName -- это имя соединения, которое было задано при его создании с помощью QSqlDatabase::addDatabase().
По окончании работы с базой данных соединение надо закрыть: QSqlDatabase::close(). Затем можно либо открыть его снова с помощью метода open(), либо удалить из списка соединений, вызвав статический метод QSqlDatabase::removeDatabase(connectionName).

Выполнение SQL-запросов (система Windows, драйвер QODBC)

Выполнение SQL-запросов (система Linux, драйвер QMYSQL)
В листингах приведён пример программы, работающей с базой данных, а на рис. показан результат её работы в Windows и Linux.
Таблица базы данных (файл examples-qt/db01/db01.cpp)
1 // Таблица базы данных 2 3 #include4 #include
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 }
Проверьте, как работает эта програма: можно изменять данные в ячейках, при редактировании чисел и дат автоматически отображаются кнопки инкремента/декремента. Но поскольку для элемента QDoubleSpinBox по умолчанию задано максимальное значение 99.99, то при попытке изменить, например, величину зарплаты работника, любое значение, большее этого максимального, автоматически усекается. Разумеется, попытка ввести число больше допустимого, оканчивается неудачей. Кроме того, после редактирования первой же ячейки таблицы, когда происходит автоматическое обновление данных, ширина столбцов и высота строк изменяется, т.к. размеры ячеек по умолчанию отличаются от тех, что установились в результате однократного выполнения методов resizeRowsToContents и resizeColumnsToContents.
Если мы хотим получать большую зарплату, выводить разные столбцы различным цветом, отображать галочки для полей логического типа, использовать календарик для ввода дат, в общем, как-то изменять заданные по умолчанию параметры отображения и редактирования ячеек, то у нас имеются две возможности: разрабатывать свой класс модели и/или представления таблицы или использовать специальные объекты-делегаты для её ячеек. Рассмотрим оба варианта по очереди.
Работа с таблицами баз данных
Самый простой способ отображения информации базы данных в виде таблицы заключается в использовании классов 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 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/db02/db02.h)
1 #include2 #include
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 };
1 // Таблица базы данных: пользовательская модель и представление 2 3 #include
4 #include
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.toInt());
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");
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 }
Для своей модели мы использовали базовый класс QSqlQueryModel, работающий с произвольным набором SQL-запросов для чтения и записи данных в БД. Но за всё приходится платить: нам пришлось подробно расписывать реализацию методов data и setData. В данном случае мы имели дело с единственной таблицей базы данных, поэтому можно было в качестве базового класса взять QSqlTableModel.
Проверьте, как работает эта программа. Намного лучше, чем предыдущая, не правда ли? Размеры ячеек теперь не "прыгают" при редактировании, а при щелчке левой кнопкой мыши по элементу QCheckBox в ячейках последнего столбца автоматически изменяется текстовая метка "Да/Нет". Но ввести трёхзначную зарплату всё ещё не получается. Мы исправим данный недостаток в следующем разделе.
Разработка модели и представления таблицы БД
Решим сначала простую задачу: в ячейках последнего столбца таблицы, где хранится только два возможных значения, будем отображать элемент QCheckBox и текст "Да" или "Нет" (рис.). Кроме того, запретим редактирование первого столбца, изменим цвет фона ячеек первого и последнего столбцов, а также параметры шрифта во втором столбце.
Для этого определим свою модель таблицы, использовав в качестве базового класс QSqlQueryModel. А чтобы управлять размерами ячеек таблицы, определим свой класс представления на основе стандартного QTableView. В листингах . и . приведён текст программы.
Делегаты для ячеек таблицы
Делегаты -- это специальные классы, которые могут использоваться для управления режимами отображения или редактирования ячеек таблицы QTableView, равно как и элементов других представлений (QListView, QTreeView). В данном разделе мы определим делегатов для редактирования дат и чисел в ячейках таблицы.
В листингах . и . приведён текст программы, а на рис. показан внешний вид окна в момент ввода даты рождения.
Делегаты (файл examples-qt/db03/db03.cpp)
1 // Таблица базы данных: делегаты 2 3 #include4 #include
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.toInt());
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");
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
(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
(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
(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
(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
(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
(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 }
Делегаты (файл examples-qt/db03/db03.h)
1 #include2 #include
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 };
Биржевая торговля: Механические торговые системы - Создание - Программирование
- Механические торговые системы (МТС)
- Технический анализ и МТС
- Разработка механических торговых систем
- Механические торговые системы
- GNU механические торговые системы
- Тестирование механических торговых систем
- MetaStock - механические торговые системы
- Omega Trade Station - механические торговые системы
- МТС - обзор языков программирования
- Си для механических торговых систем
- C# для механических торговых систем
- C++ для механических торговых систем
- Borland C++ для механических торговых систем
- C++ Builder для механических торговых систем
- Visual C++ для механических торговых систем