Учебный центр "ANIT Texno Inform"
Опубликован: 25.06.2014 | Доступ: свободный | Студентов: 2597 / 852 | Длительность: 24:39:00
Специальности: Программист
Лекция 17:

Деревья

Свойства и методы TTreeView.Items

Как уже упоминалось, при программной обработке дерева (а чаще всего, такая обработка и используется) приходится пользоваться свойством Items, которое имеет тип TTreeNodes и само является объектом, а потому имеет собственный набор свойств и методов. Разберем основные из них:

Свойства

Count - Количество узлов, входящих в дерево, включая и вложенные.
Item[Index:Integer] - Индексированный доступ к узлам. Как всегда, индексация начинается с нуля, поэтому, к примеру, изменить текст первого узла дерева можно так:
TreeView1.Item[0].Text:= 'Новый текст';
            

Нередко возникает необходимость обойти всё дерево, от первого до последнего узла. Например, изменить текст всех узлов можно так (пример выполнять не нужно):

var
  i: integer;
begin
  for i:= 0 to TreeView1.Items.Count-1 do
    TreeView1.Item[i].Text:= 'Узел №' + IntToStr(i+1);
        

В этом случае мы получим дерево с текстом

Узел №1
Узел №2
        

и так далее.

Методы

Add(SiblingNode:TTreeNode; const S):TTreeNode - добавляет новый узел в конец списка SiblingNode, с текстом S. Если добавляется корневой (не имеющий родителя) узел, то SiblingNode = NIL.
AddChild(ParentNode:TTreeNode; const S):TTreeNode - добавляет новый дочерний (вложенный) узел в конец родительского списка ParentNode, с текстом S.
AddFirst(SiblingNode:TTreeNode; const S):TTreeNode - добавляет новый узел в начало списка SiblingNode, с текстом S. Если добавляется корневой (не имеющий родителя) узел, то SiblingNode = NIL.
AddChildFirst(ParentNode:TTreeNode; const S):TTreeNode - добавляет новый дочерний (вложенный) узел в начало родительского списка ParentNode, с текстом S.
Clear - очищает список дерева от всех узлов.
Delete(Node:TTreeNode) - удаляет узел Node.

Основные свойства узла TTreeNode

К отдельному узлу можно получить доступ через свойство Item, например, TreeView1.Item[0].

ImageIndex - содержит индекс пиктограммы из связанного TImageList, которая будет отображаться в данном узле.
Text - содержит текст, отображаемый в ветке данного узла.

Пожалуйста, не путайте - свойство компонента Items имеет тип TTreeNodes, и представляет собой индексированный список узлов. А отдельный узел имеет тип TTreeNode - это не одно и то же!

Вернемся к программе. Найдем две подходящих пиктограммы для узлов дерева. Щелкните дважды по ImageList1, чтобы открыть его редактор. Нажмите кнопку "Добавить". Если вы устанавливали Lazarus в папку по умолчанию, то различные стандартные картинки будут расположены по адресу

C:\Lazarus\Images

Не будем копаться по вложенным папкам - прямо тут находятся две подходящих пиктограммы. Первым добавьте изображение folder.png, это будет пиктограмма для родительских узлов. Пиктограмма получила индекс 0. Вторым добавьте изображение template.png, это будет изображение вложенных подразделов. Картинка встала под индексом 1. Можно закрыть редактор ImageList кнопкой "ОК".

Теперь приступим к программированию кнопок. Сгенерируйте событие OnClick для кнопки "Новый раздел". Её код будет следующим:

procedure TfMain.bNewNodeClick(Sender: TObject);
var
  NodeCaption: string; //для получения заголовка нового узла
  NewNode: TTreeNode; //для создания нового узла
begin
  //сначала очистим заголовок:
  NodeCaption:= '';
  //теперь, если пользователь не ввел заголовок нового узла, выходим:
 if not InputQuery('Ввод заголовка', 'Введите заголовок раздела',
                     NodeCaption) then exit;
  //если мы здесь, то заголовок есть. создаем родительский узел:
  NewNode:= TreeView1.Items.Add(nil, NodeCaption);
  //присваиваем ему картинку под индексом 0:
  NewNode.ImageIndex:=0;
end;
        

Как видно из кода и комментариев, эта кнопка добавляет в конец списка узлов новый родительский узел. Функцией-запросом InputQuery() мы получаем у пользователя заголовок для будущего узла. Если пользователь закрыл диалог, не введя этого заголовка, то мы просто выходим из события, ничего не предпринимая. Но если он что-то туда ввел, то этот заголовок попадает в переменную NodeCaption, и мы приступаем к созданию родительского узла, что и делает код:

  NewNode:= TreeView1.Items.Add(nil, NodeCaption);
        

То, что узел родительский, говорит параметр nil - ничто, указывающий, что у нового узла нет родителя.

В заключение мы присваиваем этому узлу картинку под индексом 0, если помните, там изображение папки.

Код для кнопки "Новый подраздел" очень похож на предыдущий:

procedure TfMain.bNewChildNodeClick(Sender: TObject);
var
  NodeCaption: string;
  NewNode: TTreeNode;
begin
  NodeCaption:= '';
  if not InputQuery('Ввод заголовка', 'Введите заголовок подраздела',
                     NodeCaption) then exit;
  NewNode:= TreeView1.Items.AddChild(TreeView1.Selected, NodeCaption);
  if NewNode.Parent = nil then NewNode.ImageIndex:=0
  else NewNode.ImageIndex:=1;
