unit uDMDB;

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
  Vcl.Dialogs,  // Для InputQuery
  Contnrs,
  System.Generics.Collections, FireDAC.Comp.UI;

type
  TDMDB = class(TDataModule) // TDataModuleDataBase
    qSelect: TFDQuery;
    qInsert: TFDQuery;
    qEdit: TFDQuery;
    qDelete: TFDQuery;
    qGetKey: TFDQuery;
    FDConnection: TFDConnection;
    FDPhysMySQLDriverLink: TFDPhysMySQLDriverLink;
    qEditIndex: TFDQuery;
    qEditParentKey: TFDQuery;
    qCheckedUpdate: TFDQuery;
    qExpanded: TFDQuery;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    qLevels: TFDQuery;
    qMaxLevel: TFDQuery;
    qSelectChildren: TFDQuery;
    procedure Connect(Sender:TObject);
    procedure DataModuleCreate(Sender: TObject);

    //Вставка записи в БД и дерево
    function InsertNodeInDB(const idParent: Integer; const index: Integer;
    const name: string; const TreeView: TTreeView):integer;

    procedure InsertNodeInTree(Sender:TObject; TreeView:TTreeView);
    procedure InsertManyNodesInTree(Sender:TObject; TreeView:TTreeView);

    //Редактирование записи в дереве и БД
    procedure EditNode(Sender:TObject; TreeView:TTreeView);

    //UPDATE Cбор информации о дереве
    Function FindParentNode(Key:integer;TreeView:TTreeView):TTreeNode; //Вспомогательная
    Procedure UpdateAllTree(Sender:Tobject;TreeView:TTreeView);
    Procedure UpdateNodeCheckBox(Node:TTreeNode);

    //ДИНАМИЧЕСКАЯ ПОДГРУЗКА Первоначальное открытие узлов
    Procedure InitialUpdate(Sender: TObject; TreeView: TTreeView; DeepLevel: Integer);
    //Загрузка одного узла
    Procedure ExpandingUpdate(Node: TTreeNode; TreeView: TTreeView);

    Procedure LoadLevels(ParentNode:TTreeNode);




    //Очистка дерева
    Procedure ClearTree(TreeView:TTreeView);

    //Удаление узлов
    Procedure DeleteRecord(Node:TTreeNode);
    Procedure DeleteNode(Sender:TObject;TreeView:TTreeView);
    Procedure DeleteManyNodes(Sender:TObject;TreeView:TTreeView);
    procedure DeleteManyNodes2(Sender: TObject; TreeView: TTreeView);
    procedure DeleteManyNodes3(Sender: TObject; TreeView: TTreeView);

    procedure DeleteCheckedNodes(Sender: TObject; TreeView: TTreeView);

    Procedure NodeUP(Sender:TObject;TreeView:TTreeView);
    Procedure NodeDOWN(Sender:TObject;TreeView:TTreeView);
    Procedure CheckBoxSelection(CurrentNode:TTreeNode; TreeView: TTreeView);

    // РАССТАНОВКА ЧЕКБОКСОВ
    Procedure ListParentAndAllChildren(NodeID:Integer);
    Procedure CheckListedInDB;
    Procedure UncheckListedInDB(Node:TTreeNode);
    Procedure CheckBoxSelection2(Node:TTreeNode; TreeView: TTreeView);


    //Запоминание свернутости
    Procedure WriteExpandings(Node:TTReeNode);
    Procedure WriteCollapsings(Node:TTreeNode);
    Procedure ReadExpandingsCollapsings(Sender:TObject;TreeView:TTreeView);

     //Запоминание уровней
      Procedure WriteLevels(Sender:TObject;TreeView:TTreeView);


                            //ВСПОМОГАТЕЛЬНО
    //Поиск узла по тексту
    function FindNodeByText(Text:string;TreeView:TTReeview):TTreeNode;


    procedure ShowIndex(TreeView: TTreeView);

    procedure DecDA(DA:array of integer);  ////Уменьшение значенией массива на единицу
    procedure DataModuleDestroy(Sender: TObject);

    //Поиск детей узла
    procedure FindChildren(NodeID:integer);
    procedure DeleteNodesByID(IDList:TStringlist);

  private

  FIDList:Tstringlist;


    { Private declarations }
  public
    property IDList:Tstringlist read FIDList write FIDList;

    { Public declarations }
  end;

  //--------Потоки для Check, чтобы ускорить

  TCheckListedInDBThread=class(TThread)

   protected
   procedure execute; override;

   public destructor Destroy; override;
 end;


