Juri Shutenko Personal Homepage. Visual FoxPro.

ActiveX TreeView. Часто задаваемые вопросы. Часть I.

Индекс.

Свойство объекта node - Index (read-only в run-time). Это свойство никак не связано с иерархией объектов node в коллекции узлов nodes и формируется автоматически при вводе элементов в TreeView, то есть первый введенный в TreeView узел - объект node - получит значение индекса равное 1, следующий - 2 и так далее. При удалении объекта node индексы перестраиваются в следующем порядке: значение индекса удаляемого узла получит узел, введенный в TreeView непосредственно за удаляемым. В документации указывалось, что индексы также могут перестраиваться при сортировке дерева, хотя я этого не замечал, при тестировании. Короче говоря, нельзя считать, что индекс будет неизменным на протяжении "жизни" дерева, поэтому использование индексов, как ссылочных параметров, может привести к определенным проблемам. Предпочтительнее использовать свойство "Key".

Тогда возникает резонный, казалось бы вопрос: а зачем тогда он вообще нужен, если с его помощью нельзя однозначно определить один и тот же узел во время жизни дерева и если планируются его реконструкции во время работы с ним?

А ответ очень прост. С одной стороны, индекс - это стандартный способ адресации объекта в коллекции объектов, с другой стороны, поскольку свойство Key может быть пустым, то автоматическое осуществляемое VFP индексирование коллекции становится чуть-ли не единственным способом адресации, если не принимать во внимание наличия свойства SelectedItem. Ниже приведен пример формирования корректного дерева:

This.Nodes.Add(,,,[Branch1])
This.Nodes.Add(,,,[Branch2])
This.Nodes.Add(,,,[Branch3])
This.Nodes.Add(,,,[Branch4])
This.Nodes.Add(,,,[Branch5])

в котором свойству Key ничего не присваивается.
 
Ссылку на узел с помощью индеса в таких cлучаях можно получить следующим образом:

container_object.TreeView_object.Nodes(value_of_index)
или
container_object.TreeView_object.Nodes.Item(value_of_index)

и хотя оба способа дают правильный результат, второй способ более академичен. Какой использовать? На ваше усмотрение.

В качестве примера рассмотрим ситуацию из присланного вопроса - "Этот TreeView удаляет узлы как ему вздумается и иногда вылетает ошибка... Посмотри код, пожалуйста!"

oRemovedNode=This.Parent.tw1.SelectedItem
If oRemovedNode.Children>0
   lnIndex=oRemovedNode.Child.Index
   For _counter=1 To oRemovedNode.Children
      This.Parent.tw1.Nodes.Remove(lnIndex)
      lnIndex=lnIndex+1
   Endfor
Endif

В чем заключается проблемность приведенного фрагмента кода?
Если узла с вычисляемым индексом нет, то разумеется вылетит ошибка с примерным текстом сообщения "node doesn't evaluate to object". В таком случае можно достоверно сказать, что порядок инициализации был выстроен не по принципу - "родитель - все детки, родитель - все детки и так далее", а как получалось, или дерево достраивалось или перестраивалось в ходе программы, и в результате чего последовательность номеров индексов не соответствует последовательности узлов в дереве и, стало быть, возможно удаление неродственных веток. В таком случае, корректно работающий код должен выглядеть так:

oRemovedNode=This.Parent.tw1.SelectedItem

If oRemovedNode.Children>0
   loNode2Remove=oRemovedNode.Child
   For _counter=1 To oRemovedNode.Children
      If _counter>1
         loNode2Remove=loNode2Remove.Next
      Endif
      lnIndex=loNode2Remove.Index
      This.Parent.tw1.Nodes.Remove(lnIndex)
   Endfor
Endif

Небольшой коментарий к приведенному фрагменту кода.
В данном примере через свойство узла Child - определяем первый дочерний узел для выбранного узла, определяемой однозначно через свойство SelectedItem. Для перебора всех узлов уровня иерархии узла Child используем свойство узла Next, которое возвращает ссылку на следующий объект, данного уровня иерархии. Свойство Children возвращает число дочерних элементов первого дочернего уровня, то есть, если какой-либо из дочерних узлов имеет свои дочерние узлы, их количество никоим образом не повлияет на величину свойства Children.

Comment to TreeView FAQ. Part I. Index property

Root

Еще одно свойство, которое иногда вводит в заблуждение из-за некорректной документации - "Returns a reference to the root Node object of a selected Node".
Положим что мы инициализируем дерево следующим образом:

* TreeView Init.Example 1
This.Nodes.Add(,,'r','Root')
This.Nodes.Add('r',4,'p1','Parent 1')
This.Nodes.Add('r',4,'p2','Parent 2')
This.Nodes.Add('r',4,'p3','Parent 2')
This.Nodes.Add('p1',4,'c1p1','Child 1 for Parent 1')
This.Nodes.Add('p1',4,'c2p1','Child 2 for Parent 1')
This.Nodes.Add('p1',4,'c3p1','Child 3 for Parent 1')
This.Nodes.Add('p2',4,'c1p2','Child 1 for Parent 2')
This.Nodes.Add('p2',4,'c2p2','Child 2 for Parent 2')

