Идея динамической подгрузки веточек-узлов пришла ко мне, когда я стал добавлять по 500 узлов в узлы родители, и при загрузке дерева стала ощущаться задержка. Я так и представил разрастающиеся базы пользователей и эти долгие загрузки со всеми последствиями. Оно и понятно, ведь это обычное TreeView, которое содержит в себе данные, и эти данные загружаются в большом количестве.
Динамически опять же по разному можно подгружать. Есть вариант в +2 уровня узла. То есть, при каждом раскрытии дерева подгружается непосредственно раскрываемый узел, и узлы на следующем уровне. Выглядит логично, но в теории, если на втором уровне будет много данных – опять же может тормозить. Я остановился на относительно простом варианте – подгрузке одного узла.
Идея в том, чтобы в один момент времени подгружать 1 узел. А узлы следующего уровня создавать из 1 единственного узла – GhostNode – целью которого будет являться индикация того, что на след. уровне ещё что-то есть. При раскрытии следующего уровня, этот GhostNode удаляется и в раскрываемый узел загружаются нормальные данные. Таким образом, в один момент времени будут загружаться данные только одного узла.
Основная рабочая процедура, которая будет использоваться для загрузки одного уровня дерева, будет называться в моем коде ExpandingUpdate.
В отдельном датамодуле, я назвал его DMDB добавим FDQuery под названием qSelect
Далее, я сначала покажу, как будет использоваться процедура ExpandingUpdate(Node: TTreeNode; TreeView: TTreeView), в основном модуле, прежде чем покажу саму процедуру.
Использование ExpandingUpdate в MainForm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure TMainForm.FormShow(Sender: TObject); var i:integer; Node:TTreeNode; begin DMDB:=TDMDB.Create(Self); DMDB.Connect(Self); if DMDB.FDConnection.Connected then Self.Caption:=Self.Caption+' Connected to DB'; // Загрузка 0 и 1 уровней дерева TreeView1.Items.Clear; DMDB.ExpandingUpdate(nil,TreeView1); //Загружаем 0-й уровень if TreeView1.Items.GetFirstNode<>nil then TreeView1.Items.GetFirstNode.Expand(false); //Показываем 1-й уровень end; |
Добавляем в событие Expanding – теперь при каждом раскрытии будет вызываться наша процедура
1 2 3 4 5 6 7 8 9 |
procedure TMainForm.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); begin DMDB.ExpandingUpdate(Node, TreeView1); NotAllowed:=true; end; |
Также, если создан механизм переподчинения узлов перетаскиванием, то и в DragDrop
1 2 3 4 5 6 7 8 9 10 |
procedure TMainForm.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer); var SenderNode,Node:TTreeNode; begin ... DMDB.ExpandingUpdate(SenderNode,TreeView1); SenderNode.Expand(false); end; |
Непосредственно процедура Expanding во вспомогательном модуле DMDB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
procedure TDMDB.ExpandingUpdate(Node: TTreeNode; TreeView: TTreeView); // Процедура загружает из базы всех детей Node, и добавляет их в Node // Поскольку Expanding может повторяться, то предусмотрена // защита от повторного добавления //Также есть узел GhostNode, для последнего узла, так как при динамической //загрузке, мы видим только до определенного уровня, и если у узла есть дети //и он на последнем видимом уровне, то это надо как-то показать, я сделал это // через GhostNode; var Buffer:string; id, Obj, TrashPointer:^integer; // Указатели для вставки узлов, см. код ChildNode:TTreeNode; // Добавляемый узел GhostNode:TTreeNode; // Чтобы у последнего узла, если у него есть дети, был плюсик begin {ОБНОВЛЯЕМ УЗЕЛ ДЕРЕВА} //TreeView.Items.BeginUpdate; if Node<>nil then begin // Удаляем Ghost - вспомогательный узел для динамической подгрузки if (Node.HasChildren) and (Node.Item[0].Text='GhostNode') then TreeView.Items.Delete(Node.Item[0]); end; //--------------------------------РАБОТА С QSELECT Buffer:=qSelect.SQL.Text; new(id); if Node<>nil then begin id^:=integer(Node.Data^); end else id^:=-1; //Выбираем из базы всех детей узла qSelect.SQL.Text:='SELECT * FROM treeview_db.tree where IdParent='+id^.ToString +' order by `index`'; dispose(id); // Предотвращаем повторное добавление в узел (наблюдал за программой - выявил повторное добавление) if Node<>nil then if Node.Count<>0 then exit; with qSelect do begin Open(); while eof<>true do // Начинаем перебирать все записи begin //Добавление дочернего узла из текущей записи к Node New(Obj); Obj^:=FieldByName('id').AsInteger; ChildNode:=TreeView.Items.AddChildObject(Node,FieldByName('name').AsString,Obj); //--------------------Добавление GhostNode------------------------ // Проверяем есть ли дети у узла, чтобы добавить Ghost, если нужно qSelectChildren.SQL.Text:= 'SELECT * FROM treeview_db.tree where idParent='+Integer(Obj^).ToString; qSelectChildren.Open(); // Добавлем Ghost, если у узла есть дети, чтобы у последнего узла был плюсик //И его можно было раскрыть. Этот Ghost при раскрытии будет удаляться if not qSelectChildren.IsEmpty then begin new(TrashPointer); GhostNode:=TreeView.Items.AddChildObject(ChildNode,'GhostNode',TrashPointer); GhostNode.Parent.Collapse(false); // <<Прячем его end; qSelectChildren.Close(); //--------------------Добавление GhostNode---------------------------- //Расставляем галочки согласно базе if FieldByName('IsChecked').AsBoolean=true then ChildNode.StateIndex:=2 else ChildNode.StateIndex:=1; Next; end; Close; // qSelect TreeView.Items.EndUpdate; qSelect.SQL.Text:=Buffer; // Возвращаем запрос на место end; end; |
Тестируем
В каждой папке по 500 узлов – открывается довольно быстро, задержки не ощущается, как это было при полной загрузке всех узлов.
Основной функционал работает. Остается доработать обработку ошибок, ну и оптимизировать, оптимизировать!