Watch, Follow, &
Connect with Us

The official blog of Sergey Roschin

Внимание! Передислокация блога

Caution! Relocation of blog

Милые дамы и уважаемые господа, теперь все статьи буду выкладывать сюда: http://community.embarcadero.com/index.php/blogs/blogger/listings/http-blogs-embarcadero-com-roschinspb

Для получения новостей, подпишитесь на RSS канал:
http://community.embarcadero.com/index.php/blogs/blogger/http-blogs-embarcadero-com-roschinspb?format=feed&type=rss

New blog address:
http://community.embarcadero.com/index.php/blogs/blogger/listings/http-blogs-embarcadero-com-roschinspb

New feed address:
http://community.embarcadero.com/index.php/blogs/blogger/http-blogs-embarcadero-com-roschinspb?format=feed&type=rss

Posted by roschinspb on May 8th, 2015 under Uncategorized | Comment now »


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 появится список доступных картинок. Вы можете выбрать одно изображение, или ввести любое целое значение. Если изображения с указанным номером не существует, то компонент будет пустым.

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

Новые свойства 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 хранит данные для формирования картинок на основе исходных изображений.

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

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

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

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

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

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

:\Users\Public\Documents\Embarcadero\Studio\16.0\Samples\Object Pascal\FireMonkey Desktop\ImageList\ImageListDemo.dpr

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

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

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

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

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

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

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

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

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

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

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

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

В случае, если у вас есть только большие изображения, то можно уменьшать из размер в момент добавления. Для этого нужно выбрать 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, то будет использовано соответствующее ему изображение, иначе изображение из выделенного слоя. См. небольшой видеоклип

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 – класс который используется списком изображений для рассылки уведомлений о том, что произошли некоторые изменения. Когда меняются свойства Images, ImageIndex, или содержимое 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 хранит имя исходной картинки, взятое из существующего слоя.

if MainDataModule.ImageList1.Destination[Index].Layers.Count = 0 then
begin
Layer := MainDataModule.ImageList1.Destination[Index].Layers.Add;
Layer.SourceRect.Rect := TRectF.Create(0, 0, 16, 16);
SourceName := ”;
end
else
begin
Layer := MainDataModule.ImageList1.Destination[Index].Layers[0];
SourceName := Layer.Name;
end;

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

if not MainDataModule.ImageList1.BitmapItemByName(SourceName, Item, Size) then
begin
NewSource := MainDataModule.ImageList1.Source.Add;
if SourceName = '' then
Layer.Name := NewSource.Name
else
NewSource.Name := SourceName;

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

for I := 1 to 10 do
begin
Item := NewSource.MultiResBitmap.Add;
S := Item.Scale;
Item.Bitmap.BitmapScale := 1;
Size.cx := Round(Layer.SourceRect.Width * S);
Size.cy := Round(Layer.SourceRect.Height * S);
Item.Bitmap.SetSize(Size.cx, Size.cy);
Item.Bitmap.Clear(TAlphaColorRec.Null);
R := TRectF.Create(S + 0.5, S + 0.5, Size.Width - S - 0.5, Size.Height - S - 0.5);
DrawPicture(Item.Bitmap.Canvas, R, S);
end;

Кнопка “Update Text”

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

LayerIndex := MainDataModule.ImageList1.Destination[Index].Layers.Count - 1;
if LayerIndex >= 0 then
begin
Layer := MainDataModule.ImageList1.Destination[Index].Layers[LayerIndex];
SourceName := Layer.Name;

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

if not MainDataModule.ImageList1.BitmapItemByName(SourceName, Item, Size) then
begin
Size.cx := Round(Layer.SourceRect.Rect.Width);
Size.cy := Round(Layer.SourceRect.Rect.Height);
NewSource := MainDataModule.ImageList1.Source.Add;
NewSource.Name := SourceName;
Item := NewSource.MultiResBitmap.ItemByScale(1, False, True);
if Item = nil then
Item := NewSource.MultiResBitmap.Add;
Item.Bitmap.SetSize(Size.cx, Size.cy);
end;

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

Item.Bitmap.Clear(TAlphaColorRec.Null);
if Item.Bitmap.Canvas.BeginScene then
try
Item.Bitmap.Canvas.Font.Size := 15;
Item.Bitmap.Canvas.Fill.Color := TAlphaColorRec.Red;
Item.Bitmap.Canvas.Fill.Kind := TBrushKind.Solid;
Item.Bitmap.Canvas.FillText(TRectF.Create(1, 0, Size.cx - 1, Size.cy div 2),
Text, False, 1, [], TTextAlign.Center, TTextAlign.Center);
finally
Item.Bitmap.Canvas.EndScene;
end;

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

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

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

constructor TMainForm.Create(AOwner: TComponent);
begin
inherited;
FImageLink := TImageLink.Create;
FImageLink.Images := MainDataModule.ImageList1;
FImageLink.ImageIndex := Glyph1.ImageIndex;
FImageLink.OnChange := OnImagesChange;

end;

destructor TMainForm.Destroy;
begin
FImageLink.DisposeOf;

end;

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

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

property ImageIndex: TImageIndex read GetImageIndex write SetImageIndex
stored ImageIndexStored;

function TMyControl.GetImageIndex: TImageIndex;
begin
Result := FImageLink.ImageIndex;
end;

