Войти
ПрограммированиеСтатьиГрафика

OpenGL на Qt 4. Это просто! (часть 2) (6 стр)

Автор:

Создаём главное окно и меню

В Qt есть специальный класс QMainWindow (входит в состав модуля QtGui), обеспечивающий работу с главным окном. Главное окно уже имеет готовый каркас: меню (menu bar), панели инструментов (toolbars), доки (dock widgets), центральный виджет (central widget) и строку текущего состояния (status bar). В главном окне обязательно должен быть установлен центральный виджет; остальные компоненты могут и не присутствовать. Хорошее описание главного окна и его элементов приведено в Qt Assistant. Мы ограничимся тем, что в качестве центрального виджета возьмём объект нашего класса Scene3D и для управления им добавим в главное окно меню (menu bar) — объекты класса QMenu, в которые в свою очередь добавим объекты класса QAction. Наш класс главного окна MainWindow наследуется от класса QMainWindow (аналогично тому, как наш класс Scene3D наследуется от класса QGLWidget).

mainwindow.h

#ifndef MAINWINDOW_H 
#define MAINWINDOW_H 

#include <QMainWindow> 
#include "scene3D.h"

class MainWindow : public QMainWindow // класс MainWindow как подкласс QMainWindow
{ 
   Q_OBJECT // макрос, который нужно использовать при работе с сигналами и слотами

   private:
      Scene3D* scene1; // указатель на объект класса Scene3D

      // меню
      QMenu* texture_menu; // указатель на меню, меню текстуры            
      // дейстивие
      QAction* changeTexAct; // поменять текстуры местами
      
      // меню
      QMenu* timer_menu; // меню таймера      
      // действия
      QAction* stopTimAct; // остановить таймер
      QAction* startTimAct; // запустить таймер

      void createActions(); // создание действий
      void createMenus(); // создание меню
              
   public:   
      MainWindow(); // конструктор объекта главного окна
}; 
#endif 

Qt облегчает работу с виджетами с помощью объектов класса QAction, или действий. Это как бы абстрактные действия, которые связываются со слотами и вставляются в виджеты. В файле mainwindow.h продекларированы функция createActions(), которая создаёт объекты действия, и функция createMenus(), которая создаёт меню.

mainwindow.cpp

#include <QtGui>
#include "mainwindow.h"
#include "scene3D.h"

MainWindow::MainWindow() // конструктор
{ 
   scene1 = new Scene3D; // создаю динамический объект класса Scene3D
   setCentralWidget(scene1); // обозначаю scene1 центральным виджетом в главном окне
     
   this->setWindowTitle(tr("lecture2")); // название главного окна
   
   createActions(); // создаю действия
   createMenus(); // создаю меню
}

void MainWindow::createActions() // создать объекты действий и связать их со слотами
{
   changeTexAct = new QAction(tr("Change"), this); // создать действие
   // связываем сингалы и слоты
   connect(changeTexAct, SIGNAL(triggered()), scene1, SLOT(changeTex()));

   stopTimAct = new QAction(tr("Stop"), this);
   connect(stopTimAct, SIGNAL(triggered()), scene1, SLOT(stopTmr()));

   startTimAct = new QAction(tr("Start"), this);
   connect(startTimAct, SIGNAL(triggered()), scene1, SLOT(startTmr()));
}

void MainWindow::createMenus() // создать меню
{
   texture_menu = menuBar()->addMenu(tr("Texture")); // добавить группу меню
   texture_menu->addAction(changeTexAct); // добавить в меню действие

   timer_menu = menuBar()->addMenu(tr("Animation"));
   timer_menu->addAction(stopTimAct);
   timer_menu->addAction(startTimAct);
}

В конструкторе MainWindow() создаётся объект класса Scene3D и он устанавливается центральным виждетом главного окна с помощью setCentralWidget(). Затем мы задаём название главного окна и создаём действия и меню. В функции createActions() создаются действия, которые связываются со слотами. В качестве сигнала-функции используется triggered(). Она эмитирует сигнал, когда пользователь активирует опцию меню, например, щёлкнет по опции меню. В функции createMenus() создаётся меню. Функция menuBar() создаёт меню для главного окна, а addMenu() добавляет в него конкретную группу меню. Затем функция addAction() добавляет в эту группу меню конкретное действие.

main.cpp

#include <QtGui>
#include "mainwindow.h" 

