Принципы


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



Терминология

Перед дальнейшими пояснениями будет полезно познакомится с используемой терминологией. Пожалуйста взгляните на следующие термины.

Под Формой мы подразумеваем окно приложения с его графическим интерфейсом и поведением. Форма - это контейнер для всех элементов, которые пользователь может видеть и с которыми может взаимодействовать. Вы можете редактировать формы визуально в Конструкторе Форм являющегося частью фреймверка или же делать это полностью в исходном коде на C++. Каждую форму нужно наследовать от интерфейса IForm и его методы должны быть реализованы правильно. Уже существует вспомогательный класс CForm, в котором все нужные методы уже реализованы. Настоятельно рекомендуется наследовать формы от этого класса CForm. Конструктор Форм генерирует заголовочный файл с кодом прототипа формы, который тоже наследуется от этого класса.

Компонент представляет собой невидимый элемент на форме у которого нет графического представления но который используется для выполнения каких-то полезных функций. Например, Timer - это компонент. Он не отображается на форме однако он помогает запускать задачи с определенной периодичностью в приложении. Поскольку компоненты не имеют визуального представления, то нет смысла помещать их на элементы управления или другие компоненты. Вы можете только размещать их непосредственно на форме. Их так же нельзя повернуть или поменять им размер по той же самой причине. Вы могли уже заметить это поведение в Конструкторе Форм. Вы можете представлять себе компоненты как очень упрощенные версии элементов управления. Все диалоговые окна из Стандартного Пакета являются компонентами. Компоненты отображаются в Конструкторе Форм как небольшие квадраты с иконкой на них, но они не видны на формах в приложении. Все компоненты должны наследоваться от интерфейса IComponent и реализовывать его методы правильно. Существует класс CComponent, который уже содержит реализацию всех необходимых методов интерфейса. Все компоненты из Стандартного Пакета наследуются от этого класса. Большинство компонентов также допускают использование без размещения их на форме. Но не все. Например, можно использовать Logger и диалоговые компоненты без формы просто создав их и используя их методы. С другой стороны, компонент Timer бесполезен без формы потому что он зависит от таймеров, к которым он может получить доступ только через форму.

Элемент управления - это сущность, которая отображается на формах и обычно предназначена для взаимодействия с пользователем. Button, Edit, Label - несколько примеров элементов управления. Некоторые элементы управления, такие как Panel и GroupBox, позволяют размещать на себе другие элементы управления. Некоторые элементы управления, такие как MainMenu, могут быть размещены только непосредствено на форме. Некоторые элементы управления, такие как TabSheet могут быть созданы только с помощью редактора Иерархии и могут быть размещены только на определенных других элементах управления. Все элементы управления должны наследоваться от интерфейса IControl и правильно реализовывать его методы. Существует класс CControl, который уже реализует все необходимые методы. Мы настоятельно рекомендуем наследовать все Ваши элементы управления от этого класса. Все элементы Стандартного Пакета наследуются от него.

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

Элемент списка - это сущность, которая не может использоваться независимо, которая предназначена для использования внутри компонентов, элементов управления или других элементов списка. Элементы списка используются в большинстве таких элементов управления, которые работают с наборами одинаковых элементов. Например, ListBox представляет собой набор элементов, каждый из которых имеет только заголовок. Он может быть без элементов вообще, может имет только один или множество элементов. Каждый элемент ListBox является Элементом Списка. Некоторые Элементы Управления, такие как ListBox и ColorBox могут работать только с одним уровнем элементов. Другие элементы управления, такие как TreeView и MainMenu могут иметь деревья элементов. Это значит элементы виджета могут иметь свои элементы и виджет знает как правильно с ними работать. Более того, элементы управления как MainMenu и StatusBar могут работать одновременно с разнотипными элементами списка. Целью элементов списка является однообразная обработка списков и деревьев для разнотипных элементов. Все элементы списка наследуются от интерфейса IListItem. Некоторые элементы управления добавляют свои собственные интерфейсы для элементов списка и требуют чтобы элементы списка добавляемые к этаким элементам управления наследовались от этих новых интерфейсов. В то же время эти новые интерфейсы все равно должны наследоваться от IListItem. Например, ColorBox работает только с элементами списка наследуемыми от интерфейса IListItemColor, который в свою очередь наследуется от IListItem и добавляет некоторые методы специфические для работы с цветами. Вы можете управлять элементами списка виджетов с помощью редактора Иерархий в Конструкторе Форм.

