Управление всем UI происходит, как правило, работой с элементами DOM.
DOM (Document Object Model) — это дерево всех элементов, из которых состоит отображаемый HTML. В первом приближении можно считать, что один DOM-элемент соответствует одному теги исходного текста HTML, впрочем, из этого правила есть исключения (см. [[tutorial-dom-r#примечание_1|Примечание 1]]).
Изменение DOM (состава, внешнего вида, состояния) происходит только следующими способами (весь API для этого описан в заголовочном файле htmlayout_dom.h, тонкая C++-обертка "для примера" — htmlayout_dom.hpp):
- [[tutorial-dom-r#навигация_по_dom|Навигация по DOM]] — используется для того, чтобы найти элемент, который собираешься менять.
- [[tutorial-dom-r#добавление_удаление_элементов|Добавление/удаление DOM элементов]] — используется, собственно, для изменения элементов UI.
- [[tutorial-dom-r#изменение_html_элементов_dom|Изменение HTML элементов]] DOM — альтернатива модификации DOM путем удаления/вставки элементов
- [[tutorial-dom-r#изменение_текста_элементов_dom|Изменение текста элементов DOM]]
- [[tutorial-dom-r#изменение_аттрибутов_элементов_dom|Изменение аттрибутов элементов DOM]] — используется для изменения представления элементов
- [[tutorial-dom-r#изменение_состояния_элементов_dom|Изменение состояния элементов DOM]] — используется для того, для чего и можно предположить по названию
- [[tutorial-dom-r#изменение_значения_value_элементов_dom|Изменение "значения" (value) элементов DOM]] — быстрый способ изменения значимой части состояния элемента
Далее подробнее.
==== Навигация по DOM ====
Методы навигации:
* прямой перебор всех "детей" данной вершины: HTMLayoutGetChildrenCount, HTMLayoutGetNthChild
* прямой переход к родителю: HTMLayoutGetParentElement
* некоторые специальные функции: HTMLayoutFindElement (элемент в данной точке [x, y]), HTMLayoutGetFocusElement (текущий элемент в фокусе)
* поиск элемента по css-селектору — достаточно сложные функции, которые принимают на вход параметры для поиска и callback-функцию, которую надо вызвать для найденных элементов. Таких функций 3: HTMLayoutSelectElements (ищет вниз по DOM от заданного элемента), HTMLayoutSelectParent (ищет вверх по DOM), HTMLayoutVisitElements (ищет не по css-селектору, а по именам тагов и аттрибутов, которые могут содержать wildcards).
==== Добавление/удаление элементов ====
Реализуется, соответственно, функциями: HTMLayoutInsertElement, HTMLayoutDetachElement.
Полезно знать:
* элемент, удаленный из DOM, можно использовать и дальше (например, вставить в другое место DOM), пока он актуален. Актуальность контролируется счетчиком ссылок элемента ([[tutorial-dom-r#примечание_2|Примечание 2]]).
* элемент вставляемый в DOM при помощи HTMLayoutInsertElement, может быть:
- совершенно новым элементом, созданным с помощью HTMLayoutCreateElement
- элементом, ранее вытащенным из DOM с помощью HTMLayoutDetachElement
- клоном существующего элемента, созданным с помощью HTMLayoutCloneElement.
* чтобы отсортировать элементы, лежащие в контейнере (например, строки в таблице), не нужно их все по очереди удалять из DOM и опять вставлять: для этого существует специальная функция HTMLayoutSortElements
* чтобы поменять местами два элемента, есть функция HTMLayoutSwapElements
* если нужно сделать элемент "временно невидимым", а потом опять показать (например, как реакцию на кнопку "Скрыть/Показать подробности"), то лучше делать это не удалением/вставкой, а с помощью изменения стилей. Например, установить атрибут ''class'' в ''hidden'', а в CSS прописать:
.hidden{display:none;}
* некоторые изменения DOM вызывают side effects (вставку дополнительных, "синтетических" элементов). Например, если в таблицу с 5 колонками вставить tr, в котором всего 4 ячейки, в него добавится дополнительная, пятая синтетическая ячейка (о том, что такое "синтетический", см. [[tutorial-dom-r#примечание_3|Примечание 3]]).
* находится ли данный элемент в DOM, и если да, то какого окна, можно узнать функцией HTMLayoutGetElementHwnd.
* порядковый номер данного элемента внутри его родительского элемнта можно узнать функцией HTMLayoutGetElementIndex.
==== Изменение HTML элементов DOM ====
Выполняется, естественно, функциями HTMLayoutGetElementHtml/HTMLayoutSetElementHtml.
Важно понимать, что не все прямые изменения HTML одинаково полезны (и безопасны). Например, попытка установить для элемента
какой-нибудь другой html, кроме набора
, может привести к совершенно непредсказуемым последствиям.
Лично я предпочитаю использовать HTMLayoutSetElementHtml только в простейших случаях, когда нужно, скажем, в ячейке таблицы написать "немножко жирного текста", а все более глобальные изменения UI выполнять методами, описанными в разделе [[tutorial-dom-r#добавление_удаление_элементов|Добавление/удаление элементов]].
==== Изменение текста элементов DOM ===
Чтобы узнать текст, используются функции HTMLayoutGetElementText, HTMLayoutGetElementInnerText — первая вернет текст с "дырками" на месте дочерних элементов, вторая — полный текст. (например, для элемента "
some text with bold part
" первая вернет "some text with \0 part", вторая — "some text with bold part").
Чтобы изменить текст, используется функция HTMLayoutSetElementInnerText. Внимание! Все дочерние элементы будут удалены. (То есть вызывать эту функцию для
— не очень разумная идея).
Эти функции (как и большинство других функций HTMLayout) работают с текстом в кодировке UTF-8.
Существуют также UTF-16 варианты: HTMLayoutGetElementInnerText16, HTMLayoutSetElementInnerText16.
==== Изменение аттрибутов элементов DOM ====
Перебор всех аттрибутов: HTMLayoutGetAttributeCount, HTMLayoutGetNthAttribute
Запрос/установка значения аттрибута по имени: HTMLayoutGetAttributeByName, HTMLayoutSetAttributeByName.
Сброс всех аттрибутов: HTMLayoutClearAttributes.
Основные цели, ради которых используются аттрибуты:
* установка личных настроек стиля отдельным элементам (например, установить элементу class=important, а в таблице стилей прописать, как должны отображаться элементы этого класса);
* сохранить связанные с элементом данные для последующего считывания (например, установить элементу id="{идентификатор объекта в БД}", а потом при обновлении интерфейса, перечитать объект по идентификатору)
* отметить элемент для последующего поиска (например, установить одному элементу в списке аттрибут toDelete=true, а по нажатию какой-нибудь кнопки найти все элементы, у которых установлен такой аттрибут, и удалить их).
Имена аттрибутов можно использовать любые, соответствующие стандарту //(ссылка?)//, но обратить внимание, что есть некоторые аттрибуты, которые обрабатывает сам HTMLayout, и их изменение вызовет side effects. Примеры:
* аттрибут src тега img (изменит отображаемую картинку)
* аттрибут fixedrows тега table (изменит количество "непрокручиваемых" строк таблицы)
* аттрибут type тега input (изменит тип элемента ввода)
* **Внимание!** Этот список неполон!
==== Изменение состояния элементов DOM ====
HTMLayoutGetElementState/HTMLayoutSetElementState
Используется для запроса и установки состояний элементов, которые изменяются под действиями пользователя.
Например:
* выбранный чекбокс имеет состояние STATE_CHECKED
* элемент в фокусе имеет состояние STATE_FOCUS
* элемент, над которым находится мышка, имеет состояние STATE_HOVER
Вообще, все возможные состояния перечислены в enum ELEMENT_STATE_BITS в файле htmlayout_dom.h. Там же — подробные комментарии, какое состояние что означает.
Очевидно, что элемент может иметь несколько состояний одновременно (допустим, FOCUS | HOVER | CHANGED).
Полезные применения состояний:
* просто запросить (мышка над этим элементом?)
* установить (скажем, состояние VISITED для ссылки)
* использовать в CSS-селекторах (практически каждому STATE_XXXX соответствует CSS-селектор :xxxx) — таким образом можно, например, установить особый стиль для элемента, над которым мышь; или для нажатой кнопки (STATE_ACTIVE, :active) или для отключенного (STATE_DISABLED, :disabled)
Вообще, работа с состояниями идет согласно с человеческой логикой: если нужно изменить состояние некоего элемента (например, свернутый/развернутый для вершины дерева), нужно в первую очередь поискать соответствующую константу STATE (STATE_COLLAPSED/STATE_EXPANDED).
==== Изменение "значения" (value) элементов DOM ====
HTMLayoutControlGetValue/HTMLayoutControlSetValue — удобные "шорткаты" для работы с элементами ввода, позволяет однообразно запросить/установить значение для разных элементов. Все, что делается этими функциями, может быть сделано и без них (но несколько менее удобно).
Например:
* для поля ввода текста, value — это и будет вводимый текст; его также можно запросить/установить с помощью GetText/SetText
* для чекбокса, value — булевое значение "выбран/не выбран"; его также можно запросить/установить с помощью GetState/SetState (STATE_CHECKED)
* для списка, value — это выбранная option; чтобы узнать ее "руками", надо найти option со state = STATE_CHECKED
и т.п.
==== Дополнения ====
* в htmlayout_dom.h определено еще несколько полезных функций. Часть из них я опишу в следующих главах — работа с behaviors и events, работа с popup-ами; другую часть (HTMLayoutScrollToView и т.п.) оставляю для самостоятельного изучения.
* все функции htmlayout_dom.h возвращают HLDOM_RESULT; возможные его значения можно посмотреть все в том же файле. Кодом успеха являются HLDOM_OK, HLDOM_OK_NOT_HANDLED, все остальные — коды ошибки (операция не удалась). Впрочем, из этого правила есть исключения: например, функция HTMLayoutGetElementHwnd на "детачнутом" (отцепленном от документа) элементе вернет HLDOM_INVALID_HWND, что можно считать не ошибкой, а просто символом "неприаттаченности элемента".
* большинство изменений DOM и отдельных его элементов будут невидимы до обновления DOM - которое произойдет либо по пересчету всего окна (например, изменение его размеров), либо может быть вызвано насильственно, функцией HTMLayoutUpdateElement. Нужно иметь в виду, что выполняемые этой функцией действия могут быть относительно длительными (поскольку происходит пересчет размеров и положений всех элементов окна, которые могут друг от друга зависеть), и вызывать ее с осторожностью (то есть, если вы собираете в DOM таблицу из сотни строк, функцию лучше вызвать один раз, когда вся таблица будет собрана).
==== Примечания ====
=== Примечание 1 ===
В первом приближении можно считать, что один DOM-элемент соответствует одному теги исходного текста HTML, впрочем, из этого правила есть исключения. Например, вот такой исходный HTML:
(выпадающий список с 3-мя элементами) "на самом деле" преобразуется вот в такой:
В некоторых случаях этот нюанс оказывается важным.
=== Примечание 2 ===
Элементы DOM HTMLayout — это объекты со счетчиками ссылок, они удаляются, когда счетчик ссылок равен 0. Пользователь может контролировать счетчик ссылок функциями HTMLayout_UseElement, HTMLayout_UnuseElement. Пока элемент находится в DOM, счетчик ссылок у него > 0. Если пользователь удалил элемент из DOM (HTMLayoutDetachElement), то его можно продолжать использовать дальше, пока счетчик ссылок > 0.
**Внимание, важный нюанс!** Если элемент, который удаляется из DOM, еще нужен (вы собираетесь его куда-то вставить), то правильная последовательность действий такая:
- увеличить счетчик ссылок объекта на 1 (HTMLayout_UseElement)
- удалить элемент из DOM (HTMLayoutDetachElement) - на этом этапе счетчик уменьшится на единицу (дереву объект уже "не нужен"), и если бы мы не выполнили шаг 1, счетчик ссылок здесь стал бы равен 0, и объект был бы собран сборщиком мусора
- вставить объект, куда собирались (HTMLayoutInsertElement) - счетчик опять увеличится
- сделать HTMLayout_UnuseElement.
Чтобы во всем этом не запутаться, существенно предпочтительнее использовать какую-нибудь обертку вокруг HELEMENT с автоматическим подсчетом ссылок. Для C++ такая обертка входит в поставку HTMLayout и находится в файле htmlayout_dom.hpp
=== Примечание 3 ===
Некоторые элементы DOM требуют специального количества дочерних элементов. Главным образом, это относится к строкам таблиц - в каждой строке таблицы должно быть одинаковое количество ячеек; поэтому, если создать "кривую" таблицу (например, с тремя строками длиной 3, 2, и 4 ячейки), HTMLayout добавляет недостающие ячейки автоматически (то есть, в первую строку будет добавлена одна, а во вторую - 2 ячейки). Касательно этих "авто-добавленных" ячеек следует знать:
- "исправление" будет выполнено только после обновления-перерисовки элемента (перерисовки всего окна или вызова HTMLayoutUpdateElement), до тех пор можно заниматься конструированием безбоязненно (если, например, ваш юз-кейс требует создать таблицу с кучей строк разной длины, а затем пробежаться по ней и отрезать лишние ячейки).
- добавленные ячейки имеют установленный флаг состояния STATE_SYNTHETIC, по которому их можно отловить для каких-либо целей.