Управление всем 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, по которому их можно отловить для каких-либо целей.