Have an amazing solution built in RAD Studio? Let us know. Looking for discounts? Visit our Special Offers page!
Новости

TImageList в Fire Monkey

Author: Сергей

В основном это копия статьи TImageList в Fire Monkey XE8 взятая из старого блога,
но есть некоторые дополнения.

В XE8 появилась полноценная поддержка списка изображений для кроссплатформенных приложений, которая известна в VCL как TImageList (кажется, начиная с Delphi 5). Вот ссылка на страничку официальной документации Using TImageList Image Lists as Centralized Collections of Images.

По правде говоря, TImageList в Fire Monkey унаследовал от VCL только названия ключевых свойств. Потому что VCL-ный компонент представляет собой только простую обертку над системным объектом Windows, а в кроссплатформенном приложении потребовалось полностью реализовать всю работу. С другой стороны, это позволило наделить его большим количеством новых возможностей, не оглядываясь на совместимость с функциональностью 16-летней давности.

Есть мнение, что новый компонент получился непостижимо сложным для понимания подавляющим большинством пользователей. В этой статье я постараюсь опровергнуть такие суждения. Те читатели, кто понимают, о чем идет речь могут сразу переходить к п. 1. А тем, кто ничего не слышал об этом компоненте, или слышал, но не понимает его практической целесообразности, я бы рекомендовал начать с п. 0 (по старой программистской традиции).

0. А зачем это надо?

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

Другой вариант — это хранить изображения в приложении централизовано и для каждого контрола указывать только ссылку на изображение из некого хранилища. В более ранних версиях Вы могли использовать изображение, которое хранится в стиле. Например, в выпадающем списке свойства StyleLookup выбрать searchtoolbuttonbordered и получить изображение лупы. Сейчас эта возможность сохраняется, но она удобна для использования фиксированного набора наиболее стандартных картинок, тогда как, в обычном приложении почти всегда возникает потребность загружать и хранить свои собственные иконки, для этого создан компонент TImageList.

Упрощенно последовательность работы выглядит следующим образом:

  1. На форму, или лучше на специально созданный модуль данных Вы кидаете не визуальный компонент TImageList (список изображений).
  2. Загружаете в этот компонент нужные вам картинки.
  3. В самом контроле вы указываете ссылку на список изображений (выбираете из списка значение свойства Images).
  4. В этом же контроле выбираете из списка номер нужного изображения.

Для каждого нового контрола повторяете шаги 3, и 4. Если нужного изображения нет, то дополнительно выполняете шаг 2… тут, я полагаю, количество читающих статью уполовинится, ведь какой смысл выполнять дополнительные мышеклики если можно просто выбрать нужную картинку из некоторой папки. Но для оставшейся (лучшей) половины программистского сообщества я попытаюсь обосновать целесообразность такого подхода на простых жизненных примерах.

Моя коллекция насчитывает over-9000 картинок, и не спрашивайте где я их взял. Поиск нужного изображения в этой куче задача не тривиальная. Просто загрузка миниатюр отнимает несколько минут. При этом надо сказать, что даже в очень большом приложении количество иконок ограничено несколькими десятками может быть даже парой сотен. Многие контролы содержат одинаковые или похожие изображения и желательно соблюдать некоторую преемственность при выборе изображений. Т.е. надо помнить, что год назад на кнопку Save в форме (кстати, кто помнит, как она называлась?), Вася Пупкин (кстати, где он сейчас работает?) повесил «синюю трехдюймовую дискетку» (где он её откопал?), и сегодня на аналогичную кнопку в новой форме надо повесить такую же картинку, а не зеленую, черную, или пятидюймовую (в моей коллекции их не менее десятка, ну кто их столько наклепал?). Ни каких бы трудностей не возникло бы, если бы я мог просто выбрать в выпадающем списке интересующую меня пиктограмму.

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

Хорошо, допустим для больших приложений, есть определенный PROFIT, но большинство же мобильных приложений состоит из одной-двух форм. Какой смысл?

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

