Аннотация: Изучаются выджеты - визуальные элементы, из которых состоит графический интерфейс пользователя, их компоновка, политика размеров, сигнально-слотовые соединения, элементы графического интерфейса и их использование.

13.1 Виджеты (Widgets)

Виджеты (Widgets ) - это визуальные элементы, из которых состоит графический интерфейс пользователя.

Примеры виджетов:

  • Кнопка (класс QPushButton );
  • Метка (класс QLabel );
  • Поле ввода (класс QLineEdit );
  • Числовое поле-счётчик (класс QSpinBox );
  • Строка прокрутки (класс QScrollBar ).

В Qt есть около 50-ти готовых классов графических элементов доступных для использования. Родительским классом для всех виджетов является класс QWidget . От него наследуются все главные свойства визуальных элементов, которые мы тщательно рассмотрим. Исследование способов разработки программ с графическим интерфейсом начнём с примера.

Создадим пустой файл проекта. Запустим мастера проектов и выберем в разделе Projects (Проекты) пункт Other Project (Другой проект) . Далее выберем тип проекта Empty Qt Project (Пустой проект Qt) . К файлу проекта добавим содержимое:

TEMPLATE = app #Модули Qt, которые мы будем использовать QT += widgets #Добавляем модуль widgets для работы с виджетами (необходимо для Qt5). TARGET = widget#Название исполняемого файла SOURCES += \ main.cpp

Теперь создадим простую программу с окном, в котором мы будем выводить надпись. Установим размер окна и текст его заголовка, а также установим шрифт для надписи. Для этого создадим файл main.cpp со следующим содержанием:

#include #include int main (int lArgc, char * lArgv ) { //Создаём объект QApplication, который инициализирует и настраивает оконную программу, //управляет её выполнением с помощью цикла обработки событий QApplication lApplication (lArgc, lArgv); QLabel lLabel; //Создаём виджет QLabel - метка lLabel.setText (" I am widget! "); //Задаём текст для метки lLabel.setGeometry (200, 200, 300, 150); //Задаём размеры - позицию (x, y) ширину и высоту. Задаём выравнивание текста lLabel.setAlignment (Qt::AlignHCenter | Qt::AlignVCenter); //Класс QFont используют для настройки параметров шрифта. //Выбираем семейство шрифтов Arial Black и размер 12. QFont lBlackFont (" Arial Black ", 12); lLabel.setFont (lBlackFont); //Задаём шрифт для метки lLabel.show (); //Вызываем метод show() для показа метки на экране. return lApplication.exec (); //Запускаем программу на выполнение exec() выполняет //цикл обработки событий. Программа ожидает действия пользователя и выполняет их обработку. }

Как видим, элементы, из которых состоят интерфейсы в Qt , имеют собственные позицию и размер - так называемую "геометрию" - и, таким образом, занимают соответствующую прямоугольный участок на экране (см. рис. 13.1). Также каждый из элементов имеет настройки, которые определяют его поведение и вид.


Рис. 13.1.

Для создания структуры виджеты организовывают в иерархию по принципу "часть - целое". Каждый из виджетов может содержать другие виджеты. Такой визуальный элемент становится "родителем" (родительским виджетом) для элементов, которые он содержит. Отметим, что такие отношения не следует путать с наследованием в C++ - отношениями между классами в программе. Отношения между виджетами являются отношениями между объектами. Такие отношения порождают несколько последствий:

  • родительский элемент будет отвечать за удаление дочернего элемента: если родительский виджет удалят - то он автоматически удалит и все дочерние элементы;
  • родительский виджет размещает дочерние виджеты внутри себя, части дочерних виджетов, которые выходят за пределы родителя будут невидимыми;
  • часть состояния родительского виджета передаётся дочерним - это касается некоторых свойств (видимость, активность) и стилей, которые накладываются на визуальный элемент.

Виджеты, которые не имеют родителя (виджеты верхнего уровня), имеют вид отдельных окон в программе. Рассмотрим пример. Назовём новый проект ParentExample . Файл проекта будет содержать обычные для GUI -проекта настройки:

TEMPLATE = app TARGET = ParentExample QT += widgets

Для виджета, который мы будем использовать в качестве главного окна создадим новый класс. Для этого в категории Files and Classes (Файлы и классы) выберем раздел С++ и выберем С++ Class (см. рис. 13.2).

