Программирование на c графический интерфейс. Методология разработки приложений с использованием форм. Работа со справочной системой

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

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 .

5

Я сделал много разных разделов GUI-системы для Nintendo DS, таких как кнопки и текстовые поля и флажки, но мне нужен способ скрыть эти классы в одном классе Gui, чтобы я мог рисовать все на экране все сразу, и сразу проверьте все кнопки, чтобы проверить, нажаты ли какие-либо кнопки. Мой вопрос - как наилучшим образом организовать все классы (например, кнопки и текстовые поля) в один GUI-класс?

Вот один способ, которым я думал, но это не кажется правильным:

Edit: Я использую C++.

Class Gui { public: void update_all(); void draw_all() const; int add_button(Button *button); // Returns button id void remove_button(int button_id); private: Button *buttons; int num_buttons; }

Этот код имеет несколько проблем, но я просто хотел дать вам представление о том, чего я хочу.

  • 5 ответов
  • Сортировка:

    Активность

2

Этот вопрос очень похож на тот, который я собирался публиковать, только мой предназначен для программирования Sony PSP.

Я какое-то время что-то искал, я консультировался с некоторыми книгами и VTMs , и до сих пор это приблизительная идея простых систем ui.

Class uiElement() { ... virtual void Update() = 0; virtual void Draw() = 0; ... } class uiButton() public: uiElement { ... virtual void Update(); virtual void Draw(); ... } class uiTextbox() public: uiElement { ... virtual void Update(); virtual void Draw(); ... } ... // Other ui Elements class uiWindow() { ... void Update(); void Draw(); void AddElement(uiElement *Element); void RemoveElement(uiElement *Element); std::list Elements; ... } void uiWindow::Update() { ... for (list ::iterator it = Elements.begin(); it != Elements.end(); it++) it->Update(); ... } void uiWindow::Draw() { ... for (list ::iterator it = Elements.begin(); it != Elements.end(); it++) it->Draw(); ... }

princple является создание окна и attact элементов пользовательского интерфейса к нему, и вызвать отрисовки и обновление методы из соответствующих основных функций.

У меня пока ничего не работает, так как у меня проблемы с кодом рисования. С различными API-интерфейсами на ПК и PSP я просматриваю код оболочки для OpenGL и psp gu.

Надеюсь, это поможет.

0

Одна полезная стратегия, которую следует иметь в виду, может быть composite pattern . На низком уровне он может позволить вам легче обрабатывать все объекты GUI (и коллекции объектов) после их создания. Но я не знаю, что связано с дизайном графического интерфейса GUI, поэтому одно место, чтобы найти общее вдохновение, - это исходный код существующего проекта. WxWidgets - это кросс-платформенный графический интерфейс с доступным исходным кодом. Удачи с вашим проектом!

0

3

Для тех, кто заинтересован, вот мой с открытым исходным кодом, BSD-лицензированные GUI инструментарий для DS:

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

Если вы не поддержите это в базовом классе, вы столкнетесь с серьезными проблемами при попытке реализовать что-либо более сложное, чем текстовое поле и кнопку. Например:

  • Табличные панели можно смоделировать как несколько кнопок, сгруппированных в единый родительский элемент пользовательского интерфейса, который обеспечивает взаимную исключительность выбора;
  • Группы радиостанций (с течением времени);
  • Полосы прокрутки могут быть представлены как элемент слайдера/желоба и кнопки вверх/вниз;
  • Списки прокрутки могут быть представлены в виде контейнера и нескольких элементов пользовательского интерфейса.

Кроме того, стоит помнить, что у DS есть процессор с частотой 66 МГц и 4 МБ ОЗУ, который используется как для хранения вашей программы, так и для ее выполнения (DS-диски загружаются в ОЗУ до их запуска). Вы действительно должны рассматривать его как встроенную систему, что означает, что STL отсутствует. Я удалил STL из Woopsi и сумел сохранить 0.5MB. Не так много по настольным стандартам, но это 1/8 от общей доступной памяти DS, потребляемой STL-мусором.

Я подробно весь процесс написания пользовательского интерфейса на моем блоге:

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

Сразу оговорюсь, что 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++, Вы всегда можете сделать что-то по их примеру. С другой стороны, почему бы не использовать язык, где все эти черты присутствуют?

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

В Xamarin.Forms визуальный интерфейс состоит из страниц. Страница представляет собой объект класса Page , она занимает все пространство экрана. То есть то, что мы видим на экране мобильного устройства - это страница. Приложение может иметь одну или несколько страниц.

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

Возьмем созданный в прошлой теме проект HelloApp (или создадим новый). По умолчанию весь интерфейс создается в классе App, который располагается в файле App.xaml.cs и который представляет текущее приложение:

Его код по умолчанию:

Using System; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace HelloApp { public partial class App: Application { public App() { InitializeComponent(); MainPage = new MainPage(); } protected override void OnStart() { // Handle when your app starts } protected override void OnSleep() { // Handle when your app sleeps } protected override void OnResume() { // Handle when your app resumes } } }

Работа класса App начинается с конструктора, где сначала вызывается метод InitializeComponent() , который выполняет инициализацию объекта, а потом устанавливается свойство MainPage . Через это свойство класс App устанавливает главную страницу приложения. В данном случае она определяется классом HelloApp.MainPage, то есть тем классом, который определен в файлах MainPage.xaml и MainPage.xaml.cs.

Но данный путь не единственный. Xamarin.Forms позволяет создавать визуальный интерфейс как с помощью кода C#, так и декларативным путем с помощью языка xaml, аналогично html, либо комбинируя эти подходы.

Создание интерфейса из кода C#

Добавим в проект HelloApp обычный класс на языке C#, который назовем StartPage .

И определим в этом классе следующее содержимое:

Using Xamarin.Forms; namespace HelloApp { class StartPage: ContentPage { public StartPage() { Label header = new Label() { Text = "Привет из Xamarin Forms" }; this.Content = header; } } }

Данный класс представляет страницу, поэтому наследуется от класса ContentPage . В конструкторе создается метка с текстом, которая задается в качестве содержимого страницы (this.Content = header).

Чтобы обозначить MainPage в качестве стартовой страницы, изменим класс App:

Using Xamarin.Forms; namespace HelloApp { public partial class App: Application { public App() { InitializeComponent(); MainPage = new StartPage(); } protected override void OnStart() { // Handle when your app starts } protected override void OnSleep() { // Handle when your app sleeps } protected override void OnResume() { // Handle when your app resumes } } }

Теперь свойство MainPage указывает на только что созданную страницу StartPage.

Также стоит отметить, что в Visual Studio есть готовый шаблон для добавления новых классов страниц с простейшим кодом. Так, чтобы добавить новую страницу, надо при добавлении нового элемента выбрать шаблон Content Page (C#) :

Данный класс добавляется в главный проект решения (в данном случае это HelloApp).

Добавленный класс страницы будет иметь следующий код:

Using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using Xamarin.Forms; namespace HelloApp { public class Page1: ContentPage { public Page1() { Content = new StackLayout { Children = { new Label { Text = "Hello Page" } } }; } } }

Этот класс также будет наследоваться от базового класса ContentPage и будет иметь практически ту же самую организацию, что и выше созданный класс MainPage.

И также в классе приложения мы можем установить эту страницу в качестве стартовой:

Using Xamarin.Forms; namespace HelloApp { public partial class App: Application { public App() { InitializeComponent(); MainPage = new Page1(); } //........... } }

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