Но ведь можно же где-нибудь (кто помнит где?) создать отдельную папку для иконок приложения и помещать их туда по мере необходимости. Их там будет ограниченное количество. Надо только не забыть скопировать эту папку при переносе на другой компьютер и оставить напоминание вышедшей из декрета Дусе Ивановой… вот тут мы и получаем в десятикратном масштабе мышеклики, которые сэкономили на шагах 1-4.

Также хочется еще упомянуть, что если мы захотим в Run-Time динамически формировать большое количество контролов с картинками (например, пункты TListBox), то мы будем иметь множество копий одних и тех же графических данных. При использовании TImageList каждый пункт будет содержать только номер изображаемой картинки.

В итоге можно сказать, что использование общего списка изображений позволяет решать примерно те же проблемы, что и повторное использование кода, но применительно к графическим данным.

1. Как пользоваться?

Давайте создадим модуль данных, поместим его перед главной формой в списке автоматически созданных модулей и добавим имя юнита в список uses главного модуля. Теперь бросим на модуль данных компонент TImageList. По двойному клику на нем открывается редактор списка изображений. Нажмем кнопку Add и выберем какой-нибудь графический файл. См. видео Create new ImageList. Возможно, стоит поискать в папке:

..:\Program Files (x86)\Embarcadero\RAD Studio\16.0\Images\GlyFX\Icons\XP\PNG\16×16

Картинки попадают в горизонтальный список List of Images, Вы можете менять порядок картинок перетаскивая их мышью. Обратите внимание в нижней части диалоговой формы, есть поля Width и Height. В отличие от VCL-ного компонента, у нового списка изображений нет таких свойств, а эти поля используются только в момент добавления и экспорта изображений. Если ширина высота добавляемого изображения кратны значениям этих полей, то оно может быть разделено на несколько отдельных картинок.

Сам по себе TImageList не имеет постоянной ширины и высоты, каждая картинка будет масштабироваться и изображаться в тех размерах, которые должны быть у контрола. Вы можете менять высоту панели List of Images и убедиться в этом.

Хочется сразу предупредить: хоть Вы можете добавлять изображение любого размера, необходимо помнить об основном предназначении компонента. Он предназначен для хранения большого количества маленьких картинок, которые будут изображаться во всевозможных элементах графического интерфейса (кнопки, меню, списки). По умолчанию изображения хранятся без изменений и масштабируются до нужного размера, когда понадобятся. Поэтому если Вы загрузите несколько фотографий из фотоаппарата, то на кнопке 24×24 они будут смотреться довольно уродливо, а приложение на телефоне, скорее всего не запустится из-за нехватки системных ресурсов. Редактор позволяет уменьшать размер загружаемой картинки, но об этой и других возможностях позже.

Теперь закроем редактор, нажав кнопку OK, и найдем в палитре компонентов новый графический компонент TGlyph на панелиShapes. Он по своей функциональности похож на TImage, но не содержит графических данных. Обратите внимание на свойство Images, это список, в который попадают все, доступные в модуле, компоненты TImageList. Выберите единственный экземпляр коллекции изображений после этого в комбинированном списке свойства ImageIndex появится список доступных картинок. Вы можете выбрать одно изображение, или ввести любое целое значение. Если изображения с указанным номером не существует, то компонент будет пустым.

fmx-tglyph32-6386867TGlyph входит в состав большинства компонентов, которые поддерживают работу со списком изображений. Подробно рассматривать его не будем, остановимся только на его свойстве AutoHide, которое заставляет его брать на себя управление свойством Visible. Т.е. во время работы приложения, свойство Visible принимает значение True, в том случае если выбранное изображение содержит некоторые графические данные, иначе оно принимает значение False. При попытке поменять свойство Visible самостоятельно, новое значение игнорируется. Это имеет значение, когда свойство Align установлено в значение отличное от None, тогда пустой видимый компонент оставляет незаполненное место, а невидимый компонент не оставляет. В режиме дизайна, у автоскрываемого TGlyph, свойство Visible недоступно для изменения и всегда равно True.