Следующим шагом будет создание нескольких элементов на окне. Для этого откроем файл parentwidget.cpp и изменим код конструктора класса. Для отображения элементов достаточно создать их в конструкторе класса и задать ParentWidget как отца для них. Код parentwidget.cpp выглядит так:

#include " parentwidget.h " #include #include #include ParentWidget::ParentWidget (QWidget * parent) : QWidget (parent) { //Создаём метку, указывая родительский виджет - this, то есть экземпляр класса ParentWidget. QLabel * lLabel=new QLabel (this); //Позиция относительно левого верхнего угла родительского виджета. lLabel ->setGeometry (50, 0, 100, 30); lLabel ->setText (" TextLabel "); //Текст на метке. //Создаём кнопку, задаём "родителя", геометрию и текст QPushButton * lPushButton = new QPushButton (this); lPushButton->setGeometry (50, 50, 100, 30); lPushButton->setText (" PushButton "); //Создаём поле ввода, задаём "родителя", геометрию и текст QLineEdit * lLineEdit = new QLineEdit (this); lLineEdit ->setGeometry (50, 100, 100, 30); lLineEdit ->setText (" LineEdit "); lLineEdit ->selectAll (); //Выделяем текст в поле ввода (просто для примера) //Наконец изменяем размер родительского виджета setGeometry (x (), y (), 300, 150); //и задаём текст заголовка окна setWindowTitle (" parent widgetExample "); }

Поскольку родительским элементом является ParentWidget , то метка (QLabel ), кнопка (QPushButton ) и текстовое поле (QLineEdit) находятся в его пределах. Позицию дочерних виджетов задают относительно левого верхнего угла отца. В этом легко убедиться изменив размеры и позицию окна нашей программы. Обратите внимание на то, как мы создавали элементы пользовательского интерфейса в динамической памяти используя оператор new . Это гарантирует, что элементы не будут удалены после завершения работы конструктора ParentWidget .

1.1. Hello, Qt!

Ниже приводится текст простейшей Qt программы:

1 #include 2 #include 3 int main(int argc, char *argv) 4 { 5 QApplication app(argc, argv); 6 QLabel *label = new QLabel("Hello, Qt!", 0); 7 app.setMainWidget(label); 8 label->show(); 9 return app.exec(); 10 } Здесь, в строках 1 и 2, подключаются определения классов QApplication и QLabel .

В строке 5 создается экземпляр класса QApplication , который управляет ресурсами приложения. Конструктору QApplication передаются аргументы argc и argv , поскольку Qt имеет возможность обрабатывать аргументы командной строки.

В строке 6 создается визуальный компонент QLabel , который отображает надпись "Hello, Qt!". В терминологии Qt, все визуальные компоненты, из которых строится графический интерфейс, называются виджетами (widgets). Кнопки, меню, полосы прокрутки и разнообразные рамки -- все это виджеты. Одни виджеты могут содержать в себе другие виджеты, например, главное окно приложения -- это самый обычный виджет, который может содержать QMenuBar , QToolBar , QStatusBar и др. Аргумент 0, передаваемый конструктору QLabel (в строке 6) -- это "пустой" (null) указатель, который сообщает о том, что этот виджет не имеет "хозяина", т.е. не включается в другой виджет.

В строке 7 назначается "главный" виджет приложения. Когда пользователь закрывает "главный" виджет приложения (например, нажатием на кнопку "X" в заголовке окна), то программа завершает свою работу. Если в программе не назначить главный виджет, то она продолжит исполнение в фоновом режиме даже после того, как пользователь закроет окно.

В строке 8, метка делается видимой. Виджеты всегда создаются невидимыми, чтобы у программиста оставалась возможность настроить параметры отображения до того, как они станут видимы.

В строке 9 выполняется передача управления библиотеке Qt. С этого момента программа переходит в режим ожидания, когда она ничего не делает, а просто ждет действий пользователя, например, нажатие на клавишу или кнопку мыши.

Любое действие пользователя порождает событие (другими словами -- "сообщение"), в ответ на которое программа может вызвать одну или более функций. В этом смысле, приложения с графическим интерфейсом кардинально отличаются от обычных программ, с пакетной обработкой данных, которые приняв ввод от пользователя, они самостоятельно обрабатывают его, выдают результаты и завершают свою работу без дальнейшего участия человека.

Рисунок 1.1. Окно приложения в Windows XP