int main(int argc, char** argv) 
{ 
   QApplication app(argc, argv);  // создаётся приложение
   
   MainWindow mainwindow1; // создаётся главное окно   
   mainwindow1.resize(500, 500); // размеры главного окна
   // mainwindow1.show();  // показать главное окно
   // mainwindow1.showFullScreen();  // показать главное окно на весь экран
   mainwindow1.showMaximized(); // показать главное окно максимально развёрнутым 
 
   return app.exec();
} 

В главной функции main() создаётся главное окно, устанавливаются его размеры по умолчанию и оно показывается максимально развёрнутым. Важно отметить, что динамические объекты удалаются автоматически за счёт дерева объектов, на вершине которого будет находится объект mainwindow1.

Дерево объектов

Понятие о дереве объектов является одним из ключевых понятий в Qt, а само дерево объектов — это ноу-хау-технология библиотеки Qt. Уже в 1-ой части на примере прототипа конструктора класса Scene3D

Scene3D(QWidget* parent=0);
мы вкратце затронули рассмотрение технологии дерева объектов, которую называют также иерархией объектов. Технология дерева объектов используется для эффективного управления памятью, а именно для автоматического удаления динамических объектов, т.е. для автоматического освобождения памяти. Итак, дерево объектов представляет собой связь «родитель-потомок» между объектами (класса QObject и различных классов-наследников от него). В этом случае достаточно удалить объект-родитель, чтобы удалить все его объекты-потомки, т.е. удалить как бы всё дерево (динамических) объектов, которое следует за объектом-родителем. Это очень эффективно, так как достаточно обеспечить такую связь между объектами и забыть об освобождении памяти. При этом мы всегда уверены, что динамические объекты-потомки удалятся автоматически, когда мы удалим их объект-родитель. В 1-ой части «OpenGL на Qt 4. Это просто!» эта технология не была задействована по своему предназначению. Теперь настало время задействовать её и ощутить всё удобство и прелесть её использования. Связь «родитель-потомок» реализована в классе QObject и может переноситься во все классы-наследники от QObject, в частности, в QGLWidget, от которого в свою очередь унаследован класс Scene3D. Прототип конструктора класса QObject имеет следующий вид
QObject(QObject* parent=0);
Значение parent=0 означает, что объект не имеет родителя, т.е. он будет находится на самом верху иерархии объектов. Рассмотрим пример создания дерева объектов (иерархии объектов):
QObject* parent = new QObject;
QObject* child1 = new QObject(parent);
QObject* child2 = new QObject(child1);
QObject* child3a = new QObject(child2);
QObject* child3b = new QObject(child2);
Объект parent не имеет родителя и находится на вершине иерархии объектов (так как параметр конструктора по умолчанию равен нулю). Следующий объект child1 будет потомком объекта parent, а объект parent будет родителем для объекта child1. Аналогично, объект child2 будет потомком объекта child1, а объект child1 будет родителем для объекта child2. Далее объект child2 имеет аж двух потомков: child3a и child3b. Чтобы удалить все объекты в продемонстрированном примере, достаточно удалить только их объект-первородитель parent. Например, чтобы удалить объекты child2, child3a и child3b, достаточно удалить объект-родитель child2. Обратите внимание, что все объекты созданы динамически (через new), т.е. в куче. Все объекты-потомки всегда нужно создавать динамически (в куче). Иначе, если объекты-потомки создаются в стеке (а не в куче), то может возникнуть ошибка, связанная с вызовами деструкторов (причину такой ошибки смотрите в руководстве Qt в разделе Object Trees & Ownership). А вот объект-первородитель удобно создавать в стеке. В этом случае продемонстрированный выше пример будет выглядеть так:
QObject parent;
QObject* child1 = new QObject(&parent);
QObject* child2 = new QObject(child1);
QObject* child3a = new QObject(child2);
QObject* child3b = new QObject(child2);
Почему объект-первородитель удобно создавать в стеке? Потому что при выходе из области видимости функции он уничтожится сам, а все его объекты-потомки уничтожатся автоматически. Т.е. он уничтожится сам и прихватит с собой все остальные динамические объекты, являющиеся его потомками.

Как правило, объектом-первородителем делают объект главного окна. Именно так и сделано в нашей программе:

MainWindow mainwindow1;
Внимательный читатель скажет, что тогда требовалось бы написать в конструкторе MainWindow() следующее:
scene1 = new Scene3D(this);
чтобы объект scene1 стал потомком объекта главного окна this. Но в нашем случае связь «родитель-потомок» установится при выполнении функции setCentralWidget(scene1). Т.е. после
scene1 = new Scene3D;
setCentralWidget(scene1);
объект scene1 автоматически станет потомком родителя this. Разумеется, мы могли бы оставить и так: scene1 = new Scene3D(this). Определить объект-родитель можно с помощью функции parent(), которая возвращает указатель на объект-родитель. Пример:
if (this==scene1->parent()) // scene1 потомок родителя this
   // выводим информацию
