Использование TTreeView в Firemonkey приложениях
На днях мне пришлось столкнуться с компонентом 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; Следующим шагом разместим на форме несколько компонентов: TTreeViewTImage2 TButtonTOpenDialogя ещё добавил компонент 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