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).

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