var
  DMDB: TDMDB;
  CheckedNodes:array of integer;
  CheckedList:TStringlist;

  GlobalIDList:Tstringlist;
  GlobalNodeID:integer;

  CheckListedInDBThread:TCheckListedInDBThread;



implementation

{%CLASSGROUP 'Vcl.Controls.TControl'}

{$R *.dfm}



procedure TDMDB.CheckBoxSelection(CurrentNode:TTreeNode; TreeView: TTreeView);
var  VeryLastChild:TTreeNode; ParentNode:TTreeNode;
begin

      if not Assigned(CurrentNode) then exit;

      // Поиск самого самого самого последнего узла, начиная с текущего
      VeryLastChild:=CurrentNode;
      while (VeryLastChild.HasChildren)  do
         begin
         VeryLastChild:=VeryLastChild.GetLastChild; //<<<Записываем результат
         end;


      //Обход с CurerentNode до VeryLastChild, включая все вложенные узлы


             //Если узел не содержит детей
             if not CurrentNode.HasChildren then
               begin

                  if CurrentNode.StateIndex=2 then

                      begin
                        CurrentNode.StateIndex:=1;

                        qCheckedUpdate.Params.ParamValues['IsChecked']:=0;
                        qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qCheckedUpdate.ExecSQL;


                        //Если сняли галочку у ребенка, у родителя её тоже надо снять
                        //Проходимся по всем родителям и снимаем галку
                        ParentNode:=CurrentNode;
                        while true do
                        begin
                          if ParentNode.Parent=nil then break;
                          ParentNode:=ParentNode.Parent;
                          ParentNode.StateIndex:=1;

                          qCheckedUpdate.Params.ParamValues['IsChecked']:=0;
                          qCheckedUpdate.Params.ParamValues['id']:=Integer(ParentNode.Data^);
                          qCheckedUpdate.ExecSQL;

                        end;




                      end



                  else
                      begin
                         CurrentNode.StateIndex:=2 ;
                        qCheckedUpdate.Params.ParamValues['IsChecked']:=1;
                        qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qCheckedUpdate.ExecSQL;
                      end;



               end;



             //Если узел  содержит детей и он Checked
             if (CurrentNode.HasChildren) and (CurrentNode.StateIndex=2) then
              begin

                 while True do
                  begin

                   CurrentNode.StateIndex:=1;

                        //Запись в базу чекнутости
                        qCheckedUpdate.Params.ParamValues['IsChecked']:=0;
                        qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qCheckedUpdate.ExecSQL;

                        //Запись в базу экспанднутости
                        if CurrentNode.Expanded then

                        qExpanded.Params.ParamValues['IsExpanded']:=1
                        else
                        qExpanded.Params.ParamValues['IsExpanded']:=0;
                        qExpanded.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qExpanded.ExecSQL;




                        //Если сняли галочку у ребенка, у родителя её тоже надо снять
                        //Проходимся по всем родителям и снимаем галку
                        ParentNode:=CurrentNode;
                        while true do
                        begin
                          if ParentNode.Parent=nil then break;
                          ParentNode:=ParentNode.Parent;
                          ParentNode.StateIndex:=1;

                          qCheckedUpdate.Params.ParamValues['IsChecked']:=0;
                          qCheckedUpdate.Params.ParamValues['id']:=Integer(ParentNode.Data^);
                          qCheckedUpdate.ExecSQL;

                        end;


                   if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext
                   else break;
                  end;

                 //
              end;


             //Если узел  содержит детей и он NOT Checked
             if CurrentNode.HasChildren and (CurrentNode.StateIndex=1) then
              begin

                 while True do
                  begin

                   CurrentNode.StateIndex:=2;

                        //Запись в базу
                        qCheckedUpdate.Params.ParamValues['IsChecked']:=1;
                        qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qCheckedUpdate.ExecSQL;

                        //Запись в базу экспанднутости
                        if CurrentNode.Expanded then

                        qExpanded.Params.ParamValues['IsExpanded']:=1
                        else
                        qExpanded.Params.ParamValues['IsExpanded']:=0;
                        qExpanded.Params.ParamValues['id']:=Integer(CurrentNode.Data^);
                        qExpanded.ExecSQL;

                   if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext
                   else break;
                  end;

                 //
              end;






end;


procedure TDMDB.ListParentAndAllChildren(NodeID:Integer);

var qSelectAllChildren:TFDQuery;

