В начале мы повторим процесс создания и настройки пустого проекта для приложения, что Вы уже делали дважды, если следовали урокам. После этого мы объясним как использовать существующие виджеты, которые поддерживают режим формы и диалогового виджета, на примере виджета для выбора цвета ColorDialog. Вы так же узнаете как сделать Ваши формы полностью совместимыми с обоими режимами.
Этот урок показывает как работать с Nitisa, загруженным в виде исходного кода. Если Вы хотите следовать ему используя расширение Nitisa для Visual Studio, Вы можете найти полезную информацию о создании проекта и форм в этом уроке. Все остальное остается одинаковым и Вы можете следовать инструкциям этого урока пропустив создание формы и проекта вручную, так как это можно сделать с использованием шиблонов расширения Nitisa.
Мы начнем с подготовки фреймверка Nitisa для работы. Если Вы следовали нашим урокам, Вы уже знаете как это сделать и, если Вы уже это сделали раньше, Вы можете пропустить эту часть.
Здесь мы запускали сборку дважды. Первый раз для создания исполняемого файла Конструктора Форм и второй раз для получения файлов библиотек фреймверка, которые мы линкуем с нашим приложением. Конечно же мы могли не собирать решение второй раз, а просто использовать полученные при первой сборке библиотеки из папки C:\Nitisa-9.0.0\bin\Windows\x86\ReleaseRT. Однако лучше использовать отладочные версии библиотек при создании и отладке Ваших приложений, а уже потом, когда все хорошенько оттестировано, переключиться на финальную сборку и собрать продакшен версию приложения, которое работает с максимальной скоростью и производительностью.
После подготовки фреймверка Nitisa мы создадим пустой проект для нашего приложения. Если Вы следуете нашим урокам, Вы уже знаете как это сделать. В любом случае, мы повторим это снова для полноты урока.
Этот простой процесс создания пустого проекта всегда одинаков с Nitisa. Единственное отличие, которое может здесь быть, это использование разных папок и настройки Runtime Library. Вы даже можете создать такой проект один раз, экспортировать его как шаблон и использовать этот шаблон как начальную точку в разработке Ваших приложений.
Некоторые существующие виджеты, как Вы уже могли заметить, показывают диалоговые формы для получения данных от пользователя. Например, FindDialog показывает форму для поиска текста, виджет SysOpenDialog показывает форму для выбора файла, и так далее. Некоторые из таких виджетов могут работать как в режиме обычных форм так и в режиме диалоговых виджетов. Режим формы у них используется по умолчанию. Один их таких виджетов это виджет ColorDialog, который используется для показа интерфейса выбора цвета. Давайте сначала посмотрим как его использовать по умолчанию.
Этими простыми шагами мы создали форму для нашего приложения, которая показывает интерфейс выбора цвета при клике на кнопку и показывает выбранный цвет на панели на форме.
Теперь перейдите в Visual Studio с пустым проектом, который мы создали ранее, и сделайте следующее:
#pragma once
// Подключение файла с прототипом формы, который был сгенерирован Конструктором Форм, когда мы сохранили форму
#include "IFormMain.h"
// Так как мы разрабатываем с помощью фреймверка Nitisa, мы можем просто разместить все в этом пространстве имен для избежания добавления везде "using namespace nitisa"
namespace nitisa
{
// По умолчанию Конструктор Форм генерирует класс прототипа формы в этом пространстве имен. Так что мы так же разместим финальный класс формы в нем
namespace app
{
// Это наш класс формы, который необходимо наследовать от класса прототипа формы сгенерированного когда мы сохранили форму в Конструкторе Форм
class CFormMain :public IFormMain
{
protected:
// Здесь находятся методы, которые будут вызываться при наступлении различных событий. Мы добавили один, когда сделали двойной клик на событии "OnClick" кнопки.
// Если на Вашей форме много элементов, то легко забыть о каких-то из них. Мы рекомендуем просто открыть заголовочный файл с прототипом формы, найти там секцию "protected", и увидеть все методы, которые Вам нужны.
void Button1_OnClick(IControl *sender) override;
public:
// Конструктор формы
CFormMain();
};
// Переменная, в которой будем хранить экземпляр формы
extern CFormMain *FormMain;
}
}
// Подключение заголовочного файла с классом окна для нашей платформы
#include "Platform/Core/Window.h"
// Подключение заголовочного файла с OpenGL рендером для нашей платформы
#include "Platform/Core/Renderer.h"
// Подключение объявления нашей формы
#include "FormMain.h"
namespace nitisa
{
namespace app
{
// Глобальная переменная для экземпляра формы. Будем использовать ее позже
CFormMain *FormMain{ nullptr };
// Конструктор формы
CFormMain::CFormMain() :
// Вызов конструктора родительского класса. Ему нужно передать объекты платформенного окна и рендера
IFormMain(CWindow::Create(), CRenderer::Create())
{
// Нам здесь не нужно ничего делать
// Вся настройка и создание UI(интерфейса пользователя) сделаны в классе IFormMain, сгенерированном Конструктором Форм
}
// Метод, который вызывается когда пользователь кликает на кнопке
void CFormMain::Button1_OnClick(IControl *sender)
{
// Показываем интерфейс выбора цвета. Метод возвращает "true" если пользователь потвердждает выбор цвета
if (m_pColorDialog1->Execute())
// Если пользователь потвердил выбранный цвет, то устанавливаем цвет фона панели в этот цвет
m_pPanel1->setBackgroundColor(m_pColorDialog1->getColor());
}
}
}
#include <Windows.h>
// Подключение файла с менеджером приложения для нашей платформы
#include "Platform/Core/Application.h"
// Подключение файла с объявлением нашей формы
#include "FormMain.h"
// Указываем линковщику, что мы хотим слинковать приложение с библиотекой ядра фреймверка. Обязательно
#pragma comment(lib, "Nitisa.lib")
// Указываем линковщику, что мы хотим слинковать приложение с библиотекой Стандартного(Standard) пакета так как мы используем виджеты из этого пакета
// При использовании виджетов на форме из какого-нибудь пакета, нужно слинковать приложение с библиотекой этого пакета
// Библиотеки для линковки так же можно указать в настройках проекта, однако мы предпочитаем этот способ, так как он более нагляден
#pragma comment(lib, "Standard.lib")
// Указываем линковщику, что мы хотим слинковать приложение с библиотекой с платформо-зависимым кодом. Обязательно в большинстве случаев
#pragma comment(lib, "Platform.lib")
#pragma comment(lib, "opengl32.lib") // Линкуем с библиотекой OpenGL
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, INT)
{
nitisa::CApplication app; // Создание приложения
app.CreateForm(&nitisa::app::FormMain); // Создаем нашу форму. Наличие переменной для нее делает возможным использование "CreateForm", однако форма так же может быть создана как обычно с помощью оператора "new"
app.setMainForm(nitisa::app::FormMain); // Указываем приложению, что форма является главной формой приложения
app.Run(); // Запуск приложения
return 0;
}
Вот как виджет ColorDialog выглядит и работает по умолчанию. Как Вы видите он создает отдельную форму. Ее можно перемещать куда угодно, даже за пределы главной формы, даже на другой монитор. В дополнение, Вы не можете взаимодействовать с главной формой пока не закроете форму выбора цвета.
Предже всего интерфейс выбора цвета выглядит немного по другому теперь. В режиме диалогового виджета он использует диалоговый виджет в качестве базового класса, а не форму. Виджет для этих целей называется DialogBoxEx. Он является частью Стандартного(Standard) пакета. Внешний вид этого виджета отличается от внешнего вида формы по умолчанию. Это объясняет немного другой внешний вид. Вы также можете использовать этот виджет в качестве базового класса для разработки Ваших собственных диалоговых виджетов или использовать другие доступные.
Следующее отличие заключается в том, что метод Execute()
виджета ColorDialog возвращает true сразу же, а не ждет пока пользователь подтвердит выбор цвета. Вы можете заметить это по изменению цвета панели на черный, который является цветом по умолчанию виджета ColorDialog. И теперь, когда пользователь подтверждает выбор цвета, цвет панели не меняется. Вскоре мы объясним как это исправить.
Следующее отличие заключается в том, что интерфейс выбора цвета в режиме диалогового виджета нельзя перемещать за пределы формы приложения, как Вы можете увидеть выше. Так как диалоговый виджет это всего лишь виджет, он не может быть нарисован за пределами области формы.
И последнее важное отличие это то, что пользователь по прежнему может закрыть, переместить и изменить размер окна приложения, хотя он и не может взаимодействовать с другими виджетами формы. Это стандартное поведение, но оно так же может быть изменено при желании любым способом, который Вам нравится.
Давайте теперь посмотрим как исправить подтверждение выбора цвета. У диалоговых виджетов, так же как у форм, есть методы Show()
и ShowModal()
. Первый работает у обоих одинаково. Второй же в случае формы ждет пока форма не закроется и результат вызова метода соответствует результату с которым закрыли форму, однако в диалоговых виджетах он возвращает управление обратно сразу же. Виджет ColorDialog использует Color Form. В обычном режиме это обычная форма, в то время как в режиме диалогового виджета она, соответственно, реализована как диалоговый виджет. Метод Execute()
виджета ColorDialog просто использует метод ShowModal()
этой формы и, таким образом, он возвращает управление обратно сразу же в режиме диалоговых виджетов, абсолютно так же как метод ShowModal()
. Для получения информации о том, что пользователь подтвердил выбор цвета нужно использовать слушателя. Давайте посмотрим как его добавить и использовать.
CFormMain
в файле FormMain.h. Этим действием мы объявили класс для слушателя.// Этот маленький класс реализует слушателя, который используется для получения уведомлений о подтверждении выбора цвета пользователем
// Класс должен реализовать интерфейс IColorDialogListener. Так что он должен его наследовать
class CColorDialogListener :public virtual standard::IColorDialogListener
{
private:
// В этом члене будет храниться указатель на нашу форму
CFormMain *m_pForm;
public:
// У слушателя есть только один метод, который вызывается, когда пользователь подтверждает выбор цвета, и мы используем его для обновления цвета панели
void OnApply(standard::IColorDialog *sender) override;
// Конструктор
CColorDialogListener(CFormMain *form);
};
CColorDialogListener m_cColorDialogListener;
в private секцию класса формы сразу после объявления класса слушателя. // Подключение заголовочного файла с классом окна для нашей платформы
#include "Platform/Core/Window.h"
// Подключение заголовочного файла с OpenGL рендером для нашей платформы
#include "Platform/Core/Renderer.h"
// Подключение объявления нашей формы
#include "FormMain.h"
namespace nitisa
{
namespace app
{
// Глобальная переменная для экземпляра формы. Будем использовать ее позже
CFormMain *FormMain{ nullptr };
// В конструкторе мы просто сохраняем указатель на форму
CFormMain::CColorDialogListener::CColorDialogListener(CFormMain *form) :
m_pForm{ form }
{
}
// При подтверждении выбора цвета мы обновляем цвет панели
void CFormMain::CColorDialogListener::OnApply(standard::IColorDialog *sender)
{
m_pForm->m_pPanel1->setBackgroundColor(m_pForm->m_pColorDialog1->getColor());
}
// Конструктор формы
CFormMain::CFormMain() :
// Вызов конструктора родительского класса. Ему нужно передать объекты платформенного окна и рендера
IFormMain(CWindow::Create(), CRenderer::Create()),
// Создание слушателя и передача ему указателя на эту форму
m_cColorDialogListener{ this }
{
// Нам здесь не нужно ничего делать
// Вся настройка и создание UI(интерфейса пользователя) сделаны в классе IFormMain, сгенерированном Конструктором Форм
// Устанавливаем слушателя ColorDialog
m_pColorDialog1->setListener(&m_cColorDialogListener);
}
// Метод, который вызывается когда пользователь кликает на кнопке
void CFormMain::Button1_OnClick(IControl *sender)
{
// Показываем интерфейс выбора цвета
m_pColorDialog1->Execute();
}
}
}
Вот так вот мы можем обработать подтверждение выбора цвета в режме диалоговых виджетов виджета ColorDialog. Между прочим, это изменение кода так же будет работать и в случае, если изменить свойство UseDialogBox на false снова.
Как Вы могли заметить, еще остались кое-какие проблемы с внешним видом интерфейса выбора цвета. В частности, мы бы хотели, чтобы цвет фона был таким же как цвет фона главной формы. Так же, если хорошо присмотреться, Вы увидете, что расстояние возле правой и нижней границ отличается от расстояния до левой и верхней границ. Это обычная небольшая погрешность для всех диалоговых виджетов унаследованных от DialogBoxEx, когда они создаются с помощью Конструктора форм. Неточность возникает из-за разности между внешним видом формы и виджета DialogBoxEx установленных по умолчанию. Однако существует весьма простой способ исправить положение. Давайте исправим оба этих недостатка следующим способом.
#include "Standard/Forms/Color/DialogBoxColor.h"
Button1_OnClick
формы сразу после показа интерфейса выбора цвета методом Execute()
. Первая строка устанавливает нужный цвет фона диалогового виджета с интерфейсом выбора цвета, а вторая просто устанавливает ему нужный размер и положение.m_pColorDialog1->getDialogBox()->setBackgroundColor(getBackgroundColor());
m_pColorDialog1->getDialogBox()->AutoResize(true);
Если Вы запустите приложение и кликните на кнопке Select color..., Вы увидите изменения. Цвет фона интерфейса выбора цвета теперь такой же как и у главной формы, а его расстояния у границ одинаковые со всех сторон.
!
Единственное важное замечание, которое можно здесь сделать - это то, что в целях оптимизации диалоговое окно виджета ColorDialog, и всех виджетов, которые используют диалоговые окна, может быть недоступно до тех пор, пока не будет затребовано его отображение. Вот почему мы получаем к нему доступ и изменяем его свойства, а так же вызываем методы, только после вызова Execute()
из ColorDialog, а не до.
И последнее улучшение здесь, которое мы хотели бы показать, это проверка того находится ли ColorDialog в режиме диалогового виджета или нет перед получением доступа к его диалоговому виджету, а так же проверка что метод Execute()
вызвался успешно. Это последнее улучшение делает нашу простую форму полностью совместимой с обоими режимами: как с режимом диалоговых виджетов, так и с режимом обычных форм. Это так же делает наш код универсальным, что значит что в нем не требуется делать никаких изменений при смене режима. Все что требуется при таком изменении это компиляция проекта и все. Вы так же можете увидеть, что мы добавили булевский член класса m_bDialogBoxAdjusted
(установленный в false в конструкторе формы) для предотвращения подстройки диалогового виджета каждый раз, когда пользователь кликает на кнопке Select color.... Это просто оптимизация, так как после первой подстройки диалогового виджета он остается в правильном состоянии и последующая подстройка не требуется.
// Метод, который вызывается когда пользователь кликает на кнопке
void CFormMain::Button1_OnClick(IControl *sender)
{
// Показываем интерфейс выбора цвета и подстраиваем диалоговый виджет при необходимости и если это режим диалоговых виджетов
if (m_pColorDialog1->Execute() && m_pColorDialog1->isUseDialogBox() && !m_bDialogBoxAdjusted)
{
// Устанавливаем цвет фона диалогового виджета в тот же цвет, что у нашей формы
m_pColorDialog1->getDialogBox()->setBackgroundColor(getBackgroundColor());
// Автоматически подстраиваем размер и положение диалогового виджета
m_pColorDialog1->getDialogBox()->AutoResize(true);
// Устанавливаем флаг, чтобы не менять положение и размер диалогового виджета каждый раз, когда показываем интерфейс выбора цвета
m_bDialogBoxAdjusted = true;
}
}
Этот способ показывает использование слушателя. Есть другой способ: использовать событие OnApply
виджета ColorDialog. Мы хотели показать Вам как использовать слушатели так как все диалоговые виджеты, которые Вы будете создавать и использовать, наделены только способом получения информации использующим слушатели и у них нет событий как в случае ColorDialog.
Вы можете найти исходный код проекта, который мы только что завершили, в папке Windows → Tutorials → DialogBox решения Nitisa. Проект называется TestColorDialog.
Если Вам нравится проект Nitisa Вы можете поддержать нас небольшим пожертвованием или выбрать использование одного из платных планов для поддержки дальнейшей разработки проекта.