Watch, Follow, &
Connect with Us

Fire Monkey - Yaroslav Brovin

#1. Tips and tricks in FireMonkey

I would like to suggest your set of links about frequent firemonkey questions

1. Logging in Firemonkey on all mobile platforms

Detail reviewing of all methods of logging in FireMonkey for iOS and Android.

2. How to delete control in runtime in mobile platforms with ARC

The method description as it is necessary to delete controls in a run mode taking into account operation of the mechanism of automatic reference counting.

3. Making multi forms for Phone and Pad in FireMonkey

How to make different forms for Phone and tablet devices in Firemonkey.

4. Deploy our resources in mobile application in FireMonkey

All way to include your data files into mobile application (Deployment manager and Project Resources)

5. How to close form on mobile application?

Posted by ybrovin on January 28th, 2014 under Firemonkey (ENG) | Comment now »


#1. Подборка ссылок по FireMonkey

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

1. Средства логирования в FireMonkey

Детальное рассмотрение всех способов логирования в FIreMonkey под Андроид и iOS.

2. Как правильно удалять контролы в RunTime в мобильных платформах под ARC

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

3. Делаем в FireMonkey несколько разных форм отдельно для телефона отдельно для планшетов

Рассматриваем способ использования стандартного механизма выбора форм для разных устройств.

4. Включаем свои файлы и ресурсы в мобильное приложение

Рассматриваются все варианты включения в приложения своих файлов (картинки, графика, медиа и тд) используя менеджер развертывания и встраивание ресурсов через Project Resources.

5. Как правильно закрыть форму на мобильной платформе?

Описание приема корректного закрытия формы на мобильных платформах под ARC.

Posted by ybrovin on January 28th, 2014 under Firemonkey (RUS) | 4 Comments »


FireMonkey. Доступ к пикселям TBitmap. Аналог Scanline из VCL.

Добрый день. По скольку одним из частых вопросов по использованию TBitmap в FireMonkey является: "Как мне получить доступ к пикселям TBitmap?". То стоит рассказать про это один раз и помочь разработчикам. А так же ответить на вопрос: "Почему в FMX у TBitmap нету метода SсanLine и причины перехода на Map и UnMap?"

Вопрос: Как мне получить доступ на чтение/изменение пикселей в TBitmap?

Ответ: Для манипуляции с содержимым (точками) в TBitmpa есть два метода: TBitmap.Map и TBitmap.Unmap.

    { Direct access  }
    function Map(const Access: TMapAccess; var Data: TBitmapData): Boolean;
    procedure Unmap(var Data: TBitmapData);

Map - возвращает объект для работы напрямую с точками, информацию о формате пикселей (TBitmapData.PixelFormat), размер изображения в точках (TBitmapData.Width, TBitmapData.Height), размер одного пикселя в текущем представлении точек (TBitmapData.BytesPerPixel).

В качестве параметров функция принимает:

  1. Вид доступа (Чтение - TMapAccess.maRead, запись - TMapAccess.maWrite, чтение и запись  - TMapAccess.maReadWrite). Если вы хотите только прочитать информацию о точках, то используйте вид доступа - TMapAccess.maRead. Это позволит избежать накладных расходов на манипуляцию с данными TBitmap.
И возвращает:
  1. Объект (Data: TBitmapData) содержащий буфер TBitmap и простые методы по манипуляции с буфером.

Получив TBitmapData в вашем распоряжении арсенал по работе с точками, работа непосредственно с буфером памяти TBitmap и мета информация о формате хранения данных TBitmap.

После окончания работы с данными, НЕОБХОДИМО!!! закрыть доступ к данным применив метод TBitmap.Unmap. Иначе результат изменения не отобразится. Вот в принципе и все.

Пример:

var
  M: TBitmapData;
...
  if FBitmap.Map(TMapAccess.maWrite, M) then
    try
      for i := 0 to FColorBitmap.Width - 1 do
        for j := 0 to FColorBitmap.Height - 1 do
          M.SetPixel(i, j, TAlphaColorRec.Red);
    finally
      FColorBitmap.Unmap(M);
    end;
...

Вопрос: Зачем это сделано? Почему нельзя было оставить Scanline в TBitmap, как это сделано в VCL?

Это самый важный вопрос. VCL для хранения и работы с  bitmap изображением использует оболочку над Winapi. В FMX TBitmap это более сложный объект. Так как он должен работать как на всех платформах, так и в разных графических библиотеках. Поэтому прямого доступа к памяти нет, из-за особенностей хранения изображений в разных системах. Где-то мы можем напрямую производить изменение буфера, а где-то нет

Например: в канве GPU работа с битмапом идет через текстуры. Поэтому при вызове метода Map, создается копия буфера. Затем пользователь производит ее модификацию и по методу unmap происходит обновление текстуры новым изображением. Такая же ситуация происходит с использованием канвы D2D под Windows

Надеюсь я пролил свет на работу с буфером битмапа. Если буду вопросы, задавайте. По мере возможности напишу пару статей в формате Вопрос-Ответ.

Posted by ybrovin on June 17th, 2013 under Firemonkey (RUS) | 7 Comments »


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


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


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) | 7 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) | 33 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) | 17 Comments »




Server Response from: BLOGS1