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

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

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

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

    image

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

    image

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

    image

    В 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 (http://docwiki.embarcadero.com/RADStudio/XE7/en/Arranging_FireMonkey_Controls). Это означает, что каждый компонент может при необходимости выступить в роли контейнера для «любых» других компонентов (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 приобрело следующий вид:

    image

    Добавим код обработки нажатия кнопок и событие 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;
    


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

    image

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


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

    image

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

    image

    Код:

    { 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
    • +7
    • 14,3k
    • 7
    Embarcadero (Borland) 33,51
    Компания
    Поделиться публикацией
    Комментарии 7
    • +1
      Где-то только в середине поста дошло, что речь про дельфи… На паскалеподобных языках не прогал лет 10, но глаза помнят :)
      • +4
        Спасибо! Понятно и ясно!

        Жаль что компиляторов под Pascal\Oberon нету хороших, ведь язык-то намного изящнее и приятнее, чем C\C++

        P.S. Прошу без, холивара, что Pascal, что С — очень похожие языки, оба от Алгола пошли, а создатель TurboPascal, как известно, является создателем C#. Кроме того, Pascal\Modula\Oberon — создал ученый муж, а не студенты, поэтому говорить что Pascal — для школоты или что-то еще — глупо.
        • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Правильно ли я понял, что некоторое время назад Вы портировали VCL приложение под FireMonkey?
          Расскажите об этом подробнее, если не затруднит. Как построили процесс, много ли пришлось переписывать программной логики?
          • +3
            Вы б над русским языком чуть поменьше издевались. Всё-таки официальный блог и всё такое. Ну нельзя настолько наплевательски относиться к запятым, что уж говорить про тся/ться…
          • 0
            Зачем вы используете дерево, когда от вас требуется функциональность списка?

            P.S. С русским у вас беда, но вам об этом уже выше написали.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое