Watch, Follow, &
Connect with Us

Fire Monkey - Yaroslav Brovin

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

Наконец-то закончилось предрелизное время и теперь можно, отложив все дела, сдать должок - "Кликабельная метка". Здесь можно посмотреть запись с вебинара. Перейдем сразу к делу.

Постановка задачи

Необходимо расширить стандартную метку, известную как "TLabel", добавив возможность открытия ссылки в штатном браузере.

Проблема

Задача довольно классическая и простая с использованием FireMonkey, но имеет один нюанс. В нашем случае мы обязаны мыслить в условиях кроссплатформенности (только для компонентов, имеющих спецефичную функциональность, зависящую от операционных систем - запуск процесса, открытие ссылки и тд.). Метка должна уметь открывать ссылку в любом браузере и под любой, поддерживаемой операционной системе (Win32, Win64, OSX).

Решение

Решение будет состоять из двух частей:

  1. Реализация части компонента не зависящей от операционных систем (вывод текста, настройки, шрифты и тд)
  2. Реализация платформозависимой части (запуск системного процесса на открытие ссылки в браузере)

Общая часть

По сути наш компонент является функциональным расширением существующего компонента TLabel. Поэтому не будем реализовывать метку с нуля, а всего лишь отнаследуемся от TLabel. Назовем наш класс TCustomLinkedLabel.

Дополнительно в наш класс нужно добавить поля для хранения:

  1. Строку с URL - FUrl
  2. Цвет метки, когда ссылка не посещена - FColor
  3. Цвет метки, когда ссылка выделена - FHoverColor
  4. Цвет метки, когда ссылка посещена - FVisitedColor
  5. Флаг, Была ли ссылка посещена? - FVisited

В нашем случае наша метка не требует изменения стиля, поэтому за основу будет взят стандартный стиль. Для этого не забываем переопределить метод TCustomLinkedLabel.GetDefaultStyleLookupName и вернуть название стиля по умолчанию:

function TCustomLinkedLabel.GetDefaultStyleLookupName: string;
begin
  Result := 'labelstyle';
end;

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

procedure TCustomLinkedLabel.DoMouseEnter;
begin
  inherited;
  Cursor := crHandPoint;
  Repaint;
end;
procedure TCustomLinkedLabel.DoMouseLeave;
begin
  inherited DoMouseLeave;
  Cursor := crDefault;
  Repaint;
end;

Теперь перейдем к непосредственно самой отрисовки метки. На не нужно ее рисовать, по скольку это делегируется компоненту TLabel. Но нам необходимо выполнить только задание нужно цвета перед отрисовкой. Для этого в перекрытом методе TCustomLinkedLabel.Paint выполняем задание цвета через TextSettings.FontColor. Это новая структура для задания настроек выводимого текста:

procedure TCustomLinkedLabel.Paint;
begin
  if IsMouseOver then
    TextSettings.FontColor := FHoverColor
  else
  if FVisited then
    TextSettings.FontColor := FVisitedColor
  else
    TextSettings.FontColor := Color;
  inherited Paint;
end;

Осталось описать только открытие ссылки. Немного забегая вперед я напишу код, который раскрою позже:

procedure TCustomLinkedLabel.Click;
begin
  if Assigned(FLaunchService) then
  begin
    FLaunchService.OpenURL(Url);
    FVisited := True;
  end;
end;

Вот и все. Теперь можно приступить к платформозависимой части.

Платформозависимая часть

Все платформозависимые части FireMonkey реализуются с использованием понятия сервис. Общую информацию о том, что такое сервисы, как они устроены и как их получить, можно прочитать тут в разделе "Сервисы". А я лишь коротко скажу, что для нас сервис - это интерфейс + несколько реализаций этого интерфейса под разные платформы. Поэтому необходимо все платформозависимые функции выделить в отдельный интерфейс. У нас такая функция всего одна: "Открыть ссылку в браузере по умолчанию". Не забываем задать GUID для нашего интерфейса сервиса. В RadStudio его можно сгенерировать автоматически, используя комбинацию клавиш Ctrl + Shift + G.

IFMXLaunchService = interface
  ['{3C0888FA-61DD-4B76-9F0E-1956E6E08E86}']
  function OpenURL(const AUrl: string): Boolean;
end;

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

FLaunchService: IFMXLaunchService;

Так же не забываем, что сервис не обязательно реализуется под все платформы. И может выйти так, что сервис не реализуется в приципе на каких-то платформах. Поэтому обязательно добавляем проверка на его наличие:

if Assigned(FLaunchService) then
begin
  FLaunchService.OpenURL(Url);
  FVisited := True;
end;

Все сервисы FMX централизованно хранятся в хранилище TPlatformServices.Current. Каждый имеет право запросить реализацию любого сервиса и даже зарегистрировать там свой. Что мы и сделаем чуть позже. А пока, сервис запрашивается следующей строчкой кода:

TPlatformServices.Current.SupportsPlatformService(IFMXLaunchService, IInterface(FLaunchService));

Если реализации сервиса нет, то этот метод вернет нам nil. Если есть, то под интерфейсом FLaunchService будет скрываться реализация. Как видите, это позволяет полностью отделить наш компонент от нижнего уровня, зависимого от платформы.

Теперь можно перейти к реализации сервисов под разные платформы и посмотреть, как они регистрируются.

Реализация сервиса под Windows

Согласно правилу хорошего тона, основной компонент мы разместим в юните: FMX.LinkedLabel. Тогда как все реализации зависимые от платформы располагаются в файлах, полученных путем добавления суффикса платформы к окончанию названия базового юнита:

  1. Для Windows - FMX.LinkedLabel.Win
  2. Для OSX - FMX.LinkedLabel.OSX
Реализация сервиса под Windows довольно банальна:
type
TWinLaunchService = class (TInterfacedObject, IFMXLaunchService)
public
{ IFMXLaunchService }
function OpenURL(const AUrl: string): Boolean;
end;
{ TWinLaunchService }
function TWinLaunchService.OpenURL(const AUrl: string): Boolean;
begin
Result := ShellExecute(0, 'open', PChar(AUrl), nil, nil, SW_SHOWNORMAL) = NO_ERROR;
end;

Мы используя Winapi.ShellApi открываем ссылку.
Регистрацию нашего сервиса в системе осуществляется в секции инициализации следующим способом:

TPlatformServices.Current.AddPlatformService(IFMXLaunchService, TWinLaunchService.Create);

Реализация сервиса под OSX

Аналогично делается и для OSX

