habrahabr

Использование TTreeView в Firemonkey приложениях

  • воскресенье, 23 ноября 2014 г. в 02:10:39
http://habrahabr.ru/company/delphi/blog/243933/

На днях мне пришлось столкнуться с компонентом TTreeView. Заказчик настаивал, на привычном ему компоненте — “Дереве”, и хотел что бы приложение выглядело так же как он привык, в VCL.


Поэтому, в этой статье я хотел бы рассказать о компоненте TTreeView(ветка дерева) и его использовании в Firemonkey приложениях, а также рассмотреть в чем различия между VCL и FireMonkey реализацие.

В VCL, каждой ветке добавить свою картинку было не сложно. Всё что для этого требуется — это добавить компонент TImageList, “загрузить в него” картинки и назначить этот список свойству TreeView.Images:= ImageList;


Теперь 2 раза кликнем на дереве, и добавляем ветки. Каждой указываем порядковый номер картинки которую хотим отобразить на ветке.


После компиляции, получим такой результат:


В FireMonkey дерево слегка изменилось. Во-первых, в FMX нет класса TTreeNode. Во-вторых, нет свойства Images. Ну и в третьих, в классе TTreeViewItem, нету никаких свойств ответственных за использование картинок.

Интернет на запрос — how to add image to treeviewitem firemonkey. Предлагает воспользоваться довольно стандартным, как мне кажется, способом менять стандартные компоненты расширяя их за счет изменения стиля. monkeystyler.com/blog/entry/adding-images-to-a-firemonkey-treeview
Пример имеет полное право на жизнь и вполне необходим когда вы хотите видеть сразу результаты изменений стиля, в том числе в IDE. Но есть и другой способ, особенно если все манипуляции вы делаете в Run-Time.

Способ основывается на особенностях архитектуры FireMonkey. Если мы заглянем в документацию, то увидим такую строчку:
FireMonkey Controls Have Owners, Parents, and Children

Что означает что каждый компонент может при необходимости выступить в роли контейнера для ”любых” других компонентов (TfmxObject). Чем я и воспользуюсь.

Первым делом унаследуем новый класс ветки, и слегка “расширим” его:
type
  TNode = class(TTreeViewItem)
  strict private
    FImage: TImage;
  private
    procedure SetImage(const aValue: TImage);
  public
    constructor Create(Owner: TComponent; const aText: String; 
                                          const aImageFileName: String); reintroduce;
    destructor Destroy; override;
  published
    property Image: TImage Read FImage Write SetImage;
  end;

{ TTestNode }

constructor TNode.Create(Owner: TComponent; const aText: String;
                                            const aImageFileName: String);
begin
  inherited Create(Owner);
  Self.Text := aText;

  // Создаем картинку
  FImage := TImage.Create(Owner);
  // Особая магия FireMonkey, интересующимся - советую заглянуть в код этого метода
  Self.AddObject(FImage);
  // Позиционируем картинку
  FImage.Align := TAlignLayout.Right;
  //Загружаем из файла
  FImage.Bitmap.LoadFromFile(aImageFileName);
  // Делаем картинку фоном
  FImage.SendToBack;
end;

destructor TNode.Destroy;
begin
  Image.FreeOnRelease;
  inherited;
end;

procedure TNode.SetImage(const aValue: TImage);
begin
  FImage := aValue;
end;


Следующим шагом разместим на форме несколько компонентов:
TTreeView
TImage
2 TButton
TOpenDialog
я ещё добавил компонент TStyleBook, однако он не обязателен, а лишь меняет стандартный стиль, на один из стилей “из коробки”, которые Embarcadero предоставляет вместе с IDE.

После выполнения предыдущих операций мое IDE приобрело следующий вид:


Добавим код обработки нажатия кнопок, и событие TreeChange, для дерева:
procedure TMainForm.AddNodeClick(Sender: TObject);
var
  Node : TNode;
begin
  // Устанавливаем параметры открытия файлов
  OpenImage.Options := OpenImage.Options - [TOpenOption.ofAllowMultiSelect];

  if OpenImage.Execute then
  begin
    // Показываем картинку на форме
    MainImage.Bitmap.LoadFromFile(OpenImage.Files[0]);
    // Создаем новую ветку с нужными нам параметрами
    Node := TNode.Create(MainTree, ExtractFileName(OpenImage.Files[0]), OpenImage.Files[0]);

    // Если ничего не выбрано, добавляем ветку дереву
    if MainTree.Selected = nil then
      MainTree.AddObject(Node)
    else
      // Добавляем ветку той которая выбрана, способ аналогичен тому который описан выше
      MainTree.Selected.AddObject(Node);
  end;
