unit Un_DBTreeView;

interface

uses
   Classes,ComCtrls,CommCtrl,DB,DBTables,Controls,Messages;

type
   TpsDBTreeNode = class (TTreeNode)
   private
      FIdNode   :Integer;
   public
      property idNode:Integer read FIdNode;
   end;

   TpsDBTreeView = class(TCustomTreeView)
   private
      FOnEdited: TTVEditedEvent;
      FLDblCklick:Boolean; //показывает, что выполняется DblClick
      FDoExpColOnDblClick:Boolean; //Если True, то при DblClick не будет раскрываться/закрываться Node.
      FReopenOnExpand:Boolean;
      FQuery:TQuery;
      FIdTree:Integer;
      function GetDatabaseName: string;
      procedure SetDatabaseName(const Value: string);
      procedure SetIdTree(const Value: Integer);
      procedure AddChildren(AParent:TTreeNode);
      procedure WMLButtonDblClk(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK;
      function GetSelectedID: Integer;
      procedure SetSelectedID(const Value: Integer);
   protected
      procedure Loaded; override;
      function CreateNode: TTreeNode; override;
      function CanExpand(Node: TTreeNode): Boolean; override;
      function CanCollapse(Node: TTreeNode): Boolean; override;
      procedure DoEdited(Sender: TObject; Node: TTreeNode; var S: string);
   public
      constructor Create(AOwner:TComponent); override;
      procedure dbLoadFirstLevel;
      function dbAddChild(AParent:TTreeNode;AText:string;idNode:Integer=0):TTreeNode;
      procedure dbDeleteNode(Node:TTreeNode;ReQueryFromDB:Boolean=False);
      procedure dbMoveNode(DNode,SNode:TTreeNode;AsChild:Boolean=False;ReQueryFromDB:Boolean=False);
      property Items;
      property SelectedID:Integer read GetSelectedID write SetSelectedID;
   published
      property idTree:Integer read FIdTree write SetIdTree;
      property DatabaseName:string read GetDatabaseName write SetDatabaseName;
      property DoExpColOnDblClick:Boolean read FDoExpColOnDblClick write FDoExpColOnDblClick default True;
      property OnEdited:TTVEditedEvent read FOnEdited write FOnEdited;
   published //Из TCustomTreeView
      property Align;
      property Anchors;
      property BevelEdges;
      property BevelInner;
      property BevelOuter;
      property BevelKind default bkNone;
      property BevelWidth;
      property BiDiMode;
      property BorderStyle;
      property BorderWidth;
      property ChangeDelay;
      property Color;
      property Ctl3D;
      property Constraints;
      property DragKind;
      property DragCursor;
      property DragMode;
      property Enabled;
      property Font;
      property HideSelection;
      property HotTrack;
      property Images;
      property ReadOnly;
      property RightClickSelect;
      property RowSelect;
      property ShowButtons;
      property ShowHint;
      property ShowLines;
      property ShowRoot;
      property OnAddition;
      property OnAdvancedCustomDraw;
      property OnAdvancedCustomDrawItem;
      property OnChange;
      property OnChanging;
      property OnClick;
      property OnCollapsed;
      property OnCollapsing;
      property OnCompare;
      property OnContextPopup;
      property OnCreateNodeClass;
      property OnCustomDraw;
      property OnCustomDrawItem;
      property OnDblClick;
      property OnDeletion;
      property OnDragDrop;
      property OnDragOver;
      property OnEditing;
      property OnEndDock;
      property OnEndDrag;
      property OnEnter;
      property OnExit;
      property OnExpanding;
      property OnExpanded;
      property OnGetImageIndex;
      property OnGetSelectedIndex;
      property OnKeyDown;
      property OnKeyPress;
      property OnKeyUp;
      property OnMouseDown;
      property OnMouseMove;
      property OnMouseUp;
      property OnStartDock;
      property OnStartDrag;
      { Items must be published after OnGetImageIndex and OnGetSelectedIndex }
      //property Items;
   end;

procedure Register;

implementation

uses
   SysUtils,Variants,Forms;

const
    SQLLoadLevel  :string='EXEC upDBTreeGetChildren @idDBTree=%d,@idParent=%s';
    SQLAddChild   :string='EXEC upDBTreeAddNode @idDBTree=%d,@idParent=%s,@idPrior=%s,@idNext=%s,@Text=''%s'',@idNode=%s';
    SQLDeleteNode :string='EXEC upDBTreeDeleteNode @idDBTree=%d,@idNode=%d';
    SQLMoveNode   :string='EXEC upDBTreeMoveNode @idDBTree=%d,@idDNode=%d,@idSNode=%d,@AsChild=%d';
    SQLRenameNode :string='EXEC upDBTreeRenameNode @idDBTree=%d,@idNode=%d,@NewText=''%s''';
    SQLGetFullPath:string='EXEC upDBTreeGetFullPath @idDBTree=%d,@idNode=%d';

procedure Register;
begin
   RegisterComponents('My Components',[TpsDBTreeView]);
end;

{ TpsDBTreeView }

procedure TpsDBTreeView.AddChildren(AParent:TTreeNode);
var
   NewNode:TpsDBTreeNode;
   TheCursor:TCursor;
begin
   TheCursor:=Screen.Cursor;
   Screen.Cursor:=crHourGlass;
   try
      with FQuery do begin
         Open;
         try
            if not IsEmpty then
             while not Eof do begin
                NewNode:=Items.AddChild(AParent,FieldByName('Text').AsString) as TpsDBTreeNode;
                if not FieldByName('idFirstChild').IsNull then
                   Items.AddChild(NewNode,'HasChildren');
                with NewNode do begin
                   FIdNode:=FieldByName('idNode').AsInteger;
                end;
                Next;
             end;
         finally
            Close;
         end;
      end;
   finally
      Screen.Cursor:=TheCursor;
   end;
end;

function TpsDBTreeView.CanCollapse(Node: TTreeNode): Boolean;
begin
   if FLDblCklick and not FDoExpColOnDblClick then
      Result:=False
   else begin
      Result:=inherited CanCollapse(Node);
      //Удаление вложенных узлов
      if Result and FReopenOnExpand and (Node is TpsDBTreeNode) and Node.HasChildren then begin
         Items.BeginUpdate;
         try
            Node.DeleteChildren;
            Items.AddChild(Node,'HasItems');
         finally
            Items.EndUpdate;
         end;
      end;
   end;
end;

function TpsDBTreeView.CanExpand(Node: TTreeNode): Boolean;
begin
   if FLDblCklick and not FDoExpColOnDblClick then
      Result:=False
   else begin
      //Загрузка вложенных узлов
      if FReopenOnExpand and (Node is TpsDBTreeNode) and Node.HasChildren then begin
         Items.BeginUpdate;
         try
            Node.DeleteChildren;
            if (FIdTree<>0) and (DatabaseName<>'') then begin
               FQuery.SQL.Text:=Format(SQLLoadLevel,[FIdTree,IntToStr((Node as TpsDBTreeNode).idNode)]);
               AddChildren(Node);
            end;
         finally
            Items.EndUpdate;
         end;
      end;
      Result:=inherited CanExpand(Node);
   end;
end;

constructor TpsDBTreeView.Create(AOwner: TComponent);
begin
  FReopenOnExpand:=True;
  FDoExpColOnDblClick:=True;
  inherited;
  inherited OnEdited:=DoEdited;
  FQuery:=TQuery.Create(Self);
end;

function TpsDBTreeView.CreateNode: TTreeNode;
begin
  if Assigned(OnCreateNodeClass) then
    Result:=inherited CreateNode
  else Result:=TpsDBTreeNode.Create(Items);
end;

function TpsDBTreeView.dbAddChild(AParent: TTreeNode;AText:string;idNode:Integer=0): TTreeNode;
var
   NewNode:TTreeNode;

   function GetIdParent(Node:TTreeNode):string;
   begin
      if Assigned(Node.Parent) then
         Result:=IntToStr((Node.Parent as TpsDBTreeNode).idNode)
      else Result:='NULL';
   end;
   function GetIdPrior(Node:TTreeNode):string;
   var
      Prior:TTreeNode;
   begin
      Prior:=Node.getPrevSibling;
      if Assigned(Prior) then
         Result:=IntToStr((Prior as TpsDBTreeNode).idNode)
      else Result:='NULL';
   end;
   function GetIdNext(Node:TTreeNode):string;
   var
      Next:TTreeNode;
   begin
      Next:=Node.getNextSibling;
      if Assigned(Next) then
         Result:=IntToStr((Next as TpsDBTreeNode).idNode)
      else Result:='NULL';
   end;
   function GetIdNode(idNode:Integer):string;
   begin
      if idNode<>0 then
         Result:=IntToStr(idNode)
      else Result:='NULL';
   end;

begin
   Items.BeginUpdate;
   try
      if Assigned(AParent) and not AParent.Expanded then
         AParent.Expand(False);
      NewNode:=Items.AddChild(AParent,AText);
      with FQuery do
       if (FIdTree<>0)and(DatabaseName<>'') then begin
         FQuery.SQL.Text:=Format(SQLAddChild,[FIdTree,GetIdParent(NewNode),GetIdPrior(NewNode),GetIdNext(NewNode),AText,GetIdNode(idNode)]);
         try
            Open;
            try
              if not IsEmpty then begin
                 (NewNode as TpsDBTreeNode).FIdNode:=FieldByName('NewId').AsInteger;
                 //Выделяем добавленный узел
                 FReopenOnExpand:=False;
                 try
                    Selected:=NewNode;
                 finally
                    FReopenOnExpand:=True;
                 end;
              end else Raise Exception.Create('TpsDBTreeView.dbAddChild:Не получен идентификатор нового узла.');
            finally
              Close;
            end;
         except
            NewNode.Delete;
            Raise;
         end;
       end;
   finally
      Items.EndUpdate;
   end;
   Result:=NewNode;
end;

procedure TpsDBTreeView.dbDeleteNode(Node: TTreeNode;ReQueryFromDB:Boolean=False);
var
   AParent:TTreeNode;
begin
   if Node.HasChildren then
      Raise Exception.Create('TpsDBTreeView.dbDeleteNode:Этот узел удалить нельзя,т.к. есть вложеннные узлы.');
   with FQuery do begin
      SQL.Text:=Format(SQLDeleteNode,[FIdTree,(Node as TpsDBTreeNode).idNode]);
      ExecSQL;
   end;
   if ReQueryFromDB then
    begin
       Items.BeginUpdate;
       try
          AParent:=Node.Parent;
          if Assigned(AParent) then
           begin
              AParent.Collapse(False);
              AParent.Expand(False);
           end
          else dbLoadFirstLevel;
       finally
          Items.EndUpdate;
       end;
    end
   else Node.Delete;
end;

procedure TpsDBTreeView.dbMoveNode(DNode, SNode: TTreeNode;AsChild: Boolean=False;ReQueryFromDB:Boolean=False);
const
   BoolToInt:array[Boolean] of Integer =(0,1);
var
   DParent,SParent,Node:TTreeNode;
   TheNodeId:Integer;
begin
   if not Assigned(DNode) or not Assigned(SNode) or (DNode=SNode) then
      Exit;
   if DNode.HasAsParent(SNode) then
      Raise Exception.Create('TpsDBTreeView.dbMoveNode:Узел не может быть перемещен.')
   else begin
      with FQuery do begin
         SQL.Text:=Format(SQLMoveNode,[FIdTree,(DNode as TpsDBTreeNode).idNode,(SNode as TpsDBTreeNode).idNode,BoolToInt[AsChild]]);
         ExecSQL;
      end;
      Items.BeginUpdate;
      try
         if ReQueryFromDB then
          begin
             TheNodeId:=(SNode as TpsDBTreeNode).idNode;
             DParent:=DNode.Parent;
             SParent:=SNode.Parent;
             if Assigned(DParent) and Assigned(SParent) then
              begin
                 DParent.Collapse(False);
                 DParent.Expand(False);
                 if (DParent<>SParent) and not SParent.HasAsParent(DParent) then begin
                    DParent.Collapse(False);
                    DParent.Expand(False);
                 end;
              end
             else dbLoadFirstLevel;
             if Assigned(DParent) then
                Node:=DParent.getFirstChild
             else Node:=Items.GetFirstNode;
             while Assigned(Node) and ((Node as TpsDBTreeNode).idNode<>TheNodeId) do
                Node:=Node.getNextSibling;
             if Assigned(Node) then
                Selected:=Node;
          end
         else
            try
              if AsChild then
               begin
                 if DNode.Expanded then
                  begin
                     FReopenOnExpand:=False;
                     SNode.MoveTo(DNode,naAddChild);
                  end
                 else begin
                    Items.AddChildFirst(DNode,'HasChildren');//Надо добавить узел,что бы DNode открылся.
                    if CanExpand(DNode) then begin
                       SNode.Delete;
                       FReopenOnExpand:=False;
                       DNode.GetLastChild.Selected:=True;
                    end;
                 end;
               end
              else begin
                 FReopenOnExpand:=False;
                 SNode.MoveTo(DNode,naInsert);
              end;
            finally
              FReopenOnExpand:=True;
            end;
      finally
         Items.EndUpdate;
      end;
   end;
end;

function TpsDBTreeView.GetDatabaseName: string;
begin
   Result:=FQuery.DatabaseName;
end;

procedure TpsDBTreeView.Loaded;
begin
  inherited;
  if not (csDesigning in ComponentState) then
    dbLoadFirstLevel;
end;

procedure TpsDBTreeView.dbLoadFirstLevel;
begin
   Items.Clear;
   if not (csDesigning in Self.ComponentState) and not (csLoading in Self.ComponentState) and (FIdTree<>0) and (DatabaseName<>'') then begin
      FQuery.SQL.Text:=Format(SQLLoadLevel,[FIdTree,'NULL']);
      AddChildren(nil);
   end;
end;

procedure TpsDBTreeView.SetDatabaseName(const Value: string);
begin
   FQuery.DatabaseName:=Value;
   dbLoadFirstLevel;
end;

procedure TpsDBTreeView.SetIdTree(const Value: Integer);
begin
   FIdTree := Value;
   dbLoadFirstLevel;
end;

procedure TpsDBTreeView.WMLButtonDblClk(var Message: TWMLButtonDblClk);
begin
   FLDblCklick:=True;
   inherited;
   FLDblCklick:=False;
end;

function TpsDBTreeView.GetSelectedID: Integer;
begin
   if Assigned(Selected) and (Selected is TpsDBTreeNode) then
      Result:=(Selected as TpsDBTreeNode).idNode
   else Result:=0;
end;

procedure TpsDBTreeView.SetSelectedID(const Value: Integer);
var
   TheNode:TTreeNode;
   ThePath:array of Integer;
   I:Integer;
begin
   if (Items.Count>0)and(Items[0] is TpsDBTreeNode) then begin
      Items.BeginUpdate;
      try
        try
           TheNode:=Items[0];
           with FQuery do begin
              SQL.Text:=Format(SQLGetFullPath,[FIdTree,Value]);
              Open;
              try
                 if IsEmpty then
                    Raise Exception.Create('TpsDBTreeView.SetSelectedID:Не получен путь к узлу '+IntToStr(Value));
                 Last;First;
                 SetLength(ThePath,RecordCount);
                 I:=0;
                 while not Eof do begin
                    ThePath[I]:=FieldByName('idNode').AsInteger;
                    Inc(I);
                    Next;
                 end;
              finally
                 Close;
              end;
           end;
           for I:=0 to High(ThePath) do begin
              while Assigned(TheNode) and ((TheNode as TpsDBTreeNode).idNode<>ThePath[I]) do
                 TheNode:=TheNode.getNextSibling;
              if not Assigned(TheNode) then
                 Raise Exception.Create('TpsDBTreeView.SetSelectedID:Не найден узел '+IntToStr(ThePath[I]));
              if I<High(ThePath) then begin
                 TheNode.Expand(False);
                 TheNode:=TheNode.getFirstChild;
              end;
           end;
           if not Assigned(TheNode) then
              Raise Exception.Create('TpsDBTreeView.SetSelectedID:Не найден узел.');
           Selected:=TheNode;
        finally
           ThePath:=nil;
        end;
      finally
         Items.EndUpdate;
      end;
   end;
end;

{ TpsDBTreeNode }

procedure TpsDBTreeView.DoEdited(Sender: TObject; Node: TTreeNode;var S: string);
begin
   if Assigned(FOnEdited) then
      FOnEdited(Sender,Node,S);
   if (Node is TpsDBTreeNode) and (Node.Text<>S) then
    try //Сохраняем изменения в базе
       with FQuery do begin
          SQL.Text:=Format(SQLRenameNode,[FIdTree,(Node as TpsDBTreeNode).idNode,S]);
          try
             Open;
             if IsEmpty then
                Raise Exception.Create('TpsDBTreeView.DoEdited:Не получен результат переименования.');
             S:=FieldByName('NewText').AsString;
          finally
             Close;
          end;
       end;
    except
       S:=Node.Text;
       Raise;
    end;
end;

{ TpsDBTreeNode }

end.


Сайт управляется системой uCoz