Уведомления - это методы вызываемые когда что-то произошло и сущность может что-то сделать в этом случае. Например, когда пользователь кликает на элементе управления Button, он - элемент управления - получает уведомление о нажатии и отпускании кнопки мышы. Формы, компоненты, элементы управления и некоторые другие сущности имеют предопределенный набор уведомлений в которых они заинтересованы. Все уведомления находятся в соответствующем объекту сервисе и не должны вызываться непосредственно. Они вызываются когда пользовательское или системное событие происходит, такое как ввод пользователем или закрытие формы. Существует множество разных уведомлений. Вам нужно реализовывать только те из них, в которых Вы хотите выполнить какие-либо действия соответствующие уведомлению. Например, если Вы хотите спросить пользователя уверен ли он в том что хочет завершить приложение когда он кликает по иконке закрытия главной формы, Вам нужно реализовать метод-уведомление NotifyOnClose() в сервисе Вашей формы. Все уведомления начинаются с Notify. Вы можете найти полный список в справочной системе.

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

Под свойством мы понимаем переменную формы/компонента/элемента управления/элемента списка, доступ к которой осуществляется только с помощью соответствующих методов для получения и изменения значения(геттеры и сеттеры). Например, у элемента управления Button есть свойство Caption. Вы можете редактировать это свойство в редакторе свойств Конструктора Форм, а так же можете получить доступ к нему в коде Вашего приложения. Однако, в случае доступа из исходного кода, для получения значения нужно использоватеь геттер-метод getCaption() и сеттер-метод setCaption() для изменения значения. Есть два важных типа свойств. Свойства первого типа наследуются от IProperty и представляют собой значения не зависящие от состояния элемента управления. Свойства второго типа представляют собой набор из нескольких значений для одного и того же свойства но в разных возможных состояних элемента управления. Интерфейс для таких свойств IPropertyState.

Под интерфейсом мы подразумеваем абстрактный класс, который так же может иметь несколько публичных переменных. Все сущности ядра фреймверка имеют соответствующие интерфейсы описывающие их. Например, IForm описывает форму и ее поведение, IControl описывает элемент управления, IMouse описывает взаимодействие с мышью, и так далее. Главная цель фреймверка - быть простым в использовании и расширении. Интерфейсы являются средствами для достижения этой цели. Все взаимодействия между различными сущностями фреймверка происходят с помощью интерфейсов, а не через конечные классы. Есть несколько базовых интерфейсов, которые необходимо изучить в первую очередь для базового понимания работы фреймверка и создания своих сущностей. Имена всех интерфейсов начинаются с I.

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

Замена сервисов - это техника, которая позволяет вашим элементам управления временно изменять поведение других элементов управления. Это достигается с помощью подмены сервиса компонента/элемента управления другим сервисом. Этот сервис называется замещающим сервисом. Он также хранит иформацию об изначальном сервисе компонента/элемента управления. У компонентов и элементов управления есть метод setService(), который позволяет устанавливать новый сервис. Так же существуют вспомогательные классы для компонентов и элементов управления, реализующие поведение по умолчанию для замещающих сервисов. Вы можете наследовать Ваш замещающий сервис от этих класов и реализовать только те методы, замена функциональности которых требуется. Все остальные методы будут просто вызывать соответствующие методы сервиса, который был замещен. Это классы CComponentSpliceService и CControlSpliceService. Например, элемент управления UpDown замещает сервис элемента управления Edit когда прикрепляется к нему и добавляет свое поведение на нажание некоторых клавиш. Если Вы пользуетесь замещением сервисов, не забывайте восстанавливать изначальный сервис, когда замещение более не требуется(когда элемент управления удаляется с формы/элемента управления или отвязывается от вашего элемента; когда ваш элемент удаляется с родительского элемента управления или формы). Если Вы пользуетесь вспомогательными классами упомянутыми ранее, Вы можете не волноваться даже о множественном замещении сервисов - они умеют правильно обрабатывать и такую ситуацию. Все что от Вас в этом случае требуется - это создать экземпляр класса сервиса, который будет замещать сервис элемента управления и уничтожить его в конце, когда замещение уже не нужно(вызвав метод Release()) для восстановления оригинального сервиса.

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

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

