6 июл. 2010 г.

VBA6 и деревья

Многие из программистов VBA рано или поздно сталкиваются с необходимостью иерархического отображения данных. На ум сразу приходит компонент, именуемый TreeView, однако которого в стандартном наборе компонентов не найти. Здесь нам на помощь приходит всемогущая разработка под именем COM (Component Object Model - объектная модель компонентов). Данное средство и его поддержка в MS Excel поможет нам обрести необходимый функционал для нашей программы.


Итак, начнем по порядку. Для начала его нужно подключить. То есть добавить в палитру компонентов из доступных COM-компонентов. Делается это так:

  • открываем редактор вижуал бейсик (если вы этого еще не сделали);
  • создаем новую форму в редакторе ВБА (только так мы сможем добавить новый компонент на палитру);
  • затем выбираем Tools->Additional controls;
  • там находим Microsoft Treeview control (версии 6.0, хотя можно и поэксперементировать, взять, к примеру пятую или постарше чего, если найдется) и ставим галку на нем (заодно можно добавить Listview control - достаточно функционален в отличие от стандартных компонентов списка);
  • нужно подключить в Tools->References библиотеку Microsoft Windows Common Controls, иначе не найдет вба при компиляции класс TreeView.
Итак, у нас все готово для работы. Приступим. Вытаскиваем компонент на форму и распологаем там где потребуется. Теперь нам нужна загрузка данных в него. Возникает мысль о вполне обыденном подходе при чтении в дерево инфы, хранящейся на листах: рекурсивный обход дерева. Для этого надо расположить данные так, чтобы нам было удобно считывать информацию о связях дерева. Поэтому воспользуемся также обыденной схемой мастер-деталь, которая позволяет нам в полной мере ощутить всю прелесть реляционного подхода при хранении данных в виде иерахичных списков (об иерархических БД речи не идет, поскольку ms excel нельзя назваnm ни той ни другой, это просто инструмент для вычислений, основанный на списках). Итак, вышеупомянутый подход хранения иерархии в реляционной схеме подразумевает под собой то, что в каждой строке хранится ссылка-идентификатор на родителя. То есть сын знает своего родителя, самого первого. Родителю ничего не известно о сыновьях. Выглядит это примерно так:

На дереве это выглядит примерно так:


А теперь немного о реализации. Как я упомянул выше, загрузка дерева идет рекурсивно, в общем-то, ничего здесь такого сложного нет, далее приведу код метода загрузки этого (да и вообще любого) дерева из списка:


' имя листа со списком "мастер-деталь"
Const TREESHEET As String = "sys.Datatree"
' номер первой строки
Const FIRSTROW As Integer = 2
' родитель первого узла
Const ROOTNODE As String = "0"
' поле-итератор
Private Selector As CTableIterator
' конструктор
Private Sub Class_Initialize()
' создаем и инициализируем итератор
Set Selector = New CTableIterator
Selector.Init TREESHEET, FIRSTROW, 1
End Sub
' дополнительная инициализация класса
Public Sub Init_(tv As TreeView)
tv.Nodes.Clear
Dim x As Integer
x = 1
' выбираем элемент со значением родителя первого узла (корневой)
Selector.Select_ 3, ROOTNODE
Dim nnode As Node
' создаем корневой узел
Set nnode = tv.Nodes.Add(, , , Cells(Selector.Item(x), 2))
nnode.EnsureVisible
Dim pindex As String
pindex = Cells(Selector.Item(x), 1)
' получаем и отображаем всех детей полученного узла
GetTree tv, pindex, nnode
End Sub
'recursive procedure that fills treeview from table
Private Sub GetTree(tv As TreeView, parentindex As String, pNode As Node)
Dim ti As New CTableIterator
ti.Init TREESHEET, FIRSTROW, 1
ti.Select_ 3, parentindex
If ti.Count > 0 Then
Dim x As Integer
For x = 1 To ti.Count
Dim nnode As Node
Set nnode = tv.Nodes.Add(pNode, tvwChild, , Cells(ti.Item(x), 2))
nnode.EnsureVisible
Dim pindex As String
pindex = Cells(ti.Item(x), 1)
GetTree tv, pindex, nnode
Next x
End If
End Sub


Думаю, в коде все понятно. Хотя тут и есть видимые невооруженным взглядом недочеты (нет предела совершенству ;) ), он вполне рабочий.

На этом закончу писать статьи о VBA, перейду, пожалуй, к более человеческим языкам программирования :)
PS CTableIterator