end;
        

Разницы тут две. Во-первых, в этот раз мы используем метод Items.AddChild, который добавляет именно дочерний узел. В параметре вместо nil вы видите уже TreeView1.Selected, что означает ссылку на выделенный в данный момент раздел. Именно для этого раздела будет создаваться подраздел.

Во-вторых, мы указываем не конкретный индекс картинки, которая будет тут отображаться, а делаем проверку:

  if NewNode.Parent = nil then NewNode.ImageIndex:=0
  else NewNode.ImageIndex:=1;
        

Если у нового узла все-таки нет родителя (NewNode.Parent = nil), то присваиваем узлу изображение 0, иначе это будет изображение 1.

Для кнопки "Удалить" код будет следующим:

procedure TfMain.bDeleteClick(Sender: TObject);
begin
  if TreeView1.Selected <> nil then
    TreeView1.Items.Delete(TreeView1.Selected);
end;
        

Код очень простенький. Если выделенный узел не равен nil (то есть, если вообще какой-то узел выделен), то мы удаляем из списка этот выделенный узел.

Код для кнопки "Переименовать":

procedure TfMain.bEditClick(Sender: TObject);
var
  NodeCaption: string;
begin
  NodeCaption:= '';
  if not InputQuery('Ввод заголовка', 'Введите новый заголовок',
                     NodeCaption) then exit;
  TreeView1.Selected.Text:= NodeCaption;
end;
        

Здесь мы точно также пытаемся получить у пользователя новый заголовок. Если он его ввел, то этот новый заголовок мы присваиваем свойству Text выделенного в данный момент узла.

Теперь напишем код для кнопки "Сортировать". Её код совсем простой:

procedure TfMain.bSortClick(Sender: TObject);
begin
  TreeView1.AlphaSort;
end;
        

Метод AlphaSort возвращает истину, если сортировка прошла успешно. Но нам нет смысла проверять эту успешность, поэтому сам метод мы вызываем, а на возвращаемое им значение не обращаем внимания.

Далее на очереди у нас кнопка "Свернуть список". Её код не сложнее:

procedure TfMain.bCollapseClick(Sender: TObject);
begin
  TreeView1.FullCollapse;
end;
        

Для "Развернуть список":

procedure TfMain.bExpandClick(Sender: TObject);
begin
  TreeView1.FullExpand;
end;
        

Методы FullCollapse и FullExpand мы изучали выше.

Кнопки мы запрограммировали. Однако пока толку от нашей программы - ноль. Пользователь потратит время, заполняя список разделов и подразделов библиотеки, но стоит ему только выйти из программы, и вся эта работа потеряется. Нам нужно научить программу этот список сохранять в файл, и загружать его из файла. Файл списка назовем MyLibrary.dat. MyLibrary - потому, что так называется наша программа, dat - такое расширение традиционно имеют файлы с данными. Где лучше всего загружать этот список? Конечно, в событии OnCreate главной формы! Это событие возникает однажды, когда загружается программа, но перед ее отображением на экране. Если вам требуется сделать какую то подготовительную работу перед открытием вашей программы, то OnCreate для этого - самое место. Выделите форму fMain - это можно сделать, щелкнув по маленькому свободному участку правее компонента TreeView1, или выбрав fMain в верхней части Инспектора объектов. Затем перейдите на вкладку "События" Инспектора объектов, найдите и сгенерируйте событие OnCreate. Код будет следующим:

procedure TfMain.FormCreate(Sender: TObject);
var
  i: integer;
begin
  //если файл существует, загрузим его:
  if FileExists('MyLibrary.dat') then
    TreeView1.LoadFromFile('MyLibrary.dat');
  //теперь пройдемся по списку, и каждому узлу присвоим
  //нужную пиктограмму:
  for i:= 0 to TreeView1.Items.Count-1 do
    if TreeView1.Items[i].Parent=nil then TreeView1.Items[i].ImageIndex:=0
    else TreeView1.Items[i].ImageIndex:=1;
end;
        

Здесь мы сначала с помощью функции FileExists() проверяем, есть ли вообще в текущей папке файл MyLibrary.dat? Функция вернет истину, если такой файл есть. В этом случае мы его загружаем в дерево TreeView1. Но дерево выйдет без пиктограмм, их еще нужно загрузить. Это мы делаем в цикле for, обходя все узлы дерева. Если узел родительский (TreeView1.Items[i].Parent=nil), мы присваиваем ему картинку с индексом 0, иначе - картинку с индексом 1.

Осталось научить программу сохранять список. Делать это лучше всего при выходе из программы, в событии OnClose главной формы. В этом событии обычно делают все завершающие действия - закрывают открытые ресурсы, сохраняют параметры программы, и т.п. Код события следующий:

procedure TfMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  TreeView1.SaveToFile('MyLibrary.dat');
end;
        

Тут мы просто сохраняем список в файл. Сохраните проект, запустите его и попробуйте заполнить разделы и подразделы библиотеки. У меня получилось примерно так:

Работающая программа

Рис. 19.4. Работающая программа

Поэкспериментируйте с работой всех кнопок. При выходе из программы список должен сохраняться, а при входе - считываться. Не удаляйте проект - мы к нему еще вернемся!

Инга Готфрид
Инга Готфрид
Александр Скрябнев
Александр Скрябнев

Через WMI, или используя утилиту wmic? А может есть еще какие более простые пути...