Теперь самое время проверить работу нашего приложения. Но прежде всего -- необходимо, чтобы у вас была установлена Qt 3.2 (или более поздняя версия), а переменная окружения PATH содержала корректный путь к каталогу bin . (В Windows настройка переменной PATH выполняется автоматически, в процессе установки библиотеки Qt)

Скопируйте текст программы в файл, с именем hello.cpp , в каталог hello .

Перейдите в этот каталог и дайте команду:

Qmake -project она создаст платформо-независимый файл проекта (hello.pro), а затем дайте следующую команду: qmake hello.pro Эта команда создаст Makefile , на основе файла проекта. Дайте команду make , чтобы скомпилировать программу и затем запустите ее, набрав в командной строке hello (в Windows) или ./hello (в Unix) или open hello.app (в Mac OS X). Если вы работаете в Windows и используете Microsoft Visual C++, то вместо команды make вы должны дать команду nmake . Как альтернативный вариант -- вы можете создать проект Visual Studio из файла hello.pro , запустив команду: qmake -tp vc hello.pro и затем скомпилировать программу в Visual Studio.

Рисунок 1.2. Метка с форматированным текстом.


А теперь немного развлечемся. Изменим внешний вид метки, добавив форматирование текста в стиле HTML. Для этого, замените строку

