Программирование на C++ с использованием библиотеки Qt4

Компиляция Qt4 в Linux

Поскольку библиотека Qt является основой для KDE, то, скорее всего, она уже установлена в вашей системе. Но даже в этом случае могут отсутствовать некоторые средства разработки, т.к. в KDevelop используются свои собственные инструменты. Кроме того, обычно вместе с дистрибутивом Linux распространяются не самые свежие версии Qt.

Порядок установки:

  • Если ставится коммерческая версия Qt, то имеющийся файл лицензии скопируйте в каталог Documents and Settings\ИмяПользователя и дайте ему имя .qt-license.
  • Распакуйте содержимое архива qt-win-opensource-src-4.x.x.zip или qt-win-commercial-src-4.2.2 в любой каталог, например, c:\qt422 (полное имя каталога не должно содержать пробелов).
  • Добавьте путь к каталогу c:\qt422\bin в системную переменную PATH. Перезагрузите компьютер.
  • Перейдите в каталог c:\qt422 и запустите программу configure.exe. Если используется Visual Studio .NET, то конфигурирование и компиляцию следует проводить в окне Visual Studio .NET Command Prompt. При запуске configure могут быть указаны параметры:

  • -platform x -- платформа установки. Вместо x можно указать win32-g++, а для коммерческих версийQt: win32-icc, win32-msvc, win32-msvc.net и win32-msvc2005;
  • -release -- собирать только конфигурацию Release (без информации для отладки на уровне исходного текста);
  • -debug -- собирать только конфигурацию Debug (с информацией для отладчика);
  • -debug-and-release -- собирать обе конфигурации;
  • -shared -- собирать динамические библиотеки;
  • -static -- собирать статические библиотеки;
  • -exceptions -- использовать исключения;
  • -no-exceptions -- не использовать исключения;
  • -no-qt3support -- не компилировать модули поддержки старой версии Qt3;
  • -fast -- генерировать только те make-файлы, которые необходимы для компиляции библиотеки;
  • и многие другие. Полный список всех опций можно узнать, запустив configure с параметром -help.
  • В результате работы configure будут сгенерированы make-файлы для сборки библиотеки Qt, инструментов разработки и демонстрационных примеров. Не выходя из каталога установки, запустите процесс компиляции с помощью команды mingw32-make (или make, если используется Visual C++) и приготовьтесь к длительному ожиданию (несколько часов);
  • Если ставилась коммерческая версия Qt, то установите интегратор с Microsoft Visual Studio, для этого запустите на выполнение файл qt-vsintegration-1.2.1.exe.

  • После компиляции библиотеки Qt4 в каталоге bin появятся следующие исполняемые файлы:
  • qmake -- утилита для создания файла проекта *.pro и make-файлов;
  • assistant -- программа просмотра документации;
  • designer -- визуальный редактор графического интерфейса пользователя;
  • moc -- метакомпилятор, предназначенный для перевода исходных текстов программ, написанных с использованием библиотеки Qt, на обычный язык C++;
  • uic -- компилятор ui-файлов, создаваемых программой designer;
  • rcc -- компилятор qrc-ресурсов;
  • qtdemo -- оболочка для запуска демонстрационных программ;
  • linguist -- инструмент, облегчающий перевод интерфейса приложений на другие языки;
  • qt3to4 -- утилита для конвертации старых программ, разработанных с использованием 3-й версии библиотеки;
  • lupdate, lrelease -- утилиты для для интернационализации приложений.


  • Распакуйте содержимое архива qt-x11-opensource-src-4.x.x.tar.gz в какой-нибудь временный каталог: cd /tmp gunzip qt-x11-opensource-4.x.x.tar.gz tar xvf qt-x11-opensource-4.x.x.tar

  • Перейдите в этот каталог и запустите configure: cd /tmp/qt-x11-opensource-4.x.x ./configure
    При необходимости разрешается задать параметры сборки, список которых можно узнать, если при запуске configure указать ключ -help;
  • Не выходя из каталога установки, запустите процесс компиляции с помощью команды make и приготовьтесь к длительному ожиданию;
  • Для копирования всех необходимых файлов в системные каталоги дайте команду su -c "make install" и введите пароль суперпользователя;
  • добавьте в переменную окружения PATH путь к исполняемым файлам и библиотекам Qt. Для этого в случае использования оболочек bash, ksh, zsh или sh требуется в файл .profile добавить строки: PATH=/usr/local/Qt4/bin:$PATH export PATH Для csh и tcsh: setenv PATH /usr/local/Qt4/bin:$PATH


  • Предварительные условия:

  • для работы со свободной версией Qt4 должен быть установлен компилятор MinGW (к сожалению, поддержка компилятора Microsoft C++ включена только в коммерческие версии Qt);
  • путь к исполняемым файлам MinGW, находящимся в папке bin, должен быть указан в системной переменной PATH (напоминаем, что после редактирования значений переменных окружения требуется перезагрузить компьютер, чтобы все изменения вступили в силу).


  • 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
  • (1) Создаём файл


  • Кодеков

    (специальных объектов для перекодировки строк). Соответствующий класс QTextCodec определён в заголовочном файле с тем же именем (без расширения! Заголовочные файлы с расширением .h используются только в старых проектах Qt3). При создании кодека надо указать название используемой кодовой таблицы, например: QTextCodec *codec = QTextCodec::codecForName("CP1251"); Затем надо связать этот кодек со всеми строками C++: QTextCodec::setCodecForCStrings(codec); Поскольку при создании кодека указана конкретная кодовая таблица, то исход компиляции исходных текстов не зависит от системной кодировки той платформы, на которой производится сборка программы: результат будет везде одним и тем же. Но файлы с исходными текстами программ в этом случае нельзя подвергать перекодировке (или после перекодировки требуется изменить название кодовой таблицы, заданной при создании кодека). В листинге 3 приведён новый вариант нашей программы.

    Простейшее приложение Qt (файл examples-qt/00/00.cpp)

    1 // Простейшее приложение Qt4 (пустое окно) 2 3 #include
    4 #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 } Пояснения к программе:
  • Подключили заголовочные файлы с определениями классов QApplication (приложение) и QMainWindow (главное окно).
  • Обычный для C++ заголовок главной функции main с аргументами командной строки.
  • Объявили переменную типа QApplication (приложение), передав конструктору параметры командной строки, которые, возможно, указаны при запуске программы (argc -- число параметров, argv -- указатель на массив строковых значений).
  • Создали главное окно приложения. Первый параметр конструктора указывает на родительский элемент (в данном случае 0 -- окно не имеет родителя), а второй -- набор битовых флагов, влияющих на внешний вид окна (флаг Qt::Windows означает, что элемент будет выглядеть, как окно приложения, т.е. будет иметь строку заголовка с системными кнопками для сворачивания на панель задач, закрытия и т.д.).
  • Задали текст заголовка окна (пока мы избегаем использовать символы кириллицы, этому вопросу будет посвящены следующие примеры).
  • Определили размеры окна (ширину и высоту) в пикселах.
  • Вывели окно на экран.
  • Запустили цикл обработки событий, происходящих с элементами приложения. Пока в нашей программе никакие события не определены, кроме стандартных реакций на действия пользователя (изменение размеров и положения окна, нажатие кнопок в строке заголовка).

  • Простейшее приложение Qt (файл examples-qt/00/00.cpp)

    Перед компиляцией программы надо сначала создать проект Qt (файл с расширением .pro), для этого требуется войти в тот каталог, в котором находится cpp-файл с исходным текстом, и запустить утилиту

    Простейшее приложение Qt4

    1 // Простейшее приложение Qt4 (пустой фрейм) 2 // Вариант с кириллицей в заголовке 3 4 #include
    5 #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 }
    Простейшее приложение Qt4

    Второй вариант работы с символами кириллицы -- явное использование

    с символами национальных алфавитов связан

    1 // Простейшее приложение Qt4 (пустой фрейм) 2 // Кодеки 3 4 #include
    5 #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 #include
    5 #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-файл, для чего достаточно запустить утилиту

    .
  • (2) Создаём make-файл.
  • (4) Выполняем компиляцию в режиме Debug, т.е. с информацией для отладчика (в данном случае строка закомментирована).
  • (5) Повторяем компиляцию в режиме Release (без возможности отладки на уровне исходного текста программы).
  • (7) Ждём нажатия любой клавиши (чтобы окно не закрылось раньше времени и мы успели прочитать все сообщения компилятора).

  • Теперь, как и обещали, обсудим вопрос о символах кириллицы. Библиотека 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.
    Assistant


    файл examples-qt/01/01.cpp)

    1 // Сигналы и слоты: кнопка в окне 2 3 #include
    4 #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 #include
    2 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 };
  • (1) Подключили заголовочный файл QtGui, в котором описаны все объекты, относящиеся к элементам графического интерфейса. Это проще, чем отдельно подключать QMainWindow, QLabel, QMenu и т.д.
  • (3) Объявили новый класс MainWindow, использовав в качестве базового класс QMainWindow (наследника QObject).
  • (4) Не забыли про обязательный макрос Q_OBJECT.
  • (7) Конструктор без параметров.
  • (10-12) Виртуальные методы, вызываемые автоматически при изменении размеров окна, при перемещении указателя мыши и при закрытии окна.
  • (14-15) Описание слотов (функций-обработчиков). В данном случае здесь упомянута только функция, вызываемая при выборе пункта меню "Опрограмме" (т.к. все остальные обработчики -- это виртуальные функции или уже определённые методы базового класса).
  • (17-23) Переменные-члены класса: два действия для пунктов меню, само меню и три текстовые метки для строки состояния.
  • (25) Служебная функция, вызываемая при закрытии окна (просит подтверждения у пользователя).


  • Меню и строка состояния (файл examples-qt/02/02.cpp)

    1 #include
    2 #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 }

  • (1-2) Подключили модуль QtGui и свой заголовочный файл.
  • (4-35) Конструктор главного окна.
  • (6-7) Определили кодек.
  • (9-11) Создали действие "О программе", задали для него текст подсказки и связали с функцией about, реализованной ниже.
  • (13-16) Создали действие "Выход", задали для него текст подсказки и клавиатурное сокращение, связали его с функцией close, выполняющей закрытие окна (эта функция определена в базовом классе).
  • (18-21) Создали меню "Файл", состоящее из двух пунктов, с разделителем между ними.
  • (23-25) Создали три текстовые метки, в качестве родительского элемента указали строку состояния.
  • (27-30) Убрали из строки состояния маркер для изменения размеров окна (иначе правая текстовая метка не доходит до правого края окна, что выглядит не слишком эстетично) и разместили в строке три текстовые метки. Указали для первой коэффициент растяжимости, в два раза больший, чем для остальных.
  • (32) Вывели в первое поле строки состояния приветствие.
  • (34)Указали, что метод mouseMoveEvent будет вызываться при движении указателя мыши, даже если её кнопки не нажаты.
  • (38-42) Виртуальный метод, выполняемый при изменении размеров окна. Узнаём новые размеры с помощью функции QResizeEvent::size() и выводим их во второе поле строки состояния. Для преобразования целых значений к типу QString используем метасимволы %1 и %2 в строке шаблона (41), а также функцию QString::arg, которая замещает их строковым представлением своего аргумента и возвращает результат типа QString.
  • (44-48) Виртуальный метод, вызываемый при перемещении указателя мыши. Новые координаты указателя возвращает функция QMouseeEvent::pos(). Мы выводим их в третье поле строки состояния.
  • (50-55) Функция about выполняется при выборе пункта меню Файл | О программе. Для вывода на экран информации используется метод about, определённый для класса QMessageBox. Обратите внимание, что для форматирования текста используется язык разметки HTML.
  • (57-73) При закрытии окна программы (по команде меню Выход, нажатием комбинации клавиш Alt+F4 или с помощью крестика в верхнем правом углу окна) исполняется виртуальный метод closeEvent (67), в котором мы вызываем функцию askOnClose (57). Последняя выводит на экран запрос на подтверждение, используя метод question класса QMessageBox. Если пользователь отвечает утвердительно, то окно зарывается (69), иначе действие отменяется (71) и выполнение программы продолжается.


  • Чуть позже мы узнаем, как использовать программу

    Меню

    Горизонтальная панель меню 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 (в частности, информацию о типах во время выполнения программы и динамические свойства объектов). При этом необходимо соблюдать следующие правила:
  • в секции private при объявлении класса необходимо указать макрос Q_OBJECT
  • объявления классов, содержащих макрос Q_OBJECT, обязательно должны располагаться в заголовочном файле *.h, а не в самой программе *.cpp.

  • Утилита 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, первый параметр которого задаёт политику изменения ширины элемента, а второй -- его высоты. Каждый параметр может принимать одно из значений:
  • QSizePolicy::Fixed -- размер элемента в данном направлении не изменяется;
  • QSizePolicy::Minimum -- "идеальным" размером элемента считается его минимальный размер. Элемент может растягиваться, но не может сжиматься;
  • QSizePolicy::Maximum -- "идеальным" размером элемента считается его максимальный размер. Элемент может сжиматься, но не может растягиваться;
  • QSizePolicy::Preferred -- элемент "старается" поддерживать некоторый предпочтительный для него размер, но при необходимости может растянуться или сжаться;
  • QSizePolicy::Expanding -- элемент "старается" принять максимально возможный доступный ему размер, но при необходимости может и сжиматься.


  • Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.h)

    1 // Перерасчёт геометрии всех элементов при изменении размеров окна 2 3 #include
    4 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 };
  • (5) Объявили класс MyDialog, в качестве базового использовали стандартный класс диалога библиотеки Qt.
  • (9) Конструктор диалога.
  • (15-30) Указатели на все визуальные элементы необходимо сделать членами класса, чтобы иметь к ним доступ из метода resizeEvent.


  • Перерасчёт геометрии в методе 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 }

  • (7-8) Кодек и установка кодировки.
  • (10) Текстовая метка. Второй параметр конструктора (указатель на родительский элемент) можно опустить.
  • (12) Однострочное поле ввода. То же самое замечание относительно параметра this.
  • (14-20) Поле ввода с раскрывающимся списком. Текущим сделан второй, считая с нуля, te последний элемент списка. Вызов(19) разрешает вводить в поле любые строковые значения, независимо от того, имеются ли они в списке выбора. В (20) указано, что новые значения, введённые пользователем, будут автоматически дописываться в конец списка.
  • (22-23) Целочисленное поле ввода с кнопками инкремента/декремента.
  • (25-26) Независимый переключатель с двумя состояниями.
  • (28-32)Поле для ввода вещественного значения с кнопками инкремента/декремета. В (29) установлено максимальное допустимое значение, в (30) -- количество десятичных цифр в дробной части, а в (31) -- величина приращения при нажатии кнопки.
  • (34-36) Поле для ввода даты и времени. Заданы год, месяц, число, часы, минуты, секунды и миллисекунды.
  • (38) Рамка с надписью вокруг группы элементов.
  • (40,42,44) Три зависимых переключателя (радиокнопки) с текстовыми метками.


  • (46) Установлен второй переключатель, остальные автоматически выключены.
  • (48,50) Две кнопки.
  • (52) Заданы минимально возможные размеры окна диалога. Если вместо setMinimumSize использовать метод setFixedSize, то пользователь не сможет изменить ни ширину, ни высоту окна.
  • (55) Виртуальный метод, автоматически вызываемый при изменении размеров окна.
  • (56,57) Вычисление приращения ширины и высоты окна относительно их минимально разрешённых значений.
  • (59) Положение и размеры текстовой метки фиксированы.
  • (60) Положение однострочного поля ввода не изменяется, а ширина увеличивается на то же значение, что и ширина всего окна. В результате правый край элемента зафиксирован относительно правой границы окна.
  • (62-63) Ширина поля ввода со списком выбора увеличивается в два раза быстрее, чем ширина поля для ввода целого значения.
  • (65) Позиция и размеры независимого переключателя фиксированы.
  • (66, 68, 70) Ширина полей для ввода вещественного значения и для ввода даты/времени, а также ширина рамки вокруг группы элементов увеличивается на то же значение, что и ширина всего окна.
  • (71-73) Ширина текстовых меток зависимых переключателей растёт одинаково, на 1/3 от прироста ширины всего окна.
  • (75-76) Кнопки в нижней части диалога увеличиваются по ширине, а расстояние от них до нижней границы окна остаётся неизменным.



  • Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.cpp)

    Рис. Внешний вид окна и элементов управления в системе 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| Edit Signals/Slots

    В открывшемся диалоге (рис.) поставим галочку

    Edit Tab Order

    . На редактируемой форме появятся квадратики с числами (рис.). Будем щёлкать по ним левой кнопкой мыши в нужном порядке. После этого можно опять войти в режим предпросмотра

    Form| Preview

    ).
    Form| Preview

    Разумеется, для реализации более сложных действий (например, изменение доступности одних элементов диалога в зависимости от значений, вводимых пользователем в другие элементы) без настоящего программирования обойтись не получится.
    Сохраним описание диалога под любым именем (например, dialog) и с расширением ui.
    В листинге 11 приведён фрагмент получившегося в результате файла. Как видим, описание диалога хранится в формате XML.

    Label

    (рис.) и с помощью левой кнопки мыши перетащим его на форму. В правой части дизайнера, в окне

    Lay Out Horizontally

    (расположить по горизонтали). В результате автоматически будет создан менеджер размещения QHBoxLayout и оба выделенных виджета окажутся внутри него. Останется только задать единичное значение параметра horizontalStretch (растяжение по горизонтали) из раздела sizePolicy для однострочного поля ввода.
    Lay Out Horizontally

    Рис. Внешний вид окна 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

    ).
    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 имеются драйверы для работы со следующими СУБД:

  • 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-запросов (файл examples-qt/db00/db00.h)

    1 #include
    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 ;

    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 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 }


  • (13) Установили для диалога флаг Qt::Windows, чтобы в заголовке окна появились кнопки сворачивания и восстановления.
  • (15-22) Создали поле со списком и заполнили его названиями всех доступных драйверов SQL, кроме устаревших (17-21).
  • (24-28) Создали однострочные поля для ввода параметров соединения.
  • (30) Кнопка Старт.
  • (32-33) Многострочное поле для вывода сообщений.
  • (35-64) Расположили все элементы с помощью сеточного менеджера размещения.


  • (66) Связали кнопку Старт с функцией start().
  • (69) Функция, выполняемая при нажатии на кнопку.
  • (70) Вывели сообщение.
  • (72-77) Создали соединение с базой данных.
  • (78-83) Если соединение установлено, то менять параметры уже нельзя.
  • (93) При необходимости сообщили серверу кодовую таблицу клиента.
  • (94-95) Выяснили, существует ли в базе данных таблица employee (работники).
  • (98) Если да, то удалили её.
  • (110) Создали новую таблицу.
  • (127-162) Выполнили серию SQL-запросов с параметрами для вставки новых записей в созданную таблицу.
  • (164) Получили все записи таблицы.
  • (166-173) Получили информацию о полях (столбцах).
  • (177-193) Вывели на экран все записи (строки) таблицы.
  • (207) Виртуальная функция closeEvent выполняется при закрытии окна.
  • (209-210) Если соединение с базой данных открыто, то закрываем его.
  • (211-213) Выводим сообщение и ждём секунду перед закрытием окна. Для компиляции проектов, использующих модуль QtTest, в файл проекта *.pro надо добавить строку CONFIG += qtestlib.


  • Перед компиляцией проекта, в котором используется модуль 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 Если СУБД поддерживает механизм транзакций, то для начала новой транзакции используется метод 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-запросов

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

    Выполнение SQL-запросов (система Linux, драйвер QMYSQL)
    В листингах приведён пример программы, работающей с базой данных, а на рис. показан результат её работы в Windows и Linux.

    Таблица базы данных (файл examples-qt/db01/db01.cpp)

    1 // Таблица базы данных 2 3 #include
    4 #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 }
  • (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.
    Если мы хотим получать большую зарплату, выводить разные столбцы различным цветом, отображать галочки для полей логического типа, использовать календарик для ввода дат, в общем, как-то изменять заданные по умолчанию параметры отображения и редактирования ячеек, то у нас имеются две возможности: разрабатывать свой класс модели и/или представления таблицы или использовать специальные объекты-делегаты для её ячеек. Рассмотрим оба варианта по очереди.

    Работа с таблицами баз данных

    Самый простой способ отображения информации базы данных в виде таблицы заключается в использовании классов 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 #include
    2 #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 }


  • (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-запроса для чтения данных из БД и отображаемые заголовки столбцов таблицы.
  • (147-150) В конструкторе представления таблицы не забыли вызвать конструктор базового класса.
  • (152-156) Определяем действия, выполняемые при изменении размеров таблицы (в данном случае делается автоматическая подгонка размеров по содержимому ячеек).
  • (159-189) Процедура main повторяет текст предыдущей программы, только в (178-181) используется наш класс модели и класс для представления таблицы.


  • Для своей модели мы использовали базовый класс QSqlQueryModel, работающий с произвольным набором SQL-запросов для чтения и записи данных в БД. Но за всё приходится платить: нам пришлось подробно расписывать реализацию методов data и setData. В данном случае мы имели дело с единственной таблицей базы данных, поэтому можно было в качестве базового класса взять QSqlTableModel.

    Проверьте, как работает эта программа. Намного лучше, чем предыдущая, не правда ли? Размеры ячеек теперь не "прыгают" при редактировании, а при щелчке левой кнопкой мыши по элементу QCheckBox в ячейках последнего столбца автоматически изменяется текстовая метка "Да/Нет". Но ввести трёхзначную зарплату всё ещё не получается. Мы исправим данный недостаток в следующем разделе.

    Разработка модели и представления таблицы БД

    Решим сначала простую задачу: в ячейках последнего столбца таблицы, где хранится только два возможных значения, будем отображать элемент QCheckBox и текст "Да" или "Нет" (рис.). Кроме того, запретим редактирование первого столбца, изменим цвет фона ячеек первого и последнего столбцов, а также параметры шрифта во втором столбце.
    Разработка модели и представления таблицы БД

    Для этого определим свою модель таблицы, использовав в качестве базового класс QSqlQueryModel. А чтобы управлять размерами ячеек таблицы, определим свой класс представления на основе стандартного QTableView. В листингах . и . приведён текст программы.

    Делегаты для ячеек таблицы

    Делегаты -- это специальные классы, которые могут использоваться для управления режимами отображения или редактирования ячеек таблицы QTableView, равно как и элементов других представлений (QListView, QTreeView). В данном разделе мы определим делегатов для редактирования дат и чисел в ячейках таблицы.
    Делегаты для ячеек таблицы

    В листингах . и . приведён текст программы, а на рис. показан внешний вид окна в момент ввода даты рождения.

    Делегаты (файл examples-qt/db03/db03.cpp)

    1 // Таблица базы данных: делегаты 2 3 #include
    4 #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 }



  • (1-144) Начало листинга повторяет текст предыдущей программы.
  • (150-151) Создаём экземпляр делегата для редактирования вещественных чисел; первые два параметра конструктора -- разрешённый диапазон, третий параметр -- шаг изменения, четвёртый -- количество знаков после запятой, пятый -- родительский виджет.
  • (152) Связали только что созданный экземпляр делегата с третим (считая с нуля) столбцом таблицы.
  • (154-156) Создали экземпляр делегата для редактирования даты (первый параметр конструктора определяет, будет ли при вводе даты использоваться всплывающее окно календаря) и связали его со вторым столбцом таблицы.
  • (159-163) Реализация метода resizeEvent взята из предыдущей программы.
  • (166-177) Конструктор делегата для редактирования вещественных чисел. Параметры: диапазон, шаг, точность и указатель на родительский виджет.
  • (179-190)
  • ()
  • ()


  • Делегаты (файл examples-qt/db03/db03.h)

    1 #include
    2 #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 };
  • (4-15) Модель таблицы.
  • (18-24) Представление таблицы.
  • (27-53) Класс-делегат для вещественных чисел (на основе элемента QDoubleSpinBox).
  • (56-76) Класс-делегат для ввода даты (на основе элемента QDateEdit).


  • 

        Биржевая торговля: Механические торговые системы - Создание - Программирование