begin

   qSelectAllChildren:=TFDQuery.Create(Self);
   qSelectAllChildren.Connection:=FDConnection;

//   FIDList.Add(NodeID.ToString);
   GlobalIDList.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;




procedure TDMDB.CheckBoxSelection2(Node: TTreeNode; TreeView: TTreeView);

var NodeID:integer; ParentNode:TTreeNode;

begin


 if not Assigned(TreeView.Selected) then exit;
 NodeID:=integer(TreeView.Selected.Data^);

 GlobalNodeID:=NodeId;

 ListParentAndAllChildren(NodeID);

 //Если выделенный узел не отмечен галочкой

    if TreeView.Selected.StateIndex<>2 then
    begin




     //Отмечаем галочками все видимые, загруженные подузлы
     Node := TreeView.Selected;
      repeat
        Node.StateIndex:=2;
        Node:=Node.GetNext;
      until not (Assigned(Node)) or (Node.Level = TreeView.Selected.Level);


    //  Добавление галочек в базу в отдельном потоке
//    CheckListedInDBThread:=TCheckListedInDBThread.Create(false);
//    CheckListedInDBThread.FreeOnTerminate:=true;

    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;

        // Эта операция тормозит - убрать её в поток
         UncheckListedInDB(Node); // Сняли галочки с родителя и детей в базе

     //Снимаем галочки со всех видимых, загруженных подузлов
     Node := TreeView.Selected;
      repeat
        Node.StateIndex:=1;
        Node:=Node.GetNext;
      until not (Assigned(Node)) or (Node.Level = TreeView.Selected.Level);

    exit;
    end;

end;




procedure TDMDB.CheckListedInDB;

var i:integer;

begin
//



for i := 0 to GlobalIDList.Count-1 do
  begin
    qCheckedUpdate.Params.ParamValues['IsChecked']:=1;
    qCheckedUpdate.Params.ParamValues['id']:=GlobalIDList[i];
    qCheckedUpdate.ExecSQL;
  end;


end;

procedure TDMDB.ClearTree(TreeView:TTreeView);
var i:integer;
begin
//   showmessage('TreeView.Items.Count-1 ='+inttostr(TreeView.Items.Count-1));
  for i := 0 to TreeView.Items.Count-1  do
  begin
    if TreeView.Items.Item[i].Data<>nil then
    Dispose(TreeView.Items.Item[i].Data);

  end;
    TreeView.Items.Clear;
end;

procedure TDMDB.Connect(Sender: TObject);
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;

procedure TDMDB.DataModuleCreate(Sender: TObject);
var i:integer;
begin
FIDList:=Tstringlist.Create;

GlobalIDList:=Tstringlist.Create;

CheckedList:=TStringlist.Create;

//Подключение
Self.Connect(Self);

for I := 0 to Self.ComponentCount-1 do
  begin
    if Components[i] is TFDQuery then
     (Components[i] as TFDQUery).Connection:=FDConnection;
  end;

end;


procedure TDMDB.DataModuleDestroy(Sender: TObject);
begin
CheckedList.Free;

FIDList.Free;

GlobalIDList.Free;

end;

procedure TDMDB.DecDA(DA: array of integer);
//Уменьшение значенией массива на единицу
var i:integer;
begin
 for i := Low(DA) to High(DA) do
   DA[i]:=DA[i]-1;

end;



procedure TDMDB.DeleteManyNodes(Sender: TObject; TreeView: TTreeView);
var i,k:integer;  SL:TStringList;
begin

SL:=TStringList.Create;

 for i := 0 to TreeView.SelectionCount-1 do
   begin
    SL.Add(TreeView.Selections[i].Text);
   end;

TreeView.Items.BeginUpdate;
 for i := 0 to SL.Count-1 do
   begin
     FindNodeByText(Sl[i],TreeView);
     DeleteNode(Self,TreeView);
   end;
TreeView.Items.EndUpdate;


SL.Free;
end;

procedure TDMDB.DeleteManyNodes2(Sender: TObject; TreeView: TTreeView);
var i:integer;
    IndexOfMinimum:integer; // индекс элемента c минимальным значением в массиве
    DA:array of integer; //Вспомогательный массив для Selection
    Node:TTreeNode;  // Для цикла удаления узлов
