Часть 2. Разработка компонентов под FMX 2.0. Кнопка с картинкой
Уже прошло почти два месяца с вэбинара на тему "Разработка компонентов на FireMonkey", а время летит быстро и незаметно. Скоро новый год, поэтому все не завершенные дела надо оставить в этом году, поэтому реабилитируюсь и пишу эту статью по теме вэбинара. В этой статье я рассмотрю общие шаги по разработке своего компонента на FMX и в качестве примера рассмотрю разработку компонента TBitBtn - кнопка с картинкой. Этот компонент - аналог кнопки с картинкой взятой из VCL. Но в нашем случае он будет уметь:
- Осуществлять легкое переключение между вариантами отображения стандартных кнопок без стирания пользовательской картинки VCL.TBitBtn.Glyph
- Легко менять стандартные картинки на свои
- Работать со списком изображений, что не умеет делать VCL кнопка с картинкой.
И так приступим…
С чего начать
Будем считать, что в первом приближении вы уже познакомились с FireMonkey и обладаете первоначальными знаниями, как пользоваться библиотекой (как в дизайнере сделать небольшую форму и тд) и познакомились с моими предыдущими небольшими статьями на эту тему. А раз так, то для дальнейшего погружения в разработку своих компонентов нужно:
- Посмотреть как работают примитивные объекты (TLine, TRectangle, TCircle, TText и другие) - те, из которых строятся все остальные. Попробовать и потрогать их можно в среде IDE. Посмотреть как устроены, можно заглянув в исходный код.
- Перейти на простые стилизуемые объекты (TLabel, TCheckBox, TButton) - те, которые не являются объектами контейнерного типа (Меню, списки, выпадающие списки, группы, и тд). Источники вдохновение все те же, что и в пункте 1 + эта статья (как раз здесь будет рассмотрен компонент из этой группы).
- Сложные стилизуемые объекты контейнерного типа (TGroupBox, TPanel, TListBox, TTreeView, TComboBox и тд)
- Платформозависимые компоненты (TTimer, частично TComboBox, TMainMenu, TMediaPlayer и тд). В себе несут несколько реализаций для разных платформ через использование сервисов FMX
Шаги разработки
1 Шаг: Объектная декомпозиция
Вначале нужно провести объекту декомпозицию. Довольно известный прием проектирования. Вы берете ваш компонент и делите его на логические составляющие (сущности). Затем берете каждую сущность и опять ее делите до тех пор, пока не станет ясными общие детали реализации всех составляющих.
2 Шаг: Принимаем решение о написании компонента с нуля или берем за основу уже существующий компонент?
Естественно, если вы делаете узко специализированные компонент, которые не имеет подобия и даже частичной схожести с другими, то вам придется написать компонент с нуля. Однако, когда речь идет о расширении функциональности уже существующих компонентов, то нужно брать за основу уже существующий компонент.
3 Шаг: Разработка представления компонента в терминах полученных сущностей (для визуальных компонентов)
На этом шаге, начать лучше с внешнего вида (стиля) каждой визуальной части компонента.
Почему это лучше сделать до написания самой бизнес логики?
Потому что визуальный компонент - это логика + представление. Логика тесно связана с представлением и составным частях представления. Поэтому не имея составных частей представления, начинать с логики будет тяжелее. Так как придется работать с еще не созданными объектами представления.
4 Шаг: Кодирование
Непосредственно уже написание логики и связывание ее с уже с продуманным стилей. На этом шаге возможно корректировка стиля.
Реализация
Мы хотим сделать, описанную выше, кнопку с картинкой.
1 Шаг: Объектная декомпозиция
Кнопка состоит из трех частей:
- Форма
- Текст
- Картинка

Форма может быть построена из прямоугольника и любой другой геометрической фигуры: будь то круглая кнопка или прямоугольная, или прямоугольная с закругленными углами.
2 Шаг: Принимаем решение о написании компонента с нуля или берем за основу уже существующий компонент?
Просматриваем существующие компоненты. Наша кнопка с картинкой является функциональным расширением обычной кнопки. Поскольку кнопке добавляет только картинку. Значит за основу возьмем существующий класс TButton
3 Шаг: Разработка представления компонента в терминах полученных сущностей (для визуальных компонентов)
Раз мы взяли за основу компонент TButton, то в основу стиля возьмем стиль кнопки. Чтобы его получить нужно кинуть кнопку на FireMonkey форму, нажать на нее правой кнопкой мышки и выбрать в контекстном меню "Edit Custom Style"
В результате дизайнер среды вытащит нам стиль по умолчанию для обычной кнопки. Мы видим, что обычная кнопка состоит из фона - он же в нашей терминологии "Форма" и текста. Значит нам остается только встроить в стиль контейнер для картинки. У меня получилось это вот так:
Вы могли бы просто доработать стиль button1style1: добавить в него картинку и переименовать стиль на bitbtnstyle. Обратите внимание, что каждая часть (сущность) в стиле должна иметь имя (stylename), для дальнейшего доступа к ней из кода. В нашем случае
glyph - название контейнера изображения
text - название контейнера текста
Фон у нас не имеет имя, поскольку нам не нужен к нему доступ.
4 Шаг: Кодирование
Согласно заявленным требованиям, кнопка будет иметь три режима отображения:
TBitBtnKind = (bkStylizedGlyph, bkCustomGlyph, bkOK, bkCancel, bkHelp, bkYes, bkNo, bkClose, bkRetry, bkIgnore);
- bkCustomGlyph - Картинка для кнопки берется из TBitBtn.Glyph
- bkStylizedGlyph - Картинка для кнопки берется из StyleBook по имени стиля
- Остальные - Картинка для кнопки берется из текущего стиля по заранее заданному имени стиля
В FireMonkey нет TImageList, однако, в качестве замены можно использовать TStyleBook. Потому что по сути TStyleBook умеет хранить в себе все что угодно по имени. А значит, что в TStyleBook мы можем помещать не только стили, но и именованные картинки. bkStylizedGlyph - как раз использует stylebook для получения картинок. Подробности использования будут дальше.
Чтобы не возиться с ресурсными файлами и извлечении из них стандартных картинок формы, мы будем хранить картинки в стиле. Для получении имени картинки в стиле будем использовать следующий ассоциативный массив - тип кнопки - название стиля:
const BITBTN_KIND_STYLES : array [TBitBtnKind] of string = ('', '', 'imgOk', 'imgCancel', 'imgHelp', 'imgYes', 'imgNo', 'imgClose', 'imgRetry', 'imgIgnore');
TCustomBitBtn = class (TButton) strict private FImageBook: TStyleBook; // Хранение ссылки на внешний StyleBook с картинками FKind: TBitBtnKind; // Способ отображения кнопки FGlyph: TBitmap; // Внутренний контейнер для хранения пользовательской картинки FStyleImage: TImage; // Ссылка на элемент стиля, контейнер для вывода картинки FImageLookup: string; // Название картинки из StyleBook (Аналог ImageIndex) procedure SetGlyph(const Value: TBitmap); procedure SetKind(const Value: TBitBtnKind); procedure SetImageBook(const Value: TStyleBook); procedure SetGlyphStyle(const Value: string); protected // Перехватываем событие по загрузке стиля для получения ссылки на FStyleImage procedure ApplyStyle; override; // Перехватываем событие по освобождении стиля, для очищения ссылки FStyleImage procedure FreeStyle; override; // Возвращаем название стиля по умолчанию function GetDefaultStyleLookupName: string; override; // Процедура загрузки картинки в стиль procedure UpdateGlyph; virtual; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property ImageBook: TStyleBook read FImageBook write SetImageBook; property Kind: TBitBtnKind read FKind write SetKind default TBitBtnKind.bkStylizedGlyph; property Glyph: TBitmap read FGlyph write SetGlyph; property GlyphStyle: string read FImageLookup write SetGlyphStyle; end;
ApplyStyle и FreeStyle используется для получение ссылки на составные объекты стиля (картинку, текст и тд) Подробнее о механизме загрузки стиля и получении ссылок на составные объекты стиля есть тут.
procedure TCustomBitBtn.UpdateGlyph;
procedure FindAndSetGlyph(const AStyleName: string); var StyleObject: TFmxObject; Finded: Boolean; begin Finded := False; if Assigned(FImageBook) then begin StyleObject := FImageBook.Style.FindStyleResource(AStyleName); if Assigned(StyleObject) and (StyleObject is TImage) then begin FStyleImage.Bitmap.Assign(TImage(StyleObject).Bitmap); Finded := True; end end; FStyleImage.Visible := Finded; end;
procedure FindAndSetStandartButtonKind(const AStyleName: string); var StyleObject: TFmxObject; Finded: Boolean; Style: TFmxObject; begin // Выбираем ветку стиля: // - Если для формы указан StyleBook, то берем его. // - Если для формы не задан StyleBook, то берем стиль по умолчанию if Assigned(Scene.StyleBook) then Style := Scene.StyleBook.Style else Style := TStyleManager.ActiveStyleForScene(Scene); // Ищем стиль по указанному имени в текущей ветки стиля StyleObject := Style.FindStyleResource(AStyleName); Finded := Assigned(StyleObject) and (StyleObject is TImage); if Finded then FStyleImage.Bitmap.Assign(TImage(StyleObject).Bitmap); FStyleImage.Visible := Finded; end; begin if not Assigned(FStyleImage) then Exit; case Kind of bkStylizedGlyph: FindAndSetGlyph(FImageLookup); bkCustomGlyph: FStyleImage.Bitmap.Assign(FGlyph); else FindAndSetStandartButtonKind(BITBTN_KIND_STYLES[Kind]); end; FStyleImage.Visible := not FStyleImage.Bitmap.IsEmpty; end;
Метод FindAndSetStandartButtonKind находит стандартную картинку (ok, yes, no, cancel и другие) в текущем стиле формы.
Метод FindAndSetGlyph ищет указанную по имени картинку в StyleBook с картинками (аналог TImageList)
Обратите внимание, что FindStyleResource позволяет вам по имени вытащить объект из иерархии объекта.
TStyleManager - это менеджер выдающий всю актуальную информацию о стилях. Подробнее читайте в документации.
Вот в принципе и все.
Исходники прилагаются. Качайте, смотрите, пробуйте, улучшайте
Ссылки
Исходники: http://cc.embarcadero.com/item/29254
Запись вебинара: http://blogs.embarcadero.com/vsevolodleonov/2012/11/26/firemonkey20_componentdevelopment_brovin/
Posted by ybrovin on December 27th, 2012 under Firemonkey (RUS) |