type
 TMacLaunchService = class (TInterfacedObject, IFMXLaunchService)
 public
   { IFMXLaunchService }
   function OpenURL(const AUrl: string): Boolean;
 end;
{ TWinLaunchService }
function TMacLaunchService.OpenURL(const AUrl: string): Boolean;
var
  Workspace: NSWorkspace;
  Url: NSURL;
begin
  Workspace := TNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);
  Url := TNSUrl.Wrap(TNSUrl.OCClass.URLWithString(NSStr(AUrl)));
  Result := Workspace.openURL(Url);
end;
initialization
  TPlatformServices.Current.AddPlatformService(IFMXLaunchService, TMacLaunchService.Create);
end.

Подключение разных реализаций сервисов

Осталось рассмотреть только, как теперь нам совместить в одном проекте реализации сервиса под разные платформы. Это делается просто с использование директив условной компиляции, следующим образом:

uses
  FMX.Platform
{$IFDEF MSWINDOWS}
  , FMX.LinkedLabel.Win
{$ELSE}
  {$IFDEF MACOS}
  , FMX.LinkedLabel.Mac
  {$ENDIF}
{$ENDIF};

Заключение

Домашнее задание: Реализовать сервис под ios после релиза мобильной студии.

Posted by ybrovin on April 16th, 2013 under Firemonkey (RUS) | Comment now »


Часть 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 Comments »


FMX 2.0. Global change default platform style without using TStyleBook

Small article about how globally to replace or add standard platform styles in FireMonkey 2.0 without use additional date of modules and TStyleBook.

I think many faced some questions:

  1. How i can add style to standart style?
  2. How i can replcae default platform style for Windows and Mac OS
  3. And etc

The task often meets and has the simple decision. But to tell about it follows, as I didn’t find the sounded decisions. A situation I correct :-)

It is a little theory

Firemonkey already has some platform’s styles resources. It consists:

  1. Style for Windows 7 - win7style
  2. Style for Windows 8 - win8style
  3. Style for Mac OS - lionstyle
  4. Style for Mac OS with Retina display -lion2xstyle

To the bold are specified the name of an appropriate resource of style. To replace platform style, it is enough to include in the project the file of style with one of the specified names of resources (win7style, win8style, lionstyle, lion2xstyle)

Decision

Now we will try in practice.
1. Open fire monkey project. I used demo project ControlsDemo from XE3 samples

2. Go to "Project -> Resources and Images…"

3. Append our style for Windows 7 (for example Dark.style)

4. Build and run project.

Postcondition

If you want to replace styles for all platforms, respectively it is necessary for you to add in the project resources of files of styles for all registered platforms (win7style,win8stylelionstylelion2xstyle)

For example, when adding in the resource project for MacOS without Retina support.

Application becomes such:

Posted by ybrovin on October 29th, 2012 under Firemonkey (ENG) | 1 Comment »


FMX 2.0. Глобальная замена платформенного стиля по умолчанию без TStyleBook

Небольшой пост о том, как глобально заменить или дополнить стандартные платформенные стили в FireMonkey 2.0 без использования дополнительных дата модулей и TStyleBook’ов.

Думаю многие сталкивались с некоторыми вопросами:

  1. Как мне дополнить стандартный стиль?
  2. Как заменить стандартные стили для Windows и Mac OS
  3. и тд
Задача часто встречающаяся с простым решением. Но рассказать об это следует, поскольку озвученных решений я не нашел. Ситуацию исправляю :-)

Немного теории

В ресурсы FireMonkey уже включены наборы платформенных стилей. Сюда входят:

  1. Стиль для Windows 7 - win7style
  2. Стиль для Windows 8 - win8style
  3. Стиль для Mac OS - lionstyle
  4. Стиль для дисплея с поддержкой Retina для Mac OS Retina -lion2xstyle

Жирным указаны название соответствующего ресурса стиля. Чтобы заменить платформенный стиль, достаточно включить в проект свой файл стиля с одним из указанных названий ресурсов (win7style, win8style, lionstyle, lion2xstyle)

Решение

Теперь попробуем на практике.
1. Открываем какой-нибудь проект FireMonkey. Я использовал дэмо проект ControlsDemo, входящий в поставку Delphi XE3

2. Идем в "Project -> Resources and Images…"

3. Добавляем свой стиль для Windows 7

4. Собираем проект и смотрим.

Постусловие

Если вы хотите заменить стили для всех платформ, то вам соответственно необходимо в проект добавить ресурсы файлов стилей для всех зарегистрированных платформ (win7style,win8stylelionstylelion2xstyle)

Например, при добавлении в проект ресурса для мака без поддержки Retina.

Приложение на маке примет следующий вид.

Posted by ybrovin on October 29th, 2012 under Firemonkey (RUS) | 1 Comment »


Physical Engine + Firemonkey 2.0

There will be small article about how it to make later.

Posted by ybrovin on October 19th, 2012 under Firemonkey (ENG), Firemonkey (RUS) | 4 Comments »


TListBox в FireMonkey. Создание своих стилизованных итемов для TListBox. 2 Часть