b2ap3_5f00_icon_5f00_glyphstretch-7064728В RAD Studio 10 Seattle он же XE9 (по многочисленным просьбам трудящихся) появилось свойство TGlyph.Stretch. По умолчанию это свойство имеет значение True, и компонент ведет себя по старому, т.е. если изображение меньше, чем границы контрола, то оно растягивается с сохранением пропорций. Когда имеется несколько изображений, выбирается наиболее подходящее по размеру изображение. Если в свойстве TGlyph.Stretch установить False, то картинка не будет растягиваться, а только центрироваться и сжиматься.

Cвойства Images и ImageIndex появились во многих графических контролах (кнопки, меню, списки и т.п.), а также в действиях. Обратите внимание, что у компонентов, которые являются списками (меню, список, дерево), в инспекторе объектов есть только свойство Images, а у их элементов (пункты меню, действия) только свойство ImageIndex, при этом свойство Imagesвсегда равно этому же свойству списка. Т.е. все пункты меню всегда используют одно значение Images, которое задано в меню.

Некоторые контролы, ранее имели свойство Bitmap, которое может содержать некоторое изображение. Если для такого контрола установить Images и ImageIndex, то вместо загруженной ранее картинки, будет рисоваться изображение изTImageList. При этом графические данные из Bitmap не удаляются, а просто игнорируются. Вам придется самостоятельно удалить их, отредактировав свойство Bitmap. Автоматическое удаление не делается во избежание потери данных, например, когда вы случайно изменили ImageIndex.

2. Структура TImageList

Поскольку в VCL, TImageList представлял собой набор картинок с фиксированным размером, были некоторые трудности, когда требовалось получать изображения в разных масштабах. Приходилось создавать несколько списков для картинок с нужными размерами, при этом надо было строго следить, чтобы в разных списках номера одинаковых изображений совпадали.  Нельзя было хранить картинки разного размера. Нельзя было менять масштаб изображений. Затруднительно было динамически подменить часть изображений в Run-time.

В Fire Monkey в общем случае, нельзя заранее знать какого размера потребуется изображение. Это зависит от конкретного контрола, стиля и масштаба сцены (не обязательно 1, 1.5, 2), который в свою очередь определяется устройством. Поэтому изображения часто масштабируются, чтобы их можно было вписать в подходящие координаты. В XE7 появился классTMultiresBitmap, который хранит несколько вариантов одной картинки, оптимизированных под разные размеры, что позволило минимизировать потери качества при масштабировании.

Наконец появился TImageList, который позволяет хранить множество изображений в разных масштабах. Давайте рассмотрим его структуру, чтобы потом было легче понять назначение элементов редактора.

Компонент включает в себя две коллекции. Source хранит исходные изображения. Destination хранит данные для формирования картинок на основе исходных изображений.

imageliststructure-5123662

Каждый элемент коллекции Source имеет регистронезависимое уникальное (в рамках коллекции) имя и поле MultiResBitmap, которое в свою очередь является коллекцией, каждый элемент которой содержит изображение Bitmap в масштабе Scale.

Каждый элемент коллекции Destination может включать несколько слоев, которые входят в состав коллекции Layers. Каждый слой содержит имя исходного изображения и координаты участка исходного изображения всегда в масштабе 1 (не зависимо от того в каких масштабах оно представлено).

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

Зная структуру можно частично или полностью формировать TImageList во время работы приложения, в демонстрационном проекте есть примеры кода. Для редактирования TImageList в IDE, Вы можете пользоваться окошками Structure и Object Inspector, но использование специализированного редактора гораздо удобнее.

3. Окно редактора TImageList

Пример использования списка изображений Вы можете найти в демонстрационном проекте:

http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/trunk/Object Pascal/FireMonkey Desktop/ImageList

Рассмотрим редактор компонента на этом примере. Откроем проект, выберем модуль UnitDataModule.pas и сделаем двойной щелчок на компоненте ImageList1. 

imagelistedit-2320490

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

imageindex-1183836