Окно(Window) - это системное окно, на котором отображается форма. Окно описывается интерфейсом IWindow. Этот интерфейс описывает взаимодействие между окном операционной системы и формой. Это один из платформо-зависимых интерфейсов и его реализация зависит от операционной системы, под которую ориентировано приложение. В пакете Platform есть реализации под разные операционные системы. Форме необходимо окно, на котором она будет отображаться.

Рендер - это платформо-зависимый класс, наследуемый от интерфейса IRenderer, который ответственен за операции рисования. В Пакете Platform находится реализация OpenGL рендера для Windows, а также GLES рендер для Android и OpenGL рендер для Linux. Каждой форме нужен рендер. Все элементы управления используют рендер для отрисовывания самих себя.

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

Встроенный элемент управления(Built-in control) - это "почти" элемент управления. Он реализует всю необходимую логику работы однако он не может использоваться без реального элемента управления. Давайте взглянем на некоторые из стандартных элементов управления. Memo, ListBox, ScrollBar, DropDown - все эти элементы управления имеют нечто общее. А именно - возможность прокрутки содержимого. При наличии большого количества содержимого во всех этих элементах управления, если оно не влезает внутрь элемента управления, прокрутка помогает взаимодействовать с невлезающим содержимым. Прокрутка является отличным кандидатом во встроенные элементы управления. Вместо написания кода для управления прокруткой непостредственно во всех этих элементах управления, мы можем написать его только один раз и использовать его во всех этих элементах управления. Во фреймверке существует несколько уже реализованных встроенных элементов управления. Это выпадающий список(dropdown), прокрутка(scroll), поле ввода(textinput), текстовое поле(textarea) и календарь(month calendar). Вы можете использовать их при создании Ваших собственных элементов управления или же Вы можете написать Ваши собственные реализации. Более того, большинство элементов управления позволяют устанавливать другую реализацию встроенных элементов управления, которые они используют. Это придает им огромную гибкость.

Модуль представляет собой независимый(обычно) набор функций и/или классов для работы в определенной области. Например, Модуль скриптинга отвечает за парсинг и выполнение различного рода скриптов. Это очень мощный модуль. Он используется фреймверком для парсинга JSON и XML форматов. Все модули находятся в подпапке Modules папки с ядром фреймверка.

Пакет - это код, описывающий элементы управления, формы, компоненты и так далее. Обычно он содержит множество однотипного кода, который не представляет никакой особой функциональности но обеспечивает полное описание сущностей. Этот код не включается в Ваши приложения. Цель пакетов - это описание сущностей для Конструктора Форм. Если у Вас нет необходимости использовать элементы управления, созданные Вами, в Конструкторе Форм, то Вам не нужно создавать пакеты для них. Однако, если Вы собираетесь распространять Ваши элементы управления, то Вам все же нужно сделать для них соответствующие описания, чтобы Конструктор Форм мог их использовать.