end;

// В этом методе всё аналогично предыдущему, кроме того что здесь есть возможность выбрать несколько картинок сразу
procedure TMainForm.AddImageListClick(Sender: TObject);
var
  ImageFileName: string;
  Node : TNode;
begin
  OpenImage.Options := OpenImage.Options + [TOpenOption.ofAllowMultiSelect];
  if OpenImage.Execute then
  begin
    for ImageFileName in OpenImage.Files do
    begin
      Node := TNode.Create(MainTree, ExtractFileName(ImageFileName), ImageFileName);
      MainTree.AddObject(Node);
    end;

    MainImage.Bitmap.LoadFromFile(OpenImage.Files[0]);
  end;
end;

procedure TMainForm.MainTreeChange(Sender: TObject);
begin
  // Если выбрали ветку, то изменим картинку на картинку ветки
  if MainTree.Selected is TNode then
    MainImage.Bitmap := (MainTree.Selected as TNode).Image.Bitmap;
end;


После запуска приложение приняло такой вид:


На этом можно был бы заканчивать, однако я хотел обратить особое внимание читателей на особенности TFMXObject. А именно на метод AddObject который позволяет нам дорабатывать наши компоненты на лету.

Давайте, теперь добавим нашей ветви, например кнопку. Для этого аналогично примеру расширим наш класс, и добавим в конструктор инициализацию кнопки:
type
  TNode = class(TTreeViewItem)
  strict private
    FImage: TImage;
    FButton: TButton;
  private
    procedure SetImage(const aValue: TImage);
    procedure TreeButtonClick(Sender: TObject);
    procedure SetButton(const Value: TButton);
  public
    constructor Create(Owner: TComponent; const aText: String;
                                          const aImageFileName: String); reintroduce;
    destructor Destroy; override;
  published
    property Image: TImage Read FImage Write SetImage;
    property Button: TButton Read FButton Write SetButton;
  end;

constructor TNode.Create(Owner: TComponent; const aText: String;
                                            const aImageFileName: String);
begin
  inherited Create(Owner);
  Self.Text := aText;

  // Создаем картинку
  FImage := TImage.Create(Owner);
  // Особая магия FireMonkey, интересующимся - советую заглянуть в код этого метода
  Self.AddObject(FImage);
  // Позиционируем картинку
  FImage.Align := TAlignLayout.Right;
  //Загружаем из файла
  FImage.Bitmap.LoadFromFile(aImageFileName);
  // Делаем картинку фоном
  FImage.SendToBack;

  // Всё по сути аналогично, кроме события OnClick
  FButton := TButton.Create(Owner);
  FButton.Text := 'Hi World';
  Self.AddObject(FButton);
  FButton.Align := TAlignLayout.Center;
  FButton.SendToBack;
  FButton.OnClick := TreeButtonClick;
end;

procedure TNode.TreeButtonClick(Sender: TObject);
begin
  // Тут можно сделать обработку того какая кнопка была нажата
  ShowMessage('Hello World');
end;


Откомпилируем приложение:


Таким же образом добавим поле ввода.


Код.
{ TTestNode }

constructor TNode.Create(Owner: TComponent; const aText: String;
                                            const aImageFileName: String);
begin
  inherited Create(Owner);
  Self.Text := aText;

  FButton := TButton.Create(Owner);
  FButton.Text := 'Send';
  Self.AddObject(FButton);
  FButton.Align := TAlignLayout.Center;
  FButton.SendToBack;
  FButton.OnClick := TreeButtonClick;

  // Добавим TEdit
  FEdit:= TEdit.Create(Owner);
  Self.AddObject(FEdit);
  FEdit.Position.X := 150;
  FEdit.Position.Y := 25;
  FEdit.SendToBack;

  FImage := TImage.Create(Owner);
  Self.AddObject(FImage);
  FImage.Align := TAlignLayout.Right;
  FImage.Bitmap.LoadFromFile(aImageFileName);
  FImage.SendToBack;
end;


Вот так вот просто в Run-Time расширять собственные компоненты. И благодаря FireMonkey наше приложение получиться ещё и кросс-платформенное.

Спасибо, всем кто прочитал эту статью. Жду Ваших замечаний, и комментариев.

Ссылка на репозиторий с примером.
yadi.sk/d/lwuLryOwcsDyp