begin

 if TreeView.SelectionCount=0 then exit;

      //Копируем Selections в массив и упорядочиваем его
      SetLength(DA,TreeView.SelectionCount);

      for i := 0 to TreeView.SelectionCount-1 do
      DA[i]:=TreeView.Selections[i].AbsoluteIndex;

      TArray.Sort<integer>(DA);
      IndexOfMinimum:=DA[0];

      SetLength(DA,0);//Зануляем массив

//Когда удаляем i-й элемент, индексы в TreeView пересчитываются, и i+1 элемент становится i
// Поэтому можно всё время удалять i-й элемент в пределах SelectionCount

   TreeView.Items.BeginUpdate;
   for i := 0 to TreeView.SelectionCount-1 do
     begin
                  //Удаляем всех детей
            while TreeView.Items.Item[IndexOfMinimum].HasChildren do
              begin
                Node:=TreeView.Items.Item[IndexOfMinimum].GetLastChild;
                while Node.HasChildren do Node:=Node.GetLastChild;
                DeleteRecord(Node); //Удаляем из базы
                TreeView.Items.Delete(Node); // Удаляем из дерева
              end;

                  //Удаляем родителя
         DeleteRecord(TreeView.Items.Item[IndexOfMinimum]);
         TreeView.Items.Item[IndexOfMinimum].Delete;

     end;

   TreeView.Items.EndUpdate;

end;


procedure TDMDB.DeleteCheckedNodes(Sender: TObject; TreeView: TTreeView);

var Node:TTreeNode; i:integer;
begin

 //Присваиваем первый узел или выходим
 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;
              //  DeleteRecord(Node); //Удаляем из базы
                TreeView.Items.Delete(Node); // Удаляем из дерева

              end;

                  //Удаляем родителя
         DeleteRecord(TreeView.Items.Item[CheckedNodes[i]]);
         TreeView.Items.Item[CheckedNodes[i]].Delete;
dec(i);

 until  i=-1; //  до нулевого включительно

SetLength(CheckedNodes,0); //Освобождаем память

end;


procedure TDMDB.DeleteManyNodes3(Sender: TObject; TreeView: TTreeView);
var i,k:integer;
    IndexOfMinimum:integer; // индекс элемента c минимальным значением в массиве
    DA:array of integer; //Вспомогательный массив для Selection
    Node:TTreeNode;  // Для цикла удаления узлов
begin

 if TreeView.SelectionCount=0 then exit;

      //Копируем Selections в массив и упорядочиваем его
      SetLength(DA,TreeView.SelectionCount);

      for i := 0 to TreeView.SelectionCount-1 do
      DA[i]:=TreeView.Selections[i].AbsoluteIndex;

      TArray.Sort<integer>(DA); //Сортируем массив


//Когда удаляем i-й элемент, индексы в TreeView пересчитываются, и i+1 элемент становится i
// Поэтому можно это учесть
                              {СОБСТВЕННО УДАЛЕНИЕ ЭЛЕМЕНТОВ}
   TreeView.Items.BeginUpdate;
   for i := 0 to TreeView.SelectionCount-1 do
     begin

                  //Удаляем всех детей
            while TreeView.Items.Item[DA[i]].HasChildren do
              begin
                Node:=TreeView.Items.Item[DA[i]].GetLastChild;
                while Node.HasChildren do Node:=Node.GetLastChild;
                DeleteRecord(Node); //Удаляем из базы
                TreeView.Items.Delete(Node); // Удаляем из дерева
              end;

                  //Удаляем родителя
         DeleteRecord(TreeView.Items.Item[DA[i]]);
         TreeView.Items.Item[DA[i]].Delete;

           // Уменьшаем индекс в DA, чтобы в следующий раз удалить нужный элемент дерева
           for k := Low(DA) to High(DA) do
             DA[k]:=DA[k]-1;

     end;

   TreeView.Items.EndUpdate;

end;




procedure TDMDB.ShowIndex(TreeView:TTreeView);
var i:integer;         s:string;
begin
//showmessage(treeview.Selected.AbsoluteIndex.ToString);

for i := 0 to TreeView.SelectionCount-1 do
s:=s+TreeView.Selections[i].AbsoluteIndex.ToString()+#13#10;

showmessage(s);

end;


procedure TDMDB.DeleteNode(Sender: TObject; TreeView: TTreeView);
var Node:TTreeNode;
begin
  if not Assigned(TreeView.Selected) then exit;

  //Удаляем всех детей
  while TreeView.Selected.HasChildren do
    begin
      Node:=TreeView.Selected.GetLastChild;
      while Node.HasChildren do Node:=Node.GetLastChild;
      DeleteRecord(Node); //Удаляем из базы
      TreeView.Items.Delete(Node); // Удаляем из дерева


    end;

 //Удаляем родителя
 DeleteRecord(TreeView.Selected);
 TreeView.Selected.Delete;