В первой части статьи (http://blogs.embarcadero.com/yaroslavbrovin/2012/10/11/listboxitem_styling/) я рассмотрел способ создания своих итемов для TListBox с использование события TListBox.OnApplyStyleLookup. В этой части мы рассмотрим другой способ, основанный на расширении функциональности TListBoxItem. Этот способ я считаю более удобным. Поскольку передаваемые данные в стиль будут храниться не во внешнем хранилище, а прямо в итемах лист бокса.

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

Ниже представлен интерфейс нашего примера.

Описание примера смотрите в предыдущей статье (http://blogs.embarcadero.com/yaroslavbrovin/2012/10/11/listboxitem_styling/).

Скачать исходный код проекта можно тут: http://cc.embarcadero.com/item/29080

Теория

Пара слов о реализации

  1. Мы создаем отдельный класс итема TMyListBoxItem отнаследовавшись от TListBoxItem.
  2. Данные для стиля храним в нашем классе итема TMyListBoxItem.
  3. В момент загрузки стиля TStyledControl.ApplyStyle вытаскиваем составные части стиля  и передаем их в стиль.

Пара слов о работе со стилем с точки зрения TStyledControl

Как мы уже говорили, TStyledControl добавляет в TControl функции по работе со стилями.

Наиболее важные для нас методы, предоставляемые нам TStyledControl:

  1. TStyledControl.ApplyStyle - Момент загрузки стиля. Стиль загружен.
  2. TStyledControl.FreeStyle - Момент выгрузки стиля.
  3. TStyledControl.GetDefaultStyleLookupName - Получение название стиля для контрола по умолчанию (если не указано в StyleLookupName).
  4. TStyledControl.FindStyleResource(const AStyleLookup: string) - Найти в текущем стиле объект с StyleName = AStyleLookup.
  5. TStyledControl.ApplyStyleLookup - Применить стиль. Если стиль не менялся и уже применен (TStyledControl.FNeedStyleLookup), то метод не приведет к обновлению стиля.
  6. TStyledControl.FResourceLink - ссылка на объект стиля

Что означает применением стиля?

В момент отрисовки контрола, происходит применение стиля. Стиль применяется, если стиль еще не применен (TStyledControl.FNeedStyleLookup). Применение стиля заключается в поиске подходящего стиля для контрола (TStyledControl.GetStyleObject). Приоритеты мест поиска стиля следующие:

  1. Поиск в текущем TStyleBook формы.
  2. Поиск активного стиля сцены TStyleManager (Обычный стиль или стиль высокого разрешения Retina).
  3. Поиск в платформенном стиле. Стиль применяемый по умолчанию на данной платформе.
Если StyleLookup не указан, то наименование стиля вычисляется в следующем порядке:
  1. Вначале проверяется наличие стиля по TStyledControl.DefaultStyleLookupName
  2. Если стиль с именем DefaultStyleLookupName не найден, то формируется дефолтное имя стиля: <Имя класса без T> + style

Реализация

Как я уже описал, создаем свой класс от TListBox. В нем будем хранить наши данные ({ Item’s data}) и ссылки на объекты стиля ({ Style parts }).

{ TMyListBoxItem }

  TMyListBoxItem = class (TListBoxItem)
  strict private
    { Item's data }
    FInProgress: Boolean;
    FProgress: Byte;
    FColor: TAlphaColor;
    FButtonClick: TNotifyEvent;
    { Style parts }
    FIndicator: TAniIndicator;
    FButton: TControl;
    FProgressBar: TProgressBar;
    FRectangle: TRectangle;
  private
    procedure SetButtonClick(const Value: TNotifyEvent);
    procedure SetColor(const Value: TAlphaColor);
    procedure SetInProgress(const Value: Boolean);
    procedure SetProgressValue(const Value: Byte);
  protected
    procedure ApplyStyle; override;
    procedure FreeStyle; override;
    function GetDefaultStyleLookupName: string; override;
    procedure UpdateStyleData; virtual;
  published
    property InProgress: Boolean read FInProgress write SetInProgress;
    property ProgressValue: Byte read FProgress write SetProgressValue;
    property Color: TAlphaColor read FColor write SetColor;
    property ButtonClick: TNotifyEvent read FButtonClick write SetButtonClick;
  end;

Повнимательнее рассмотрим реализацию ключевых методов. В ApplyStyle мы ищем в стиле нужные составные части стиля. Для поиска использует FindStyleResources. Поскольку стиль не зависим и может не иметь определенных стилей, то необходимо выполнять проверку по доступности стиля и классовому соответствию. После накатывания стиля, UpdateStyleData.

procedure TMyListBoxItem.ApplyStyle;
var
  StyleObject: TFmxObject;
begin
  inherited ApplyStyle;

  // Ищем в текущем стиле ProgressBar и проверяем, что это TProgressBar
  StyleObject := FindStyleResource('progress');
  if Assigned(StyleObject) and (StyleObject is TProgressBar) then
    FProgressBar := StyleObject as TProgressBar;

  StyleObject := FindStyleResource('button');
  if Assigned(StyleObject) and (StyleObject is TControl) then
    FButton := StyleObject as TControl;

  StyleObject := FindStyleResource('aniindicator');
  if Assigned(StyleObject) and (StyleObject is TAniIndicator) then
    FIndicator := StyleObject as TAniIndicator;

  StyleObject := FindStyleResource('rectangle');
  if Assigned(StyleObject) and (StyleObject is TRectangle) then
    FRectangle := StyleObject as TRectangle;

  UpdateStyleData;
end;

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

procedure TMyListBoxItem.FreeStyle;
begin
  FIndicator := nil;
  FButton := nil;
  FProgressBar := nil;
  FRectangle := nil;
  inherited FreeStyle;
end;

По умолчанию, стиль этого класса будет называться MyListBoxItemStyle. Чтобы задать свое название стиля по умолчанию, используем метод GetDefaultStyleLookupName.

function TMyListBoxItem.GetDefaultStyleLookupName: string;
begin
  Result := 'customstyle1';
end;

И напоследок реализации TMyListBoxItem код установки данных в стиль:

procedure TMyListBoxItem.UpdateStyleData;
begin
  if Assigned(FRectangle) then
    FRectangle.Fill.Color := FColor;

  if Assigned(FProgressBar) then
    FProgressBar.Value := FProgress;

  if Assigned(FButton) then
    FButton.OnClick := FButtonClick;

  if Assigned(FIndicator) then
    FIndicator.Enabled := FInProgress;
end;

На этом наш собственный итем реализован. Осталось только переписать код для заполнения лист бокса.

procedure TFormMain.CreateListBoxItem(const AStyleName: string);
var
  Item: TMyListBoxItem;
begin
  Item := TMyListBoxItem.Create(Self);
  Item.Parent := ListBox;
  Item.StyleLookup := AStyleName;
  Item.Text := 'Item ' + IntToStr(LastItemId);
  Item.IsChecked := Random(2) = 1;
  Item.ProgressValue := Random(100);
  Item.InProgress := Random(2) = 1;
  Item.Color := MakeColor(Random(255), Random(255), Random(255), 255);
  Item.ButtonClick := ActionItemClick.OnExecute;

  Inc(LastItemId);
end;

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

Скачать исходный код проекта можно тут: http://cc.embarcadero.com/item/29080

Posted by ybrovin on October 15th, 2012 under Firemonkey (RUS) | 2 Comments »


TListBox в FireMonkey. Создание своих стилизованных итемов для TListBox. 1 часть

Компонент TListBox обладает большими возможностями по стилизации своих итемов (элементов). О том, как сделать так же, как на приведенном ниже рисунке, и пойдет речь в этой статье.

Введение

В этом примере будет продемонстрирован один из способов создания своих итемов для ListBox и заполнение их своими данными. В примере буду представлены три типа итемов: по умолчанию и два своих.

Ссылка на архив проекта: http://cc.embarcadero.com/Item/29075

Немного теории

Пара слов о загрузке стилей

Из моей предыдущей статьи вы узнали, что стиль – это по сути иерархия оъектов прикрепленная к стилизуемому компоненту. Как только стиль загружен (иерархия объектов прикреплена к компоненту) – сразу приходит оповещение об этом через событие TStyledControl.OnApplyStyleLookup, компонент получает полный доступ к объектам стиля и, пользуясь этим, задает им нужные значения.

Так же я упоминал, о процессе смены стиля. При смене стиля, прикрепленная иерархия стиля удаляется из ветки компонента, а на ее место вставляется новый стиль. Естественно, что при смене стиля, компоненту нужно заново проинициализировать объекты стиля нужными значениями.

Например, при загрузке стиля кнопки, кнопке нужно указать свое название в соответствующий объект TText у стиля.
При смене стиля, нужно это сделать повторно.

Эта информация понадобиться нам для понимания процесса передачи своих данных в стиль ListBoxItem.

Пара слов о работе TListBox

Представьте себе, например, лист бокс с 10 000 элементов. Для каждого элемента нужно загрузить свой стиль. Это требует большого объема дополнительной памяти для хранения каждого стиля в памяти. Однако, реально мы можем видеть только малую часть из всех элементов.

Так зачем же нам грузить стили для всех элементов?

Правильно, не за чем. Именно по этому наш TListBox может иметь сколь угодно большое число элементов, однако стили будут только у видимой части элементов. Это сокращает в разы расход память, однако дает нам дополнительную задачу о передачи данных элементов (TListBoxItem.Text, TListBoxItem.isChecked) в объекты стилей. Для решения этой задачи каждый TListBoxItem хранит в себе значение Text и isChecked. Поэтому при загрузке стиля, он их проставляет в стиль в момент загрузки стиля TListBoxItem.ApplyStyle.

А как быть, если мы хотим задать в стиль свои данные?

Например, хотим использовать TListBox как средство отображения загружаемых файлов. Включить в стиль TProgressBar и отображать в нем процесс загрузки файла.

На этот вопрос есть два способа. Отличающиеся только местом хранения данные для последующей передачи их в стиль.

1 способ. Простой.

Мы храним наши данные у себя в моделе.

Например. Храним в моделе список файлов, их состояния загрузки (загружен или нет) и размеры загруженных частей.
Далее в обработчике событий TListBoxItem.OnApplyStyleLookup мы передаем эти данные в соответствующие элементы стиля.

2 способ. Посложнее.

Мы создаем отдельный класс итема отнаследовавшись от TListBoxItem, храним наши данные там и в момент загрузки стиля TStyledControl.ApplyStyle передаем их в стиль. Таким способом сделан, например, TMetropolisUIListBoxItem.

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

Практика

Вначале сделаем набросок формы, как на приведенном ниже рисунке. Самое главное для нас – это непосредственно сам TListBox и хранилище наших стилей TStyledBook.

На этой форме опции группы “ListBox options”, позволяют менять некоторые настройки лист бокса:

  1. Прокрутка итемов с использованием мышки, а не с использованием ScroolBox.
  2. Многоколоночное отображение.
  3. Отображение и скрытие CheckBox у итемов.

Группа контролов “Action” позволяет нам заполнить лист бокс итемами с выбранным стилей.

Создание стилей

В этом примере мы создадим два своих стиля для отображения своих данных в элементах ListBox. На каждом из них помимо стандартных данных (Текст и чекбокс), будут еще: индикатор загрузки (TAniIndicator), полоса состояния (TProgressBar), кнопка (TButton) и прямоугольник для отображения цвета (TRectangle). Пример этих стилей приведен ниже.

Для создания стиля, открываем дизайнер стилей (двойное нажатие на TStyleBook ). В панели “Structure” отображаются текущие стили выбранного StyleBook.

Создание стилей производится путем перетаскивания нужных компонентов с “Tool Palette” в дерево стилей на панели “Structure”. Принцип создание стиля такой же, как создание интерфейса формы.

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

Чтобы задать имя стиля, нужно использовать свойство TStyledControl.StyleName.

Из доступных компонентов я набросал два стиля (См. рисунки ниже).

Используя свойство StyleName у TLayout верхнего уровня, я условно назвал стили CustomStyle1 и CustomStyle2.

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

Примечание: Свойство Name не используется для поиска составных частей стиля.

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

  1. TCheckBox.styleName = check
  2. TText.styleName = text
  3. TAniIndicator.styleName = aniindicator
  4. TButton.styleName = button
  5. TProgressBar.styleName = progress
  6. TRectangle.styleName = rectangle

Стили почти готовы. Осталось только разобраться со свойством HitTest. Поскольку стиль встраивается в контрол, то все события перемещения мыши, фокус, и тд. в начале перехватываются контролами непосредственно лежащие ближе к нам (по оси Z), а именно контролы стиля. А затем, если они не могут их обработать, то события спускаются вниз до тех пор, пока кто-нибудь их не обработает. HitTest отвечает за то, будет ли контрол перехватывать события мыши или их стоит передать родительскому контролу.

Поскольку для реализации прокрутки элементов TListBox требуется спустить событие мыши до TListBox, то соответственно всем котролам стиля объекта TListBoxItem, не используемым события мыши, нужно проставить HitTest = False.

Например: Кнопке нужны события мыши, потому что благодаря им, она перехватывает события клика кнопки.
А вот TText не требуется события мыши, потому как этот объект в нашем случае только отображает данные.

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

Реализация

Для хранения наших данных элементов, я создал запись следующего вида:

TListItemData = record
  Text: string;
  Checked: Boolean;
  InProgress: Boolean;
  Progress: Byte;
  Color: TAlphaColor;
  Click: TNotifyEvent;
end;

В качестве хранилища данных выбрал словарь (“Идентификатор элемента – его данные”). Идентификатор нам понадобиться для дальнейшего получения данных для ListBoxItem:

FItemsData: TDictionary;

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

procedure TFormMain.GenerateItemsData(const ACount: Integer);
var
  I: Integer;
  ItemData: TListItemData;
begin
  for I := 1 to ACount do
  begin
    Inc(LastItemId);
    ItemData.Text := 'Item ' + IntToStr(LastItemId);
    ItemData.Checked := Random(2) = 1;
    ItemData.Progress := Random(100);
    ItemData.InProgress := Random(2) = 1;
    ItemData.Color := MakeColor(Random(255), Random(255), Random(255), 255);
    ItemData.Click := ActionItemClick.OnExecute;

    FItemsData.Add(LastItemId, ItemData);
  end;
end;

Теперь процедура динамического создания итемов и передачи наших данных в составные части стиля для лист бокса:

procedure TFormMain.CreateListBoxItem(const AItemKey: Integer; const AStyleName: string);
var
  Item: TListBoxItem;
Begin
  // Создаем элемент
  Item := TListBoxItem.Create(Self);
  // Вставляем его в листбокс
  Item.Parent := ListBox;
  // Через свойство Tag передаю ключ записи из нашего хранилища.
  // В дальнейшем через него мы получим доступ к данным хранилища FItemsData
  Item.Tag := AItemKey;
  // Подвешиваем обработчик, в котором будет происходить непосредственно
  // передача наших данных в составные части стиля
  Item.OnApplyStyleLookup := DoItemApplyStyle;
  // Указываем имя стиля
  Item.StyleLookup := AStyleName;
end;

procedure TFormMain.DoItemApplyStyle(Sender: TObject);
var
  Item: TListBoxItem;
  ItemKey: Integer;
  StyleObject: TFmxObject;
  AniIndicator: TAniIndicator;
  Rectangle: TRectangle;
begin
  Assert(Sender is TListBoxItem, 'This handler is not intended for other objects. Only for TListBoxItem');

  if not (Sender is TListBoxItem) then
    Exit;
  // Вытаскиваем TListBoxItem, в который загружен стиль, и ключ для получения данных из FItemsData
  Item := Sender as TListBoxItem;
  ItemKey := Item.Tag;
  // TListBoxItem сам хранит значения (текста и CheckBox) и предоставляет нам
  // свойства Text, IsChecked для их задания.
  Item.Text := FItemsData[ItemKey].Text;
  Item.IsChecked := FItemsData[ItemKey].Checked;
  // StyledData – это один из способов передачи данных в указанный элемент стиля (через StyleName),
  // используемый механизм RTTI. Фактически внутри идет проверка типа передаваемого значения
  // TValue и в соответствии с ним происходит задание предопределенных свойств.
  // Отмечу, что этим способом можно задать значения только для некоторых свойств.
  Item.StylesData['progress'] := TValue.From(FItemsData[ItemKey].Progress);
  Item.StylesData['button'] := TValue.From(FItemsData[ItemKey].Click);

  // Этот способ самый универсальный.
  // Метод FindStylResource ищет в стиле объект с указанным stylename.
  StyleObject := Item.FindStyleResource('aniindicator');
  if Assigned(StyleObject) and (StyleObject is TAniIndicator) then
  begin
    AniIndicator := StyleObject as TAniIndicator;
    AniIndicator.Enabled := FItemsData[ItemKey].InProgress;
  end;

  StyleObject := Item.FindStyleResource('rectangle');
  if Assigned(StyleObject) and (StyleObject is TRectangle) then
  begin
    Rectangle := StyleObject as TRectangle;
    Rectangle.Fill.Color := FItemsData[ItemKey].Color;
  end;
end;

Вот в принципе и все важные детали. Остальное смотрите в примере, который можно скачить от сюда http://cc.embarcadero.com/Item/29075 Смотрите, пробуйте, спрашивайте.

Posted by ybrovin on October 11th, 2012 under Firemonkey (RUS) | 5 Comments »


Pickers in FireMonkey. What are they, what they are necessary for, and how to use them?

Here we discuss the Picker control elements–what are they, what they are necessary for, and how to use them to program cross-platform UI elements, which behavior depends upon the used platform. Pickers are available in FireMonkey 2.0.

Introduction

Each platform has its own methodology of UI and its own set of available services. They both dictate the general principles of the user interaction with the UI and define the available sets of such UI control elements as buttons, switches, drop-down lists, and others.

To demonstrate, let us compare –how to look native date-selection controls under different platforms:

Windows 7: The ordinary Calendar.

Date selection under Windows OSs

Date selection under Windows OSs

Mac OS: The Calendar has almost the same control elements as under Windows. The main difference is only in presentation of controls.

Date selection under Mac Lion

Date selection under Mac Lion

iOS: Provides absolutely new way of date selection with the rotating wheels.

Date selection under iOS with the rotating wheel controls

Date selection under iOS with the rotating wheel controls

Problem

What can we do when a control not only has to look like a ‘native’ under each OS, but also have to provide the proper behavior? One can use Pickers that are provided with FireMonkey 2.0 framework.

Solution

Pickers provide the platform-specific engine giving controls with a different view and different behavior under different platforms.

In RAD Studio XE3, the FireMonkey 2.0 framework provides two types of pickers:

  1. DateTimePicker - the platform-specific control supporting selection of date (and time under iOS).
  2. ComboBoxPicker - the platform-specific control supporting selection of data from a drop-down list.

Pickers are activated on different platforms using the new IFMXPickerService service. (About usage of service approach you can read in the Darren Kosinski’s blog "FireMonkey 2 Under The Hood Changes: PlatformServices" http://blogs.embarcadero.com/darrenkosinski/2012/10/01/51/.)

How to use Pickers?

Components, which need to use the platform-specific behavior for date (and time) selection and for selection of data from a drop-down list, can use the IFMXPickerService service.

To use the IFMXPickerService service:

1. Check whether the required service is supported by the current platform (this trick can be used to check whether any service is supported by a platform):

var
  PickerService: IFMXPickerService;
begin
  if PlatformServices.Current.SupportsPlatformService(IFMXPickerService, IInterface(PickerService)) then
    raise Exception.Create(‘Picker Service does not support’);

2. Request for the required picker and pass to it the appropriate parameters and event handlers:

2.1. For the date (and time) selection:

procedure ShowComboBoxPicker(AParentControl: TControl; AValues: TStrings; const AItemIndex: Integer;
  const ACountVisibleItem: Integer; AOnChange: TOnValueChanged;
  AOnClose: TOnClosePicker; AOnShow: TNotifyEvent);

2.2. For the data selection from the drop-down list:

procedure ShowDateTimePicker(AParentControl: TControl; const ADate: TDateTime;
  const AOptions: TCalendarViewOptions; AOnChange: TOnDateChanged;
  AOnClose: TOnClosePicker; AOnShow: TNotifyEvent);

For example, look how ShowComboBoxPicker is used in the FMX.ListBox.TCustomComboBox.DropDown method to show the control providing native behavior and native view under all supported platforms.

Results

These pickers provide the platform-specific behavior of corresponding control elements. For example, FMX.ListBox.TComboBox under Windows 8 centers a drop-down list on the selected element.

Under Windows 8 a drop-down list centers on the selected element.

Under Windows 8 a drop-down list centers on the selected element.

In order to make this behavior effective, you need:

  1. Set ddkNative to the TComboBox.DropDownKind property.
  2. The TStyleDescriptor property should provide the [METROPOLISUI] style. The PlatformTarget property defines platforms to which this style can be implemented. If you do not specify this style, them the selection control works as standard (Win7, MacOS) drop-down list without centering on the selected element.
"]Описание стиля должно иметь значение [METROPOLISUI]
Posted by ybrovin on October 11th, 2012 under Firemonkey (ENG) | 2 Comments »


Часть 1. Разработка компонентов под FMX 2.0. Обзор архитектуры. Первое приближение

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

Ниже представлена диаграмма основных частей библиотеки.

Основные части FMX 2.0

Основные части FMX 2.0

Я условно поделил все составные части на два слоя:

  1. Верхний уровень. Слой готовых компонентов, предоставляемых пользователю для создания пользовательских приложений.
  2. Слой платформы. Стэк базовых классов, сервисы, графика, анимация, жесты, сенсоры, эффекты и стили, используемые для построения всех компонентов верхнего уровня.

1. Слой компонентов для построения UI

Верхний слой – это слой компонентов, предназначенных для построения пользовательского 2D и 3D интерфейса. Включает в себя двухмерные элементы управления, аниматоры, стили, сенсоры, таймеры, эффекты, трехмерные объекты, камеры, освещение, текстуры и материалы и многие другие компоненты. Со списком всех компонентов этого слоя вы можете познакомиться на панели инструментов в среде Rad Studio Delphi XE3.

На рисунке слева изображена часть 2D элементов управления. А справа форма с 3D компонентами.

Каждый компонент этого уровня построен только с использованием стека классов, предоставляемого слоем платформы. Поэтому не имеет прямого доступа к низкоуровневым библиотекам операционных систем (WinApi, D2D, GDI+, MacApi и др.). Как вы понимаете это сделано с целью поддержки кроссплатформенности и с целью относительно легкого добавления новой платформы. Добавление новой платформы сводиться к реализации слоя платформы с использованием низкоуровневых библиотек, . Однако “относительно легко” – потому что в так же требуется новый компилятор, так и новые обертки низкоуровневых библиотек добавляемой платформы для языка Delphi.

С появлением мобильных устройств, появляются характерные для этих платформ компоненты. Такие как сенсоры, мобильные сервисы: телефоны, камеры, записи и тд. Назовем их сервисами. Однако такие сервисы не обязательно должны присутствовать во всех платформах. Возникает логичная задача, как нам дать пользователю использование всех возможностей целевых платформ по максимуму. Решение этой задачи простое. В Fire Monkey 2.0 появился сервис-ориентированный подход. Суть его проста. Все базовые и специфичные для платформы функции входят в состав какого-нибудь сервиса. В понимании Fire Monkey Сервис – это интерфейс, описывающий набор функций для всех платформы, и его реализации под разные платформы.

Например: Создание и управление родными окнами в каждой операционной системе происходит по разному.
Чтобы отделить специфику создания от верхнеуровневых компонентов, создается интерфейс IFMXWindowService.
Далее этот интерфейс реализуется в каждой операционной системе по-своему. В Windows с использованием WinApi,
в MacOS – MacApi и тд.

Все необходимые условия (события мыши, клавиатуры, окон и многое другое) для работы компонентов верхнего уровня предоставляются нижним.

2. Слой платформы

Слой платформы с точки зрения верхнего уровня является кросплатформенной прослойкой. И он для нас представляет наибольший интерес с точки зрения разработки своих компонентов. Поскольку он дает нам фундамент для построения своих компонетов. Он содержит:

  1. Набор базовых классов для построения пользовательских компонентов 2D и 3D (TFmxObject, TControl, TControl3D, TStyledControl и тд)
  2. Механизм стилизации компонентов (TStyledControl, TStyleBook, TStyleManager, TStyleDescriptor)
  3. Отрисовку графики в 2D и 3D (TCanvas, TBrush, IScene, TFont, TTextLayout, TBitmap, TCanvasManager, TContext3D и тд)
  4. Набор сервисов (TPlatformServices)
  5. Анимацию объектов (TAnimation)
  6. Эффекты ImageFX (TFilterManager, TEffect).
  7. Сенсоры
  8. Поддержку жестов (TGestureManager)
  9. Действия

2.1 Набор базовых классов для построения пользовательских компонентов

Набор базовых классов содержит родительские классы, которые можно использовать для создания своих компонентов. Однако, я бы выделил всего несколько наиболее важных из них:

TFmxObject

Как помните из части 0, это основа для всех компонентов Fire Monkey. Класс дает возможность организовывать иерархическую (древовидную) связь между компонентами с использованием списка дочерних объектов (TFmxObject.Children). Он знает непосредственно все о своих дочерних компонентах на уровне TFmxObject. Дает виртуальные методы по отслеживанию изменения иерархии (но только на уровне, который он непосредственно хранит у себя DoAddObject, DoDeleteObject, DoInsertObject). Умеет сортировать дочерние узлы. Поддерживает методы по выполнению управлению анимации у всех своих дочерних объектов и реализует интерфейс IFreeNotification, предназначенный для оповещения подписавшихся на его уведомления объектов о своем уничтожении. Этот объект является основой для всех компонентов.

TControl

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

TStyledControl

TStyledControl – это TControl с поддержкой стилей. По сути стиль – это дерево из компонентов Fire Monkey, которое можно присоединить к любому объекту TStyledControl. В этом случае создается копия стиля, которая добавляется в дочерние узлы. При этом объект становится составным и выглядит как объекты стиля. TStyledControl может выступать как элемент стиля. В этом случае он именуется через StyleName и может использоваться для стилизации других объектов TStyledControl. Чтобы стилизовать TStyledControl достаточно указать имя стиля через StyleLookup. Этот класс используется для написания своих визуальных компонентов, с поддержкой стилей.

TControl3D

TControl3D – аналог TControl, но для 3D визуальных компонентов.

2.2 Механизм стилизации компонентов

Механизм стилизации в FireMonkey – это не набор “шкурок”, представляющих собой набор растровых картинок, составленных из нарезанных картинок. Fire Monkey дает нам новый подход к стилизации компонентов – представление компонента может быть составлено из любых других компонентов.

Пример: Я продемонстрирую пример создания простого стиля для компонента TGroupBox.

На рисунке стиль для TGroupBox состоит из трех компонентов: прямоугольника и текста. Слой TLayout используется в этом случае для именования стиля через StyleName = groupboxstyle. На картинке ниже, представлено применение этого стиля любому другому компоненту.

До применения стиля объект GroupBox с точки зрения представления имеет только размеры, свое положение в системе координат родительского компонента и всю бизнес логику. Другими словами GroupBox совмещает в себе модель с контроллером.

Стиль применяется через свойство GroupbBox.StyleLookup = GroupBoxStyle. Это свойство дает нам указание на то, какое представление нужно использовать для представления. Далее нужный стиль ищется в текущем стиле приложения, затем создается его копия и включается дочерним объектом в GroupBox. Таким образом представление подключается к компоненту.

Как только стиль загружен, GroupBox имеет доступ ко всем составляющим объектам стиля и может напрямую взаимодействовать с ними. Например, задать заголовок группы, задав заголовок в объект стиля TText.Text. Так же GroupBox информирует представление о произошедших событиях через механизм триггеров. При смене стиля, ветка старого представления удаляется из GroupBox, а новая подгружается. Отсюда следует, что если вы что-то задали объектам стиля, то при смене стиля необходимо это что-то заново задать, но уже для нового стиля.

Подробнее о стилях мы поговорим позже.

2.3 Отрисовка графики в 2D и 3D

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

У каждой формы есть только одна канва, которая передается всем объектам формы. Каждый контрол либо рисует себя сам (как это сделано в наших примитивах: TRectangle, TLine и др.), либо делегирует процесс отрисовки своим дочерним объектам. А поскольку в дочерние объекты могут входить так же и объекты стиля, то таким образом происходит отрисовка стилей у компонентов (TButton, GroupBox и тд).

Отрисовка в Fire Monkey сводится к понятию сцена. Сцена имеет такое же значение, как и в театре. Чтобы показать спектакль, мы за занавесом создаем сцену, затем расставляем декорации. А когда уже все готово, открываем занавес. В FireMonkey мы только не расставляем объекты, а рисуем их. Причем если на сцене в нужном месте уже нарисован объект, то мы не будем его повторно рисовать. Это позволяет нам рисовать только те части сцены, которые требуют перерисовки.

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

Поскольку канва одна, то теоретически каждый объект может нарисовать себя в любом месте. В каких-то случаях это бывает полезно, в каких-то недопустимо. Для управления областью отрисовки служит свойство TControl.ClipChildren. Если ClipChildren = true, то область отрисовки всех дочерних объектов как минимум ограничена размерами родителя. Если же ClipChildren = false, то контролы могут рисовать себя в пределах ограничений верхних по иерархии объектов (если, конечно, эти ограничения есть).

Алгоритм формирования сцены по шагам:

  1. Форма в начале отрисовки получает от всех своих контролов информацию о необходимости обновления того или иного контрола (TControl.UpdateRect).
  2. Объединяет полученные области в один список (PClipRect)
  3. Далее происходит инициализация канвы TCanvas.BeginScene. На этом шаге канве передается системный контекст для отрисовки графики AContextHandle и список областей отрисовки PClipRects, в которых будет происходить отрисовка.
  4. Затем сверху вниз, друг за другом каждый контрол себя рисует TControl.Paint.
  5. Сформированная сцена заканчивается выводом изображения на экран TCanvas.EndScene.

В связи с такой организацией отрисовки, появляется существенное отличие от VCL: рисовать можно только в момент формирования сцены (4 шаг). Если вы попробуете выполнить рисование в другой момент, то скорее всего вы ничего не увидите. Запомните, что безопасно рисовать можно только в специально отведенных местах TCustomForm.DoPaint, TControl.Paint и тд.

Отрисовка в FireMonkey осуществляется с использованием нескольких низкоуровневых библиотек. Под Windows отрисовка может осуществляться средствами GDI+ (старыми машинами)+ или новыми машинами с использованием Direct 2D. Под Mac – это Quartz. Под Windows система отрисовки позволяет в момент инициализации приложения установить нужный тип канвы (GDI+, D2D), задать команду на использование ClearType и другие настройки. Смотрите подробнее выдержку из модуля FMX.Types.

var
  // On low-end hardware or mobile bitmap effects are slowly
  GlobalDisableFocusEffect: Boolean = False;
  // Use Direct3D 10 in Windows Vista or Windows 7 by default
  GlobalUseDX10: Boolean = True;
  // Use Direct3D 10 in Windows Vista or Windows 7 in software mode
  GlobalUseDX10Software: Boolean = False;
  // Use Direct2D in Windows Vista or Windows 7 by default
  GlobalUseDirect2D: Boolean = True;
  // Use ClearType rendering in GDI+ renderer
  GlobalUseGDIPlusClearType: Boolean = True;
  // Use HW accelerated effect if possible
  GlobalUseHWEffects: Boolean = True;
  // Force GLSL on every Mac
  GlobalUseGLSL: Boolean = True;

Графика - это отдельная тема, требующая детального обзора. Поэтому подробнее с ней ознакомимся с ней позже.

2.4 Сервисы

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

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

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

По умолчанию в список сервисов входят, например, такие как:

  1. Управление приложением: IFMXApplicationService, IFMXHideAppService (Запуск, прерывание, название, обработка сообщений,сворачивание и тд)
  2. Управление созданием окон: IFMXWindowService (Создание, уничтожение, отображение, скрытие, поиск, сворачивание, получение и задание позици, размера, клиентской часть окна, активация и тд ).
  3. Drag and Drop: IFMXDragDropService
  4. Диалоги: IFMXDialogService (Создание и управление нативными диалоговыми окнами)
  5. Нативные таймеры: IFMXTimerService (создание и удаление)
  6. Виртуальная клавиатура: IFMXVirtualKeyboardService (Отображение, скрытие и получение ввода с виртуальной клавиатуры)
  7. Управление рамкой окна: IFMXWindowBorderService (Создание стилизованной рамки окна)
  8. Получение текущего класса канвы: IFMXCanvasService
  9. Локаль: IFMXLocaleService
  10. Элементы выбора значений: IFMXPickerService
  11. Меню: IFMXMenuService (Создание нативного меню, Перевод горячих клавиш, отображение меню и тд.)
  12. Экран: IFMXScreenService (Размеры)
  13. Положение мыши: IFMXMouseService
  14. Задание курсора: IFMXCursorService
  15. Получение класса текущего контекста для 3D: IFMXContextService
  16. И другие в том числе, возможно, и ваши сервисы.

Подробнее о работе с сервисами, а также о создании и необходимости своих сервисов мы поговорим позже.

2.5 Анимация

Анимация выполняется с использованием аниматоров. Аниматоры позволяют изменять published свойства родителя в пределах установленных за указанный промежуток времени, к которому они прикреплены (Помните про древовидную структуру TFmxObject).

Аниматор позволяет запускать и останавливать анимацию по срабатыванию триггера. Подробнее о триггерах поговорим позже. А пока Триггер - это некоторое событие, которое наступило у родительского компонента (Нажатие мыши, наведение курсора мыши на контрол, получение фокуса и тд). Именно аниматоры в купе с триггерами делают стили не просто статичной картинкой, а добавляют отображение реакции на внешние воздействия пользователя.

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

TAnimation – абстрактная основа всех аниматоров. На основе TAnimation создаются аниматоры, изменяющие типизированные значения publshed свойств: TFloatAnimation (изменяет значение float свойства), TRectAnimation (изменяет размер и положение TRect свойства), TColorAnimation (Изменяет цвет) и др. Скорее всего этот класс, как и следующий вам не придется расширять.

С доступными аниматорами вы можете ознакомиться на вкладке “Animations”

2.6 Эффекты

TEffect – это база для создания эффектов ImageFX. Все эффекты наследуются от этого класса и переопределяют метод применения эффекта TEffect.ProcessEffect. Все эффекты используют понятие фильтра. Фильтр – может выступать как совокупность оболочки для использования шейдеров и алгоритма шейдеров, позволяющая изменять изображение согласно алгоритму шейдеров, так и любые свои алгоритмы.

Posted by ybrovin on October 9th, 2012 under Firemonkey (RUS) | Comment now »


Часть 0. Разработка компонентов под FMX 2.0. История сотворения мира

Начну длительный цикл статей, посвященных теме создания своих компонентов для библиотеки FireMonkey 2.0. Так как рассказать есть о чем, то сколько сюда войдет частей, пока не знаю.

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

Эта часть имеет нулевой номер неспроста. Она не содержит технических деталей и взгляда на разработку компонентов. Ниже идет импровизация. Прошу читателей меня не судить.

Неполная история зарождения

Вначале не было ничего, что напоминало бы нам эту планету. Как это часто бывает в космических просторах, пустота, темнота, космические газы и сгустки энергии объединились воедино и образовали планету. Эта планета была пустой, черно-белой, без каких либо признаков жизни. Однако с годами в этом мире зародилась жизнь и появились первые примитивные жители этой планеты. Они обосновались в поселении TFmxObject.

Некоторые из них сбивались в стаи, возглавляемые и управляемые вожаками. Организовав стаю, вожак знал теперь все дела стаи: кто вступил в стаю (DoAddObject, DoInsertObject), кто в каком порядке следовал за ним (Children , Index), какого размера у него стая (ChildrenCount), кто выбыл из нее (DoRemoveObject) – другими словами знал непосредственно все о своих подопечных.

Время шло, и стаи перемещались по миру под четким руководством вожака, объединяясь с другими. На других планета поговаривали, что такую организацию стай на планете GoF называли “компоновщиком” http://ru.wikipedia.org/wiki/Composite.

Все было хорошо, жители объединялись, но оставались бесформенными личностями, пассивными и не обладавшими желаемой степенью свободы.

Эволюция не стояла на месте, жители перемещались по планете и тогда зародилось новое поселение из уроженцев города TFmxObject, наследники TFmxObject. Их касту стали называть TControl. Это были новые жители, который умел все то же, что и жители TFmxObject. Но при этом обрели физическое тело: размеры (Width, Height) и положение в мире (Position), умели делать перемещаться, вращаться (Rotate), изменять свои размеры, увеличиваться и уменьшаться (Scale), скрываться и появляться (Visible).

Надо отметить, что в то время на планете не было своего источника света, поэтому жители жили были в темноте и не видели цветов и не знали о существовании красок. Однако как появилось солнце на нашей планете, так и появился на этой планете свет других галактик. Свет озарил половину планеты и открыл для жителей краски с кистями TBrush. Эти кисти могли наносить любые рисунки, используя множество эффектов. Жители города TControl стали называть освещенную светом планету местом TCanvas. Место, где каждый мог обрести цвет и на рисоваться себе любой костюм. Место TСanvas было уникальным и единственным на всей планете. Жители частенько посещали это место по одиночке или группами с вожаками. Вожаки раздавали указания как наряжаться своим подчиненным.

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

Краски с холстом открыли творческие способности жителей и тут и там стали появляться новые города: TRectangle, TLine, TRoundRect, TEllipse, TCircle, TArc и др. Жители отличались формой своих нарядов, у кого-то она была прямоугольной, у кого-то круглой, а у кого и просто в виде линии…

Поскольку планета была большой, то встала острая необходимость удобного перемещения по ней. Эта необходимость вылилась в появление транспортных средств - аниматоров. Назначение их было простым, перемещать жителей.

Чем больше времени проходило, тем более развитыми становились жители. Развитие привело к появлению нового типа существ TStyledControl. Они любили моду, стиль, любили красоваться в последних нарядах и быть в тренде последних новинок. Они оказались намного проворнее своих сородичей, и стали использоваться других жителей (TRectangle, TLine, TRoundRect, TEllipse, TCircle, TArc) себе в угоду – а именно использовать их и их наряды, чтобы красоваться самим. Если стиль менялась, то жители TStyledControl легко меняли бедных жителей на других и тем самым опять же оставались в писке последней моды. Тогда же и появилось новое устройство аниматор цвета TColorAnimation, которое позволяло менять цвета чего-угодно и на что угодно.

Ну а дальше я думаю уже ясно, стали появляться новые направления моды и новые жители от TStyledControl,такие как TButton, TLabel, TListBox и много других.


Ну а теперь перейдем от шуток к делу. Что следует вынести из этой истории:

  1. Каждый объект библиотеки FireMonkey может состоять из других объектов. Доступ к этому списке осуществляется через TFmxObject.Children.
  2. Каждый объект библиотеки FireMonkey прямой или дальний предок TFmxObject.
  3. Каждый визуальный компонент наследник TControl. Он содержит канву и дает возможность рисовать на ней. Так же TControl имеет размер и содержит методы и события по получению событий от мыши, клавиатуры, фокуса и тд.
  4. Каждый объект управляет своими дочерними объектами.
  5. Холст TCanvas общий для всех компонентов. Это значит, что каждый наследник TControl в рамках одной формы работает с одной и той же канвой.
  6. Стилизованные компоненты, всегда наследуются от TStyledControl. TStyledControl Позволяет динамически загружать стиль, меняя внешний вид, а иногда внешнее поведение.
  7. Стиль – это набор компонентов, задающих внешний вид и иногда поведение компонента.
  8. Аниматоры – это не визуальные компоненты, которые позволяют легко менять практически любую характеристику компонента за интервал времени.

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

На сегодня все. Всех хороших выходных!

Posted by ybrovin on October 5th, 2012 under Firemonkey (RUS) | 7 Comments »




Server Response from: BLOGS2