procedure TGlyph.SetImageIndex(const Value: TImageIndex);
begin
FImageLink.ImageIndex := Value;
end;

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

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

Posted by roschinspb on March 3rd, 2015 under Image, ImageList | 3 Comments »


MultiResBitmap Editor usage

The small filmloop TELLING about the MultiResBitmap Editor usage. Storyteller is Vsevolod Leonov

То же но с субтитрами на великом и могучем:

Posted by roschinspb on February 3rd, 2014 under Image | 3 Comments »


Всплывающие формы в XE5

TCommonCustomForm.FormStyle

В XE5, вместо свойств StaysOpen, ShowActivated и TopMost появилось свойство FormStyle. Это свойство управляет поведением формы как и аналогичное свойство в VCL (не путайте со StyleBook, которое управляет визуальными стилями формы). Может принимать одно из трех значений:

TFormStyle = (fsNormal, fsPopup, fsStayOnTop);

Смысл fsNormal и fsStayOnTop довольно очевиден. Это обычная форма и форма которая находится всегда поверх обычных форм. Если раньше было установлено свойство TopMost, то FormStyle получит значение TFormStyle.fsStayOnTop.

Два значения fsMDIForm и fsMDIChild отсутствуют, т. к. эти значения могли бы использоваться только в Windows.

Интерес я думаю представляет значение fsPopup. Это особая форма которая используется в комбинированных списках и меню. Если раньше одно из свойств StaysOpen, или ShowActivated имело значение False, то FormStyle получит значение TFormStyle.fsPopup.

Всплывающая форма имеет отличное от других форм поведение:

  • Не может быть активной. В классе TScreen, всплывающие формы находятся в отдельном списке, см. TScreen.PopupForms и TScreen.PopupFormCount.
  • На мобильных платформах обычные формы всегда находятся в полноэкранном режиме, но к всплывающим формам это не относится.
  • Не остаётся видимой длительное время. Если кликнуть мышью (или пальцем на тачпаде) вне границ всплывающей формы, то она закроется. На экране может быть несколько всплывающих форм, связанных (или не связанных) как родительская-дочерняя. Если кликнуть на одну из всплывающих форм, то закроются все всплывающие формы кроме этой формы и всех её родителей.

Для всплывающих форм, также характерны следующие значения свойств:

BorderStyle := TFmxFormBorderStyle.bsNone; // Отсутствует рамка окна
Popup.Transparency := True;  // Форма имеет прозрачный фон

TCommonCustomForm.ParentForm

Для определения "родственных связей" форм, добавлено свойство ParentForm. Значение этого свойства устанавливается, когда Вы меняете свойство Parent. Если объект Parent является формой, он же будет использован в качестве ParentForm, иначе будет определена форма на которой лежит объект Parent.
После изменения ParentForm выполняется виртуальный метод DoParentFormChanged.

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

TCustomPopupForm

Для упрощения создания всплывающих форм, создан специальный класс форм TCustomPopupForm, который используется в TPopup, TComboEdit, TPopupBox, TMenuBar и т.п. У форм данного класса по умолчанию нет рамки и фона. После закрытия происходит автоматическое разрушение. При создании не производится поиск fmx-файла формы, т. е. этот класс ориентирован только на работу в Run-time.

Поскольку у форм данного класса не видны ни рамка ни фон и они не должны выходить за границы экрана, физические координаты окна не имеют большого практического значения. Управление расположением формы осуществляется с помощью других свойств: PlacementPlacementTargetPlacementRectangle, Size. Самостоятельно менять Position, Height, Width не надо.

Экземпляры таких форм создаются с использованием конструктора:

constructor Create(AOwner: TComponent; AStyleBook: TStyleBook = nil; APlacementTarget: TControl = nil); reintroduce;

Здесь AStyleBook указывает стили используемые формой, а APlacementTarget указывает контрол рядом с которым должна появиться форма.

PlacementTarget — некоторый контрол в окрестностях которого, определенным образом располагается форма (здесь и далее для обозначения такого размещения позвольте использовать слово "прилипать"). Если он задан, то расчет экранных координат формы осуществляется относительно данного контрола, иначе относительно левого верхнего угла экрана.

PlacementRectangle — координаты прямоугольника к которому осуществляется "прилипание" формы. Расчет экранных координат PlacementRectangle, осуществляется относительно PlacementTarget если он задан, иначе относительно левого верхнего угла экрана.

  • Если PlacementRectangle не задан (ширина, или высота равны 0), но задан PlacementTarget, то используются его координаты и размеры.
  • Если не задан ни PlacementRectangle, ни PlacementTarget то "прилипание" левого верхнего угла формы осуществляется к курсору мыши.

ScreenPlacementRect — экранные координаты прямоугольника к которому осуществляется "прилипание" с учетом PlacementTarget и PlacementRectangle. На рисунке обозначен малиновым цветом.

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

  • plBottom — форма снизу, если выходит за границы экрана, то сверху. Этот вид расположения обычно используется в комбобоксах.
  • plTop — форма сверху (см. картинку), если выходит за границы экрана, то снизу.
  • plLeft — форма слева, если выходит за границу экрана, то справа.
  • plRight — форма справа, если выходит за границу экрана, то слева. Этот вид расположения обычно используется в меню.
  • plCenter — форма по центру.
  • plBottomCenter — форма снизу и посередине.
  • plTopCenter — форма сверху и посередине.
  • plLeftCenter — форма слева и посередине.
  • plRightCenter — форма справа и посередине.
  • plAbsolute — для определения координат формы используется PlacementRectangle, при этом PlacementTarget игнорируется (используются координаты относительно левого верхнего угла экрана). Если ширина, или высота PlacementRectangle равны 0, то используются свойство Size.
  • plMouse — левый верхний угол прилипает к курсору мыши. PlacementRectangle и PlacementTarget игнорируется. Для определения размера используется свойство Size.
  • plMouseCenter — центр прилипает к курсору мыши. PlacementRectangle и PlacementTarget игнорируется. Для определения размера используется свойство Size.