end;







procedure TDMDB.DeleteNodesByID(IDList: TStringlist);
var i:integer;
begin

for i := 0 to IDList.Count-1 do

  begin
     with qDelete.Params do
     ParamValues['ID']:=IDList[i];
     qDelete.ExecSQL;
  end;


end;

procedure TDMDB.DeleteRecord(Node: TTreeNode);
var Obj:^Integer;
begin
  Obj:=Node.Data;
  qDelete.Params.ParamValues['id']:=Obj^;
  qDelete.ExecSQL;
  Dispose(Obj);
end;

procedure TDMDB.EditNode(Sender: TObject; TreeView: TTreeView);
var Node:TTreeNode;
    s:string;

begin
 if Assigned(TreeView.Selected) then
 begin

         Node:=TreeView.Selected;
         S:=Node.Text;

         if InputQuery('Редактируем узел','Новое название',s)=true then

    begin
         with qEdit.Params do
         begin
           ParamValues['name']:=S;
           ParamValues['id']:=Integer(Node.Data^);
        end;
        qEdit.ExecSQL;
        Node.Text:=s;
    end else exit;
 end;

end;





procedure TDMDB.ReadExpandingsCollapsings(Sender: TObject; TreeView: TTreeView);
var Node:TtreeNode;
begin


 with qSelect do begin
 Open();

   while not eof do
    begin



       Node:=Self.FindParentNode(FieldByName('id').AsInteger,TreeView);

       if Node<>nil then
        begin
          if FieldByName('IsExpanded').AsBoolean=true then Node.Expand(false)
          else Node.Collapse(false);

        end;


    Next;
    end;

 Close();
 end;



end;



procedure TDMDB.InitialUpdate(Sender: TObject; TreeView: TTreeView; DeepLevel: Integer);
                        //ПЕРВОНАЧАЛЬНАЯ ЗАГРУЗКА ДО УРОВНЯ DEEPLEVEL
//Процедура загружает данные в дерево до глубины DeepLevel
//DeepLevel - это последний видимый уровень в дереве

var Buffer:string;  
    Obj:^Integer;
    Node,ChildNode,GhostNode:TTreeNode;
    MaxLevel:integer; // Максимальный уровень дерева по базе
    TrashPointer:^Integer;


begin
Buffer:=qSelect.SQL.Text;
qSelect.SQL.Text:='SELECT * FROM treeview_db.tree where Level<='+DeepLevel.ToString
+' order by `index`';


  //Получение максимального уровня дерева из базы

  with qMaxLevel do
  begin
  Open();
  MaxLevel:=FieldByName('MaxLevel').AsInteger;
  Close();
  end;




                                {ЗАГРУЗКА В ДЕРЕВО ИЗ БАЗЫ}
TreeView.Items.BeginUpdate;
  ClearTree(TreeView);

  with qSelect do
   begin
     Open();

     while eof<>true do
     begin



       New(Obj);
       Obj^:=FieldByName('id').AsInteger;
       Node:=Self.FindParentNode(FieldByName('idParent').AsInteger,TreeView);
       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) and  (ChildNode.Level<MaxLevel) 
             and (ChildNode.Level=DeepLevel)  then
             
             begin
             new(TrashPointer);
            
             GhostNode:=TreeView.Items.AddChildObject(ChildNode,'GhostNode',TrashPointer);
             GhostNode.Parent.Collapse(false); // <<Прячем его
             end;
          //--------------------Добавление GhostNode----------------------------





       // Расставляем галочки
       if TreeView.StateImages<>nil then
         begin
          if FieldByName('isChecked').AsBoolean=false
           then
           ChildNode.StateIndex:=1 else ChildNode.StateIndex:=2;
         end;


       Next;
     end;
      Close;
   end;

TreeView.Items.EndUpdate;
qSelect.SQL.Text:=Buffer;



end;


///////////////////////////////////////////////////////////////////////////////


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;



procedure TDMDB.LoadLevels(ParentNode: TTreeNode);

var Buffer:string; id:^Integer;


begin
Buffer:=qSelect.SQL.Text;


//---------------Выбираем всех детей узла----------------------------

   if ParentNode<>nil then

   begin
       new(id);
       id^:=integer(ParentNode.Data^);
       dispose(id);
   end else id^:=-1;

      //Выбираем из базы всех детей узла
      qSelect.SQL.Text:='SELECT * FROM treeview_db.tree where IdParent='+id^.ToString
      +' order by `index`';