QLabel *label = new QLabel("Hello, Qt!", 0); на QLabel *label = new QLabel("

Hello " "Qt!

", 0); и пересоберите приложение.


1.2. Обработка сигналов.

Следующий пример показывает -- как организовать реакцию приложения на действия пользователя. Это приложение содержит кнопку, при нажатии на которую программа закрывается. Исходный текст очень похож на предыдущий пример, за исключением того, что теперь, в качестве главного виджета, вместо QLabel используется QPushButton , и добавлен код, который обслуживает факт ее нажатия.

Рисунок 1.3. Приложение Quit.


1 #include 2 #include 3 int main(int argc, char *argv) 4 { 5 QApplication app(argc, argv); 6 QPushButton *button = new QPushButton("Quit", 0); 7 QObject::connect(button, SIGNAL(clicked()), 8 &app, SLOT(quit())); 9 app.setMainWidget(button); 10 button->show(); 11 return app.exec(); 12 } Виджеты Qt имеют возможность посылать приложению сигналы , извещая его о том, что пользователь произвел какое-либо действие или о том, что виджет изменил свое состояние . Например, экземпляры класса QPushButton посылают приложению сигнал clicked() , когда пользователь нажимает на кнопку. Сигнал может быть "подключен" к функции-обработчику (такие функции-обработчики в Qt называются слотами ). Таким образом, когда виджет посылает сигнал, автоматически вызывается слот. В нашем примере мы подключили сигнал clicked() , от кнопки, к слоту quit() , экземпляра класса QApplication . Вызовы SIGNAL() и SLOT() -- это макроопределения, более подробно мы остановимся на них в следующей главе.

Теперь соберем приложение. Надеемся, что вы уже создали каталог quit и разместили в нем файл quit.cpp . Дайте команду qmake , для создания файла проекта, а затем второй раз -- для создания Makefile:

Qmake -project qmake quit.pro Теперь соберите приложение командой make и запустите его. Если вы щелкнете по кнопке "Quit" или нажмете на клавиатуре клавишу "Пробел", то приложение завершит свою работу.

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

Рисунок 1.4. Приложение Age.


Приложение содержит три виджета: QSpinBox , QSlider и QHBox (область горизонтальной разметки). Главным виджетом приложения назначен QHBox . Компоненты QSpinBox и QSlider помещены внутрь QHBox и являются подчиненными , по отношению к нему.

Рисунок 1.5. Виджеты приложения Age.


1 #include 2 #include 3 #include 4 #include 5 int main(int argc, char *argv) 6 { 7 QApplication app(argc, argv); 8 QHBox *hbox = new QHBox(0); 9 hbox->setCaption("Enter Your Age"); 10 hbox->setMargin(6); 11 hbox->setSpacing(6); 12 QSpinBox *spinBox = new QSpinBox(hbox); 13 QSlider *slider = new QSlider(Qt::Horizontal, hbox); 14 spinBox->setRange(0, 130); 15 slider->setRange(0, 130); 16 QObject::connect(spinBox, SIGNAL(valueChanged(int)), 17 slider, SLOT(setValue(int))); 18 QObject::connect(slider, SIGNAL(valueChanged(int)), 19 spinBox, SLOT(setValue(int))); 20 spinBox->setValue(35); 21 app.setMainWidget(hbox); 22 hbox->show(); 23 return app.exec(); 24 } В строках с 8 по 11 создается и настраивается QHBox . Чтобы вывести текст в заголовке окна, вызывается setCaption() . А затем устанавливается размер пустого пространства (6 пикселей) вокруг и между подчиненными виджетами.

В строках 12 и 13 создаются QSpinBox и QSlider , которым, в качестве владельца, назначается QHBox .

Не смотря на то, что мы явно не задали ни положение, ни размеры виджетов QSpinBox и QSlider , тем менее они очень аккуратно расположились внутри QHBox . Собственно для этого и предназначен QHBox . Он выполняет автоматическое размещение подчиненных виджетов, назначая им координаты размещения и размеры, в зависимости от их требований и собственных настроек. В Qt имеется много классов, подобных QHBox , которые избавляют нас от рутинной работы по ручной подгонке положения и размеров визуальных компонентов.

В строках 14 и 15 устанавливаются допустимые пределы изменения счетчика и ползунка. (Мы можем смело предположить, что возраст нашего пользователя едва ли превысит 130 лет.) Два вызова connect() , в строках с 16 по 19 синхронизируют ползунок и счетчик, благодаря чему они всегда будут отображать одно и то же значение. Всякий раз, когда значение одного из виджетов изменяется, он посылает сигнал valueChanged(int) , который поступает в слот setValue(int) другого виджета.

В строке 20 устанавливается первоначальное значение (35) счетчика. Когда это происходит, счетчик посылает сигнал valueChanged(int) , со значением входного аргумента, равным 35. Это число передается в слот setValue(int) виджета QSlider , который устанавливает значение этого виджета равным 35. После этого уже QSlider посылает сигнал valueChanged(int) , поскольку его значение только что изменилось, вызывая таким образом слот setValue(int) виджета QSpinBox . Но на этот раз счетчик не посылает сигнал, поскольку его значение и так было равно 35. Таким образом предотвращается бесконечная рекурсия. Рисунок 1.6 иллюстрирует эту ситуацию.

Рисунок 1.6. Изменение одного значения вызывает изменение другого.


В строке 22 QHBox делается видимым (вместе со всеми подчиненными виджетами).

Подход к формированию интерфейса в Qt очень прост для понимания и чрезвычайно гибок. В общем случае, программист выбирает необходимые ему виджеты, размещает их внутри областей выравнивания (layouts), которые в свою очередь принимают на себя обязанности по размещению виджетов, и настраивает свойства виджетов. На заключительном этапе устанавливаются взаимосвязи виджетов, через механизм сигналов и слотов, которые обусловливают поведение пользовательского интерфейса.

1.3. Работа со справочной системой.

Справочная система в Qt -- это пожалуй самый основной инструмент любого разработчика. Она описывает все классы и функции в этой библиотеке. (Документация к Qt 3.2 включает в себя описанее более 400 классов и 6000 функций.) В этой книге вы встретитесь с большим количеством классов и функций Qt, но далеко не со всеми. Поэтому совершенно необходимо, чтобы вы самостоятельно ознакомились со справочной системой Qt.

Стили виджетов
Скриншоты, которые мы до сих пор видели, были получены в Windows XP. Однако внешний вид виджетов изменяется, в зависимости от платформы, на которой запускается приложение. С другой стороны, Qt в состоянии эмулировать внешний вид любой из поддерживаемых платформ.



Последнее обновление: 07.10.2019

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

Определение интерфейса

Для определения интерфейса используется ключевое слово interface . Как правило, названия интерфейсов в C# начинаются с заглавной буквы I , например, IComparable, IEnumerable (так называемая венгерская нотация), однако это не обязательное требование, а больше стиль программирования.

Что может определять интерфейс? В целом интерфейсы могут определять следующие сущности:

  • Свойства

    Индексаторы

  • Статические поля и константы (начиная с версии C# 8.0)

Однако интерфейсы не могут определять нестатические переменные. Например, простейший интерфейс, который определяет все эти компоненты:

Interface IMovable { // константа const int minSpeed = 0; // минимальная скорость // статическая переменная static int maxSpeed = 60; // максимальная скорость // метод void Move(); // движение // свойство string Name { get; set; } // название delegate void MoveHandler(string message); // определение делегата для события // событие event MoveHandler MoveEvent; // событие движения }

В данном случае определен интерфейс IMovable, который представляет некоторый движущийся объект. Данный интерфейс содержит различные компоненты, которые описывают возможности движущегося объекта. То есть интерфейс описывает некоторый функционал, который должен быть у движущегося объекта.

Методы и свойства интерфейса могут не иметь реализации, в этом они сближаются с абстрактными методами и свойствами абстрактных классов. В данном случае интерфейс определяет метод Move, который будет представлять некоторое передвижение. Он не имеет реализации, не принимает никаких параметров и ничего не возвращает.

То же самое в данном случае касается свойства Name. На первый взгляд оно похоже на автоматическое свойство. Но в реальности это определение свойства в интерфейсе, которое не имеет реализации, а не автосвойство.

Еще один момент в объявлении интерфейса: если его члены - методы и свойства не имеют модификаторов доступа, но фактически по умолчанию доступ public , так как цель интерфейса - определение функционала для реализации его классом. Это касается также и констант и статических переменных, которые в классах и структурах по умолчанию имееют модификатор private. В интерфейсах же они имеют по умолчанию модификатор public. И например, мы могли бы обратиться к константе minSpeed и переменной maxSpeed интерфейса IMovable:

Static void Main(string args) { Console.WriteLine(IMovable.maxSpeed); Console.WriteLine(IMovable.minSpeed); }

Но также, начиная с версии C# 8.0, мы можем явно указывать модификаторы доступа у компонентов интерфейса:

Interface IMovable { public const int minSpeed = 0; // минимальная скорость private static int maxSpeed = 60; // максимальная скорость public void Move(); protected internal string Name { get; set; } // название public delegate void MoveHandler(string message); // определение делегата для события public event MoveHandler MoveEvent; // событие движения }

Начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Это значит, что мы можем определить в интерфейсах полноценные методы и свойства, которые имеют реализацию как в обычных классах и структурах. Например, определим реализацию метода Move по умолчанию:

Interface IMovable { // реализация метода по умолчанию void Move() { Console.WriteLine("Walking"); } }

С реализацией свойств по умолчанию в интерфейсах дело обстоит несколько сложнее, поскольку мы не можем определять в интерфейсах нестатические переменные, соответственно в свойствах интерфейса мы не можем манипулировать состоянием объекта. Тем не менее реализацию по умолчанию для свойств мы тоже можем определять:

Interface IMovable { void Move() { Console.WriteLine("Walking"); } // реализация свойства по умолчанию // свойство только для чтения int MaxSpeed { get { return 0; } } }

Стоит отметить, что если интерфейс имеет приватные методы и свойства (то есть с модификатором private), то они должны иметь реализацию по умолчанию. То же самое относится к любым статическим методам и свойствам (не обязательно приватным):

Interface IMovable { public const int minSpeed = 0; // минимальная скорость private static int maxSpeed = 60; // максимальная скорость // находим время, за которое надо пройти расстояние distance со скоростью speed static double GetTime(double distance, double speed) => distance / speed; static int MaxSpeed { get { return maxSpeed; } set { if (value > 0) maxSpeed = value; } } } class Program { static void Main(string args) { Console.WriteLine(IMovable.MaxSpeed); IMovable.MaxSpeed = 65; Console.WriteLine(IMovable.MaxSpeed); double time = IMovable.GetTime(100, 10); Console.WriteLine(time); } }

Модификаторы доступа интерфейсов

Как и классы, интерфейсы по умолчанию имеют уровень доступа internal , то есть такой интерфейс доступен только в рамках текущего проекта. Но с помощью модификатора public мы можем сделать интерфейс общедоступным:

Public interface IMovable { void Move(); }

Стоит отметить, что в Visual Studio есть специальный компонент для добавления нового интерфейса в отдельном файле. Для добавления интерфейса в проект можно нажать правой кнопкой мыши на проект и в появившемся контекстном меню выбрать Add -> New Item... и в диалоговом окне добавления нового компонента выбрать пункт Interface .

Сразу оговорюсь, что C++ мой любимый язык, я на нем пишу практически «с детства» и отрицать его важность, как лучшего одного из лучших языков для написания программ для любых целей, не буду. Тем более не вижу смысла начинать очередной холивар или меряться «указками». Это статья - лишь описание неудачного опыта работы с языком, объясняющая некоторые его аспекты, знание которых поможет другим программистам в будущем.

Однажды я столкнулся с развивающейся библиотекой GUI класса. С точки зрения C++, а точнее его классов, экземпляров и иерархий, этот язык представляется невероятно близким к концепции управления GUI, в частности таким элементам, как виджеты, окна классов и подокна. OO модели C++ и оконной системы тем не менее разные. C++ был задуман как «статический» язык с охватом лексем, статической проверкой типов и иерархий определенных во время компиляции. Окна и их объекты с другой стороны, по своей природе динамичны, они обычно живут за рамкам отдельной процедуры или блока, с помощью которой были созданы; иерархии виджетов в значительной степени определены расположением, видимостью и потоками событий. Основы графического пользовательского интерфейса, такие как динамические и геометрические иерархии окон и управления, протекания событий, не поддерживаются непосредственно синтаксисом С++ либо его семантикой. Таким образом, эти функции должны быть воспроизведены в коде C++ GUI. Это приводит к дублированию графического инструментария, или функциональности оконного менеджера, код «раздувается», мы вынуждены отказываться от многих «сильных» особенностей C++ (например, проверки типов во время компиляции). В статье приведено несколько простых примеров C++ / GUI «не стыковок».

Не создавайте конструкторы (или, по крайней мере, не используйте их)

Когда полученный класс переопределяет виртуальный метод родительского класса, надо иметь в виду, что переопределение не вступает в силу, пока конструктор базового класса выполняется. Это особенно раздражает, когда объекты запрашивают виджеты, которые реагируют на события GUI. Предположим, что класс Basic_Window был предназначен для создания ванильного черно-белого окна на экране:

Class Basic_Window {
public:
Basic_Window(Rect rect) { gt_create_window(rect,visible,this); }
virtual void handle_create_event() { set_background(WHITE); }
};

Здесь gt_create_window() отвечает за низкоуровневый вызов основного графического инструментария (например, xvt_win_create() ). Эта функция выделяет место под данные инструментария, уведомляет оконный менеджер, регистрирует этот объект, как получателя событий, и в примере выше, инициализирует графический вывод в окно на экране.
Предположим, мы хотим создать экземпляр Basic_Window , но с красным фоном. Обычно, чтобы изменить поведение класса, надо извлечь из него и переопределить соответствующие виртуальные методы. Мы пишем:

Class RedWindow: public Basic_Window {
virtual void handle_create_event() { set_background(RED); }
public:
RedWindow(Rect rect) : Basic_Window(Rect rect) {}
};
RedWindow red_window(default_rect);

Но red_window появится белым, а не красным! Чтобы создать RedWindow , родительский объект должен быть создан первым. После завершения Basic_Window::Basic_Window() , виртуальные таблицы RedWindow вступают в силу, метод handle_create_event() становится неприменимым, и конструктор RedWindow() выполняется. Конструктор Basic_Window() регистрирует объект графического инструментария, который мгновенно начинает посылать события объекту (например, CREATE-событие). Конструктор Basic_Window() еще не закончен (это не гарантировано), поэтому переопределенный виртуальный метод еще не на месте. Таким образом, CREATE-событие будет обрабатываться Basic_Window::handle_create_event() . Виртуальные таблицы RedWindow класса будут созданы лишь тогда, когда базовый класс полностью построен, то есть, когда окно уже на экране. Изменение цвета окна на данном этапе приведет к досадной ошибке.

Есть простой обходной путь: запретить каждому конструктору регистрировать объект графического инструментария. Обработка событий будет построена таким образом, чтобы сдержать окончание инициализации до производных классов. Очень заманчиво рассматривать виджеты на экране, как «лицо» объекта GUI приложения в памяти. Как показывает приведенный выше пример, эта связь между экраном и C++ объектом реализовать не так просто: они рождаются отдельно.

Нет синтаксических средств переключения событий

Предположим, что библиотека классов включает в себя графический интерфейс класса PictWindow , который отображает фотографию в окно:

Class PictWindow {
Picture picture;
public:
virtual void repaint() { gt_draw_pict(picture); }
...
};

Мы хотели бы наложить небольшой прямоугольник в определенной области изображения. С этой целью, мы можем попытаться создать подкласс PictWindow :


Rect rect;
virtual void repaint() { gt_draw_rect(rect); }
};

К сожалению, когда мы создадим экземпляр OvWindow , то увидим только прямоугольник в пустом окно, и никакого изображения. С того момента, как OvWindow::repaint() переопределяет PictWindow::repaint() , последняя функция не будет вызываться, когда окно должно быть отрисовано. Мы должны были реализовать OvWindow так:

Class OvWindow: public PictWindow {
Rect rect;
virtual void repaint() { PictWindow::repaint(); gt_draw_rect(rect); }
public:
OvWindow(void) : PictWindow() {}
};

Конструктор OvWindow изложен, чтобы подчеркнуть, что метод OvWindow::repaint() должен быть отложен до суперкласса, как делают конструкторы. В самом деле, конструктор производного объекта с начала вызывает конструктор корреспондирующего объекта. repaint() следует отложить его родитель: методу в базовом классе, который его переопределяет.

В итоге: Плохая совместимость C++ / GUI

C++ был разработан как «статический» язык:

  • с отслеживанием лексем
  • проверкой статических типов
  • со статическими иерархиями классов
  • без «сборки мусора»
  • с системой сообщений без определенных иерархий времени компиляции

GUI объекты:

  • характерны динамические объекты, а зачастую и единственные в своем роде
  • как правило, живут далеко за рамками, в которых они были созданы
  • иерархии определяются в значительной степени потоками событий и их расположением, а не наследованием классов
  • иерархии строятся и разрушаются во время выполнения, зачастую в ответ на непредсказуемые действия пользователя

C + + не предназначен для поддержки динамической защиты обмена сообщениями и передачами (кроме исключений). Все это приводит к дублированию графического инструментария и функциональности оконного менеджера, разрастанию кода, использованию небезопасных функций и отказ от многих сильных сторон C++.

Выводы

Конечно, все эти «коряги» не являются фатальными. C++ является универсальным и мощным языком и, следовательно, способен выразить все возможные алгоритмы расчета. Поэтому, если приложение требует динамических функций, таких как те, что есть в Tcl / Tk , Scheme / Tk , Postscript и подобных; используя C++, Вы всегда можете сделать что-то по их примеру. С другой стороны, почему бы не использовать язык, где все эти черты присутствуют?

C++ - один из самых мощных и востребованных языков программирования. Ежедневно на нём пишут сотни приложений, зачастую использующих GUI. Однако работа с графикой не всегда удобна программисту - в таком случае применяют готовые графические библиотеки. Они позволят сделать разработку графической части приложений максимально быстрой и удобной.

SFML

Qt

Кроссплатформенная библиотека Cocos2D-X призванна упростить разработку мобильных игр. Поддерживает все те же платформы, что и Qt. Из плюсов стоит отметить доступность, удобство эксплуатации и создание отдельного конструктора игр, основанного на библиотеке Cocos Creator . В списке игр, основанных на движке, есть всемирно известная BADLAND, работающая на всех доступных платформах.

Кое-что ещё

Если при создании игры вам нужно работать с графикой и анимацией в больших объёмах, то лучше использовать Unity вместо Cocos2D-X. В Unity имеется возможность плавной интеграции с такими инструментами, как Photoshop, Maya или Blender. В Cocos2D-X вся графика добавляется извне и на неё ссылаются из кода.

В Рунете нет уроков по этой библиотеке, но на английском языке есть отличный курс от разработчиков.

Juce

Пожалуй, одна из самых известных графических библиотек. GTK+ - графический фреймворк, широко применямый во многих системах. Изначально он задумывался как компонент GIMP, но за 20 лет после выпуска первой стабильной версии он нашёл применение в сотнях других приложений.

Сейчас GTK+ - полноценный графический фреймворк, не уступающий тому же QT. Он поддерживает разные языки программирования и продолжает развиваться.

Кое-что ещё

В своё время библиотека создавалась в качестве альтернативы Qt, которая была платной. GTK+ - один из немногих фреймворков, которые поддерживают язык C. Библиотека кроссплатформенная, но есть мнение , что программы на Linux выглядят более нативно, чем на Windows или Mac (GTK+ хорошо поддерживается даже на KDE). Интересно, что из-за некоторых проблем с кроссплатформенностью Wireshark перешла на Qt.

Пример первой программы можно посмотреть на Википедии .

Другие интересные статьи по C++ можно посмотреть у нас .

Заключение

Выше представлены наиболее популярные технологии для работы с GUI не только на C++, но иногда и на других языках (например, Qt и GTK+). Однако всегда следует учитывать особенности той или иной технологии. Выпишите список функций своего приложения, ещё раз прочитайте описания всех библиотек и фреймворков, и только после этого выбирайте то, что действительно подходит больше всего для проекта.