Визуальные примитивы
Любая библиотека визуальных компонент является мощным инструментом создания пользовательских интерфейсов. Да вот незадача. По мере визуального и функционального усложнения конкретного библиотечного компонента закономерно расширяется и набор его свойств. Так, например, такой относительно простой компонент как Кнопка с иконкой и надписью, насчитывает их больше сорока. При таком их обилии, диалоговая доступность отдельного свойства для изменения его значения, уже оставляет желать лучшего, и далее, по мере расширения набора, продолжит прогрессивно ухудшаться.
Но угнетает вовсе не это. Сколь ни обширна сама библиотека, сколь ни функциональны ее компоненты, а в очередном проекте обязательно объявится потребность, причем самая настоятельная, которую не удается удовлетворить уже имеющимся набором.
Средством преодоления этих проблем может стать библиотека, содержащая минимальный набор простых компонент-примитивов, образующих своего рода визуальный алфавит. Такой, который позволяет комбинированием экземпляров входящих в его состав примитивов реализовать визуальный интерфейс абсолютно произвольной сложности. Аналогичный прием уже показал свою эффективность применительно к данным.
Предметом дальнейшего рассмотрения будет крохотная библиотека, содержащая всего четыре компонента: Image, Text, Canvas и Enum.
Как следует из их названий, каждый из примитивов ассоциирован с конкретной информационной ”сущностью” (определение через тип данных в данном случае плохо применимо), обладающей уникальным набором собственных свойств, и в том числе визуальных. Примитив Image воспроизводит изображения, из любой формы их существования. Примитив Text позволяет отобразить значения, представимые в виде символьной последовательности, такие как String, а также Numeric с производными. Примитив Canvas умеет активировать назначенное ему событие пользователя, но в основном используется в пассивной форме — как родительский компонент, а также элемент статического оформления и управления разметкой. Производный от Canvas примитив Enum напротив, пассивной формы не имеет, и прочно ассоциирован с конкретным множеством, часть которого он отображает методом последовательного перечисления его элементов.
Так как целью настоящей статьи является систематизация свойств примитивов, и определяемого этими свойствами поведения, то описывать полный перечень свойств каждого примитива и особо сильно углубляться в детали реализации не планируется. Также за кадром останутся всякие приятные мелочи, существенно облегчающие процесс редакции координат и свойств экземпляров. Но многие очевидные вещи тем не менее будут упомянуты, исключительно для сохранения связанности изложения.
Каждый экземпляр примитива ”рисует” себя сам, и делает это на поверхности (канве) своего родителя Parent, с принудительным ”обрезанием” частей, выступающих за пределы предоставленной канвы.
Направление и последовательность отрисовки определяет Вектор отображения (Vector). Изменяя у экземпляра значение Vector можно повернуть Image на угол, кратный 90', отобразить Text справа-налево, а также изменить местоположение области Header у Canvas. Применительно к Enum, Vector понимается как Вектор перечисления.
Размер экземпляра в пикселях, и его положение на канве Parent`а, задаются двумя парами координат Begin … End — по одноименной паре на каждую ось ординат. Смысловое значение координат от Вектора отображения не зависит.
Значение отдельной координаты экземпляра представляет собой смещение Offset относительно базисной координаты Parent`а на общей оси ординат, при этом для любой координаты в качестве базисной может выступать как Begin, так и End координата Parent. Если величина Offset положительна, то базисной является координата Begin, иначе (Offset<0) — координата End. Если Offset =0, то базисной безальтернативно считается одноименная координата родителя.
Если Begin и End экземпляра в качестве базиса используют одну и ту же координату родителя Parent (односторонний якорь), то по этой оси экземпляр будет иметь собственный фиксированный размер. В противном же случае (разносторонний якорь за одноименные координаты), при изменении размера Parent`а размер экземпляра также будет изменяться. Но существует также свойство Variable Size (рассматривается отдельно), использование которого включает обратную зависимость — увеличение размера дочернего экземпляра приводит к увеличению размера его Parent.
Для реализации взаимной зависимости координат за пределами отношений Parent-Child, в качестве базисной может быть использована независимая координата, называемая Binder. Помимо этого, экземпляр может получить свою координату извне, от Data- компонента уровня back-end, идентификатор которого IDD он хранит в свойстве Data. Таким образом, каждая отдельная координата Begin и End характеризуется набором свойств: Offset, Binder и Data. Альтернативные способы формирования и интерпретации значения координаты, основанные на значениях свойств Binder и Data, будут рассмотрены отдельно.
Помимо ”визуального” родителя Parent, у экземпляра есть еще и владелец Owner, с которым связано его существование. Как правило, в качестве Parent и Owner выступает один и тот же родительский экземпляр, но для сопутствующих Надписей к отдельным элементам интерфейса, например, это — разные экземпляры. Для экземпляра Canvas, играющего в многооконном интерфейсе роль отдельного окна (Формы), в силу его самостоятельности Owner не задается, а Parent'ом является ”безродный” экземпляр Canvas, который выполняет функцию Рабочего стола (он же — главное окно приложения).
Для регистрации дочерних по отношению к нему экземпляров, родительский экземпляр использует общий (для Owner и Parent) список Child's List. Порядок следования элементов в списке определяет последовательность отрисовки дочерних экземпляров (Z-ордер), и этим порядком можно управлять.
Набор рассмотренных выше свойств: Owner, Parent, Child's List, X [Begin, End], Y [Begin, End], описывает абстрактный Rectangle, который является предком всех остальных примитивов, наследующих эти свойства и связанные с ними методы. Сразу отметим, что не всегда унаследованное свойство будет использовано — очевидно, что экземпляры примитивов Image и Text не могут быть ни Owner, ни Parent.
Если по факту его создания у экземпляра примитива отсутствует связь с внешним источником данных (значения или события), то этот экземпляр считается пассивным, и выступает исключительно в качестве статического элемента оформления интерфейса. При наличии отображаемого значения (Image и Text) экземпляр хранит его в собственном контейнере Value.
Активный экземпляр примитива при своем создании получает идентификатор IDD ассоциированного с ним компонента Data- уровня back-end. Этот идентификатор экземпляр хранит в свойстве Data, и использует как для получения отображаемого значения, так и для адресной передачи пользовательского ввода в back-end. Активности (редактируемости) экземпляра можно придать динамически управляемый характер, если экземпляр связать с Data- логического типа, и сохранить полученный IDD в свойстве Lock.
Управление свойствами экземпляра, унаследованными у Rectangle, осуществляет Редактор координат — инструментальный визуальный компонент, ассоциированный с Rectangle, и представляющий собой редактор первого уровня. В функции этого редактора входит групповое и индивидуальное управление координатами, удаление текущего экземпляра или создание на его канве дочерних экземпляров, а также вызов инструментальных компонент редакторов второго уровня. Эти компоненты удобно оформить в виде всплывающих панелей, на которых размещены пиктограммы доступных для администрирования частных свойств примитива данного типа.
Внешний вид Редактора координат вполне себе тривиален: визуально прозрачная панель, дополненная по краям восемью визуальными ”кубиками” условных координат. Цвет фона и обрамления отдельного ”кубика” однозначно указывают на тип якоря и его состояние для соответствующей координаты, а события ”кубика” обеспечивают управление свойствами этой координаты, включая вызов диалога ”привязки” к внешним данным.
Примитив Image формирует результирующее растр из исходного значения, представляющего собой изображение в любом известном графическом формате, после чего масштабирует его в собственные координаты, заданные ему Редактором координат на канве Parent. По умолчанию масштабирование выполняется независимо для каждой оси, но при включенном свойстве Proportional — с сохранением соотношения сторон исходного изображения. Если Parent обеспечивает скроллирование своей канвы или разрешает изменение собственного размера, экземпляру Image становится доступна опция Original. При ее включении изображение или полностью сохраняет свой исходный размер и пропорции (Scroll on Parent см. ниже), или пытается сохранить его за счет увеличения размер Parent до достижения им предельно возможного размера по одной из осей.
Значение для отображения активный экземпляр Image получает из выборки данных, используя для этого IDD из свойства Data. Пассивный экземпляр Image, используемый для украшения сценария визуальными образами, использует значение из контейнера Value.
Отображаемым значением можно управлять, используя переключатель Alternative, который ассоциирован с логическим Data-, и по аналогии с базовым свойством Data хранит его идентификатор. Если Alternative получает true, то Image отображает значение из контейнера Alt Value, игнорируя Data и Value как источники. Если же контейнер Alt Value пуст, то Alternative по смыслу становится флагом Hidden.
Примитив Text превращает исходную последовательность символов (текст) в последовательность их образов, используя для этого шрифт Font с кеглем Font Size, цвет Font Color, а также эффекты выделения Font Style {полужирный, курсив, подчеркивание, зачеркивание, тень и контур} если таковые назначены. На канве Parent результирующие образы символов образуют строки с интервалом Line Spacing между ними. Если количество строк Lines =0, то отображаемым является весь текст, иначе — только текст до конца указанной строки. Если отображаемый текст не вписывается полностью в размер, заданный экземпляру Text, то вместо выступающего за границу остатка текста, отображается символ ”…” — признак разрыва. Если Parent разрешает дочерним экземплярам изменение собственного размера, то исходный текст может увеличить необходимый ему для отображения размер, динамически расширяя канву Parent до тех пор, пока не будет достигнут предельно возможный размер Parent. Если Parent обеспечивает скроллирование своей канвы, то текст выводится без ограничений.
Значение Align {0 … 8} управляет выравниванием итогового текста внутри собственных координат — по центру или прилегание к границе. Значение Align задается непосредственно Редактором координат.
Цветом и эффектами отображаемого текста можно управлять, используя альтернативные значения Alt Color и Alt Style, а также переключатель Alternative, который хранит IDD ассоциированного с ним логического Data-. Если Alternative получает true, то Text использует альтернативные значения. Если Alt Color не задан, то Alternative по смыслу становится флагом Hidden.
На отдельной Форме сценария все активные экземпляры Text и Image, то есть обладающие актуальным значением Data, а также все Enum, образуют логическую цепочку, один элемент в которой визуально выделен и является текущим получателем ввода с клавиатуры (обладает фокусом ввода). Чтобы дополнительно подчеркнуть редактируемость активных элементов, при наведении на них курсор мыши меняет свою форму.
Для собственно ввода нового пользовательского значения активный экземпляр вызывает внешний по отношению к нему Редактор значения соответствующего функционального типа. Для Image роль такого Редактора будет выполнять диалог выбора файла, включающий в себя также опцию загрузки с камеры. Примитив Text для этих целей будет использовать обычный или расширенный Редактор текста. Для Enum, ассоциированного с множеством, роль Редактора будет играть вся совокупность интерфейсных источников как событий навигации, так и событий добавления/удаления элементов.
Редактируемостью экземпляра можно управлять, назначив его свойству Lock идентификатор Data- логического типа. Если актуализированный таким образом Lock получает true, то экземпляр не может получить фокус ввода, и соответственно вызвать Редактор значения.
Экземпляры примитива Canvas используется прежде всего как элементы визуального оформления, а также как средство ввода, обеспечивающее получение пользовательского события Action. И при этом отдельный экземпляр выступает в качестве родителя (и Parent и Owner), и в том числе для других экземпляров, что делает Canvas основным структурообразующим компонентом интерфейса.
Если экземпляр Canvas ассоциирован с Data- компонентом (в свойстве Data хранит его идентификатор IDD), то он вместе со всеми своими дочерними экземплярами выступает как активный элемент сценария, способный реагировать на прямое воздействие пользователя. Это воздействие сам экземпляр никак не интерпретирует, а лишь транслирует сам факт воздействия своему back-end партнеру, где собственно реакция на событие и реализуется. При этом, по аналогии с Image/Text, активностью экземпляра Canvas управляет свойство Lock.
Функционально, примитив Canvas состоит из двух условно независимых частей: центральной области Background, и ее обрамления Border.
Визуальное обрамление экземпляра Canvas создается выбранным именованным (идентифицируемым) методом рисования, совокупность которых существует в виде библиотеки стилей Line Style. Все стили обрамления подразделяются на два функциональных типа: Linear Border и Graphic Border.
Графическое обрамление из семейства Graphic Border используется для придания изображению рельефных очертаний, при этом некоторые методы рисования могут включать в себя анимационные эффекты, имитирующие в частности поведение визуальной кнопки при нажатии на нее.
Линейное обрамление из семейства Linear Border используется для разграничения и разметки плоского изображения прямоугольниками и отдельными линиями, и характеризуется стилем из Line Style (непрерывная линия или различные виды штриховки), толщиной линии Line Width, основным Line Color или альтернативным Alt Line Color цветом линии, а также маской Border Mask, позволяющей выключить из отображения отдельные стороны обрамления. Выключить отображение Border целиком можно также установив нулевую толщину линии.
Область Background характеризуется основным цветом Color, с возможностью его динамической замены на альтернативный Alt Color по условию Alternative. Размер (координаты) области Background определяется динамически, путем уменьшения размера Canvas на величину текущего размера Border. Так как свойства родителя присущи исключительно Background, то любое упоминание визуальной канвы или размещения на Canvas подразумевает именно эту область.
Такой массовый элемент интерфейса как визуальная Кнопка не входит в число примитивов, а представляет собой агрегат, в максимальной комплектации состоящий из трех экземпляров: Canvas + Image + Text. Активный Canvas обеспечивает генерацию события, а дочерние ему пассивные Image и Text отвечают за визуальную смысловую нагрузку.
Преимущества агрегата перед монолитом очевидны:
- ускоряется доступ к частному свойству выделенной составной части;
- сократилось общее количество администрируемых свойств: значительная часть свойств, так или иначе связанных с координатами — всевозможные отступы и якоря, задается Редактором координат.
- открываются возможности по управлению компонентным составом: ту же Кнопку можно украсить второй иконой, или еще одной надписью (для Кнопки это вообще-то неактуально, но очень востребовано во многих других местах).
Важно подчеркнуть, что при формировании интерфейса приложения любой из упомянутых здесь и далее агрегатов не собирается дизайнером поэлементно, а создается комплексно по шаблону. При этом роль шаблона самым естественным образом выполняет Face- часть частного Представления сущности данных.
Также отметим, что количество предлагаемых к выбору шаблонов-представлений не ограничено ничем. Так новый вариант Кнопки создают по шаблону, затем модернизируют во что-то оригинальное, и одним нажатием экспортируют в новый шаблон на правах еще одной формы Представления сущности.
По умолчанию, поле ввода, используемое для представления литеральных значений, также включает в себя три экземпляра: экземпляр Canvas с Linear Border + активный экземпляр Text, ассоциированный с исходным значением, + экземпляр Text — сопровождающая надпись вне Canvas.
Этот агрегат включает в себя активный Canvas + пассивные Image (образ флага) и Text (надпись), и применяется для представления значений логического типа. Экземпляр Image в свойствах Value и Alt Value хранит образы сброшенного и установленного флагов, а управление отображением требуемого образа осуществляется через свойство Alternative, которое хранит IDD целевого значения.
Активность агрегату придает Canvas, также связанный с целевым значением, и события которого инвертируют это значение. Дополняет картину экземпляр Text, размещенный или как надпись — вне Canvas по образцу Edit Box, или как наименование — на Canvas по образцу Button.
Но вернемся к свойствам и особенностям поведения примитива Canvas, и в частности, его канвы Background.
Размер детей ограничен родительской канвой. Но в реализацию этого весьма строгого правила несложно добавить обратную зависимость, в соответствии с которой дочерний экземпляр получает возможность увеличить собственный размер за счет динамического расширения родительской канвы.
Если экземпляру Canvas установить флаг Expandable по выбранной оси, то перед началом каждого цикла отрисовки его координата End по этой оси вычисляется как максимальная из всех расчетных координат End дочерних экземпляров. При этом рассчитанное значение может быть ограничено координатой End любого из родительских экземпляров этого Canvas, не обладающих флагом экспансии.
В качестве альтернативы динамическому расширению канвы выступает эффект ее скроллирования.
Если экземпляру Canvas установить флаг Scroll для выбранной оси ординат, то по этой оси его дочерние экземпляры получают в свое распоряжение виртуальную бесконечную канву, на которой отображают себя безо всяких ограничений в размерах. Бесконечность здесь конечно же условна, и эмулируется следующим образом: если флаг Scroll установлен, то вся область Background выступает как окно видимости, через которое результирующее изображение, сформированное дочерними экземплярами на виртуальной канве, можно просматривать по частям, изменяя условное положение окно видимости над канвой.
Для отображения положения окна видимости и управления его перемещением относительно канвы, Canvas использует экземпляры служебного компонента, известного всем как полоса прокрутки Scroll. Экземпляр Scroll отображается в окне видимости соосно своей оси на стороне End окна видимости, закономерно уменьшая ортогональный размер окна на свою ширину. Визуально Scroll состоит из двух частей (аналогия с агрегатом Canvas+Canvas вполне уместна): основной канвы Scroll — логической ассоциации с результирующей канвой, и бегунка — ассоциации с окном видимости. Для корректного отображения экземпляру Scroll передают размеры канвы и окна, и их текущее относительное положение, а в ответ на воздействие пользователя Scroll возвращает родительскому Canvas новое положение окна видимости. Как правило, в наличии имеется несколько различных визуальных форм Scroll на правах стиля его отображения. Стиль, в котором отображаются все экземпляры Scroll, устанавливается параметром Scroll Style глобально.
При установленном флаге Scroll рабочий экземпляр компонента Scroll не отображается, если все дочерние Canvas`у экземпляры вписываются в исходный размер области Background.
Далеко не всегда требуется скроллировать всю без исключения область Background. Как минимум в двух случаях: на шапке экранной Формы (окна приложения), а также в заголовке таблицы, размещенные там элементы оформления должны сохранять неизменным свое исходное положение всегда, то есть находиться за пределами виртуально перемещаемого окна видимости. Иными словами, в области Background можно выделить некоторую статическую ее часть, именуемую Заголовок.
В данном случае речь идет о таком свойстве Canvas как координата Header, представляющей собой смещении по оси, совпадающей с направлением Вектора отображения, которая делит Background по этой оси на две части: заголовок и основную канву. Экземпляры, размещенные в области заголовка, обладают установленным флагом on-Header, и отсчитывают свое смещение непосредственно от координаты Begin родителя. Все прочие экземпляры, размещенные на основной канве, отсчитывают свое смещение не от Begin, а от Header. Значение координаты Header автоматически равно наибольшему значению координаты End экземпляра с on-Header. Если в области заголовка нет ни одного экземпляра, то Header =0.
Экранная Форма многооконного сценария Form, существующая на правах независимого окна приложения, является типичным агрегатом, и представляет собой обычный Canvas, в области Header которого размещены Image и Text, необходимые для визуальной пользовательской идентификации Формы, а также несколько генераторов событий Button (пар Canvas+Image), выполняющих функции органов управления Формой.
Если из области Header убрать все размещенные там экземпляры, то Форма лишится своего заголовка, и примет вид, типичный для выпадающего списка Combo Box.
Этот агрегат включает в себя три экземпляра: активный Canvas + пассивный Image + активный Text.
При исполнении, событие Canvas или завершение ввода в Text инициируют появление выпадающего списка путем открытия назначенной списковой Формы класса с перечнем объектов для выбора. Как обычно, все самое интересное происходит за кулисами, в компонентах Data- на back-end, начиная с процесса открытия Формы, и заканчивая присвоением ссылочного значения в результате завершения Диалога выбора.
Динамически перемещаемый Splitter логически и визуально делит область Background по выбранной оси ординат на две виртуальных условно-бесконечных канвы, начинающихся с координат Begin и Splitter соответственно, и два окна видимости для каждой из них с координатами Begin … Splitter и Splitter … End.
Смысл такого разделения состоит в том, что при нем обе канвы со своими окнами используют общую ось ординат, ортогональную Splitter, что позволяет по этой оси обеспечить визуальную синхронность отображения в окнах двух произвольно взятых частей результирующих изображений. Для примера, такой прием часто используется во всевозможных менеджерах работ, когда в левой части отображается перечень работ, а в правой — календарный график их выполнения (т.н. диаграмма Гантта). Или в таблицах со множеством колонок — при необходимости визуально сопоставлять разнесенные в пространстве колонки.
По факту, свойство Splitter представляет собой отдельную динамически изменяемую координату в диапазоне Begin … End. При инициализированном значении Splitter, дочерние экземпляры размещаются на канве или перед Splitter, или за ним по соответствующей оси. Экземпляры, размещенные за Splitter помечаются флагом on-Splitter, который указывает, что их положение на родительской канве следует отсчитывать не от Begin родителя, а от координаты Splitter. Для Canvas же актуальность Splitter подразумевает автоматическое включение служебного компонента Scroll по обеим осям ординат, причем по оси Splitter будут задействованы сразу два экземпляра Scroll — по одному для каждого окна видимости. Собственно сам визуальный Splitter является таким же служебным компонентом, как и Scroll, и при этом ассоциирован с одноименным свойством экземпляра для соответствующей оси.
По умолчанию, каждый экземпляр определяет свою координату на родительской канве как смещение Offset относительно выбранной базисной координаты родителя, и эта координата является статической. Между тем в качестве базисной можно выбрать координату, глобальную в пределах Формы, и эта координата может динамически менять свое значение в пределах, не нарушающих основные правила отображения примитивов. Более того, привязкой к такой координате можно реализовать взаимную зависимость положения/размера двух и более экземпляров, вследствие чего эта координата именуется Binder.
Так как связываемые через Binder экземпляры потенциально могут принадлежать разным Parent, то в качестве значения Binder используется его смещение относительно начала канвы (Begin) корневого Canvas окна приложения — прямого или опосредствованного родителя всех экземпляров, образующих Форму. Поэтому владельцем всех действующих биндеров Формы является корневой Canvas, в свойствах которого они образуют Binder List раздельно для каждой оси ординат.
Если декларация координаты какого-либо экземпляра Формы обладает актуальным значением свойства Binder, а именно – его индексом в Binder List, то экземпляр определяет положение этой координаты на родительской канве вычитанием из текущего значения Binder суммы Offset координаты Begin всех родителей вплоть до корневого Canvas. В свою очередь, если связанная с Binder координата меняет свое положение при изменении размера экземпляра или под прямым воздействием пользователя, то она изменяет значение этого Binder с обратным пересчетом изменения в его абсолютное значение.
Типичный пример использования Binder – связывание экземпляров, образующих смежные колонки Таблиц.
Вообще-то, способность к перечислению является врожденным свойством Canvas. Но так как эта способность напрямую связана с другим типом ассоциированной сущности данных — Множеством (а не Событием), то она делегирована примитиву Enum, непосредственно производному от Canvas. А учитывая то, что Enum обычно размещают на Canvas, то унаследованные им визуальные свойства Border и Background исключены из перечня публичных свойств Enum, и вместо них на первый план вынесены свойства, управляющие собственно перечислением.
Логика работы интерфейсного перечислителя достаточно проста. Выступая в качестве визуального Представления множества, Enum получает из back-end для своих дочерних экземпляров не единственный набор данных для отображения, а N наборов, сгруппированных в выборку данных. При поступлении очередной выборки, Enum для каждого из полученных наборов инициирует свои дочерние экземпляры на отрисовку, на каждой итерации предоставляя им указатель на очередной набор, а также начальную координату свободного остатка своей визуальной канвы. Таким образом, чтобы увидеть на экране привычное табличное представление, следует дочерние экземпляры примитивов на канве Enum визуально сгруппировать в одну строку, при этом занятая экземплярами часть канвы образует так называемую область перечисления. Зная ее размер несложно посчитать общее количество потенциально отображаемых строк, а значит и запрашиваемое у back-end количество элементов множества.
Тут важно лишний раз подчеркнуть тот факт, что Enum изолирован от исходного множества/списка. Он только инициирует отображение полученного количества элементов — окно выборки, а также генерирует события навигации, изменяющие положение этого окна в списке на стороне back-end. За хранение текущего положения окна, реакцию на события навигации, фильтрацию в списке, и прочее отвечает логически связанный с Enum компонент Data-CE, обладающий соответствующей функциональностью курсора.
Основу агрегата для табличного представления данных образует Enum, в паре с пассивным Canvas, отвечающим за фон и общее обрамление. Каждая колонка таблицы образована двумя парами Canvas+Text. Первая пара размещается в области Header, вторая образует строку перечисления на основной канве Enum. Координаты End у Canvas, образующих предшествующую колонку, и координаты Begin у Canvas последующей ”привязаны” к общему Binder. Для корректного отображения положения и размера ползунка Scroll создается виртуальная канва, для расчета которой в выборке, помимо собственно наборов данных, передается также положение окна выборки в исходном множестве/списке.
Как видим — ничего сложного. Все элементы агрегата создаются по шаблону, и по шаблону же добавляются новые колонки, образуя простую однорядную таблицу. Внутреннюю структуру сформированной таким образом таблицы, при необходимости, можно сколь угодно усложнять достаточно очевидными манипуляциями.
Получая выборку данных в объеме, достаточном для заполнения Формы, визуальный интерфейс ничего не ”знает” о том, как реально устроены и взаимодействуют данные в back-end. Enum последовательно отображает наборы данных элементов перечисления выборки, нисколько не заботясь о наличии каких либо логических связей в исходном множестве.
Между тем, если источником множества является атрибут обратной ссылки рекурсивного отношения, или множество образовано системой логически связанных списков (Заказчики ← Стройки ← Работы ← Ресурсы), то это множество будет иметь ярко выраженную иерархическую организацию. Для того, чтобы Enum мог эту организацию отобразить, ему в каждом элементе перечисления в составе выборки передают так называемую структуру подчиненности этого элемента. Структура подчиненности состоит из N+1 позиций, где N – общее количество прямых и опосредованных владельцев (уровень вложенности) элемента. Каждая из N позиций содержит одно из значений: ”├”, ”└”, ”│”, ” ” (пусто), характеризующих внешний вид связи элемента с уровнем, а позиция +1 еще и дополнительные значения, характеризующие наличие у текущего элемента дочерних элементов в прямом подчинении и статус их ”открытости”.
Если структура подчиненности в выборке данных не передана, то Enum отобразит образованную ячейками однородную таблицу. Иначе, Enum отобразит графические элементы, соответствующие значениям из структуры подчиненности, и только затем инициирует отрисовку дочерних экземпляров в текущей области перечисления, предварительно сместив ее координату Begin на размер отображения структуры подчиненности.
Простой пропорцией произвольное числовое значение можно преобразовать в экранную координату на канве Parent, если у этого Parent инициализировано свойство Scale — обеим его координатам Begin и End на выбранной оси ординат сопоставлены конкретные числовые значения. Иными словами, если родительскому экземпляру назначить шкалу преобразования, то размером и местоположением дочернего экземпляра можно динамически управлять непосредственно из back-end.
Для этих целей в составе каждой координаты общего абстрактного предка Rectangle есть свойство Data, значением которого является указатель IDD на внешний источник значения. Родительский экземпляр использует это свойство для задания базиса шкалы в виде диапазона граничных значений, а дочерний экземпляр — для преобразования внешнего значения в координату на шкале и обратно. При этом не имеет значения, с точки зрения корректности преобразования, является ли связь Parent-Child экземпляров прямой или опосредствованной. Шкала Scale, единожды назначенная для выбранной оси ординат какому-либо экземпляру на Форме логически глобальна в пределах всей этой Формы.
Данный агрегат в составе Canvas+Image+Enum+... удобно использовать для визуализации интерактивных локационных карт и схем размещения.
Экземпляр Canvas является общим родителем и источником шкалы для обеих осей одновременно. Экземпляр Image занимает весь Background Canvas и обеспечивает агрегат фоновым рисунком, поверх которого отображаются перечисленные Enum дочерние ему экземпляры. Визуально прозрачный Enum занимает также весь Background, как и Image, но размещен поверх Image, и ”перечисляет” дочерние экземпляры, координаты которых связаны с внешним источником данных.
Масштабирование может быть реализовано как динамическим изменением размера родительского Canvas, так и одновременной заменой фонового рисунка и значений шкалы, совокупно образующих общий логический набор данных.
Агрегат включает в себя Canvas+Enum+..., и отличается от Map тем, что шкала для каждой оси ординат назначается отдельно, располагается за пределами области перечисления Enum, и должна иметь вполне конкретную форму собственного визуального отображения для назначенного ей диапазона значений.
Чтобы удовлетворить этим требованиям, шкала назначается не корневому Canvas агрегата, а дочернему ему экземпляру Canvas, в свойствах которого задается дескриптор библиотечного метода, который выполняет отрисовку изображения шкалы в области Background этого экземпляра. Если в дальнейшем предполагается масштабирование путем изменения шага дискретизации шкалы, то в свойствах экземпляра следует задать коллекцию методов отображения.
Собственно диаграммная часть отображения формируется экземплярами, размещенными в области перечисления Enum. И здесь возможны варианты. Так для формирования диаграммы Гантта достаточно Canvas в перечислительной части и одной шкалы времени. Для отображения столбчатой диаграммы следует или повернуть вектор перечисления на -90 градусов, или использовать две шкалы. Для отображения графика понадобится не только две шкалы, но и назначение корневому Canvas агрегата библиотечного метода, который нарисует плавную линию через точки отсчета, содержащиеся в перечислительной части.
Природа обожает простые решения. А что может быть проще бесконечного комбинирования экземпляров элементов, взятых из относительно небольшого базисного набора. И что очень важно, такой подход позволяет создавать визуальный интерфейс, не прибегая к написанию программных кодов.