//-------------------------------------------------------------------







qSelect.SQL.Text:=buffer;
end;




procedure TDMDB.InsertManyNodesInTree(Sender: TObject; TreeView: TTreeView);
var name:string;
    Node:TTreeNode;
    idParent,ItemIndex:integer;
  i: Integer; // Ключ и индекс узла
   Obj:^Integer; s:string;


begin

if InputQuery('Сколько узлов вставить?','',s)=true then

begin


         if Assigned(TreeView.Selected) then
               begin
                 //Назначаем родителя новому узлу
                 idParent:=Integer(TreeView.Selected.Data^);
                 ItemIndex:=TreeView.Items.Count+1;

                 end else
               //Если никакой узел не выбран, владельца нет
               begin
                idParent:=-1;
                ItemIndex:=0;
               end;

       TreeView.Items.BeginUpdate;
            for i := 0 to s.ToInteger-1 do
           begin
             //Собственно вставка в дерево
               name:='N'+i.ToString;
               New(Obj);
                    // Вставка в БД
               Obj^:=InsertNodeInDB(idParent, Itemindex, name, TreeView);
                    // Вставка в само дерево
               Node:=TreeView.Items.AddChildObject(
               TreeView.Selected,name,obj
               );

                        //Проверка "чекнутости" родителя в базе, чтобы
             //выставить Checked/not Checked ребенку

             if (Node.Parent<>nil) then
             begin
               if (Node.Parent.StateIndex=2) then Node.StateIndex:=2 else
               Node.StateIndex:=1;
             end else Node.StateIndex:=1;


           end;
       TreeView.Items.EndUpdate;




           // Оставляем фокус на том узле, на который нажали
              if TreeView.Selected<>nil then
              begin
              TreeView.Selected.Expanded:=true;
              TreeView.SetFocus;
              TreeView.Selected.Selected:=true;
              end;
end;

end;

function TDMDB.InsertNodeInDB(const idParent: Integer; const index: Integer; const name: string; const TreeView: TTreeView): integer;
begin

//Вставка записи
  with qInsert.Params do
   begin
     ParamValues['idParent']:=idParent;
     ParamValues['Index']:=Index;
     ParamValues['Name']:=Trim(Name);
     ParamValues['isChecked']:=false; // По умолчанию галочка не отмечена


   // Записываем уровень вставляемого узла в базу
   if  FindParentNode(idParent,TreeView)<>nil then
   begin
     ParamValues['Level']:=FindParentNode(idParent,TreeView).Level+1
   end else ParamValues['Level']:=0;



   end;

qInsert.ExecSQL;

 //Получение id последней записи
 with qGetKey do
 begin
   Open;
   Result:=qGetKey.FieldByName('NewKey').AsInteger;
   Close;
 end;

end;

procedure TDMDB.InsertNodeInTree(Sender: TObject; TreeView: TTreeView);
var name:string;
    Node:TTreeNode;
    idParent,ItemIndex:integer; // Ключ и индекс узла

    Obj:^Integer;
begin
    if InputQuery('Новая запись','',name)=true then
    begin
      if Assigned(TreeView.Selected) then
       begin
         //Назначаем родителя новому узлу
         idParent:=Integer(TreeView.Selected.Data^);
         ItemIndex:=TreeView.Items.Count+1;

       end else
       //Если никакой узел не выбран, владельца нет
       begin
        idParent:=-1;
        ItemIndex:=0;
       end;


       //Собственно вставка в дерево

       New(Obj);
            // Вставка в БД
       Obj^:=InsertNodeInDB(idParent, Itemindex, name, TreeView);
            // Вставка в само дерево
       Node:=TreeView.Items.AddChildObject(
       TreeView.Selected,name,obj);

             //Проверка "чекнутости" родителя в базе, чтобы
             //выставить Checked/not Checked ребенку

             if (Node.Parent<>nil) then
             begin
               if (Node.Parent.StateIndex=2) then Node.StateIndex:=2 else
               Node.StateIndex:=1;
             end else Node.StateIndex:=1;




   // Оставляем фокус на том узле, на который нажали
      if TreeView.Selected<>nil then
      begin
      TreeView.Selected.Expanded:=true;
      TreeView.SetFocus;
      TreeView.Selected.Selected:=true;
      end;




    //   Node .Selected:=true;

      //Dispose(Obj) <<Высвобождать будем при закрытии либо
      //Удалении, иначе выскакивает ошибка Invalid Pointer Operation
    end;

// InsertNodeInTree
end;




procedure TDMDB.NodeDOWN(Sender: TObject; TreeView: TTreeView);
//Процедура опускания узла дерева
var CurrentNode,AfterNode:TTreeNode;

begin

  if (TreeView.Selected=nil) or (TreeView.Selected.getNextSibling=nil) then exit;

   CurrentNode:=TreeView.Selected;
   AfterNode:=CurrentNode.GetNextChild(CurrentNode);
   TreeView.Items.BeginUpdate;
   AfterNode.MoveTo(CurrentNode,naInsert);

   TreeView.Items.EndUpdate;

//Держим фокус
   TreeView.SetFocus;
   CurrentNode.Selected:=true;

  with qEditIndex.Params do
  begin
    ParamValues['Index']:=CurrentNode.AbsoluteIndex;
    ParamValues['id']:=Integer(CurrentNode.Data^);
  end;
   qEditIndex.ExecSQL;

    with qEditIndex.Params do
  begin
    ParamValues['Index']:=AfterNode.AbsoluteIndex;
    ParamValues['id']:=Integer(AfterNode.Data^);
  end;
  qEditIndex.ExecSQL;

//UpdateTree(Self,TreeView);


//Обновление картинки StateIndex после перемещения

CheckBoxSelection(CurrentNode,TreeView);
if CurrentNode.StateIndex=2 then CurrentNode.StateIndex:=1
 else CurrentNode.StateIndex:=2;

CheckBoxSelection(AfterNode,TreeView);
if AfterNode.StateIndex=2 then AfterNode.StateIndex:=1
 else AfterNode.StateIndex:=2;

//
end;

procedure TDMDB.NodeUP(Sender: TObject; TreeView: TTreeView);
//Процедура поднятия узла дерева
var CurrentNode,BeforeNode:TTreeNode;

begin

  if (TreeView.Selected=nil) or (TreeView.Selected.getPrevSibling=nil) then exit;

   CurrentNode:=TreeView.Selected;
   BeforeNode:=CurrentNode.GetPrevChild(CurrentNode);
   TreeView.Items.BeginUpdate;
   CurrentNode.MoveTo(BeforeNode,naInsert);
   TreeView.Items.EndUpdate;

   TreeView.SetFocus;
   CurrentNode.Selected:=true;

  with qEditIndex.Params do
  begin
    ParamValues['Index']:=CurrentNode.AbsoluteIndex;
    ParamValues['id']:=Integer(CurrentNode.Data^);
  end;
   qEditIndex.ExecSQL;

    with qEditIndex.Params do
  begin
    ParamValues['Index']:=BeforeNode.AbsoluteIndex;
    ParamValues['id']:=Integer(BeforeNode.Data^);
  end;
  qEditIndex.ExecSQL;


//Обновление картинки StateIndex после перемещения
CheckBoxSelection(CurrentNode,TreeView);
if CurrentNode.StateIndex=2 then CurrentNode.StateIndex:=1
else CurrentNode.StateIndex:=2;

CheckBoxSelection(BeforeNode,TreeView);
if BeforeNode.StateIndex=2 then BeforeNode.StateIndex:=1
 else BeforeNode.StateIndex:=2;

//
end;

procedure TDMDB.WriteCollapsings(Node: TTreeNode);
begin
 if Node=nil then exit;

   with qExpanded.Params do begin

    ParamValues['IsExpanded']:=0;
    ParamValues['id']:=Integer(Node.Data^);
    qExpanded.ExecSQL;
    end;
end;

procedure TDMDB.WriteExpandings(Node:TTReeNode);

begin

 if Node=nil then exit;


    with qExpanded.Params do begin

    ParamValues['IsExpanded']:=1;
    ParamValues['id']:=Integer(Node.Data^);
    qExpanded.ExecSQL;
    end;

end;



procedure TDMDB.WriteLevels(Sender: TObject; TreeView: TTreeView);
// Запоминание уровней
var Node:TTreeNOde;
begin
   if TreeView.Items.GetFirstNode<>nil then
      Node:=TreeView.Items.GetFirstNode;

  repeat

      if Node.Text<>'GhostNode' then // Была ошибка, при закрытии формы, поставил ограничение
      begin

            with qLevels.Params do
             begin

                ParamValues['Level']:=Node.Level;

                ParamValues['id']:=Integer(Node.Data^);


             end;
                  qLevels.ExecSQL;


      end;

     Node:=Node.GetNext;
  until not Assigned(Node) ;