Обработчик свойств/Редактор свойства - это объект, который обрабатывает редактирование свойств в Редакторе Свойств Конструктора Форм. Элементы управления могут иметь множество свойств различного типа. Создавая свои элементы управления Вы так же можете добавлять новые типы свойств. Если Вы хотите чтобы эти новые свойства были редактируемы в редакторе свойств Конструктора Форм, Вы должны создать для них соответствующие обработчики свойств. Существует множество уже реализованных обработчиков свойств для всех используемых в Стандартом Пакете типов свойств. Вы можете использвать их для свойств своих элементов управления если свойства и обработчики имеют совместимый тип. Обработчик свойств должен обрабатывать как обычные простые свойства, так и их вариант расчитанный на использование в разных состояниях(если такие свойства вообще существуют конечно же).

Кастинг - это попытка получения одного интерфейса из другого. Часто при разработке элементов управления требуется работать со специфическим классом в то время как интерфейс IControl может вернуть только базовый интерфейс. Например, все элементы управления реализуют свой собственный сервис наследуемый от IControlService. То есть Button имеет CButtonService, который не напрямую но наследуется от IControlService. Он возвращается в форме IControlService методом QueryService(). Что если необходимо работать с некоторыми методами CButtonService но у Вас есть доступ только к QueryService? Используйте кастинг. Существует вспомогательная шаблонная функция cast, которая пытается преобразовать один тип в другой. Она не выбрасывает никаких исключений. Вместо этого она возвращает пустой указатель в случае если указанный объект не может быть преобразован к необходимому. Ее использование очень просто. Например, чтобы преобразовать IControlService в CButtonService, все что нужно это написать cast<CButtonService*>(service), где "service" это значение которые вернул метод QueryService() элемента управления Button и который имеет тип IControlService. Будте осторожны, всегда проверяйте результат кастинга! Конвертировать несовместимые типы неполучится. Это значит, что Вы получите пустой результат от функции cast если "service" не имеет никакого отношения к CButtonService.

Протекание событий

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

Фреймверк управляется событиями. Это означает что какая-то работа происходит только когда происходит какое-то событие. Это может быть пользовательский ввод или системные события. Пожалуйста взгляните на диаграмму ниже. Она показывает как распространяются события между частями фреймверка. Диаграмма нарисована в терминах интерфейсов. В коде используются классы.

Базовое распространение собыьий в приложении

Множество событий приходят от системы и пользователя. IApplication ответственен за получение всех этих событий и передачи их нужной форме. Строго говоря, он передает события IWindow. А после этого IWindow передает их соответствующей IForm. IApplication так же ответственен за закрытие и уничтожение всех зависимых окон и форм при закрытии главной формы. После того, как форма получает событие, она определяет какой компонент или элемент управления должен его получить и передает событие далее нужному объекту.

Все события обрабатываются с помощью механизма уведомления и функций обратного вызова событий. Уведомления используются только для внутренней работы форм и виджетов и они доступны в соответствующем объекту сервисе. Функции обратного вызова событий используются конечными пользователями. Пользователь может привязать свою функцию, которая будет выполнять какие-нибудь действия, к событию. Все уведомления сервисов представляют собой просто методы, название которых начинается с Notify. Например, метод NotifyOnClose() сервиса формы вызывается когда пользователь кликает на кнопке закрытия формы. Уведомление элементов управления NotifyOnLeftMouseButtonDown() вызывается когда пользователь нажимает левую кнопку мышы на элементе управления. Множество уведомлений имеет соответствующие события, которые начинаются с On и принадлежат уже не сервисам, а непосредственно форме/компоненту/элементу управления.

Не все события являются результатом системных событий или пользовательского ввода. Многие события генерируются формой и элементами управления. Например, когда элемент управления удаляется с соответствующей формы, форма генерирует уведомление NotifyOnDetaching() и событие OnDetaching если к нему привязана функция обратного вызова. Другой пример это событие изменения компонента/элемента управления. Как компоненты так и элементы управления могут генерировать этот тип событий, когда в них произошли какие-то изменения и они хотят дать знать об этом всем остальным компонентам и элементам управления на форме. Например, компонент ImageList генерирует событие ComponentChange каждый раз, когда его список картинок изменяется. Элементы управления, которые используют ImageList, получают уведомление и перерисовывают себя.