В результате мы получим следующее дерево:

Comment to TreeView FAQ. Part I. Root property

В этом случае, для всех узлов свойство Root вернет одно и тоже значение, его ключ будет равен - "r", его текст будет -"Root". И если узел Root удалить, то мы получим чистый TreeView.

Теперь инициализируем дерево используя код приведенный ниже:

this.Nodes.Add(,,"r0","Root")
this.Nodes.Add("r0",0,"c1","What's new?")
this.Nodes.Add("r0",0,"c2","Few words about FoxPro...")
this.Nodes.Add("r0",0,"c3","Useful books about FoxPro...")
this.Nodes.Add("r0",0,"c4","WEB-links related to FoxPro...")
this.Nodes.Add("c1",4,"s1c1","Operators")
this.Nodes.Add("c1",4,"s2c1","Functions")
this.Nodes.Add("c1",4,"s3c1","Controls")
this.Nodes.Add("c3",4,"s1c3","Haker's guide")
this.Nodes.Add("c3",4,"s2c3","Object oriented programing under Visual FoxPro")
this.Nodes.Add("c2",4,"s1c2","FoxPro for Windows ver. 2.5")
this.Nodes.Add("c2",4,"s2c2","Visual FoxPro ver. 6.0")
this.Nodes.Add("c4",4,"s1c4","FoxShare mailing list provided by Darcy White")
this.Nodes.Add("c4",4,"s2c4","ProFox mailing list provided by Ed Leafe")
this.Nodes.Add("c4",4,"s3c4","Visual FoxPro Yellow Pages")
this.Nodes.Add("c4",4,"s4c4","Visual FoxPro Universal Thread")

Какой узел в таком случае будет корневым? Узел с ключом "r0" и текстом "Root"? Ничего подобного! Root узлом для этого дерева будет узел введенный в дерево командой:

this.Nodes.Add("r0",0,"c4","WEB-links related to FoxPro...")

то есть последний введенный в дерево узел, для которого в качестве параметра Relationship был передан 0.
 
Создайте в дизайнере новую формы, положите на нее TreeView и две командные кнопки. В метод, ассоциированный с событием Init элемента TreeView скопируйте приведенный фрагмент кода. Переименуйте дерево в "tv1", то есть измените его свойство Name в окне Properties. Присвойте свойству Caption первой командной кнопки значение "Show root node texr", второй - "Remove root node".
В метод, ассоциированный с событием Init первой кнопки впишите следующий код:

WAIT This.Parent.tv1.SelectedItem.Root.Text WINDOW NOWAIT

В метод, ассоциированный с событием Init второй кнопки впишите:

This.Parent.tv1.Nodes.Remove(This.Parent.tv1.SelectedItem.Root.Index)

Запустите форму. Откройте ветку "Useful books about FoxPro..." и выберите "Haker's guide". Щелкните по первой кнопке - посмотрите рзультат. Теперь щелкните по второй кнопке. Какой узел удален? И снова щелкните по первой кнопке - посмотрите результат. Теперь Root узлом стал узел с текстом "Useful books about FoxPro...", то есть введенный предпоследним c параметром Relationship равным нулю.

Был задан вопрос - "как определить корень для последнего "родственника" текущей ветки". После уточнения выяснилось, что под последним "родственником" подразумевается узел лежащий на 4-м уровне иерархии, а корень это не Root дерева. То есть:

  1. Root
    1. 1 Node
      1. 2 Child node
        1. 3 Child-child node
          1. 4 Child-child-child node

и нужно получить значение ключа 1.1 Node.
 
Если дерево инициализировалось по принципу, указанному в примере TreeView Init.Example 1, то есть с четким определением узла Root, то можно использовать следующий код:

Local lcRootNodeKey, lcTopParentKey
lcRootNodeKey=This.Parent.tv1.SelectedItem.Root.Key
loNode=This.Parent.tv1.SelectedItem
Do While .T.
   If loNode.Parent.Key!=lcRootNodeKey
      loNode=loNode.Parent
   Else
      lcTopParentKey=loNode.Key
      WAIT lcTopParentKey WINDOW TIMEOUT 2
      Exit
   Endif
Enddo

Если же при инициализации дерева вводилось несколько узлов с параметром Relationship равным нулю, то приведенный выше код использовать нельзя, вместо этого следует использовать следующий код:

Local loNode, lcTopParentKey
loNode=This.Parent.tv1.SelectedItem
Do While .T.
   If Isnull(loNode.Parent)
      Exit
   Else
      loNode=loNode.Parent
   Endif
Enddo
lcTopParentKey=loNode.Key

Будет продолжено в других частях, для сокращения времени загрузки страницы.

Селектор для быстрого перехода на сайты, связанные с Visual FoxPro.