В данной статье описан алгоритм удаления узлов, отмеченных галочками в случае, когда дерево загружается динамически (то есть не полностью). Сам алгоритм работает из отдельного потока.
Идея в том, чтобы собрать список “чекнутых” узлов, рекурсивно пройтись по базе, чтобы узнать есть ли у “чекнутых” узлов потомки и если есть – добавить их в список на удаление.
После этого произвести непосредственное удаление – сначала из базы, потом непосредственно из дерева. Причем, если загружены не все узлы, то удаление происходит только для видимых узлов в дереве, но для всех потомков этих узлов в базе. При последующем обновлении дерева – информация берется из базы, поэтому данные отображаются корректно.
Алгоритм относительно быстро удаляет данные для 200-300 узлов – 1-2 секунды, для 500 узлов, в районе 7 секунд, если ветка раскрыта, если ветка закрыта, также за 1-2 секунды. На мой взгляд, для традиционного TreeView это приемлемо. Ну и в принципе, для большинства проектов.
Интерфейсная часть
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 |
unit uDeleteCheckedThread; { Собрать в список IDList все, отмеченные галочками Собрать всех их потомков Удалить все собранные в список IDList из базы и из дерева } interface uses System.SysUtils, System.Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.UI.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Phys, FireDAC.VCLUI.Wait, FireDAC.Phys.MySQLDef, FireDAC.Phys.MySQL, Data.DB, FireDAC.Comp.Client, FireDAC.Comp.DataSet, Vcl.ComCtrls, // Для TTreeView Contnrs,vcl.dialogs, syncobjs,vcl.Forms,vcl.Controls; type TDeleteCheckedThread = class(TThread) private { Private declarations } FDConnection:TFDConnection; procedure Connect; Procedure ListParentAndAllChildren(NodeID:Integer); Procedure ListAllChecked(TreeView:TTreeView); Procedure DeleteNodesFromDB(IDList:TStringlist); procedure DeleteNodesFromTree2; protected procedure Execute; override; published end; var IDList:Tstringlist; CheckedNodes:array of integer; CS:TCriticalSection; |
IMPLEMENTATION
Connect – соединение с базой
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
procedure TDeleteCheckedThread.Connect; var oParams:Tstrings; begin oParams := TStringList.Create; // oParams.Add('DriverID='+DriverID); oParams.Add('DataBase=treeview_db'); oParams.Add('Password=masterkey'); oParams.Add('User_Name=root'); oParams.Add('Port=3306'); oParams.Add('Server=localhost'); oParams.Add('CharacterSet=utf8'); FDConnection.Params.Assign(oParams); FDConnection.DriverName:='MySQL'; FDConnection.Connected:=true; oParams.Free; end; |
ListParentAndAllChildren – добавляет в IDList соответствующие ID узла с NodeID и всех его потомков. Процедура рекурсивная.
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 |
procedure TDeleteCheckedThread.ListParentAndAllChildren(NodeID:Integer); var qSelectAllChildren:TFDQuery; begin qSelectAllChildren:=TFDQuery.Create(nil); qSelectAllChildren.Connection:=FDConnection; //Если ID не был добавлен ранее, тогда добавляем if IDList.IndexOf(NodeID.Tostring)=-1 then IDList.Add(NodeID.Tostring); // Записываем родителя и всех детей //Ищем всех детей with qSelectAllChildren do begin SQL.Text:='SELECT * FROM treeview_db.tree where IdParent='+NodeID.ToString; Open(); while not eof do begin // Для каждого ребенка вызываем рекурсивно ListParentAndAllChildren(FieldByName('id').AsInteger); Next; end; Close(); end; qSelectAllChildren.Free; end; |
ListAllChecked – проходит по всему дереву и собирает все узлы, отмеченные галочкой и их потомков IDList
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 |
procedure TDeleteCheckedThread.ListAllChecked; var i:integer; Node:TTreeNode; NodeID:integer; TreeView:TTreeView; begin TreeView:=MainForm.TreeView1; if TreeView.Items.GetFirstNode=nil then exit else Node:=TreeView.Items.GetFirstNode; repeat if Node.StateIndex=2 then begin NodeID:=Integer(Node.Data^); ListParentAndAllChildren(NodeID); // Добавляем в ListID end; Node:=Node.GetNext; until not Assigned(Node) ; end; |
DeleteNodesFromDB – удаление всех узлов из базы по списку из ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
DeleteNodesFromDB var qDelete:TFDQuery; i:integer; begin qDelete:=TFDQuery.Create(nil); qDelete.SQL.Text:='DELETE FROM `treeview_db`.`tree` WHERE `id`=:id;'; qDelete.Connection:=FDConnection; for i := 0 to IDList.Count-1 do begin with qDelete.Params do ParamValues['ID']:=IDList[i]; qDelete.ExecSQL; end; qDelete.Free; end; |
DeleteNodesFromTree2 – удаление узлов из дерева
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 |
procedure TDeleteCheckedThread.DeleteNodesFromTree2; // Удаляет только узлы с галочками из дерева (не из базы) var Node:TTreeNode; i:integer; TreeView: TTreeView; begin CS.Enter; TreeView:=MainForm.TreeView1; //Присваиваем первый узел или выходим if TreeView.Items.GetFirstNode<>nil then Node:=TreeView.Items.GetFirstNode else exit; SetLength(CheckedNodes,0); repeat if Node.StateIndex=2 then begin SetLength(CheckedNodes,length(CheckedNodes)+1); CheckedNodes[High(CheckedNodes)]:=Node.AbsoluteIndex; end; Node:=Node.GetNext; until not Assigned(Node) ; //Выходим, если ничего не выделено if length(CheckedNodes)=0 then exit; //От последнего узла идем к первому i:=High(CheckedNodes); repeat //Удаляем всех детей while TreeView.Items.Item[CheckedNodes[i]].HasChildren do begin Node:=TreeView.Items.Item[CheckedNodes[i]].GetLastChild; while Node.HasChildren do Node:=Node.GetLastChild; Dispose(Node.Data); // <<Удаляем место под указателями TreeView.Items.Delete(Node); // Удаляем из дерева end; //Удаляем родителя TreeView.Items.Item[CheckedNodes[i]].Delete; dec(i); until i=-1; // до нулевого включительно SetLength(CheckedNodes,0); //Освобождаем память CS.Leave; end; |
Execute потока
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 |
procedure TDeleteCheckedThread.Execute; var i:integer; begin // Создание IDList:=Tstringlist.Create; FDConnection:=TFDConnection.Create(nil); CS:=TCriticalSection.Create; // Основная работа Self.Connect; Synchronize(ListAllChecked); // << Сбор всех видимых чекнутых в один список DeleteNodesFromDB(IDList); //Удаляем собранные из базы Screen.Cursor:=crHourGlass; Synchronize(DeleteNodesFromTree2); Screen.Cursor:=crDefault; IDList.Clear; // Уничтожение IDList.Free; FDConnection.Free; CS.Free; { Place thread code here } end; |
Вызов в основном потоке
Глобальные переменные
1 2 |
var DeleteCheckedThread:TDeleteCheckedThread; |
Вызов потока по кнопке
1 2 3 4 5 6 7 8 |
procedure TMainForm.bDeleteChecked2Click(Sender: TObject); begin DeleteCheckedThread:=TDeleteCheckedThread.Create(false); //<<Удаление из базы DeleteCheckedThread.FreeOnTerminate:=true; end; |
Скачать модуль uDeleteCheckedThread.pas