Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Дерево на базе MSSQL

Автор: Пенов Сергей
WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Дерево на базе MsSQL 7/2000 и DELPHI6 (BDE,ADO)

Узел дерева описывается через idParent,idPrior,idNext,idFirstChild.
В следствии такого подхода в многопользовательской среде достигается
минимальное количество блокировок при изменении узлов дерева.
Все функции реализованы в хранимых процедурах. Компанент, порожденный
от TTreeView, является интерфейсом для работы с деревом в клиенте.
Тексты хранимых процедур на странице
http://spenov.narod.ru/DBTree/DBTreeView.html

Зависимости: Classes,ComCtrls,CommCtrl,DB,DBTables,Controls,Messages,ADODB
Автор:       Пенов Сергей, spenov@narod.ru, ICQ:122597033, Москва
Copyright:   http://spenov.narod.ru/DBTree/DBTreeView.html
Дата:        6 сентября 2002 г.
***************************************************** }

//Тексты хранимых процедур на странице
// http://spenov.narod.ru/DBTree/DBTreeView.html
unit Un_TADODBTreeView;

interface

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

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

  TADODBTreeView = class(TCustomTreeView)
  private
    FRootID: string;
    FOnEdited: TTVEditedEvent;
    FLDblCklick: Boolean; //показывает, что выполняется DblClick
    FDoExpColOnDblClick: Boolean;
    //Если True, то при DblClick не будет раскрываться/закрываться Node.
    FReopenOnExpand: Boolean;
    FConnection: TADOConnection;
    FRecordset: _Recordset;
    FIdTree: Integer;
    procedure SetRootID(Value: string);
    procedure SetConnection(Value: TADOConnection);
    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);
    procedure Notification(AComponent: TComponent; Operation: TOperation);
      override;
  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 RootID: string read FRootID write SetRootID;
    property idDBTree: Integer read FIdTree write SetIdTree;
    property Connection: TADOConnection read FConnection write SetConnection;
    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 PopupMenu;
    property StateImages;
    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;
    //property Visible;
    { Items must be published after OnGetImageIndex and OnGetSelectedIndex }
    //property Items;
  end;

procedure Register;

implementation

uses
  SysUtils, Variants, Forms, DBLogDlg;

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('Penov', [TADODBTreeView]);
end;

{ TADODBTreeView }

procedure TADODBTreeView.AddChildren(AParent: TTreeNode);
var
  NewNode: TADODBTreeNode;
  TheCursor: TCursor;
  Buf: TTVExpandedEvent;
begin
  TheCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  try
    Buf := OnAddition;
    OnAddition := nil;
    try
      with FRecordset do
      begin
        if RecordCount > 0 then
          while not Eof do
          begin
            NewNode := Items.AddChild(AParent, Fields['Text'].Value) as
              TADODBTreeNode;
            with NewNode do
            begin
              HasChildren := not VarIsNull(Fields['idFirstChild'].Value);
              FIdNode := Fields['idNode'].Value;
            end;
            if Assigned(Buf) then
              Buf(Self, NewNode);
            MoveNext;
          end;
      end;
    finally
      OnAddition := Buf;
    end;
  finally
    Screen.Cursor := TheCursor;
  end;
end;

function TADODBTreeView.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 TADODBTreeNode) and
      Node.HasChildren then
    begin
      Items.BeginUpdate;
      try
        Node.DeleteChildren;
        Items.AddChild(Node, 'HasItems');
      finally
        Items.EndUpdate;
      end;
    end;
  end;
end;

function TADODBTreeView.CanExpand(Node: TTreeNode): Boolean;
var
  crBuf: TCursor;
begin
  if FLDblCklick and not FDoExpColOnDblClick then
    Result := False
  else
  begin
    //Загрузка вложенных узлов
    if FReopenOnExpand and (Node is TADODBTreeNode) and Node.HasChildren then
    begin
      Items.BeginUpdate;
      try
        Node.DeleteChildren;
        if (FIdTree <> 0) and Assigned(FConnection) then
        begin
          crBuf := Screen.Cursor;
          Screen.Cursor := crSQLWait;
          try
            FRecordset := FConnection.Execute(Format(SQLLoadLevel, [FIdTree,
              IntToStr((Node as TADODBTreeNode).idNode)]));
          finally
            Screen.Cursor := crBuf;
          end;
          try
            AddChildren(Node);
          finally
            FRecordset := nil;
          end;
        end;
      finally
        Items.EndUpdate;
      end;
    end;
    Result := inherited CanExpand(Node);
  end;
