На днях мне пришлось столкнуться с компонентом 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
Автор: DenisVasilyev