Некоторые события передаются только специфическим элементам управления, другие - всем. Например, когда компонент только добавили к форме, все остальные элементы управления и компоненты получают событие ComponentAttach.

Ввод пользователя производит события одновременно и для формы и для соответствующего элемента управления.

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

Элемент управления, который получает уведомление может быть изменен так называемым "захватом ввода". Представьте себе форму с меню вверху(MainMenu) и множеством элементов управления. Когда меню закрыто и пользователь кликнул на одном из элементов управления ниже него, то этот элемент управления, который находится под указателем мыши, получает уведомление о клике. Это правильное поведение. Но что когда меню открыто и элемент управления теперь находится под ним? В этом случае MainMenu захватывает ввод и форма посылает уведомления меню вместо элемента управления, который находится в действительности под указателем мышы. Это и есть цель захвата ввода. Он делается с помощью методов формы CaptureMouse(), ReleaseCaptureMouse(), CaptureKeyboard(), ReleaseCaptureKeyboard().

Event bubbling

Some events can be automatically sent to parent widgets of the widget they appear on. Such events are: all keyboard events, all mouse events except for mouse hover and mouse leave ones, and drop files event. When one of the events happens on some widget it is automatically sent to all his parent widgets in widget to parent, to parent of parent and so on order. It is so called "event bubbling". To stop propagating event up onto widget hierarchy the processed argument value should be set to true in the corresponding notification method or event callback function. Usually if widget processes corresponding event by itself it sets the processed value to true indicating the event is processed and no need to send it to parents. There is also a way to indicate whether widget wants to receive such a bubbled events or not. It is done with help of isAcceptBubbledEvents() method of IControl. If the method returns true than widget will receive bubbled events. If the method returns false than widget won't receive bubbled events. The CControl class implementation, which is usually the base class for a widget, returns false in that method so if you develop a widget which requires to receive bubbled events, you have to overwrite the method and return true instead.

Consider the following example showing where event bubbling can be useful. Lets say we have ScrollBox widget with many other widgets on it. One among the child widgets is the DropDown. Child widgets doesn't fit into ScrollBox client area and it has scroll bars and ability to scroll its content. Without event bubbling we could have scrolled only when mouse pointer is over ScrollBox scroll bars or area free of child widgets because of only in this case the ScrollBox receives mouse wheel events. With event bubbling the ScrollBox receives scroll events even when mouse pointer is over any child widget unless that child widget prevent sending scroll events to the parent. DropDown widget processes mouse wheel scroll event in its own way (it changes active item) and so it sets processed to true in the NotifyOnMouseVerticalWheel() notification of its own service thus preventing sending the event up to parents. So, in our example, when user scrolls mouse wheel when mouse pointer is over the DropDown it works as expected: items are changed and no scroll happens in ScrollBox. On contrary when user scrolls over, lets say Button, scrolling of ScrollBox takes place because of the Button doesn't process mouse wheel scroll.

To identify whether an event is regular or bubbled one you may use IControl *Target member or a message data structure provided to notification method or event callback function. For example, see MessageMouse structure reference page. That member indicates the widget where event originally happened. It is equal to the widget which service receives a notification if it is regular event and doesn't equal if it's bubbled one.

Главные интерфейсы

Не смотря на то что фреймверк содержит множество интерфейсов и классов, только некоторые из них представляют наибольшую важность. Остальные являются лишь удобным дополнением. Вот список важных интерфейсов обязательных к использованию.

  • IApplication отвечает за управление окнами и формами. А так же позволяет взаимодействовать с системой.
  • IWindow представляет системное окно, интерпретирует события для связанной формы.
  • IForm ответственен за управление интерфейсом пользователя. Форма может содержать компоненты и элементы управления. Она ответственна за правильное распределение событий и управление виджетами.
  • IComponent - это невидимый вспомогательный обьект, который можно разместить на форме и использовать для определенных задач. Например, Timer, SysOpenDialog, ImageList - это компоненты без визуального представления на форме, однако они используются в коде для выполнения некоторых полезных действий, таких как периодическое выполнение определенных задач, хранения списка картинок, выбора файла пользователем для открытия.
  • IControl представляет визуальный элемент для взаимодействия с пользователем. Примеры: Button, Edit, Panel, Label, TreeView, и так далее.
  • IRenderer представляет собой важный интерфейс который используется элементами управления для отрисовки.

