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

На днях мне пришлось столкнуться с компонентом TTreeView. Заказчик настаивал, на привычном ему компоненте — «Дереве», и хотел что бы приложение выглядело так же как он привык, в VCL.6db8313cad114a69b3371d70a8e71850.png9895359b3d6c45c2addcf96e040a9502.pngПоэтому, в этой статье я хотел бы рассказать о компоненте TTreeView (ветка дерева) и его использовании в Firemonkey приложениях, а также рассмотреть в чем различия между VCL и FireMonkey реализацие.

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

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

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

В 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; Следующим шагом разместим на форме несколько компонентов: TTreeViewTImage2 TButtonTOpenDialogя ещё добавил компонент TStyleBook, однако он не обязателен, а лишь меняет стандартный стиль, на один из стилей «из коробки», которые Embarcadero предоставляет вместе с IDE.

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

Добавим код обработки нажатия кнопок, и событие 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; После запуска приложение приняло такой вид: 9895359b3d6c45c2addcf96e040a9502.png

На этом можно был бы заканчивать, однако я хотел обратить особое внимание читателей на особенности 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; Откомпилируем приложение: 585fb07aae774c76860e8c0570836643.png

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

Код.

{ 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

© Habrahabr.ru