На этом рисунке изображена всплывающая форма в которую помещен прямоугольник TRectangle с эффектом тени TShadowEffect. Прямоугольник выравнивается по всей клиентской области alClient. Различными цветами представлены некоторые важные свойства. Проект Вы можете загрузить здесь.

Padding — это свойство как и в обычной форме задает отступы от краёв клиентской области формы. По этим отступам будут выравниваться все контролы на форме. На рисунке это серый квадрат (с зеленой рамкой). По умолчанию используются значение 8, чтобы эффекты (например тень) не обрезались физическими границами окна.

Size — размер рабочей области формы. Как видно на рисунке физические размеры окна (красный пунктир) представляют малый интерес. Данное свойство (зеленый пунктир) задает те размеры по которым будут выравниваться все контролы. Размеры формы ClientWidth, ClientHeight устанавливаются автоматически с учетом Size и Padding. Если задано свойство PlacementRectangle и Placement имеет значение plAbsolute, то Size игнорируется.

ContentPadding — задает отступы от рабочей области формы, к которым осуществляется "прилипание".

ScreenContentRect — экранные координаты прямоугольника, к которому осуществляется "прилипание". Это прямоугольник внутри которого расположено полезное содержимое формы. Некоторые декоративные элементы (например треугольничек в CalloutPanel) могут вылезать за границы формы, или на кнопку.

Offset — смещение всплывающей формы. Это свойство указывает на сколько пикселей надо сместить форму относительно того расположения, которое было получено с учетом рассмотренных ранее свойств. В зависимости от Placement меняется направление смещения. Это свойство используется к примеру в многоуровневых меню, когда новые формы располагаются внахлест.

DragWithParent — это свойство указывает, надо ли перемещать форму после того как она была показана. Например: если Placement имеет значение plMouse или plMouseCenter то форма будет перемещаться вслед за курсором мыши. На мобильных устройствах, после смены ориентации (портрет/ландшафт) форма будет менять расположение чтобы не оказаться частично за пределами экрана.

Извините получилось довольно много букв, надеюсь что хоть часть текста оказалась понятной.

Posted by roschinspb on September 3rd, 2013 under Forms | 13 Comments »


Каретка в Fire Monkey

 В Fire Monkey новой версии XE4, в редакторах TEdit и TMemo появилось новое свойство Caret.

Это наследник TCustomCaret и TPersistent (располагается в FMX.Types) и имеет следующие свойства:

  • Color - цвет каретки. Если Null то используется цвет DefaultColor. Если и DefaultColor имеет значение Null, то используется цвет текста.
  • DefaultColor - умолчательный цвет каретки. Это значение берется из элемента стиля caretcolor. Если этот элемент стиля отсутствует то свойство получает значение Null.
  • Interval - время (мс) в по истечении которого исчезает и появляется Flasher (тот самый мигающий прямоугольничек). Если 0, то используется умолчательное значение 500, если -1, то Flasher не мигает и виден постоянно.
  • Width - ширина Flasher`а. Если 0, то используется умолчательное значение которое получается при помощи специального платформенного сервиса IFMXSystemInformationService (например для Windows это будет 1, для iOS - 5).

Полагаю для большинства случаев этой информации будет достаточно.

Дополнительная информация

В редакторах TEdit и TMemo для создания каретки используется виртуальный метод CreateCaret, который вы можете перекрыть для того, чтобы использовать свой собственный класс кареток. Можно также создать свой собственный Flasher, но это несколько сложнее. Eсли кто-то проявит интерес, то я напишу об этом как-нибудь в другой статье.

Если Вы хотите создать свой собственный контрол использующий каретку но не являющийся наследником (TCustomEdit и TMemo), то для правильной работы он должен поддерживать интерфейс ICaret.

Рассмотрим другие свойства и методы каретки.

  • Owner - владелец каретки. Это свойство получает значение параметра AOwner в конструкторе. Это должен быть редактор которому принадлежит каретка и обязательно поддерживать интерфейс IControl.
  • Control - контрол которому принадлежит каретка. См. Owner.
  • Pos - положение каретки (координаты левого верхнего угла).
  • Size - размеры каретки.
  • Visible - видимость каретки. Установка этого свойства не означает что сразу же в текущем контроле замигает Flasher, это всего лишь установка признака говорящего о том, что в данном контроле он может замигать при подходящих условиях.
  • CanShow - это виртуальный метод в дополнение к Visible, который возвращает True, если контрол виден, доступен, в нем фокус ввода и он располагается на активной форме.
  • Displayed - это свойство говорит о том, что данная каретка находится в фокусном контроле и в него может осуществляться ввод с клавиатуры. Это свойство только для чтения, но оно меняется при выполнении методов Show и Hide.
  • Show - если Visible = True и CanShow возвращает True, то Flasher перемещается в контрол и свойство Displayed принимает значение True. Этот метод вызывается когда контрол получает фокус ввода.
  • Hide - делает Flasher невидимым, и свойство Displayed равным False.
  • UpdateFlasher - обновление всех свойств Flasher`а. Выполняется при изменении свойств. Этот метод запускает виртуальный метод DoUpdateFlasher который вы можете перекрыть. Традиционно если вы хотите временно отменить обновление то можно воспользоваться методами BeginUpdate и EndUpdate.
  • Flasher - мигающий прямоугольничек, ну или синее ведерко если использовать аналогию из автомобилизма. А на самом деле это интерфейс IFlasher, который отвечает за графическое отображение каретки. Сам по себе класс TCustomCaret ни чего не отображает, а только содержит данные которые используются Flasher’ом. Строго говоря, Flasher может представлять из себя что угодно, но по умолчанию используется наследник TRectangle, который объявлен в FMX.Objects (см. TCaretRectangle).

