Решил добавить многопоточности в дерево. Cтолкнулся с тем, что при большом количестве потомков у узла, больше 500, начинаются тормоза с проставлением галочек у всех потомков. Я запрограммировал так, что при нажатии на узел родителя, если у него есть узлы дети, – галочки у них у всех проставляются либо снимаются.
Создал отдельный юнит для потомков New > ThreadObject
В интерфейсной части 4 основных метода, отвечающих за логику и запись в базу “чекнутости” узлов.
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 |
unit uCheckBoxSelectionThread; 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, syncobjs; type TCheckBoxSelectionThread = class(TThread) private FDConnection:TFDConnection; procedure Connect; Procedure ListParentAndAllChildren(NodeID:Integer); // <<Список всех потомков по базе procedure CheckListedInDB; // << Запись в базу всех "чекнутых" узлов procedure UncheckListedInDB(Node: TTreeNode); // << Запись в базу всех "Анчекнутых" узлов procedure CheckBoxSelection; // << Интеграция 3 предыдущих процедур function FindParent(Node:TTreeNode;ParentNode:TTreeNode):boolean; { Private declarations } protected procedure Execute; override; end; var IDList:Tstringlist; CS:TCritiCalSection; |
Connect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
procedure TCheckBoxSelectionThread.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
Данная процедура рекурсивно собирает ID узла родителя и всех его потомков до последнего
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 |
procedure TCheckBoxSelectionThread.ListParentAndAllChildren(NodeID:Integer); var qSelectAllChildren:TFDQuery; begin qSelectAllChildren:=TFDQuery.Create(nil); qSelectAllChildren.Connection:=FDConnection; // NodeID:=Integer(FNode.Data^); 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; |
CheckListedInDB – это запись из собранного списка в базу
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 |
procedure TCheckBoxSelectionThread.CheckListedInDB; // Расстановка галочек в базе var i:integer; qCheckedUpdate:TFDQuery; begin // qCheckedUpdate:=TFDQuery.Create(nil); qCheckedUpdate.Connection:=FDConnection; qCheckedUpdate.SQL.Text:= 'UPDATE `treeview_db`.`tree` SET `IsChecked`=:IsChecked WHERE `id`=:id;'; for i := 0 to IDList.Count-1 do begin qCheckedUpdate.Params.ParamValues['IsChecked']:=1; qCheckedUpdate.Params.ParamValues['id']:=IDList[i]; qCheckedUpdate.ExecSQL; end; qCheckedUpdate.Free; end; |
UncheckListedInDB. Снятие галочек в базе
Эта процедура немного сложнее потому что добавлена логика по родителям. Если снимаем галочку у потомка, то снимается у всех родителей до последнего.
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 |
procedure TCheckBoxSelectionThread.UncheckListedInDB(Node:TTreeNode); //Снятие галочек в базе var i: Integer; ParentNode:TTreeNode; qCheckedUpdate:TFDQuery; begin qCheckedUpdate:=TFDQuery.Create(nil); qCheckedUpdate.Connection:=FDConnection; qCheckedUpdate.SQL.Text:= 'UPDATE `treeview_db`.`tree` SET `IsChecked`=:IsChecked WHERE `id`=:id;'; // Снимаем галочку у всех родителей, если они есть if Node.Parent<>nil then begin ParentNode:=Node.Parent; while true do begin if ParentNode.Parent=nil then begin qCheckedUpdate.Params.ParamValues['IsChecked']:=0; qCheckedUpdate.Params.ParamValues['id']:=Integer(Node.Parent.Data^); qCheckedUpdate.ExecSQL; break; end; qCheckedUpdate.Params.ParamValues['IsChecked']:=0; qCheckedUpdate.Params.ParamValues['id']:=Integer(Node.Parent.Data^); qCheckedUpdate.ExecSQL; ParentNode:=ParentNode.Parent; end; end; //Снимаем галочку у всех, кто в списке for i := 0 to IDList.Count-1 do begin qCheckedUpdate.Params.ParamValues['IsChecked']:=0; qCheckedUpdate.Params.ParamValues['id']:=IDList[i]; qCheckedUpdate.ExecSQL; end; qCheckedUpdate.Free; end; |
Вспомогательная функция – поиск родителя узла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function TCheckBoxSelectionThread.FindParent(Node, ParentNode: TTreeNode): boolean; begin Result:=false; Repeat if Node=ParentNode then begin Result:=true; break; end; Node:=Node.Parent; Until not Assigned(Node); end; |
Работа с деревом. Интеграция методов
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 |
procedure TCheckBoxSelectionThread.CheckBoxSelection; var NodeID:integer; Node:TTreeNode; ParentNode:TTreeNode; TreeView:TTreeView; begin CS.Enter; TreeView:=MainForm.TreeView1; if not Assigned(TreeView.Selected) then exit; Node := TreeView.Selected; NodeID:=integer(TreeView.Selected.Data^); ListParentAndAllChildren(NodeID); // Собрали родителя и всех потомков в список //Если выделенный узел не отмечен галочкой if TreeView.Selected.StateIndex<>2 then begin //Отмечаем галочками все видимые, загруженные подузлы Node := TreeView.Selected; repeat Node.StateIndex:=2; Node:=Node.GetNext; until not (Assigned(Node)) or not FindParent(Node,TreeView.Selected);// (Node.Level > TreeView.Selected.Level); CheckListedInDB; //Отметили галочкой все подузлы в базе exit; // Выход, иначе далее галочка опять снимется end; //Если выделенный узел отмечен галочкой if TreeView.Selected.StateIndex=2 then begin //Снимаем галочки со всех родителей в TreeView if Node.Parent<>nil then begin ParentNode:=Node.Parent; while true do begin if ParentNode.Parent=nil then begin ParentNode.StateIndex:=1; break; end; ParentNode.StateIndex:=1; ParentNode:=ParentNode.Parent; end; end; //Снимаем галочки со всех видимых, загруженных подузлов //Node стоит на Selected, начинаем перебор с него repeat Node.StateIndex:=1; Node:=Node.GetNext; until not (Assigned(Node)) or not FindParent(Node,TreeView.Selected); Node := TreeView.Selected; //Снова поставили ссылку на TreeView.Selected UncheckListedInDB(Node); // Сняли галочки с родителя и детей в базе exit; end; IDList.Clear; // Чистим список CS.Leave; end; |
Execute – основной метод потока
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure TCheckBoxSelectionThread.Execute; begin //Создание FDConnection:=TFDConnection.Create(nil); IDList:=Tstringlist.Create; CS:=TCriticalSection.Create; //Основная работа Self.Connect; Self.CheckBoxSelection; // Здесь нужно либо Synchronize либо Queue //Уничтожение IDList.Free; FDConnection.Free; CS.Free; { Place thread code here } end; |
Вызов потока в главном модуле
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var CheckBoxSelectionThread:TCheckBoxSelectionThread; ... procedure TMainForm.TreeView1Click(Sender: TObject); begin if NotAllowed then exit else if Assigned(NodeUnderMice) and (MouseOnCheckBox) then begin CheckBoxSelectionThread:=TCheckBoxSelectionThread.Create(false); CheckBoxSelectionThread.FreeOnTerminate:=true; end; |