end;

constructor TADODBTreeView.Create(AOwner: TComponent);
begin
  FRootID := 'NULL';
  FReopenOnExpand := True;
  FDoExpColOnDblClick := True;
  inherited;
  inherited OnEdited := DoEdited;
end;

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

function TADODBTreeView.dbAddChild(AParent: TTreeNode; AText: string; idNode:
  Integer = 0): TTreeNode;
var
  NewNode: TTreeNode;
  Buf: TTVExpandedEvent;
  crBuf: TCursor;

  function GetIdParent(Node: TTreeNode): string;
  begin
    if Assigned(Node.Parent) then
      Result := IntToStr((Node.Parent as TADODBTreeNode).idNode)
    else
      Result := FRootID;
  end;
  function GetIdPrior(Node: TTreeNode): string;
  var
    Prior: TTreeNode;
  begin
    Prior := Node.getPrevSibling;
    if Assigned(Prior) then
      Result := IntToStr((Prior as TADODBTreeNode).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 TADODBTreeNode).idNode)
    else
      Result := 'NULL';
  end;
  function GetIdNode(idNode: Integer): string;
  begin
    if idNode <> 0 then
      Result := IntToStr(idNode)
    else
      Result := 'NULL';
  end;

begin
  Result := nil;
  Buf := OnAddition;
  OnAddition := nil;
  try
    Items.BeginUpdate;
    try
      if Assigned(AParent) and not AParent.Expanded then
        AParent.Expand(False);
      NewNode := Items.AddChild(AParent, AText);
      if (FIdTree <> 0) and Assigned(FConnection) then
      begin
        crBuf := Screen.Cursor;
        Screen.Cursor := crSQLWait;
        try
          FRecordset := FConnection.Execute(Format(SQLAddChild, [FIdTree,
            GetIdParent(NewNode), GetIdPrior(NewNode), GetIdNext(NewNode),
              AText,
              GetIdNode(idNode)]));
        finally
          Screen.Cursor := crBuf;
        end;
        try
          try
            if FRecordset.RecordCount > 0 then
            begin
              (NewNode as TADODBTreeNode).FIdNode :=
                FRecordset.Fields['NewId'].Value;
              //Выделяем добавленный узел
              FReopenOnExpand := False;
              try
                Selected := NewNode;
              finally
                FReopenOnExpand := True;
              end;
            end
            else
              raise
                Exception.Create('TADODBTreeView.dbAddChild:Не получен идентификатор нового узла.');
          except
            NewNode.Delete;
            raise;
          end;
        finally
          FRecordset := nil;
        end;
      end;
    finally
      Items.EndUpdate;
    end;
    Result := NewNode;
    if Assigned(Buf) then
      Buf(Self, NewNode);
  finally
    OnAddition := Buf;
  end;
end;

procedure TADODBTreeView.dbDeleteNode(Node: TTreeNode; ReQueryFromDB: Boolean =
  False);
var
  AParent: TTreeNode;
begin
  if Node.HasChildren then
    raise
      Exception.Create('TADODBTreeView.dbDeleteNode:Этот узел удалить нельзя,т.к. есть вложеннные узлы.');
  FConnection.Execute(Format(SQLDeleteNode, [FIdTree, (Node as
      TADODBTreeNode).idNode]));
  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 TADODBTreeView.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('TADODBTreeView.dbMoveNode:Узел не может быть перемещен.')
  else
  begin
    FConnection.Execute(Format(SQLMoveNode, [FIdTree, (DNode as
        TADODBTreeNode).idNode, (SNode as TADODBTreeNode).idNode,
      BoolToInt[AsChild]]));
    Items.BeginUpdate;
    try
      if ReQueryFromDB then
      begin
        TheNodeId := (SNode as TADODBTreeNode).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 TADODBTreeNode).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;

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

procedure TADODBTreeView.dbLoadFirstLevel;
var
  crBuf: TCursor;