RSS Feed

December 28th, 2012 at 12:11 pm
Что означает цифра "64" на картинке, которая есть иконка на кнопке?
December 28th, 2012 at 12:33 pm
Ничего кроме просто картинке. На этом месте могла быть любая другая картинка.
January 15th, 2013 at 5:14 am
Добрый день, Ярослав, при просмотре вебинара меня очень сильно заинтересовала одна особенность, Ваш только что описанный класс налету был подхвачен инспектором объектов, собственно это меня и удивило. Сам я написанием своих компонентов не занимался, только читал у Михаила Флёнова как это делается, но мною написанные компоненты могли быть изменены только программно (компонент был просто описан в отдельном модуле). Собственно сам вопрос, а точнее их два: или это я не правильно что-то делал пытаясь создать свой компонент, или же это возможности ХЕ3? Просто часто бывает что для именно этого проекта нужно сделать из нескольких компонентов один, но описывать все исключительно программно долго и неудобно, из-за этого приходится выдумывать другие менее удобные способы реализации интерфейса.
January 15th, 2013 at 12:37 pm
Добрый День. Поясните, пожалуйста, что вы имеете в виду под "описанный класс налету был подхвачен инспектором объектов"?
Вы имеете в виду, что созданная кнопка, сразу появилась в Tool Palette среды?
January 17th, 2013 at 7:51 am
Прошу прощения, подумал если об установке речи не шло, то достаточно описать его программно и создать ему стиль, а инсталлировать компонент не обязательно, но когда добрался до IDE и посмотрел исходники понял что был не прав, каюсь =)
January 17th, 2013 at 2:35 pm
Увы,
Если мы говорим именно о новом компоненте с поддержкой дизайн тайма, то нужно будет его инсталлировать.
Если же мы делаем компонент прямо на форме (составляем из компонентов), то в этом случае его устанавливать не нужно. Но при этом из среды не будут доступны пользовательские свойства (Glyph, GlyphKind и тд.)
Кстати, если нам нужно сделать только стиль для компонента, то иногда это удобнее делать не в дизайнере стилей, а прямо на форме. При этом если вы назовете, накиданную группу компонентов (стиль), используя StyleName (как это вы делаете в дизайнере стилей), то вы спокойно можете использовать это имя в StyleLookup других компонентах.
Если что, обращайтесь
January 17th, 2013 at 6:08 pm
Спасибо большой за разъяснения, буду иметь ввиду.
Успехов Embarcadero и всем кто пишет на RAD Studio. Как по мне то MS VS куда менее удобная IDE, и очень бы хотелось чтоб к Delphi вернулась его популярность. Раздражает что на тебя вечно косо смотрят, когда узнают что программируешь на Delphi, хотя толком никто не может сказать чем она хуже С++. Но дело не в лучше или хуже, а в том что из-за отсутствия популярности тяжело найти кому нужен программист под Delphi, а так хочется развиваться и расти далее как личность и программист. Как мне кажется для этого правильней было бы найти хорошего наставника, а не пытаться все загуглить, ведь специалиста надо "вырастить", а не "дайте нам с 10-тилетним опытом работы 20-тилетнего специалиста". Так что удачи вам, а она принесет успех остальным:-).
January 17th, 2013 at 6:35 pm
Спасибо за приятные слова.
А тема конкуренции языков слишком большая, да и по мне без толковая. Каждому своё.
Дровосеку главное уметь правильно рубить деревья, знать с каких сторон делать надпилы, чтобы дерево упало куда нужно.
А чем он это будет делать, пилой, топором или бензопилой, как говорил Карлсон - "Дело житейское".
Над Delphi будем стараться