Вы можете обращаться к Flasher для того, например, чтобы узнать реальный цвет, или координаты каретки. Но будьте внимательны, в отличие от проблесковых маячков, существует только один экземпляр этого объекта и располагается на том редакторе, в котором находится фокус ввода. IFlasher имеет свойство Caret которое указывает на каретку контрола в котором располагается Flasher.

Каретка и клавиатура

Надо отметить, что каретка занимается не только перемещением Flasher`а, но и тесным образом связана с виртуальной клавиатурой. Когда меняется фокусный контрол, или свойство Displayed, то должна появиться или исчезнуть виртуальная (экранная) клавиатура. На мобильных устройствах с сенсорным экраном клавиатура обычно (но не всегда) видна, тогда когда отображается каретка. При этом учитываются еще пара свойств:

  • ReadOnly - если это свойство True, то виртуальная клавиатура не показывается, даже если каретка видна.
  • TemporarilyHidden - каретка временно скрывается хотя клавиатура видна и допускается ввод в фокусный контрол, при этом Displayed остаётся равным True.

Виртуальная клавиатура атоматически появляется при подходящих условиях, на платформе iOS, а в Windows Вы можете добиться подобной работы, если установите глобальную отладочную переменную VKAutoShowMode равной vkasAlways.

Чтобы отловить момент появления и сокрытия клавиатуры используйте события формы OnVirtualKeyboardShown и OnVirtualKeyboardHidden это может быть полезно для того, чтобы осуществить смещение рабочей области окна. Более подробно про скроллинг было написано в статье Платформонезависимый скроллинг

См. примеры samples\ScrollableFormDemo и samplesmobile\ScrollableForm. Если на рабочем компьютере нет примеров, то можно посмотреть здесь: ScrollableFormDemo.

Posted by roschinspb on April 15th, 2013 under Caret, Text, on-Screen Keyboard | 2 Comments »


Платформонезависимый скроллинг в Fire Monkey

Одно из нововведений в XE4 это платформонезависимый скроллинг.
С самого начала в Fire Monkey был компонент TScrollBox, который является предком для многих других компонентов (TListBox, TTreeView, TMemo и т.п.). Это контейнер который может содержать различные контролы, при этом они могут выходить далеко за границы видимой области TScrollBox.

На картинке изображается TScrollBox в RunTime и в DesignTime. Если упрощенно рассмотреть его структуру, то данный компонент (стилизованный контрол), содержит в себе полосы прокрутки и контрол ContentLayout обозначающий видимую область (на нижней картинке выделен темно-серым прямоугольником). Размеры ContentLayout соответствуют свойству ViewportSize для полос прокрутки.

На ContentLayout располагается контрол Content (содержимое ScrollBox`а). Когда мы бросаем какую-нибудь кнопку в TScrollBox, то она в реальности попадает в Content и когда мы перемещаем изображение внутри TScrollBox, на самом деле изменяются координаты Content.

Рассмотрим некоторые свойства:

  • ViewportPosition - если говорить о физическом смысле, то это координаты левого верхнего угла видимой области на Content (см. зеленый текст). Эти координаты соответствую свойству Position для полос прокрутки. Сами полосы прокрутки VScrollBar и HScrollBar перенесены в раздел protected, поскольку это элементы стиля (строго говоря могут отсутствовать) и обращаться к ним надо осторожно.
  • ContentBounds - границы, внутри которых располагаются все контролы (см. красный прямоугольник и текст). Эти координаты соответствуют свойствам Min и Max для полос прокрутки.
  • OnViewportPositionChange - событие вызывается из виртуального метода ViewportPositionChange, после изменения координат, или размера видимой области.
  • OnVScrollChange, OnHScrollChange - события вызываются при непосредственном перемещении ползунков на полосах прокрутки. Т.е. если мы меняем ViewportPosition или перетаскиваем содержимое мышью или пальцем, или содержимое перемещается по инерции, то эти события не срабатывают.
  • OnCalcContentBounds - событие вызывается после рассчета нового значения ContentBounds, используя его мы можем изменить границы содержимого.
  • AutoHide - если это свойство имеет значение True, то соответствующая полоса прокрутки скрывается когда размер ContentBounds меньше размера ContentLayout. В противном случае она становится недоступной.
  • ShowScrollBars - если это свойство имеет значение False, то полосы прокрутки не видны ни когда, иначе они могут быть видны в зависимости от размеров, AutoHide, AutoShowing.
  • ShowSizeGrip - это свойство разрешает отображение декоративного элемента, который располагается в правом нижнем углу окнон в Win-приложениях. На самом деле он виден если существует соответсвтующий элемент стиля и видны обе полосы прокрутки.