Быстро добавить картинку в список можно с помощью кнопки Add, или сочетания клавиш Ctrl+O. После выбора изображения появится окно ввода имени, по умолчанию соответствующее имени файла. Как уже отмечалось, если размеры добавляемого изображения кратны значениям, указанным в полях Width и Height, то вам будет предложено разделить его на несколько фрагментов с этими размерами.

Вы можете сохранить список изображений в виде графического файла, состоящего из фрагментов размером Width×Height используя кнопку Export.

Для удаления выбранного изображения можно использовать кнопку Delete.

Чтобы закрыть окно, применив изменения (если они были) нажмите кнопку OK, или сочетание клавиш Ctrl+Enter. Чтобы применить изменения не закрывая редактор, нажмите кнопку Apply. Чтобы закрыть окно, не применяя изменений, нажмите кнопку Cancel.

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

Теперь перейдем к отличиям

На основе вопросов которые часто задавались, прошу обратить внимание на следующие моменты:

  1. widthheight-9906223Width и Height ни как не влияют на размер исходного загружаемого изображения. Эти размеры используются только при загрузке и сохранении изображений, которые состоят из нескольких фрагментов.
  2. Координаты и размеры для выделенного фрагмента в Selected Image указываются с учетом масштаба. Масштаб изображения выбирается автоматически (когда вы нажимаете кнопку Add в нижней части редактора) если размеры исходного изображения кратны 8 или 10. widthheight2-3919374Например, при добавлении картинки 32×32 будет добавлено изображение с масштабом 2, в то время как изображаются координаты для масштаба 1. В этом можно убедиться, при двойном клике на миниатюре. Исходное изображение будет иметь физические размеры 32 на 32 пикселя. Вы можете добавить еще несколько изображений в других масштабах, но координаты всегда отображаются для масштаба 1.

Sources of Images. Это выбранные при добавлении файлы (элементы коллекции Source), которые хранятся в TImageList. Обратите внимание, когда Вы удаляете картинку из нижнего списка, исходное изображение остается. Вы можете удалить его, используя верхнюю кнопку вторую слева.

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

Если Вы нажмете первую кнопку слева, Вы можете добавить в этот список новый элемент, предварительно указав имя. После добавления откроется редактор MultiresBitmap, также он открывается по двойному щелчку на строке списка. Используя этот редактор можно добавить несколько изображений, которые оптимизированы под разные масштабы (во время работы приложения будут выбираться наиболее подходящие). Пример работы можно увидеть в статье MultiResBitmap Editor usage.

bigpicture-8009172

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

В случае, если у вас есть только большие изображения, то можно уменьшать из размер в момент добавления. Для этого нужно выбрать Custom Size и указать нужные ширину и высоту, тогда при добавлении нового изображения в MultiResBitmap оно будет автоматически растягиваться. Если у Вас сохранился путь к исходным изображениям, то вы можете заново загрузить изображения с новыми размерами, нажав кнопку посередине. Обратите внимание, что исходный файл не меняется, а меняется только размер изображений, которые будут храниться в fmx, или dfm-файле.

Также отмечу, что имя файла, пользовательские размеры и прозрачный цвет используются только при дизайне приложения, но хранятся в fmx-файле. Чтобы удалить лишнюю информацию из файла Вы можете использовать пятую кнопку слева. В редакторе MultiResBitmap она удаляет информацию только из текущего изображения, а в редакторе ImageList изо всех исходных изображений.

Selected Image. На этой панели можно видеть более подробную информацию о выбранном в нижнем списке изображении. Каждая картинка может состоять из нескольких слоев (этот термин позаимствован из Photo Shop). Каждый слой содержит имя исходного изображения (Sources of Images) и координаты прямоугольного участка (для масштаба 1), который будет изображаться на результирующем изображении. Если исходного изображения с указанным именем не существует, или участок окажется за границами изображения, то изображение не рисуется и исключение не поднимается. В Run-time Вы можете создать картинку с подходящими параметрами, и все контролы обновятся автоматически.

Обычно каждое изображение содержит один слой, но в некоторых случаях полезно создавать картинки из нескольких слоев. Например, стандартной выглядит ситуация, когда «перечеркивание», или «увеличительное стекло» отображается поверх других картинок («папка», «конверт»). Используя слои вам не потребуется исходных картинок «перечеркнутая папка», или «лупа над конвертом».

