QtУчим Qt вместе. Part 2

Ну вот и продолжение цикла о QT. Сори что так долго. В прошлой статье мы создали минимальное приложение с одной кнопкой на форме. В этой статье разберем более сложный пример в котом мы будем использоваться меню, строка состояния а также научимся создавать свои слоты.

Создаем консольный проект demo02 в QT Creator`е, добавляем в него два файла(mainwindow.h & mainwindow.cpp).

demo02.pro
QT += gui
TARGET = demo02
#CONFIG += console
#CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
    mainwindow.cpp
HEADERS += mainwindow.h


main.cpp
#include <QApplication>
#include <QMainWindow>

#include "mainwindow.h"

int main(int argc, char *argv[])
{

    QApplication app(argc, argv);

    MainWindow *mw = new MainWindow();
    mw->setGeometry(200,200,600,400);
    mw->show();

    return app.exec();
}


mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTextCodec>
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QCheckBox>
#include <QFileDialog>
#include <QWidget>


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

private:
    QAction *actionOpen;
    QAction *actionExit;
    QAction *actionAbout;
    QMenu *menuFile;
    QMenu *menuHelp;
    QLabel *labelMenu;
    QLabel *labelFile;
    QLabel *labelImage;
    QPushButton *butOpen;
    QCheckBox *cbSize;
    QVBoxLayout *vlayout;
    QHBoxLayout *hlayout;
    QWidget *mainWidget;

private slots:
    void slotOpen();
    void slotAbout();
    void slotImageSize(bool on);

};

#endif // MAINWINDOW_H


mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow()
{
    QTextCodec *codec = QTextCodec::codecForName("UTF8");
    QTextCodec::setCodecForTr(codec);

    actionOpen = new QAction(tr("О&ткрыть"), this);
    actionOpen->setStatusTip(tr("Открыть рисунок"));
    connect(actionOpen, SIGNAL(triggered()), this, SLOT(slotOpen()));

    actionExit = new QAction(tr("В&ыход"), this);
    actionExit->setStatusTip(tr("Выход из программы"));
    actionExit->setShortcut(tr("Ctrl+Q"));
    connect(actionExit, SIGNAL(triggered()), this, SLOT(quit()));

    actionAbout = new QAction(tr("&О программе"), this);
    actionAbout->setStatusTip(tr("Сведения о программе"));
    connect(actionAbout, SIGNAL(triggered()), this, SLOT(slotAbout()));

    menuFile = menuBar()->addMenu(tr("&Файл"));
    menuFile->addAction(actionOpen);
    menuFile->addSeparator();
    menuFile->addAction(actionExit);

    menuFile = menuBar()->addMenu(tr("&Справка"));
    menuFile->addAction(actionAbout);

    labelMenu = new QLabel(statusBar());
    labelFile = new QLabel();

    statusBar()->setSizeGripEnabled(false);
    statusBar()->addWidget(labelMenu, 1);
    statusBar()->addWidget(labelFile, 2);

    mainWidget = new QWidget();
    setCentralWidget(mainWidget);

    labelImage = new QLabel;

    butOpen = new QPushButton(tr("Открыть"));
    butOpen->setStatusTip(tr("Открыть рисунок"));
    connect(butOpen, SIGNAL(clicked()), this, SLOT(slotOpen()));

    cbSize = new QCheckBox(tr("Вписать рисунок в окно"));
    connect(cbSize, SIGNAL(toggled(bool)), this, SLOT(slotImageSize(bool)));

    vlayout = new QVBoxLayout;
    hlayout = new QHBoxLayout;

    vlayout->addWidget(labelImage);
    hlayout->addWidget(butOpen);
    hlayout->addWidget(cbSize);
    hlayout->addStretch(1);
    vlayout->addLayout(hlayout);
    mainWidget->setLayout(vlayout);
}

void MainWindow::slotOpen()
{
    labelImage->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

    QString fileName = QFileDialog::getOpenFileName(0, tr("Открыть файл"), QDir::currentPath());
    if (!fileName.isEmpty())
    {
        QImage image(fileName);
        labelImage->setPixmap(QPixmap::fromImage(image));
        labelFile->setText(fileName);
    }
}

void MainWindow::slotAbout()
{
    QMessageBox::about( 0 , tr("О программе..."), tr("©2009 Dmitry <a href='http://example.com'>http://example.com</a>< br>Специально для <a href='http://open-life.org'>OpenLife</a>"));
}

void MainWindow::slotImageSize(bool b)
{
    labelImage->setScaledContents(b);
}


Сигналы и слоты

Сигналы и слоты — это фундаментальный механизм Qt, позволяющий связывать объекты друг с другом. Cвязанным объектам нет необходимости что-либо «знать» друг о друге. Сигналы и слоты гораздо удобнее механизма функций обратного вызова (callbacks) и четко вписываются в концепцию ООП.
Для использования этого механизма объявление класса должно содержать специальный макрос Q_OBJECT на следующей строке после ключевого слова class:
class MyClass {
Q_OBJECT
 
public:
...
 
};

После макроса Q_OBJECT не нужно ставить точку с запятой. Перед выполнением компиляции, Meta Object Compiler (MOC) анализирует такие классы и автоматически внедряет в них всю необходимую информацию (используются отдельные файлы, разработчик может их игнорировать).
Наследники QObject могут иметь любое количество сигналов и слотов.

Сигналы

Сигнал может быть определен следующим образом:
class MyClass: public QObject {
Q_OBJECT
 
public:
...
 
signals:
   void mySignal();
 
};

Для того чтобы инициировать сигнал (выслать сигнал) нужно ипользовать ключевое слово emit.
void MyClass ::sendMySignal()
{
   emit mySignal();
}

Сигналы могут использовать параметры для передачи дополнительной информации.

Слоты

Слоты практически идентичны обычным членам-методам C++, при их объявлении можно использовать стандартные спецификаторы доступа public, protected или private. Слоты можно вызывать напрямую, как обычные члены-функции класса C++. Главная особенность слотов — это возможность связывания с сигналами, в этом случае слоты будут вызваться автоматически при каждом возникновении соответствующих сигналов. В слотах нельзя использовать параметры по умолчанию.
Слот может быть определен следующим образом:
class MyClass: public QObject {
Q_OBJECT
 
public slots:
   void mySlot()
   {
      ...
   }
 
};

В теле слота можно узнать, какой объект выслал сигнал.

Соединение сигналов и слотов

Для соединения сигналов и слотов можно использовать статический метод connect, определенный в классе QObject. В общем виде соединение выглядит следующим образом:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
sender и receiver — это указатели на QObject.
signal и slot — сигнатуры сигнала и слота.

Пример соединения:
QObject::connect(spinBox, SIGNAL(valueChanged(int)),slider, SLOT(setValue(int)));

В приведенном выше примере, сигнал, возникающий при каждом изменении объекта spinbox, связывается с соответствующим слотом объекта slider. Вызов слота slider.setValue(int) происходит автоматически при каждом возникновении сигнала spinBox.valueChanged(int).
Существует множество различных вариантов соединения сигналов и слотов:

* Один сигнал может быть соединен со многими слотами:
connect(slider, SIGNAL(valueChanged(int)),spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)),this, SLOT(updateStatusBarIndicator(int)));
При возникновении сигнала, слоты вызываются один за другим, порядок не определен.

* Множество сигналов могут быть соединены с единственным слотом:
connect(sender0, SIGNAL(overflow()),receiver1, SLOT(handleMathError()));
connect(sender1, SIGNAL(divisionByZero()),receiver1, SLOT(handleMathError()));

* Сигналы могут быть соединены между собой:
connect(sender1, SIGNAL(function1()),receiver, SIGNAL(function2()));
При возникновении первого сигнала, автоматически генерируются все сязанные сигналы. Не считая этого, соединение сигнал-сигнал неотличимо от соединения сигнал-слот.

* Метод disconnect можно использовать для того, чтобы удалить соединение между сигналом и слотом.
disconnect(sender0, SIGNAL(overflow()),receiver1, SLOT(handleMathError()));
На практике прямой вызов disconnect используется редко, так как Qt автоматически удаляет все соединения при удалении объектов.

Локализация

В QT существует три стандартных способа локализации приложения, но мы будем использовать наиболее предпочтительный. Этот способ предлагает использовани специальной функции перевода tr, с помощью которой осуществляется интернационализация приложений. Эта статическая функция является членом всех классов Qt, порождённых от базового класса QObject, но если, как сейчас, мы собираемся вызвать её в главной программе, а не в каком-либо методе класса, то приходится указывать какой-нибудь подходящий объект, например, QObject::tr. Для указания кодировки, используемой функцией перевода, надо создать соответствующий кодек и передать его в качестве аргумента методу setCodecForTr.
QTextCodec *codec = QTextCodec::codecForName("UTF8");
    QTextCodec::setCodecForTr(codec);


Строка состояния

Строка состояния QStatusBar создаётся автоматически в нижней части главного окна приложения, если в программе хоть раз вызвается метод MainWindow::statusBar. При наведении указателя мыши на кнопку панели инструментов или пункт меню в строке состояния на время появляется текст подсказки, если этот текст определён для данной кнопки или данного пункта.
По умолчанию строка состояния представляется в виде одной панели, располагаемой по всей ширине родительского окна. Но её можно разбить по ширине на отдельные поля, если вставить в неё другие элементы, например, QLabel. Для этого предназначены методы addWidget, addPermanentWidget и insertWidget.
Элементы, добавляемые с помощью метода addPermanentWidget, располагаются в правой части строки состояния и не затираются сообщениями, выводимыми с помощью showMessage.
В нижней правой части строки состояния по умолчанию отображается специальный маркер, который можно «зацепить» указателем мыши для изменения размеров окна. Его показ можно запретить, вызвав QStatusBar::setSizeGripEnabled(false). При этом возможность изменять размеры окна по-прежнему остаётся.
statusBar()->setSizeGripEnabled(false);
    statusBar()->addWidget(labelMenu, 1);
    statusBar()->addWidget(labelFile, 2);


Меню

Горизонтальная панель меню QMenuBar создаётся автоматически, если мы обращаемся к ней для добавления хотя бы одного вертикального меню QMenu.
actionOpen = new QAction(tr("О&ткрыть"), this);
    actionOpen->setStatusTip(tr("Открыть рисунок"));
    connect(actionOpen, SIGNAL(triggered()), this, SLOT(slotOpen()));

    actionExit = new QAction(tr("В&ыход"), this);
    actionExit->setStatusTip(tr("Выход из программы"));
    actionExit->setShortcut(tr("Ctrl+Q"));
    connect(actionExit, SIGNAL(triggered()), this, SLOT(quit()));

    menuFile = menuBar()->addMenu(tr("&Файл"));
    menuFile->addAction(actionOpen);
    menuFile->addSeparator();
    menuFile->addAction(actionExit);


Размещение элементов в окне

«Ручное» размещение

С помощью метода 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&). Недостатком жёсткого варианта размещения элементов интерфейса является то, что пользователь не может изменить размер окна диалога (или изменение размеров окна не влияет на взаимное положение и размеры всех его элементов). В результате при низком разрешении монитора всё выглядит слишком крупно, а то и вовсе не помещается на экран, при высоком — наоборот, слишком мелко. Кроме того, в различных операционных системах используются разные шрифты, поэтому надписи и поля ввода, прекрасно смотревшиеся в одной системе, при переносе на другую платформу могут не уместиться в прежних границах. К тому же в солидных программных продуктах принято давать пользователю возможность настраивать интерфейс программы по своему вкусу, в частности, изменять гарнитуру и размер шрифта. А при локализации (переводе интерфейса программы на другой язык) всё ещё больше усложняется.

Менеджеры размещения

Менеджер размещения (layout manager) — это объект, который управляет размерами и положением виджетов. В Qt имеются классы QHBoxLayout, QVBoxLayout и QGridLayout, которые специально предназначены для управления положением и размерами элементов в окне. Первый позволяет располагать элементы друг за другом по горизонтали, второй — по вертикали, а третий размещает виджеты в ячейках воображаемой таблицы, причём каждый элемент может занимать несколько смежных ячеек по вертикали и/или горизонтали.
mainWidget = new QWidget();
    setCentralWidget(mainWidget);

    labelImage = new QLabel;

    butOpen = new QPushButton(tr("Открыть"));
    butOpen->setStatusTip(tr("Открыть рисунок"));
    connect(butOpen, SIGNAL(clicked()), this, SLOT(slotOpen()));

    cbSize = new QCheckBox(tr("Вписать рисунок в окно"));
    connect(cbSize, SIGNAL(toggled(bool)), this, SLOT(slotImageSize(bool)));

    vlayout = new QVBoxLayout;
    hlayout = new QHBoxLayout;

    vlayout->addWidget(labelImage);
    hlayout->addWidget(butOpen);
    hlayout->addWidget(cbSize);
    hlayout->addStretch(1);
    vlayout->addLayout(hlayout);
    mainWidget->setLayout(vlayout);




Ну вот на сегодня все.
Если какие то моменты остались не понятными пишите добавлю в описание статьи.
  • +16
  • DmitryG
  • 13 июля 2009, 12:35

Комментарии (9)

Спасибо за статью.
Очень хотелось бы увидеть что-нибудь подобное с использованием Qt Designer.
Хорошая статья. Автору спасибо. Простите за офтоп… Qt с английского, кстати, «милашка» )))
Табуляцию в коде парсер съел или...?
За статью спасибо, ждём продолжение.
Табуляцию в коде парсер съел или...?

Парсер съел! :(
А что, если использовать ХабраРедактор? С табуляцией наглядней :)
Сейчас вот попробывал! В Хаброредакторе код смотрится хорошо, а здесь уже плавет!
MOAR! Статья очень понравилась. Хочу еще :) Если есть возможность, опиши как работать с QtDesigner (который по-моему входит в состав QtCreator). На мой взгляд, его использование ускоряет разработку. Как вообще привязывать свои функции к слотам (в дизайнере есть возможность прикрепления дефолтных функций типа quit).
Как вообще привязывать свои функции к слотам (в дизайнере есть возможность прикрепления дефолтных функций типа quit).

Да я знаю. Это для общего развития, что бы лучше понимать как это все работает! Всегда надо начинать с основ!!!(или желательно :) )
Ну что-ж, тогда будем ждать более развернутого продолжения :)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.