Если создавать наследника TScrollBox, то можно перекрыть виртуальные методы: HScrollChange, VScrollChange, ViewportPositionChange, DoCalcContentBounds вместо использования событий.

За плавное перемещение отвечает свойство AniCalculations

Вся логика отвечающая за поведение контрола при перетаскивании содержимого, вынесена в класс TAniCalculations который находится в модуле FMX.InertialMovement.

Свойство TScrollBox.AniCalculations создаётся с использованием виртуального метода CreateAniCalculations, перекрыв этот метод можно использовать свои классы для управления перемещением.

Умолчательное поведение скроллинга отличается на разлных платформах, узнать его можно с помощью функции GetScrollingBehaviours. Внутри виртуального метода TScrollBox.DoUpdateAniCalculations устанавливаются некоторые свойства AniCalculations по умолчанию. Вы можете перекрыть этот метод чтобы изменить умолчательные значения. Например:

procedure TVertScrollBox.DoUpdateAniCalculations(const AAniCalculations: TScrollCalculations);
begin
  inherited DoUpdateAniCalculations(AAniCalculations);
  AAniCalculations.TouchTracking := AAniCalculations.TouchTracking - [ttHorizontal];
end;

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

  • Animation - разрешается перемещение по инерции.
  • AutoShowing - разрешается плавное скрытие и показ полос прокрутки. Если это свойство имеет значение True, то скроллбыры обычно полностью прозрачны и не видны. Когда начинается движение они появляются, а после остановки плавно меняют прозрачность до 0. Просьба не путать со свойствами AutoHide и ShowScrollBars
  • BoundsAnimation - разрешается перемещение содержимого за границы видимой обрасти, как на рисунке.
  • TouchTracking - разрешается перетаскивание рабочей области с помощью мыши, или пальца по вертикали и по горизонтали (см. ttVertical, ttHorizontal).
  • Averaging - это свойство несколько меняет логику работы. Когда происходит перемещение пальцем (ssTouch in Shift) это значение должно быть True.
  • DecelerationRate - степень замедления, кода содержимое перемещается по инерции. Умолчательное значение DecelerationRateNormal = 1.95 примерно соответствует замедлению на родных приложениях iOS.
  • Moved - это свойство указывает на то, что произошло перемещение, принимает значение False при выполнении метода MouseDown, и значение True в методе MouseMove, когда изменятся координаты. Если установлены свойства Averaging и Animation, то перемещение должно быть на расстояние больше DeadZone. Это на случай, если у пользователя с похмелья дрожат пальцы.
  • Opacity - текущее значение прозрачности, используемое полосам прокрутки. См. AutoShowing.
  • Down - это свойство указывает на то, что пользователь нажал кнопку мыши, или прикоснулся к сенсорному экрану.

Для начала пожалуй хватит…

Posted by roschinspb on April 8th, 2013 under Scrolling | 3 Comments »


Про категории в TActionList

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

Подкатегории

Теперь, если в названии категории используется ".", то в редакторе списка действий они изображаются в виде дерева. Число уровней вложенности ограничено, поэтому если раньше кто-то активно использовал точки в названиях категорий, то в новой версии не получится излишнего ветвления.