Смысл кнопок на панели Selected Image, кажется очевидным. Первая кнопка добавляет новый слой, при этом используется выделенное изображение в списке Sources of Images и его размеры. Вторая кнопка удаляет выбранный слой. Кнопки со стрелками позволяют изменить порядок следования слоев.

Если координаты отображаемого прямоугольника меньше размеров исходной картинки, например, когда она включает в себя несколько изображений, то при наведении курсора мыши на картинку в слое появятся две кнопки со стрелками вправо и влево. Используя эти кнопки, Вы можете смещать координаты изображаемого участка. Тоже самое можно делать, используя Ctrl + колесико мыши.

По двойному клику на изображении в слое открывается редактор MultiResBitmap. Обратите внимание, что в области предварительного просмотра (справа) можно увидеть прямоугольник, который отображается в слое. Используя мышь Вы можете менять его координаты.

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

Есть еще недокументированная читерская возможность массового добавления картинок из некоторой папки. В редакторе TImageList нажмите сочетание клавиш Alt+Ctrl+»серый плюс», затем выберите нужную папку. Все картинки из папки будут загружены в список изображений. Если у вас хранятся изображения оптимизированные для разных масштабов в разных папках вы можете последовательно загружать все изображения из папок начиная с самого маленького масштаба. При этом названия файлов должны либо совпадать, либо соответствовать маске FileName_XX.png, где XX размер изображения. Вот, что может получиться:
lotofpictures-1213625

Естественно, контроль количества и размеров картинок, а также адекватность расхода ресурсов остается на совести программиста. Следует помнить, что в телефоне объём памяти несколько меньше чем на рабочем компьютере.

4. Работа в Run-time

Модуль System.ImageList.pas

В XE8 появился новый RTL-модуль System.ImageList.pas, в который был перенесен базовый, общий код для VCL и FMX. Обратите внимание теперь класс Vcl.ImgList.TCustomImageList наследуется от System.ImageList.TBaseImageList, также как и FMX.ImgList.TCustomImageList.

Модуль System.ImageList в основном содержит код для обеспечения взаимодействия списка изображений со всеми контролами, которые его используют. Для тех, кто хочет разрабатывать свои компоненты будет полезна информация о базовых классах. Обратите внимание, что для всех новых классов имеется XML-документация, которая появляется в окошке Help Insight.

TImageLink – класс который используется списком изображений для рассылки уведомлений о том, что произошли некоторые изменения. Когда меняются свойства ImagesImageIndex, или содержимое TImageList выполняется виртуальный методChange.

Чтобы ваш контрол мог адекватно реагировать на изменения он должен содержать экземпляр наследника TImageLink в котором правильно заданы свойства Images и ImageIndex а также перекрыт метод Change (либо задан обработчик событияOnChange). В TBaseImageList есть свойство-массив Links который содержит ссылки на экземпляры TImageLink. Когда вы устанавливаете свойство TImageLink.Images то в этот массив добавляется новый элемент. Наследники TBaseImageList после любых изменений проходят по массиву и последовательно выполняют метод Change у всех, или части элементов.

TImageLink.IgnoreIndex – если это свойство имеет значение True, то метод Change не выполняется при изменнии ImageIndex, но выполняется при изменении любого изображения в списке. Обычно уведомления приходят только если поменялось изображение с соответствующим номером. Это свойство используется в тех случаях, когда требуется отображение всего списка изображений, например, в окне редактора.

TImageLink.IgnoreImages – если это свойство имеет значение True, то метод Change не выполняется при изменении свойстваImages. Используется для совместимости со старым VCL-ным кодом.

Смысл методов TBaseImageList мне кажется не требует дополнительных комментариев.

Класс TCustomImageList

Теперь рассмотрим некоторые неочевидные свойства и методы компонента FMX.ImgList.TCustomImageList который содержит основную функциональность. TImageList традиционно отличается от TCustomImageList только наличием публичных свойств отображаемых в инспекторе объектов.