else // scene1 не является потомком родителя this
   // выводим информацию
Как определить объекты-потомки с помощью функции children(), смотрите в Qt Assistant. А вот связь «родитель-потомок» между объектом таймера и объектом класса Scene3D устанавливается как на вышеуказанных схемах:
timer = new QTimer(this);
Схожим образом установится связь «родитель-потомок» между объектом changeTexAct и объектом главного окна:
changeTexAct = new QAction(tr("Change"), this);
и аналогично для объектов stopTimAct и startTimAct.

На рисунке изображено дерево объектов нашей программы:

tree | OpenGL на Qt 4. Это просто! (часть 2)


Любопытно, что объекты texture_menu и timer_menu связываются с объектом-первородителем mainwindow1 с помощью объекта-посредника при выполнении функции menuBar(). На рисунке этот динамический объект-посредник обозначен как *... .

Именно благодаря дереву объектов в Qt-программах используются либо деструкторы по умолчанию, либо деструкторы с пустым телом, что фактически одно и то же (например, мы не указываем деструктор для класса MainWindow и создаём деструктор с пустым телом ~Scene3D() для класса Scene3D).

Включение изображения в исполняемый файл

Часто бывает удобно включить в исполняемый файл изображения, которые используются в программе. Тогда уже папка с изображениями будет не нужна при запуске приложения. Для этого для компиляции нужно создать файл ресурсов. В нашем случае файл ресурсов picture.qrc будет иметь вид:

pictures.qrc

<RCC> 
<qresource> 
<file>textures/picture1.jpg</file>
<file>textures/picture2.jpg</file>
</qresource>
</RCC>

Этот файл также нужно указать в файле-проекте. Мы приведём тело всего файла-проекта:

lecture2.pro

TEMPLATE = app
TARGET = 
RESOURCES = pictures.qrc

HEADERS += glext.h scene3D.h mainwindow.h
SOURCES += main.cpp scene3D.cpp mainwindow.cpp
QT += opengl

Изображения после компиляции будут включены в исполняемый файл, и размер файла заметно увеличится. Элемент TARGET соответствует имени файла, но так как параметра нет, то имя файла по умолчанию будет таким же, как и имя проекта, т.е. lecture2.

Заключение

Мы рассмотрели наиболее распространённые аспекты «классического» OpenGL. В статье был сделан упор на вводный курс информации, чтобы читатель смог создать собственное приложение с трёхмерной графикой и при необходимости самостоятельно изучить интересующие его вопросы. На этом вторая часть статьи заканчивается. Спасибо за внимание!

Литература

C++:
1) Аверкин В.П., Бобровский А.И., Веснич В.В., Радушинский В.Ф., Хомоненко А.Д. «Программирование на C++» (очень хорошая книга для начинающих, отличный самоучитель)
2) Глушаков С.В., Коваль А.В., Смирнов С.В. «Язык программирования C++» (хороший справочник)
3) Дейтел Х., Дейтел П. «Как программировать на C++» (ОЧЕНЬ БОЛЬШАЯ книга, в ней много всего полезного)

OpenGL:
1) Райт Р.С.-мл., Липчак Б. «OpenGL. Суперкнига» (отличная книга по OpenGL, отличный самоучитель)
2) Ву М., Девис Т., Нейдер Дж., Шрайнер Д. «OpenGL. Руководство по программированию» (отличный справочник)
3) Уроки NeHe (уроки, ставшие классикой)

Qt:
1) Qt Assistant и Qt Examples and Demos (помощь и много нужных примеров, поставляются вместе с Qt)
2) Бланшет Ж., Саммерфилд М. «QT 4: программирование GUI на C++»
3) Шлее М. «Qt4. Профессиональное программирование на C++»
4) Боровский А. «Qt и ваша видеокарта» (статья, посвященная сегодняшним и будущим технологиям библиотеки Qt при работе с OpenGL/ES и OpenCL)

Дублирование авторской статьи

Список частей:

  • OpenGL на Qt 4. Это просто! (часть 1)
  • OpenGL на Qt 4. Это просто! (часть 2)
  • Страницы: 1 2 3 4 5 6

    #3D, #графика, #OpenGL, #Qt, #Qt4

    10 августа 2011 (Обновление: 2 янв. 2013)

    Комментарии [17]