В данной статье посмотрим как копировать записи иерархических структур в DBTreeView, который мы уже на протяжении нескольких статей создаем своими руками. В этом посте только копирование записей в базе. В следующей добавлю обновление в дереве.
Для меня задача оказалась нетривиальной. Столкнулся с таким впервые. Сложность была в том, чтобы синхронизировать обновления в базе с обновлениями в самом дереве, а также правильно найти всех потомков и установить родственные связи во вновь вставленной структуре.
Я разложил задачу на этапы.
Сначала собирается список IDListChecked выделенных узлов.
Далее записи в базе копируются по этому списку – создается новое множество записей.
И наконец, восстанавливается первоначальная структура узлов. Если у узла был родитель и он в списке IDListChecked, тогда в базе поле idParent этого узла обновляется. Если родителя в списке собранных узлов не оказалось – тогда родителем становится выбранный узел.
Выглядит это примерно следующим образом.
Создадим SourceFolder и отметим её галочками. Выделим DestFolder и нажмем на кнопку Test, на которой создан обработчик копирования записей в базе и в результате получим следующее.
Узлы полностью скопировались, поменялся только родитель. Технически – сама процедура копирования оформлена в виде отдельного потока.
Начали!
В интерфейсной части у нас следующее
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 uCopyAndPasteThread; 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, uDBConnection; type TCopyAndPasteThread = class(TThread) private // Сбор списка отмеченных узлов Procedure ListParentAndAllChildren(NodeID:Integer); Procedure ListAllChecked; // Поиск родителя в базе Function FindParentInDB(ID:integer):integer; // << // Проверка на папку или файл по базе function IsFolderByID(NodeID:integer):boolean; //Копирование записи в базе по SourceID Function CopyRecordInDB (SourceID:integer):integer; //Копирование записей в базе, соответствующих выделенным узлам Procedure CopyAndPasteIDListCheckedInDB2;; //Структурирование нового списка, восстановление иерархии в БД Procedure Structuring; protected procedure Execute; override; end; var IDListChecked:Tstringlist; IDListInserted:Tstringlist; DBConnection:TDBConnection; |
Главный метод Execute потока
Главный метод потока выглядит следующим образом
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure TCopyAndPasteThread.Execute; var i:integer; begin // Создание IDListChecked:=Tstringlist.Create; IDListInserted:=Tstringlist.Create; DBConnection:=TDBConnection.Create(nil); ListAllChecked; CopyAndPasteIDListCheckedInDB2;// << Вставка скопированного множества записей Structuring; // << Восстановление иерархии во вставленном множестве // Уничтожение IDListChecked.Free; IDListInserted.Free; DBConnection.Free; end; |
2 процедуры для формирования 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 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 |
//--------------------------ФОРМИРУЕМ IDList-------------------------------- procedure TCopyAndPasteThread.ListParentAndAllChildren(NodeID: Integer); // Рекурсивно собирает ID родителя и всех его детей var qSelectAllChildren:TFDQuery; begin qSelectAllChildren:=TFDQuery.Create(nil); qSelectAllChildren.Connection:=DBConnection.FDConnection; //Если ID не был добавлен ранее, тогда добавляем if IDListChecked.IndexOf(NodeID.Tostring)=-1 then IDListChecked.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; //------------------------------Формируем IDListChecked--------------------------------------- procedure TCopyAndPasteThread.ListAllChecked; // Собирает все отмеченные галочками в дереве узлы в IDListChecked 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; //--------------------------------Формируем ID LIST--------------------------------- |
Копирование записей в БД
Это ключевая процедура, она копирует записи при помощи конструкции MySQL Insert… Select…
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 |
//---------------------------------Копирование записей в БД----------------------------- function TCopyAndPasteThread.CopyRecordInDB(SourceID: integer): integer; var qSelectInsert:TFDQuery; qGetKey:TFDQuery; begin qSelectInsert:=TFDQuery.Create(nil); qSelectInsert.Connection:=DBConnection.FDConnection; qSelectInsert.SQL.Text:= 'INSERT INTO `treeview_db`.`tree`'+ '(`IdParent`,'+ '`Name`,'+ '`Index`,'+ '`isChecked`,'+ '`IsExpanded`,'+ '`Level`,'+ '`IsFolder`)'+ 'SELECT'+ '`IdParent`,'+ '`Name`,'+ '`Index`,'+ '`isChecked`,'+ '`IsExpanded`,'+ '`Level`,'+ '`IsFolder`'+ ' from'+ '`treeview_db`.`tree` where id=:id' ; qSelectInsert.Params.ParamValues['id']:=SourceId; qSelectInsert.ExecSQL; qSelectInsert.Free; //----------Получение ID последней вставленной записи qGetKey:=TFDQuery.Create(nil); qGetKey.Connection:=DBConnection.FDConnection; qGetKey.SQL.Text:='SELECT Max(id) as newkey FROM treeview_db.tree;'; //Получение id последней записи with qGetKey do begin Open; Result:=qGetKey.FieldByName('NewKey').AsInteger; Close; end; // Освобождение qGetKey.Free; end; |
Вспомогательная процедура – проверка на папку или файл
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//-------------------------------Проверка на файл или папку по ID------------------------- function TCopyAndPasteThread.IsFolderByID(NodeID: integer): boolean; var qCheckIsFolder:TFDQuery; begin qCheckIsFolder:=TFDQuery.Create(nil); qCheckIsFolder.Connection:=DBConnection.FDConnection; qCheckIsFolder.SQL.Text:='Select * from `treeview_db`.`tree` where id='+NodeID.ToString; qCheckIsFolder.Open(); if qCheckIsFolder.FieldByName('IsFolder').AsBoolean=true then result:=true else Result:=false; qCheckIsFolder.Close; qCheckIsFolder.Free; // 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 |
//------------------------------Вставка всех узлов из IDList 2 вариант------------------ procedure TCopyAndPasteThread.CopyAndPasteIDListCheckedInDB2; var i:integer; TreeView:TTreeView; LastInsertedID:integer; begin //Ограничение - можно копировать только в папку if not IsFolderByID(Integer(MainForm.TreeView1.Selected.Data^)) then exit; //Нельзя скопировать папку в саму себя // Если в IDListChecked содержится выделенная папка, то выходим if IDListChecked.IndexOf(Integer(MainForm.TreeView1.Selected.Data^).ToString())<>-1 then exit; TreeView:=MainForm.TreeView1; for i := 0 to IDListChecked.Count-1 do begin LastInsertedID:=CopyRecordInDB(IDListChecked[i].ToInteger()); IDListInserted.Add(LastInertedID.ToString()); end; end; |
Вспомогательная процедура поиска родителя по ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function TCopyAndPasteThread.FindParentInDB(ID: integer): integer; var qSelectParent:TFDQuery; begin qSelectParent:=TFDQuery.Create(nil); qSelectParent.Connection:=DBConnection.FDConnection; with qSelectParent do begin SQL.Text:='Select * from `treeview_db`.`tree` where id='+ID.ToString; Open; Result:=FieldByName('idParent').AsInteger; Close; end; qSelectParent.Free; 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 |
//------------------------Востановление иерархии в записях из списка IDListInserted---------- procedure TCopyAndPasteThread.Structuring; var i: Integer; IDParent:Integer; ParentIndexInIDListChecked:integer; qUpdateIdParent:TFDQuery; begin //Готовим qUpdateIDParent qUpdateIdParent:=TFDQuery.Create(nil); qUpdateIdParent.Connection:=DBConnection.FDConnection; qUpdateIdParent.SQL.Text:='UPDATE `treeview_db`.`tree` SET `IdParent`=:idParent WHERE `id`=:id;'; //---------------------------------------- // Цикл for i := 0 to IDListChecked.Count-1 do begin IDParent:=FindParentInDB(IDListChecked[i].ToInteger()); // <<< Ищем ID родителя по базе // Если родитель не в списке IDListChecked if IDListChecked.IndexOf(IDParent.ToString())=-1 then // showmessage('Родитель в списке IDListChecked '); begin with qUpdateIdParent.Params do begin ParamValues['idParent']:=(Integer(MainForm.TreeView1.Selected.Data^)); ParamValues['id']:=IDListInserted[i]; //<<< Так работает end; qUpdateIdParent.ExecSQL; end else begin ParentIndexInIDListChecked:=IDListChecked.IndexOf(IDParent.ToString()); // IDListInserted[i] - индекс конкретного ребенка для которого найден родитель; with qUpdateIdParent.Params do begin // showmessage(IDListInserted[ParentIndexInIDListChecked]); ParamValues['idParent']:=(IDListInserted[ParentIndexInIDListChecked]); ParamValues['id']:=IDListInserted[i]; //<<< Так работает end; qUpdateIdParent.ExecSQL; end; // for... end; qUpdateIdParent.Free; end; |
Вызов в главном потоке
1 2 3 4 5 |
// Глобальные переменные ... var CopyAndPasteThread:TCopyAndPasteThread; ... |
1 2 3 4 5 6 7 8 |
procedure TMainForm.bTestClick(Sender: TObject); begin CopyAndPasteThread:=TCopyAndPasteThread.Create(false); CopyAndPasteThread.FreeOnTerminate:=true; end; |