end;



procedure TDMDB.FindChildren(NodeID:integer);

// Рекурсивная процедура
//Выбирает детей для узла с ID в базе NodeID, добавляет их в IDList
//Для каждого ребенка, если у него есть дети - повторяется, уходит в глубь
//Если у него нет детей, продолжает по верхнему уровню идти до последнего ребенка

var   qSelectChildren2:TFdquery;

begin


qSelectChildren2:=TFdquery.Create(Self);
qSelectChildren2.Connection:=FDConnection;


FIDList.Add( NodeId.ToString );  //<< Добавили родителя


with qSelectChildren2 do
 begin

   

  //Выбираем из базы всех детей узла

  SQL.Text:='SELECT * FROM treeview_db.tree where IdParent='+NodeID.ToString;

  Open();

    if IsEmpty then exit; //Выход, если пустое множество

    while not eof do
     begin

     FindChildren(FieldByName('id').AsInteger);


     Next;
     end;

  Close();

 end;


qSelectChildren2.Free;

end;

function TDMDB.FindNodeByText(Text:string; TreeView:TTReeView): TTreeNode;
var Searching:boolean; Noddy:TTreeNode;
begin
  Noddy:=TreeView.Items[0]; Searching:=true;

  while (Searching) and (Noddy<>nil) do
  begin
    if Noddy.Text=Text then
     begin
       Searching:=false;
       TreeView.Selected:=Noddy;
       TreeView.SetFocus;
     end else
     Noddy:=Noddy.GetNext;


  end;


end;

function TDMDB.FindParentNode(Key: integer;TreeView:TTreeView): TTreeNode;
var i:integer;
begin
 Result:=nil; //Если родитель не будет найден, то это нулевой уровень
//Иначе, это будет какой-то конкретный узел
 for i := 0 to TreeView.Items.Count-1 do
  if Integer(TreeView.Items.Item[i].Data^)=Key then

   begin
     Result:=TreeView.Items.Item[i];
     Break;
   end;


end;


procedure TDMDB.UpdateNodeCheckBox(Node: TTreeNode);
begin


 with qSelect  do
 begin
 Open();
   if Locate('Name',Node.Text,[])=false then exit;

//If   Locate('Name',Node.Text,[])=true then showmessage('Найдено');
  if FieldByName('isChecked').AsBoolean=false
           then
           Node.StateIndex:=1 else Node.StateIndex:=2;
 Close();
 end;


end;




procedure TDMDB.UncheckListedInDB(Node:TTreeNode);
var
  i: Integer; ParentNode:TTreeNode;
begin
     // Снимаем галочку у всех родителей, если они есть
    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 GlobalIDList.Count-1 do

      begin

          qCheckedUpdate.Params.ParamValues['IsChecked']:=0;
          qCheckedUpdate.Params.ParamValues['id']:=GlobalIDList[i];
          qCheckedUpdate.ExecSQL;
      end;

end;

procedure TDMDB.UpdateAllTree(Sender: Tobject;TreeView:TTreeView);
var Obj:^Integer; Node,ChildNode:TTreeNode;
begin


TreeView.Items.BeginUpdate;
  ClearTree(TreeView);

  with qSelect do
   begin
     Open();

     while eof<>true do
     begin



       New(Obj);
       Obj^:=FieldByName('id').AsInteger;
       Node:=Self.FindParentNode(FieldByName('idParent').AsInteger,TreeView);
       ChildNode:=TreeView.Items.AddChildObject(Node,FieldByName('name').AsString,Obj);




       // Расставляем галочки
       if TreeView.StateImages<>nil then
         begin
          if FieldByName('isChecked').AsBoolean=false
           then
           ChildNode.StateIndex:=1 else ChildNode.StateIndex:=2;
         end;


       Next;
     end;
      Close;
   end;

TreeView.Items.EndUpdate;

end;

{ TCheckInDBThread }

destructor TCheckListedInDBThread.Destroy;
begin

  inherited;
end;

procedure TCheckListedInDBThread.execute;

//var DMDB2:TDMDB;
begin
  inherited;


DMDB:=TDMDB.Create(nil);

DMDB.ListParentAndAllChildren(GLobalNodeID);

DMDB.CheckListedInDB;

GlobalIDList.Clear;



//FreeAndNil(DMDB2);



end;

end.