Dormant – возвращает True, если все Bitmap’ы в коллекции Source имеют это свойство также равное True. Когда Вы присваиваете True, то у всех Bitmap’ов оно принимает такое же значение. Свойство Dormant позволяет сократить расход памяти: когда оно установлено, графические данные Bitmap’а хранятся в запакованном формате, при первом обращении к графическим данным картинка распаковывается и свойство принимает значение False.

CacheSize – максимальное количество картинок в кэше. Как вы уже заметили процесс рисования любого изображения состоит из нескольких довольно трудозатратных шагов. Чтобы при перерисовке каждого контрола не приходилось заново масштабировать и выводить несколько слоев, готовое изображение попадает во внутренний массив и в последствии оно будет извлекаться из этого массива. Новое изображение добавляется в конец массива, сдвигая все старые элементы к началу. Если количество изображений превышает CacheSize, то наиболее старое изображение удаляется из кэша. Очевидно, что большие значения CacheSize увеличивают быстродействие за счет увеличения расхода памяти. Значение по умолчанию 8, минимальное значение 1.

ClearCache – удаляет из кэша изображения с указанным номером. В кэше может хранится несколько изображений с одинаковым номером, но с разным размером. Если указано -1 (по умолчанию) то удаляются все. Обычно этот метод вызывается автоматически при изменениях TImageList.

BitmapExists – возвращает True, если изображение с указанным номером существует, и содержит некоторый рисунок. Т.е. имеется хотя бы один слой, в котором указано имя существующего исходного изображения и подходящие координаты. Несуществующие изображения в IDE показываются как пунктирный квадрат. Если изображаемый участок содержит графические данные, но он полностью прозрачный,  то функция вернет True, однако картинка будет пустой. Эта функция используется в TGlyph для установки свойства Visible, когда AutoHide имеет значение True.

Bitmap – возвращает изображение указанного размера с указанным номером. Если изображения не существует, то возвращается nil. Обратите внимание, что полученное значение нельзя самостоятельно разрушать и хранить как ссылку. Потому что это одно из значений находящихся в кэше изображений, оно всегда создается и разрушается средствамиTImageList. Если вам просто нужно нарисовать некоторую картинку, используйте метод Draw. Если все-таки требуется сохранить полученное изображение для дальнейшего использования, можно создать новый экземпляр TBitmap и воспользоваться методом Assign.

BitmapItemByName – функция ищет исходное изображение по указанному имени, и возвращает элемент коллекцииMultiResBitmap с масштабом наиболее близким к 1. В случае успеха возвращается True, а выходные параметры Item и Sizeсодержат найденный элемент и его размер, иначе возвращается False, а выходные параметры не меняются.

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

Source – коллекция исходных изображений, поле класса TSourceCollection. Создается виртуальным методомTCustomImageList.CreateSource. Вы можете перекрыть этот метод для того, чтобы создать коллекцию своего собственного типа.  Интерес в этом классе представляет функция IndexOf, которая возвращает номер элемента коллекции по его имени. Если элемент отсутствует, возвращается -1.

TCustomSourceItem – класс элементов коллекции TSourceCollection. Имеет уникальное в пределах коллекции регистронезависимое имя и свойство MultiResBitmap которое хранит несколько исходных изображений адаптированных для разных масштабов.

Destination – коллекция с информацией для формирования готовых изображений, поле класса TDestinationCollection. Создается виртуальным методом TCustomImageList.CreateDestination.

TCustomDestinationItem – класс элементов коллекции TDestinationCollection. Содержит свойство-коллекцию Layers которое создается виртуальным методом CreateLayers. Также обратите внимание на функцию LayersCount которая возвращает количество непустых слоев, просьба не путать со свойством Layers.Count (общее количество слоев в коллекции).

Коллекция слоев TLayers содержит элементы класса TLayer. При формировании готового изображения последовательно рисуются все непустые слои с нулевого до последнего.

TLayer – содержит данные об исходном изображении, которые используются при рисовании.

