Аннотация: Изучаются выджеты - визуальные элементы, из которых состоит графический интерфейс пользователя, их компоновка, политика размеров, сигнально-слотовые соединения, элементы графического интерфейса и их использование.
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
Как видим, элементы, из которых состоят интерфейсы в 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
Поскольку родительским элементом является ParentWidget , то метка (QLabel ), кнопка (QPushButton ) и текстовое поле (QLineEdit) находятся в его пределах. Позицию дочерних виджетов задают относительно левого верхнего угла отца. В этом легко убедиться изменив размеры и позицию окна нашей программы. Обратите внимание на то, как мы создавали элементы пользовательского интерфейса в динамической памяти используя оператор new . Это гарантирует, что элементы не будут удалены после завершения работы конструктора ParentWidget .
В этой статье речь пойдет о создании простейших приложений с использованием форм на языке C++. Сразу хочу внести ясность: разработка «формовых» С++ приложений будет осуществляться в среде Microsoft Visual Studio (обойдем стороной главного конкурента от компании Borland Software). Стоит отметить, что в Visual Studio есть два способа сделать приложение с формами, решение принимается в момент создания нового проекта.
Первый из них - использовать Windows Forms, реализующий графический интерфейс пользователя и входящий в Microsoft.NET Framework. Данный подход упрощает доступ к интерфейсным элементам Microsoft Windows благодаря созданию обёртки для Win32 API в управляемом коде. Если изложить свои мысли в более понятной форме, то данный подход очень схож с построением консольного приложения, но чуть более сложен т.к. использует формы.
Второй способ основан на использовании Microsoft Foundation Classes (MFC), библиотеке, которая берет на себя заботу о создании каркаса приложения. В отличие от первого, MFC «из коробки» использует патерн MVC (Model-View-Cont roller). Данный подход сложнее первого, но опираясь на него можно запросто создать каркас для весьма интересных приложений, к примеру, текстового редактора или использовать компонент Ribbon и сделать меню как в небезызвестном MS Office 2010.
Создание приложения в MS Visual Studio
Давайте создадим новое приложение: File->New->Project. В появившемся окне как на рисунке выше найдите и выберите Windows Forms Application, далее укажите название (app1) и расположение нового проекта и подтвердите его создание нажатием кнопки «OK».
Прежде чем приступить непосредственно к программированию, следует понять, что такое событие. Событие это действие, которое происходит при определённых условиях. Самыми простыми (и наиболее распространенным и) можно считать Load,Click… рассмотрим некоторые из них более подробно:
- Activated - событие, возникающее при активизации элемента.
- Click - возникает при одиночном нажатии на элемент.
- DoubleClick - двойной щелчок по элементу.
- HelpRequested - срабатывает при нажатии клавиши
. - Paint - возникает, когда элемент перерисовывается.
- MouseLeave - событие срабатывает, когда курсор покидает границы элемента.
Не стоит забывать, что свойственные элементу события зависят от его типа. Чтобы просмотреть все доступные для объекта формы события следует выделить его и в окне свойств выбрать значок молнии. Ниже показана часть событий для элементаForm1. Как можете видеть для события Load выбрана функция Form1_Load, следовательно код, содержащийся в этой функции будет вызван при загрузке формы.
Откройте файл Form1.h, там будет такой код:
Private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e){ }
Это та самая функция Form1_Load, срабатывающая при загрузке формы. Давайте убедимся в этом, добавив на форму компонент TextBox.
Для этого откройте ресурс формы и выделите его. Далее выберите панель с инструментами Toolbox и перетащите компонент TextBox на форму. Модифицируйте функцию Form1_Load следующим образом:
Private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) { textBox1->Text = "Hello, World!"; //textBox1 – имя добавленного вами текст бокса }
Запустите проект, в итоге должно появиться следующее сообщение:
На этом пока все, продолжение в следующих уроках.
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
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, что намного эффективнее и проще для понимания), советы по оптимизации и т. д.
Последнее обновление: 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 .
1.1. Hello, Qt!
Ниже приводится текст простейшей Qt программы:
1 #include
В строке 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
Теперь соберем приложение. Надеемся, что вы уже создали каталог 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
В строках 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 в состоянии эмулировать внешний вид любой из поддерживаемых платформ. | |
|
|