Добавлены также новые пункты меню (Change Multiple Actions), которые позволяют быстро изменить свойства Enabled и Visible.

  • Если вызвать контекстное меню в правой половине редактора, то будут изменены свойства у всех выделенных действий.
  • Если вызвать контекстное меню в левой половине редактора, то будут изменены свойства у всех действий данной категории и всех её подкатегорий.
  • Добавлен также метод TContainedActionList.EnumByCategory, который позволяет вызвать анонимный метод TEnumActionListRef для всех действий указанной категории (и всех подкатегорий).
    TEnumActionListRef = reference to procedure(const Action: TContainedAction; var Done: boolean);
    Если аргументу Done присвоить значение True, то перечисление действий прервется и EnumByCategory вернет True, во всех остальных случаях возвращается False.

    TEnumActionListRef выполняется для всех подходящих действий на момент вызова EnumByCategory. Т.е. если вы поменяете название категории внутри метода TEnumActionListRef это не повлияет на процесс перечисления.

    Для названий категорий могут использоваться национальные символы, регистр игнорируется. Всё выше указанное относится и к VCL и к FM.

    Posted by roschinspb on April 3rd, 2013 under Action | Comment now »


    Setting Text Parameters in FireMonkey

    One of small but rather useful new features of FireMonkey introduced in XE3 version is the FMX.Types.ITextSettings interface.
    Often we need to change some parameters of an output text for a component, which class is not known a priori. For example, a component can be of the TText or TTextControl classes. These classes have Color or FontColor properties respectively. In general case, to set color to one of these properties, one need first check the type of an object instance and then cast the type:

    if Obj is Ttext then
      TText(Obj).Color := MyColor
    else if Obj is TTextControl then
      TTextControl(Obj).FontColor := MyColor;

    Also you can access public properties using RTTI (Working with RTTI Index), but this looks not too fine especially taking into account that Color is not the only property of a text.

    ITextSettings Interface

    Usage of the ITextSettings interface makes such task much more simple and universal:
    var
      Settings: ITextSettings;
      Instance: TComponent;
    begin
    ...
      if IInterface(Instance).QueryInterface(ITextSettings, Settings) = S_OK then
      begin
        // Obtained ITextSettings interface for the component
      end;

    You can use numerous ways to obtain the value of the Settings interface variable. In case of success, Settings has not nil value. In this case, the particular type of Instance is not important. What is important is that the obtained IInterface(Instance) interface contains the following properties: DefaultTextSettings, TextSettings, and StyledSettings.

    These properties have the following meaning:

    DefaultTextSettings: TTextSettings — the default values of text properties. For styled controls, the DefaultTextSettings are set during the style loading in the FMX.Controls.TStyledControl.ApplyStyle method. For primitive controls (do not having styles), DefaultTextSettings are the same as TextSettings. In general case of a styled control, whether DefaultTextSettings are the same as TextSettings depends upon a particular realization of the control’s class. Notice that for the FMX.Objects.TText class, DefaultTextSettings and TextSettings are equal.

    TextSettings: TTextSettings — are customized text properties set manually.

    StyledSettings: TStyledSettings — This property defines, which text properties are taken from a style (see DefaultTextSettings) and which are set manually (see TextSettings) FMX.Types.TStyledSettings.

    You can also use the following constants:

    DefaultStyledSettings - specifies the set of styled text settings that are usually set by default.

    AllStyledSettings - specifies the full set of styled text settings.

    These constants have the following declarations:

    unit FMX.Types;
    ...
    type
      TStyledSetting = (ssFamily, ssSize, ssStyle, ssFontColor, ssOther);
      TStyledSettings = set of TStyledSetting;
    const
      AllStyledSettings: TStyledSettings = [TStyledSetting.ssFamily,
                                            TStyledSetting.ssSize,
                                            TStyledSetting.ssStyle,
                                            TStyledSetting.ssFontColor,
                                            TStyledSetting.ssOther];
      DefaultStyledSettings: TStyledSettings = [TStyledSetting.ssFamily,
                                                TStyledSetting.ssSize,
                                                TStyledSetting.ssStyle,
                                                TStyledSetting.ssFontColor];

    Keep in mind that, for example, when you are changing the value of the TextSettings.FontColor property, then the actual changing of the control’s view happens only if the StyledSettings property does not contain the TStyledSetting.ssFontColor property. Descendants of TTextControl, for example, TLabel provide possibility to edit the published StyledSetting property in the Object Inspector. When you change the color value, from the default value, the Object Inspector automatically sets ssFontColor = False. But such automatic changing of the ssFontColor property is made by the Object Inspector only at design time.


    This is the fragment of code executed when a property influencing onto a text view is changed:

    FTextObject.QueryInterface(ITextSettings, LISettings);
    LISettings.TextSettings.Assign(FDefaultTextSettings);
    LISettings.TextSettings.AssignNoStyled(TextSettings, FStyledSettings);

    You see that this code:
    1. First, set s all default values to the internal FTextObject text object and then
    2. Sets values of some properties that were specified manually. During this process, the code takes into account the StyledSettings property (FMX.Types.ITextSettings.StyledSettings).

    TTextSettings Class

    The main aim of the TTextSettings class is to manage appearance properties of text objects. TTextSettings is the descendant of TPersistent. Classes using text objects TTextControl (FMX.Controls.TTextControl), TMemo (FMX.Memo.TMemo), TCustomEdit (FMX.Edit.TCustomEdit) , and their descendants have the public TextSettings property. That is, if you know the component type a priory, for example TLabel (FMX.StdCtrls.TLabel), then you do not obliged to retrieve the ITextSettings interface; you can use the appropriate text property of the particular component, as follows:

    Label1.TextSettings.FontColor := claDarkkhaki;

    Actually, the public TLabel.FontColor property "is raised" from the corresponding property of TTextSettings class (FMX.Types.TTextSettings.FontColor).

    Notice that not all controls support all TTextSettings properties in the full extent. For example, TButton (FMX.StdCtrls.TButton) cannot change the text color correctly (because the color is changed when a button is pointed by the mouse cursor). Therefore, only fully supported properties are declared published. As the result, the TextSettings property is declared in the TTextControl (FMX.Controls.TTextControl) class as public, and the TextSettings property is kept public in standard controls. However, creating your own descendants of TTextControl, you can re-declare the TextSettings property as published. Then the Object Inspector provides possibility to edit values of sub-properties of the TextSettings property.

    Let Us Shortly Review What the TTextSettings Class Contains

    Equals—Checks whether all properties of the current TTextSettings instance and of the specified TObject object are equal. If values of all properties are equal, then Equals returns True.

    DoChanged—This virtual method is called from other methods when changes of some text object properties happen. In descendants, you can override the DoChanged method and execute appropriate update of the component view basing on results of analysis of the IsChanged and IsAdjustChanged properties. Do not call DoChanged explicitly–to enforce an explicit control updating, call the Change method.

    Change—This method is called when any of the styled properties of the current TTextSettings object is changed. Method Change sets IsChanged = True, then, if the object is not in an updating state (UpdateCount = 0), calls DoChanged and then clears IsChanged and IsAdjustChanged (sets to False).

    BeginUpdate and EndUpdate

    BeginUpdate increases by one the UpdateCount number of started and not accomplished updates of text properties of the current TTextSettings object. Till UpdateCount > 0, FireMonkey does no execute any update of the visual representation of the TTextSettings object.

    EndUpdate decreases UpdateCount. If UpdateCount becomes = 0 and any text property of the current TTextSettings object is changed (IsChanged or IsAdjustChanged is True), then EndUpdate calls DoChanged — to fire the OnChanged event. Then EndUpdate clears IsChanged and IsAdjustChanged (sets them to False).

    Therefore if you need to change several text properties at once and do not want to redraw the visual representation of the TTextSettings object after changing of each property, then you can use the code like this:

    TextSettings.BeginUpdate;
    try
      TextSettings.FontColor := MyColor;
      TextSettings.Trimming := True;
      ...
    finally
      TextSettings.EndUpdate;
    end;

    IsChanged — This property becomes True, when any styled text property (Font.Family, Font.Size, Font.Style, FontColor, HorzAlign, HorzAlign, Trimming or WordWrap) is changed.

    IsAdjustChanged — This property becomes True, when any styled text property that can modify geometry parameters of a text. For example, the font color cannot modify the text size, but the font size

    Assign — This method copies the parameters of the specified Source object to the current TTextSettings (FMX.Types.TTextSettings) object. The specified Source parameter should contain whether the TTextSettings type object or nil. If Source is nil, then all properties obtain the default values. The TTextSettings constructor calls Assign(nil), if your descendant class should be initiated with some parameters having not the default values, then you need to override the Assign method in your class.

    AssignNoStyled — This method is similar to Assign, but it copies, from the specified TextSettings object, values only of that styled text properties, which are not pointed in the StyledSettings parameter.

    Trimming — If the value of this property is not ttNone and the text does not fit to the drawing area, then it is trimmed to fit the area and ellipsis sign is printed after the trimmed text.

    HorzAlign, VertAlign — These properties define parameters of horizontal and vertical text alignment.

    WordWrap — If this property is True, then the text wraps when it longer than the the drawing area.

    FontColor — The color to draw a text.

    Font — The font to draw a text.

    OnChanged — This event is fired when any styled text property is changed.

    Posted by roschinspb on February 5th, 2013 under Text | Comment now »


    CustomText и Text.

    Вдогонку к статье TAction в XE3.
    У меня на днях поинтересовались, что за свойство CustomText. В статье упоминаний о нем нету, так что исправляюсь.

    И так, некоторые действия могут иметь умолчательный текст, который рекомендуется использовать, например "Quit Project1". Проблема в том, что при дизайне формы мы еще не знаем что приложение будет иметь именно это "креативное" название, поэтому просто установить свойство Text в Object Inspector`е будет бесполезно. Необходимо создать обработчик события OnUpdate и там подставлять правильное название. Чтобы освободить разработчика от однотипной работы в Fire Monkey, действие TFileExit обновляет свойство Text самостоятельно и его нет среди публичных свойств. Тем не менее на тот случай, если Вы всё-таки хотите использовать свой собственный текст введено свойство CustomText.

    Т.е. если установлено значение CustomText, то используется именно оно, иначе (при CustomText=”) используется умолчательный текст.

    Добавлю, что protected-свойство CustomText, объявлено в классе TCustomAction, но поддерживается только некоторыми действиями у которых оно "поднято" в раздел published. Если Вы хотите создать своё действие с поддержкой CustomText, вам нужно перекрыть виртуальный метод CustomTextChanged и изменять в нем свойство Text. В случае, если умолчательный текст не является константой и может меняться при работе приложения, перекройте метод Update и вызывайте в нем CustomTextChanged.


    function TCoolAction.Update: Boolean;
    begin
      Result := inherited Update;
      if not Result then
        CustomTextChanged;
    end;

    Posted by roschinspb on November 30th, 2012 under Action | Comment now »


    Установка параметров текста в Fire Monkey 2

    Среди незаметных, но полезных, на мой взгляд, нововведений в XE3 имеется интерфейс ITextSettings, который описан в модуле FMX.Types.

    Часто нам надо изменить некоторые параметры выводимого текста для компонента, класс которого нам заранее неизвестен, например это может быть  TText, или TTextControl у которых есть свойства Color и FontColor. Раньше пришлось бы проверять тип экземпляра и выполнять приведение типов:

    if Obj is Ttext then
      TText(Obj).Color := MyColor
    else if Obj is TTextControl then
      TTextControl(Obj).FontColor := MyColor;

    Можно еще обращаться к публичным свойствам через RTTI, но как-то это меня печалит учитывая, что кроме цвета есть и другие свойства текста…

    ITextSettings

    Использование интерфейса существенно упростит задачу и сделает решение более универсальным.

    var
      Settings: ITextSettings;
      Instance: TComponent;
    begin
    ...
      if IInterface(Instance).QueryInterface(ITextSettings, Settings) = S_OK then
      begin
        // Получен интерфейс ITextSettings для компонента
      end;

    Вы можете получить значение интерфейсной переменной Settings разными способами и в случае успеха она будет иметь отличное от nil значение. Что представляет собой Instance в данном случае не важно, главное, что полученный интерфейс содержит следующие свойства:

    DefaultTextSettings: TTextSettings — умолчательные настройки текста. Для стилизованных контролов это значение устанавливается при загрузке стиля в методе ApplyStyle. Для не стилизованных это тоже самое что и TextSettings (в общем случае всё зависит от реализации конкретного класса, но для TText это так) .

    TextSettings: TTextSettings — настройки текста установленные вручную.

    StyledSettings: TStyledSettings — это свойство указывает какие текстовые настройки берутся из стиля (см. DefaultTextSettings), а какие устанавливаются вручную (см. TextSettings). Вы можете использовать также две константы:

    AllStyledSettings — используются все настройки из стиля

    DefaultStyledSettings — используются некоторые настройки стиля. Это значение обычно задается по-умолчанию.

    unit FMX.Types;
    type
      TStyledSetting = (ssFamily, ssSize, ssStyle, ssFontColor, ssOther);
      TStyledSettings = set of TStyledSetting;
    
    const
      AllStyledSettings: TStyledSettings = [TStyledSetting.ssFamily,
                                            TStyledSetting.ssSize,
                                            TStyledSetting.ssStyle,
                                            TStyledSetting.ssFontColor,
                                            TStyledSetting.ssOther];
    
      DefaultStyledSettings: TStyledSettings = [TStyledSetting.ssFamily,
                                                TStyledSetting.ssSize,
                                                TStyledSetting.ssFontColor];
      Важно не забывать, что меняя, например, TextSettings.FontColor, фактическое изменение внешнего вида произойдет, если StyledSettings не содержит значения TStyledSetting.ssFontColor. Наследники TTextControl, такие как TLabel позволяют редактировать свойство StyledSettings в инспекторе объектов. При изменении цвета на значение отличное от умолчательного, свойство ssFontColor автоматически примет значение False. Но это происходит только в Design Time.



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

    FTextObject.QueryInterface(ITextSettings, LISettings);
    LISettings.TextSettings.Assign(FDefaultTextSettings);
    LISettings.TextSettings.AssignNoStyled(TextSettings, FStyledSettings);


    Как можно видеть, для внутреннего текстового объекта FTextObject сначала присваиваются все умолчательные значения, затем некоторые значения установленные вручную, при этом учитывается свойство StyledSettings.

    TTextSettings

    Для настройки внешнего вида текста создан класс TTextSettings, унаследованный от TPersistent. Наследники TTextControl имеют свойство TextSettings в секции public. Т.е. если заранее известен тип компонента, то получать интерфейс не обязательно:

    Label1.TextSettings.FontColor := MyColor;

    Публичное свойство TLabel.FontColor, на самом деле "поднимает" аналогичное свойство от TextSettings.

    Замечу, что не все контролы, в полной мере, поддерживают все свойства TTextSettings, например TButton не может правильно менять цвет текста (т.к. он меняется при наведении мыши), поэтому в секции published объявлены только поддерживаемые свойства, а TextSettings оставлено в public. Однако создавая своих наследников TTextControl вы можете поместить это свойство в секцию published и изменять его в инспекторе объектов.

    Рассмотрим кратко что содержит данный класс

    Equals — эта функция позволяет определить, различаются ли два экземпляра TTextSettings. Если значения всех свойств одинаковы, то возвращается True.

    DoChanged — этот виртуальный метод вызывается в случае, если произошли какие-то изменения свойств. Вы можете перекрыть этот метод в наследниках и выполнять некоторые действия по обновлению внешнего вида компонента, анализируя свойства IsChanged и IsAdjustChanged. Но не следует вызывать этот метод непосредственно. Для принудительного обновления контрола используйтем метод Change.

    Change — этот метод устанавливает свойство IsChanged в True, затем в случае, если экземпляр не находится в состоянии обновления вызывается метод DoChanged и сбрасываются свойства IsChanged и IsAdjustChanged.

    BeginUpdate и EndUpdate — если вам необходимо изменить сразу несколько свойств, то не смысла после каждого изменения перерисовывать компонент. Чтобы этого избежать используйте такой, примерно, код:

    TextSettings.BeginUpdate;
    try
      TextSettings.FontColor := MyColor;
      TextSettings.Trimming := True;
      ...
    finally
      TextSettings.EndUpdate;
    end;

    IsChanged — это свойство принимает значение True, если изменилось любое свойство

    IsAdjustChanged — это свойство принимает значение True, если изменилось свойство, которое может повлиять на геометрические размеры текста. Например изменения цвета ни как не может повлиять на размер текста, а размер шрифта — может.

    Assign — этот виртуальный метод присваивает значения всех свойств указанного в качестве параметра экземпляра. Единственный параметр Source должен быть либо экземпляром TTextSettings, либо nil. Если nil, то все свойства принимают умолчательные значения. В конструкторе вызывается Assign(nil), если в наследнике требуется изменить умолчательные значения, перекройте этот метод.

    AssignNoStyled — этот метод аналогичен Assign, но присваивает только некоторые свойства с учетом параметра StyledSettings.

    Trimming — если свойство отлично от ttNone и текст не помещается в область перерисовки, то он будет сокращен и в конец видимой части будет добавлено три точки.

    HorzAlign, VertAlign — выравнивание текста по горизонтали и вертикали.

    WordWrap — ели это свойство имеет значение True, то текст может автоматически разделяться на несколько строк.

    FontColor — цвет выводимого текста.

    Font — шрифт используемый при выводе текста.

    OnChanged — событие которое происходит при изменении свойств.

    Posted by roschinspb on October 9th, 2012 under Text | 6 Comments »




    Server Response from: BLOGS1