TLayer.Name – имя по которому будет найдено исходное изображение в коллекции Source. При поиске концевые пробелы удаляются.

TLayer.SourceRect – прямоугольник на исходном изображении который будет выводится при рисовании слоя. Обратите внимание, что задаются координаты всегда для масштаба 1, для других масштабов используются пересчитанные координаты.

TLayer.MultiResBitmap – исходное изображение найденное по указанному имени. Может быть nil.

5. Примеры кода

Посмотрите, на второй вкладке демонстрационного проекта есть кнопки, которые позволяют менять свойство Dormant, размер кэша и список изображений. Рассмотрим более подробно нижние кнопки.

Кнопка “Add New Source”

Создает исходное изображение и добавляет его список изображений выполняя метод AddSourceToItem. Внутренняя процедура DrawPicture просто рисует на канве некоторый рисунок, поэтому сразу перейдем к телу метода. В начале если слоев нет, то добавляем новый слой для картинки с номером Index (в демонстрационном проекте для девятой картинки), иначе используем нулевой (нижний) слой. Переменная SourceName хранит имя исходной картинки, взятое из существующего слоя.

Далее используя метод TCustomImageList.BitmapItemByName мы пытаемся получить один из элементов коллекцииTMultiResBitmap по имени исходной картинки. Если это удалось, то считаем, что изображение уже добавлено и ничего не делаем, иначе добавляем новый элемент в коллекцию Source. Если в существующем слое было задано имя исходного изображения SourceName, то присваиваем это имя новому элементу NewSource, иначе используем его умолчательное значение.

Теперь в цикле можно создать несколько картинок для разных масштабов. Добавляем новый элемент в TMultiResBitmap, получаем его умолчательный (1, 2, 3…) масштаб S. С учетом масштаба и размера прямоугольника SourceRect из слоя, устанавливаем размер исходной картинки. Затем заливаем эту картинку используя прозрачный цвет и рисуем некоторое изображение.

Кнопка “Update Text”

Демонстрирует, как можно рисовать на одном из слов используя метод DrawTextOnLayer, который поверх основного изображения выводит текст. Сначала мы получаем номер верхнего слоя картинки с номером Index (в демонстрационном проекте это восьмая картинка), если нет ни одного слоя, то мы ничего не делаем, иначе сохраняем его в переменной Layer, а имя исходного изображения в переменной SourceName.

Далее с помощью метода TCustomImageList.BitmapItemByName пытаемся получить элемент коллекции MultiresBitmap по имени SourceName. Если это не удалось, добавляем новое исходное изображение, и добавляем новый элемент коллекцииMultiresBitmap (при отсутствии такового). Затем устанавливаем размер исходной картинки равный размеру прямоугольникаSourceRect из слоя.

Теперь на картинке полученного элемента MultiresBitmap мы можем нарисовать некоторый текст. Нажимая кнопку видно, как меняется текст над конвертиком во всех контролах которые используют 8 картинку.

Использование списка изображений в своих контролах

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

В конструкторе создаем поле класса TImageLink и присваиваем обработчик события OnChange который вызывается, когда нужно перерисовать изображение на контроле. Вы также можете создать класс потомок, перекрыв метод Change вместо присваивания OnChange. Не забудьте, что в деструкторе надо удалить созданное поле.

Если ваш контрол поддерживает работу с действиями, скорее всего должен будет поддержать интерфейс IGlyph. В этом случае предпочтительно создать экземпляр класса TGlyphImageLink, который будет вызывать метод IGlyph.ImagesChanged, тогда не потребуется присваивать OnChange.

Свойства Images и ImageIndex должны иметь правильные значения, обычно для доступа к ним используются свойства такого вида

Cобственно вывод картинки из списка изображений осуществляется в событии OnPaint. См. также видео.

Спасибо, если дочитали до этого места.

P. S. Пожалуйста задавайте вопросы по теме статьи, буду рад помочь.


Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.
Start Free Trial   Upgrade Today

   Free Delphi Community Edition   Free C++Builder Community Edition

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

IN THE ARTICLES