Рисование

Вы уже знаете о первом важном моменте при рисовании. Интерфейс IRenderer ответственнен за все операции рисования. Конечно же у него не может быть совершенно всех нужных методов для рисования для всех возможных случаев. Если какой-то отсутствует, Вы всегда можете нарисовать все сами получив непосредственный доступ к графическим данным.

Вторая важная вещь, которую нужно знать, - это то, что ни форма ни элементы управления не рисуются на форме сразу. Это достигается с помощью так называемой "блокировки перерисовки". У формы есть два метода LockRepaint() и UnlockRepaint(). Все события вызываются между блокировкой и разблокировкой перерисовки. Это делается для оптимизации чтобы предотвратить многократное рисование одного и того же. Реальное рисование происходит только когда существует часть окна которая более не актуальна и вызывается последний UnlockRepaint(). Да, можно вызывать LockRepaint() много раз и нужно вызывать такое же количество UnlockRepaint(). Например, когда Вы добавляете элементы управления на форму при создании формы или же позже при изменении ее облика, правильно будет добавлять элементы управления между LockRepaint() и UnlockRepaint(). В случае, если Вы забудете добавить блокировки, форма будет перерисовываться каждый раз, когда Вы добавляете элемент управления или меняете его свойства. Если их немного, то это не проблема. Однако если у Вас десятки элементов управления и изменяются десятки свойств каждого, тогда сотни перерисовок формы произойдут. Это не только займет много времени, но и пользователь так же будет видеть как элементы управления появляются друг за другом. Если Вы пользуетесь выравниванием, внешний вид формы может очень сильно менятся при добавлении следующего элемента управления и все эти метаморфозы будут показываться пользователю. Это не хорошо. Не забывайте блокировать перерисовку, когда Вы производите действия, которые приводят к нескольким перерисовываниям формы. Так же можно использовать вспомогательный класс CLockRepaint, который упрощает процесс блокировки-разблокировки.

И, наконец, в-третьих, важная вещь, которую нужно знать, это то, что рисование происходит в 2 этапа. Событие рисования емеет соответствующий признак указывающий произошло ли событие в первой или в последней фазе рисования. Первая фаза используется для обычного рисования, в то время как последняя используется для рисования только тех частей, которые должны быть поверх всего остального. Хороший пример это элемент управления DropDown. Когда он закрыт(список элементов скрыт), он рисует себя только в первой фазе(или шаге), но когда он открыт, часть со списком элементов рисуется в последней фазе и располагается поверх всех остальных элементов управления.

Ошибки & исключения

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

Пространства имен

Пространства имен широко используются во фреймверке Nitisa. Код ядра фреймверка, а так же пакеты, помещены в пространство имен nitisa. Это глобальное пространство имен. Некоторые части фреймверка размещаются внутри пространств имен внутри этого глобального. Это такие части как модули и пакеты.

Код модулей размещается в пространствах имен с именами близкими к названиям модулей. Например, код модуля скриптинга размещается в пространстве имен nitisa::scripting, код Модуля искуственного интеллекта находится полностью в пространстве имен nitisa::ai и пространствах имен внутри него, и так далее.

Код Стандартного Пакета(Standard Package) находится внутри пространства имен nitisa::standard, Пакета Графиков(Charts Package) - в пространстве имен nitisa::charts, и так далее.

Хотя это не обязательно, мы рекомендуем использовать подобную структуру пространств имен в Ваших проектах, особенно если Вы планируете распространять их.