begin
  Items.Clear;
  if not (csDesigning in Self.ComponentState) and not (csLoading in
    Self.ComponentState) and (FIdTree <> 0) and Assigned(FConnection) then
  begin
    crBuf := Screen.Cursor;
    Screen.Cursor := crSQLWait;
    try
      FRecordset := FConnection.Execute(Format(SQLLoadLevel, [FIdTree,
        FRootID]));
    finally
      Screen.Cursor := crBuf;
    end;
    try
      AddChildren(nil);
    finally
      FRecordset := nil;
    end;
  end;
end;

procedure TADODBTreeView.SetConnection(Value: TADOConnection);
begin
  if Assigned(FConnection) and (FConnection.Owner <> Self.Owner) then
    FConnection.RemoveFreeNotification(Self);
  FConnection := Value;
  if Assigned(Value) then
  begin
    if Value.Owner <> Self.Owner then
      Value.FreeNotification(Self);
    dbLoadFirstLevel;
  end
  else
    Items.Clear;
end;

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

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

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

procedure TADODBTreeView.SetSelectedID(const Value: Integer);
var
  TheNode: TTreeNode;
  ThePath: array of Integer;
  I: Integer;
  crBuf: TCursor;
begin
  if (Items.Count > 0) and (Items[0] is TADODBTreeNode) then
  begin
    Items.BeginUpdate;
    try
      try
        TheNode := Items[0];
        crBuf := Screen.Cursor;
        Screen.Cursor := crSQLWait;
        try
          FRecordset := FConnection.Execute(Format(SQLGetFullPath, [FIdTree,
            Value]));
        finally
          Screen.Cursor := crBuf;
        end;
        try
          if FRecordset.RecordCount <= 0 then
            raise
              Exception.Create('TADODBTreeView.SetSelectedID:Не получен путь к узлу ' + IntToStr(Value));
          SetLength(ThePath, FRecordset.RecordCount);
          I := 0;
          while not FRecordset.Eof do
          begin
            ThePath[I] := FRecordset.Fields['idNode'].Value;
            Inc(I);
            FRecordset.MoveNext;
          end;
        finally
          FRecordset := nil;
        end;
        for I := 0 to High(ThePath) do
        begin
          while Assigned(TheNode) and ((TheNode as TADODBTreeNode).idNode <>
            ThePath[I]) do
            TheNode := TheNode.getNextSibling;
          if not Assigned(TheNode) then
            raise Exception.Create('TADODBTreeView.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('TADODBTreeView.SetSelectedID:Не найден узел.');
        Selected := TheNode;
      finally
        ThePath := nil;
      end;
    finally
      Items.EndUpdate;
    end;
  end;
end;

{ TADODBTreeNode }

procedure TADODBTreeView.DoEdited(Sender: TObject; Node: TTreeNode; var S:
  string);
var
  crBuf: TCursor;
begin
  if Assigned(FOnEdited) then
    FOnEdited(Sender, Node, S);
  if (Node is TADODBTreeNode) and (Node.Text <> S) then
  try //Сохраняем изменения в базе
    crBuf := Screen.Cursor;
    Screen.Cursor := crSQLWait;
    try
      FRecordset := FConnection.Execute(Format(SQLRenameNode, [FIdTree, (Node as
          TADODBTreeNode).idNode, S]));
    finally
      Screen.Cursor := crBuf;
    end;
    try
      if FRecordset.RecordCount = 0 then
        raise
          Exception.Create('TADODBTreeView.DoEdited:Не получен результат переименования.');
      S := FRecordset.Fields['NewText'].Value;
    finally
      FRecordset := nil;
    end;
  except
    S := Node.Text;
    raise;
  end;
end;

procedure TADODBTreeView.SetRootID(Value: string);
var
  I: Integer;
begin
  if (UpperCase(Value) = 'NULL') or (Value = '') then
    FRootID := 'NULL'
  else
  begin
    for I := 1 to Length(Value) do
      if not (Value[I] in ['0'..'9']) then
        raise Exception.Create('"' + Value + '" - is not integer or NULL');
    FRootID := Value;
  end;
  dbLoadFirstLevel;
end;

procedure TADODBTreeView.Notification(AComponent: TComponent; Operation:
  TOperation);
begin
  if (Operation = opRemove) and (AComponent = FConnection) then
    SetConnection(nil);
end;

{ TADODBTreeNode }

end.
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay