Watch, Follow, &
Connect with Us

Fire Monkey - Yaroslav Brovin

Часть 2. Разработка компонентов под FMX 2.0. Кнопка с картинкой

Уже прошло почти два месяца с вэбинара на тему "Разработка компонентов на FireMonkey", а время летит быстро и незаметно. Скоро новый год, поэтому все не завершенные дела надо оставить в этом году, поэтому реабилитируюсь и пишу эту статью по теме вэбинара.  В этой статье я рассмотрю общие шаги по разработке своего компонента на FMX и в качестве примера рассмотрю разработку компонента TBitBtn - кнопка с картинкой. Этот компонент - аналог кнопки с картинкой взятой из VCL. Но в нашем случае он будет уметь:

  1. Осуществлять легкое переключение между вариантами отображения стандартных кнопок без стирания пользовательской картинки VCL.TBitBtn.Glyph
  2. Легко менять стандартные картинки на свои
  3. Работать со списком изображений, что не умеет делать VCL кнопка с картинкой.

И так приступим…

С чего начать

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

  1. Посмотреть как работают примитивные объекты (TLine, TRectangle, TCircle, TText и другие) - те, из которых строятся все остальные. Попробовать и потрогать их можно в среде IDE. Посмотреть как устроены, можно заглянув в исходный код.
  2. Перейти на простые стилизуемые объекты (TLabel, TCheckBox, TButton) - те, которые не являются объектами контейнерного типа (Меню, списки, выпадающие списки, группы, и тд). Источники вдохновение все те же, что и в пункте 1 + эта статья (как раз здесь будет рассмотрен компонент из этой группы).
  3. Сложные стилизуемые объекты контейнерного типа (TGroupBox, TPanel, TListBox, TTreeView, TComboBox и тд)
  4. Платформозависимые компоненты (TTimer, частично TComboBox, TMainMenu, TMediaPlayer и тд). В себе несут несколько реализаций для разных платформ через использование сервисов FMX

Шаги разработки

1 Шаг: Объектная декомпозиция

Вначале нужно провести объекту декомпозицию. Довольно известный прием проектирования. Вы берете ваш компонент и делите его на логические составляющие (сущности). Затем берете каждую сущность и опять ее делите до тех пор, пока не станет ясными общие детали реализации всех составляющих.

2 Шаг: Принимаем решение о написании компонента с нуля или берем за основу уже существующий компонент?

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

3 Шаг: Разработка представления компонента в терминах полученных сущностей (для визуальных компонентов)

На этом шаге, начать лучше с внешнего вида (стиля) каждой визуальной части компонента.

Почему это лучше сделать до написания самой бизнес логики?

Потому что визуальный компонент - это логика + представление. Логика тесно связана с представлением и составным частях представления. Поэтому не имея составных частей представления, начинать с логики будет тяжелее. Так как придется работать с еще не созданными объектами представления.

4 Шаг: Кодирование

Непосредственно уже написание логики и связывание ее с уже с продуманным стилей. На этом шаге возможно корректировка стиля.

Реализация

Мы хотим сделать, описанную выше, кнопку с картинкой.

1 Шаг: Объектная декомпозиция

Кнопка состоит из трех частей:

  1. Форма
  2. Текст
  3. Картинка


Форма может быть построена из прямоугольника и любой другой геометрической фигуры: будь то круглая кнопка или прямоугольная, или прямоугольная с закругленными углами.

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);
  1. bkCustomGlyph - Картинка для кнопки берется из TBitBtn.Glyph
  2. bkStylizedGlyph - Картинка для кнопки берется из StyleBook по имени стиля
  3. Остальные - Картинка для кнопки берется из текущего стиля по заранее заданному имени стиля
Если первый вариант отображения, хорошо известен из VCL. То второй нужно пояснить.

В 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) |



8 Responses to “Часть 2. Разработка компонентов под FMX 2.0. Кнопка с картинкой”

  1. Vsevolod Leonov Says:

    Что означает цифра "64" на картинке, которая есть иконка на кнопке?

  2. Yaroslav Brovin Says:

    Ничего кроме просто картинке. На этом месте могла быть любая другая картинка.

  3. Edward Says:

    Добрый день, Ярослав, при просмотре вебинара меня очень сильно заинтересовала одна особенность, Ваш только что описанный класс налету был подхвачен инспектором объектов, собственно это меня и удивило. Сам я написанием своих компонентов не занимался, только читал у Михаила Флёнова как это делается, но мною написанные компоненты могли быть изменены только программно (компонент был просто описан в отдельном модуле). Собственно сам вопрос, а точнее их два: или это я не правильно что-то делал пытаясь создать свой компонент, или же это возможности ХЕ3? Просто часто бывает что для именно этого проекта нужно сделать из нескольких компонентов один, но описывать все исключительно программно долго и неудобно, из-за этого приходится выдумывать другие менее удобные способы реализации интерфейса.

  4. ybrovin Says:

    Добрый День. Поясните, пожалуйста, что вы имеете в виду под "описанный класс налету был подхвачен инспектором объектов"?

    Вы имеете в виду, что созданная кнопка, сразу появилась в Tool Palette среды?

  5. Edward Artjuh Says:

    Прошу прощения, подумал если об установке речи не шло, то достаточно описать его программно и создать ему стиль, а инсталлировать компонент не обязательно, но когда добрался до IDE и посмотрел исходники понял что был не прав, каюсь =)

  6. ybrovin Says:

    Увы,

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

    Если же мы делаем компонент прямо на форме (составляем из компонентов), то в этом случае его устанавливать не нужно. Но при этом из среды не будут доступны пользовательские свойства (Glyph, GlyphKind и тд.)

    Кстати, если нам нужно сделать только стиль для компонента, то иногда это удобнее делать не в дизайнере стилей, а прямо на форме. При этом если вы назовете, накиданную группу компонентов (стиль), используя StyleName (как это вы делаете в дизайнере стилей), то вы спокойно можете использовать это имя в StyleLookup других компонентах.

    Если что, обращайтесь :-)

  7. Edward Artjuh Says:

    Спасибо большой за разъяснения, буду иметь ввиду.
    Успехов Embarcadero и всем кто пишет на RAD Studio. Как по мне то MS VS куда менее удобная IDE, и очень бы хотелось чтоб к Delphi вернулась его популярность. Раздражает что на тебя вечно косо смотрят, когда узнают что программируешь на Delphi, хотя толком никто не может сказать чем она хуже С++. Но дело не в лучше или хуже, а в том что из-за отсутствия популярности тяжело найти кому нужен программист под Delphi, а так хочется развиваться и расти далее как личность и программист. Как мне кажется для этого правильней было бы найти хорошего наставника, а не пытаться все загуглить, ведь специалиста надо "вырастить", а не "дайте нам с 10-тилетним опытом работы 20-тилетнего специалиста". Так что удачи вам, а она принесет успех остальным:-).

  8. ybrovin Says:

    Спасибо за приятные слова.

    А тема конкуренции языков слишком большая, да и по мне без толковая. Каждому своё.

    Дровосеку главное уметь правильно рубить деревья, знать с каких сторон делать надпилы, чтобы дерево упало куда нужно.
    А чем он это будет делать, пилой, топором или бензопилой, как говорил Карлсон - "Дело житейское".

    Над Delphi будем стараться :-)

Leave a Comment



Server Response from: BLOGS1