我想将treeview保存到数据库中,treeview要支持添加,删除,拖放操作。node 的level,index 都在随时变化,怎么样才能保存啊?
我想不出好的方案,只好每次都保存整棵树,能不能每次只修改变动的那个节点?

解决方案 »

  1.   

    关注。你可以在添加,删除,拖放的时候, 每一个操作,都更新一次数据库, 而不是等到最后
    才一起保存啊!如果你是因为和数据库对应不上的话,我的做法是:在数据库的表里有一个 id字段(自动增量),  每一次从数据库里去数据来苟建树的时候,
    都设置 aNode.Tag := TreeTable.FieldByName('id').AsInteger;如果某个结点被改动了, 就可以通过 
    TreeTable.Locate('id', aNode.Tag,[loCaseInsensitive]);找到这个结点在数据库里的
    对应信息,进行修改!
      

  2.   

    对不起,我忘了  TTreeNode 没有Tag属性。
    你可以使用 aNode.StateIndex  来与  TreeTable表里的 id字段进行一一对应!
      

  3.   

    生成树时应调用递归,表中一般有父节点号,和节点号等字段,靠父节点号关联。
    至于删除和添加只不过时根据主键在数据库中加一条记录。
    我可以发一个树窗体给你,我的E_mail: [email protected]
      

  4.   

    呵呵,我想你们都不太明白我的意思.
    我们决定一个节点的位置,靠的是他的父节点和他自己是老几,有了这2项,就可以精确定位节点.
    但是,靠什么来定义父节点呢?如果用Index的话,在保存,读出整棵树时是可以的,但是用户增加,删除了节点后,Index发生了变化,那就会找错老爸.靠Text更不行,会有好几个老爸,
    靠Data吧,要留着有别的用途.如果增加别的数据来确定唯一的节点,那会增加了找老爸的难度.
    由此可见,上面几位用的都是我在用的笨办法,就是,每修改一个节点,必须保存整棵树,否则你就无法读出!!
    如果树的枝叶茂盛,保存整棵树是很大的开销,增加额外得数据来确定父节点的话,开销也很大,每找一个老爸要select一下数据库.怎么找个好的算法平衡一下,很伤脑筋.
    还有,如果不到万不得已,不要用递归,这是我老师很多年前教我的,我保存树和读出都不用的,靠的是INDEX.
    这个问题不是很容易的,不要小看了.
      

  5.   

    我的意思是数据库表中两个字段: 父节点代码,节点代码。与Treeview中的level,index 完全无关,完全是动态建立。不用递归也可以,但要写存储过程,用循环解决。
    我实现过权限树,每个用户权限不同,所以要动态建立
      

  6.   

    DevExpress有一组控件可以实现你的,如果不想自己写程序的话可以采用.
      

  7.   

    To 文:
    不明白你的意思,如果你不用index,如何让数据库内的记录和TreeView的节点对应呢?
    父节点代码是什么?子节点代码又是什么,麻烦说清楚一点.
    其实用Level也不好,是通过递归得到Level的.
      

  8.   

    给每个新增的节点编个号,这样每个节点都有节点编号和父节点编号。
    level 0: 01,02
    level 1: 0101,0102,0202,0203
    level 2:010101,010102
    这样很容易定位节点,如果这样编号可以不要父节点号
    如此这样编号后,你应该很容易建树了吧
      

  9.   

    这个是三层的树了
    上面有它的添加,删除,与修正的代码,你可能看一下有没有用了,但我现在就是用这个与数
    做的
    数据库的结构为:
      学校,年级,班级,层次编号
      //============通过数据表立TREE
    procedure TPupilCataLogForm.FlatSpeedButton1Click(Sender: TObject);
    begin
    Case  Levlint of
          0:begin
              If Flatcombobox1.Text='' then
              begin
                  application.MessageBox('学校名称不能为空','添加提示框',MB_OK);
                  Exit;
              end;
              with dm.ADOPup  do
              begin
              append;
              Fieldbyname('学校名称').AsString :=Flatcombobox1.Text ;
              Fieldbyname('层次编号').AsInteger:=1;
              Fieldbyname('录入时间').AsString :=datetostr(date);
              post;
              end;
              PupTree.Items.AddChild(SelectView,Flatcombobox1.Text);
              SelectView.GetLastChild.ImageIndex:=1;
              SelectView.GetLastChild.SelectedIndex:=2;
              Application.MessageBox('添加学校成功','添加提示框',Mb_ok);
            end;
          1:begin
                 If Flatcombobox2.Text='' then
                 begin
                  application.MessageBox('年级名称不能为空','添加提示框',MB_OK);
                  Exit;
                 end;
                 with dm.ADOPup  do
                 begin
                 append;
                 Fieldbyname('学校名称').AsString :=Flatcombobox1.Text ;
                 Fieldbyname('年级名称').AsString :=Flatcombobox2.Text ;
                 Fieldbyname('层次编号').AsInteger:=2;
                 Fieldbyname('录入时间').AsString :=datetostr(date);
                 post;
                 end;
                 PupTree.Items.AddChild(SelectView,Flatcombobox2.Text);
                 SelectView.GetLastChild.ImageIndex:=3;
               SelectView.GetLastChild.SelectedIndex:=4;
                 Application.MessageBox('添加年级成功','添加提示框',Mb_ok);
            end;
          2:begin
              If Flatcombobox3.Text='' then
              begin
                  application.MessageBox('班级名称不能为空','添加提示框',MB_OK);
                  Exit;
              end;
              with dm.ADOPup  do
              begin
              append;
              Fieldbyname('学校名称').AsString :=Flatcombobox1.Text ;
              Fieldbyname('年级名称').AsString :=Flatcombobox2.Text ;
              Fieldbyname('班级名称').AsString :=Flatcombobox3.Text ;
              Fieldbyname('层次编号').AsInteger:=3;
              Fieldbyname('录入时间').AsString :=datetostr(date);
              post;
              end;
              PupTree.Items.AddChild(SelectView,Flatcombobox3.Text);
              SelectView.GetLastChild.ImageIndex:=5;
              SelectView.GetLastChild.SelectedIndex:=6;
              Application.MessageBox('添加班级成功','添加提示框',Mb_ok);
            end;
           3:begin
                  Application.MessageBox('班级层为最下层不能再添加新层,请选上一层','添加提示框',Mb_ok);
                  exit;
             end;
    end;
    end;
    //===========选中的对像
    procedure TPupilCataLogForm.FlatSpeedButton5Click(Sender: TObject);
    Var
        Lev:integer;
        PrNexNode:TTreeNode;
        nubme:integer;
    begin
         Schoolname:='';
         ClassName:='';
         GradeName:='';
         SelectView:=PupTree.Selected;
         LevlInt:=SelectView.Level ;
         //======添加学校   SchoolName   ClassName     gradeName
         If Levlint=0 then
         Begin
              LevView:=SelectView;
              FlatCombobox1.Enabled :=True;
              FlatCombobox2.Enabled :=False;
              FlatCombobox3.Enabled :=False;
         End;
         //====学校
         if Levlint=1 then
         Begin
              LevView:=SelectView.Parent ;
              Schoolname:=selectview.Text ;
              FlatCombobox1.Enabled :=False;
              FlatCombobox2.Enabled :=True;
              FlatCombobox3.Enabled :=False;
         End;
         //====年级
         if Levlint=2 then
         begin
              prnexnode:=SelectView.Parent;
              SchoolName:=prNexNode.Text;
              Levview:=prnexnode;
              gradeName:=selectView.Text ;
              FlatCombobox1.Enabled :=False;
              FlatCombobox2.Enabled :=False;
              FlatCombobox3.Enabled :=True;
         end;
         //====
         if Levlint=3 then
         begin
              prnexnode:=SelectView.Parent;
              gradeName:=prNexNode.Text;
              prnexnode:=prnexnode.Parent;
              SchoolName:=prnexnode.Text ;
              Levview:=prnexnode;
              ClassName:=selectView.Text ;
              FlatCombobox1.Enabled :=False;
              FlatCombobox2.Enabled :=False;
              FlatCombobox3.Enabled :=False;
         end;
         Flatcombobox1.Text :=SchoolName;
         Flatcombobox2.Text :=GradeName;
         Flatcombobox3.Text :=ClassName;
    end;
     {=========根据数据表生成目录=========}
    procedure TPupilCataLogForm.FlatSpeedButton4Click(Sender: TObject);
    var
         tempTreeNode:TTreeNode;
         tempText:string;
         childtext:string;
         NonceTreeNode:TTreeNode;
         NonceText:string;
         i,j:integer;
    begin    //========建立学校目录
        With dm.ADOQuePup do
        begin
             sql.Clear ;
             sql.Add('Select * from  学生资料结构表 where  层次编号=:LelVes');
             Parameters.ParamByName('LelVes').Value:=1;
             open;
             first;
             While not(eof) do
             begin
                  tempTreeNode:=PupTree.Items[0];
                  ChildText:=Fieldbyname('学校名称').AsString;              PupTree.Items.AddChild(tempTreeNode,ChildText);
                  TemptreeNode.GetLastChild.ImageIndex:=1;
                  TemptreeNode.GetLastChild.SelectedIndex:=2;
                  Next;
             end;
              //PupTree.Items.Add (puptree.Items[0] ,'ddd');
        end;   //========建立年级目录
        For i:=0 to  PupTree.Items[0].Count-1  do
        begin
             NonceTreeNode:=PupTree.Items[0].Item[i];
             NonceText:=NonceTreeNode.Text;
             with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('select * from 学生资料结构表 where 层次编号=:LelVes and 学校名称=:SChoolName');
                  parameters.ParamByName('LelVes').Value:=2;
                  parameters.ParamByName('SchoolName').Value:=NonceText;
                  open;
                  first;
                  while not(Eof) do
                  begin
                       ChildText:=Fieldbyname('年级名称').AsString ;
                        PupTree.Items.AddChild(NonceTreeNode,ChildText);
                         NonceTreeNode.GetLastChild.ImageIndex:=3;
                        NonceTreeNode.GetLastChild.SelectedIndex:=4;
                       Next;
                  end;
             end;    end;    //===========建立班级目录
        For i:=0 to PupTree.Items[0].Count-1 do
        begin
             NonceTreeNode:=PupTree.Items[0].Item[i];  //学校目录
             NonceText:=NonceTreeNode.Text;   //学校名称
             For j:=0 to NonceTreeNode.Count-1 do
             begin
                  tempTreeNode:=NonceTreeNode.Item[j];//年纪目录
                  tempText:=tempTreeNode.Text ;       //年纪名称              with dm.ADOQuePup do
                  begin
                  sql.Clear ;
                  sql.Add('select * from 学生资料结构表 where 层次编号=:LelVes and 学校名称=:SChoolName and 年级名称=:PGrade');
                  parameters.ParamByName('LelVes').Value:=3;
                  parameters.ParamByName('SchoolName').Value:=NonceText;
                  parameters.ParamByName('PGrade').Value:=tempText;              open;
                  first;
                  while not(Eof) do
                  begin
                       ChildText:=Fieldbyname('班级名称').AsString ;
                       PupTree.Items.AddChild(tempTreeNode,ChildText);
                       TemptreeNode.GetLastChild.ImageIndex:=5;
                       TemptreeNode.GetLastChild.SelectedIndex:=6;
                       Next;
                  end;
                  end;
             end;
        end;end;
    //======选中
    procedure TPupilCataLogForm.PupTreeMouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);begin
        FlatSpeedButton5click(self);
    end;
      

  10.   

    //删除对像
    procedure TPupilCataLogForm.FlatSpeedButton7Click(Sender: TObject);
    Var
       st:string;
    begin
    try
    Case  Levlint  of
        0:begin
               application.MessageBox('这是一个总目录不能删除','删除对话框',MB_OK);
               exit;
          end;
        1:try         st:='你确认要删除'+Flatcombobox1.Text+'学校的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'删除对话框',MB_OKCANCEL)=2 then exit;
             PupTree.Items.Delete(SelectView);
             with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('DELETE from 学生资料结构表 where 学校名称=:SChoolName');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('学校删除成功','删除对话框',MB_OK);
          except
          end;
        2:begin
             st:='你确认要删除'+Flatcombobox2.Text+' 年级的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'删除对话框',MB_OKCANCEL)=2 then exit;
             PupTree.Items.Delete(SelectView);
             with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('DELETE from 学生资料结构表 where  学校名称=:SChoolName and 年级名称=:PGrade');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  parameters.ParamByName('PGrade').Value:=Flatcombobox2.Text;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('年级删除成功','删除对话框',MB_OK);
          end;
        3:begin
                 st:='你确认要删除'+Flatcombobox3.Text+'班级的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'删除对话框',MB_OKCANCEL)=2 then exit;
             PupTree.Items.Delete(SelectView);
             with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('DELETE from 学生资料结构表 where  学校名称=:SChoolName and 年级名称=:PGrade and 班级名称=:className');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  parameters.ParamByName('PGrade').Value:=Flatcombobox2.Text;
                  parameters.ParamByName('className').Value:=Flatcombobox3.Text;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('班级删除成功','删除对话框',MB_OK);
          end;
    end;
    except
    end;
    end;
    //======修正
    procedure TPupilCataLogForm.FlatSpeedButton6Click(Sender: TObject);
    var
       st:string;
    begin
    Case  Levlint  of
        0:begin
               application.MessageBox('这是一个总目录不能删除','删除对话框',MB_OK);
               exit;
          end;
        1:begin
             st:='你确认要修正'+Flatcombobox1.Text+'学校的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'修正对话框',MB_OKCANCEL)=2 then exit;
             st:=Flatcombobox1.Text;
             IF not(InputQuery('请输入修正的信息','修正对话框',St)) then  Exit;
             SelectView.Text:=st;
              with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('UPDATE  学生资料结构表 set 学校名称=:UPst where 学校名称=:SChoolName');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  parameters.ParamByName('Upst').Value:=st;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('学校修正成功','修正对话框',MB_OK);
          end;
        2:begin
                st:='你确认要修正'+Flatcombobox2.Text+'年级的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'修正对话框',MB_OKCANCEL)=2 then exit;
             st:=Flatcombobox2.Text;
             IF not(InputQuery('请输入修正的信息','修正对话框',St)) then  Exit;
             SelectView.Text:=st;
              with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('UPDATE  学生资料结构表 set 年级名称=:UPst where 学校名称=:SChoolName and 年级名称=:PGrade');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  parameters.ParamByName('Upst').Value:=st;
                  parameters.ParamByName('PGrade').Value:=Flatcombobox2.Text;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('年级修正成功','修正对话框',MB_OK);
          end;
        3:begin
             st:='你确认要修正'+Flatcombobox3.Text+'班级的信息与他下面的全部信息吗?';
             if application.MessageBox(pchar(st),'修正对话框',MB_OKCANCEL)=2 then exit;
             st:=Flatcombobox3.Text;
             IF not(InputQuery('请输入修正的信息','修正对话框',St)) then  Exit;
             SelectView.Text:=st;
              with dm.ADOQuePup do
             begin
                  sql.Clear ;
                  sql.Add('UPDATE  学生资料结构表 set 班级名称=:UPst where 学校名称=:SChoolName and 年级名称=:PGrade and  班级名称=:className');
                  parameters.ParamByName('SchoolName').Value:=Flatcombobox1.Text;
                  parameters.ParamByName('Upst').Value:=st;
                  parameters.ParamByName('PGrade').Value:=Flatcombobox2.Text;
                  parameters.ParamByName('className').Value:=Flatcombobox3.Text;
                  ExecSQL;
            end;
            dm.ADOPup.Refresh ;
            application.MessageBox('班级修正成功','修正对话框',MB_OK);
          end;
    end;
    end;
      

  11.   

    SelectView:TTreeNode;
         LevlInt:integer;
         LevView:TTreeNode;
         SchoolName:string;
         gradeName:string;
         ClassName:string;
      

  12.   

    /*
      我给个比较实用的方法,支持无限级,效率也高,
      下面给出关键生成树源代码,不过是BCB的。1。数据库表的结构:采用节点ID ,节点父ID(PID)及其他字段,ID,PID的类型无所谓,
       数字,字符都行,也不需要有规律,只要保证ID字段为主键即可,比如可以用GUID
       来作ID。2。TTreeNode的Data指向一个结构,结构内有一个域记录节点的ID,其他的域根据需要
       可自己定义。3。从数据库生成树,这是要好好考虑的,要作到效率高,最重要的是“避免反复查询
       数据库或遍历数据集”,最好是一次就能生成整棵树。这也是可以做到的,请看代码:
    */struct NODEDATA  //节点关联的数据
    {
      int         id;
      int         pid;
      AnsiString  name;
      AnsiString  memo;
    };void __fastcall FillTree()
    {
      struct NODEDATA *ndata=NULL;
      TStringList     *ss=NULL;  TTreeNode   *aNode=NULL;
      TTreeNode   *bNode=NULL;
      TTreeNode   *pNode=NULL;  Query1->Close();
      Query1->SQL->Text="select id,pid,name,memo from tree_tab";
      Query1->SQL->Open();  try
      {
        ss=new TStringList();
        TreeView->Items->BeginUpdate();//禁止刷新,提高速度
        //遍历记录集
        for(Query1->First(); !Query1->Eof; Query1->Next())
        {
          ndata=new NodeData();
          ndata->id   = Query1->FieldByName("id")->AsInteger;
          ndata->pid  = Query1->FieldByName("id")->AsInteger;
          ndata->name = Query1->FieldByName("name")->AsString;
          ndata->memo = Query1->FieldByName("memo")->AsString;
          //生成一个根节点加入TreeView
          aNode=TreeView1->Items->AddObject(NULL,ndata->name,ndata);
          //记录id-TreeNode对应关系,便于下面查找
          ss->AddObject(AnsiString(ndata->id),aNode);
        }
        Query1->Close();
        ndata=NULL;
        
        //调整树,根据节点的pid,把节点移到相应的TreeNode下
        int idx;
        for(aNode=TreeView1->Items->GetFirstNode(); aNode!=NULL ;)
        {
          ndata = (NODEDATA*)(aNode->Data);
          if(-1==(idx=ss->IndexOf(AnsiString(ndata->pid))))
          {
            aNode=aNode->getNextSibling();
            continue;
          }
          else
          {
            pNode = (TTreeNode*)(ss->Objects[idx]);
            bNode=aNode;
            aNode=aNode->getNextSibling();
            bNode->MoveTo(pNode,naAddChild);
          }  
        }
      }
      __finally
      {
        delete ss; ss=NULL;
        Query1->Close();
        TreeView->Items->EndUpdate();
      }
    }
      

  13.   

    treeview的添加,删除,拖放操作也不难,每次都通过被操作的TreeNode->Data取出
    id,pid等数据,组成sql语句更新数据库即可......
      

  14.   

    myy()的方法不错,先生成一棵全是根节点的树再调整,这样就避免了先读出子节点,找不到父节点的尴尬。
      

  15.   

    我同意 cultureright(文) 的方法,我就是用这种方法,除了在修改节点这个功能有些困难,暂时没有完成,其他的比如添加,删除都可以完成了,而且删除可以实现将子节点全部删除的功能,我是将记录导入到文本文件中,然后根据所在的位置空出n个TAB空格值来,这样就在文本中形成了一棵树,在将文本到如树中就可以自动形成了,这是我的老师告诉我的方法,但是在修改一个节点而使其全部子节点自动的修改这个问题还没有解决。
    myy()的方法你也可以试试。
      

  16.   

    在文本中形成的格式
    (01)aaa
           (0101)aaa.bbb
           (0102)aaa.ccc
                      (010201)aaa.ccc.ddd
           (0103)aaa.eee
    (02)fff
           (0201)fff.ggg
    (03)hhh
           (0301)hhh.iii
                     (030101)hhh.iii.jjj
    然后用treeview导入就会自动生成树了
      

  17.   

    to hotdog911(昱):
    我们讨论的是在数据库中动态修改节点后如何保存,如果要保存整棵树的话,没什么技术问题,大家都用的办法都一样,根本不用管什么父节点,什么子节点,有SaveToFile方法.你可以打开用Treeview.SaveTOFile方法保存的文件,和你的结构几乎一样(你的01,0102这些编号是多余的,没有必要).
    如果我一次只修改了一个节点(我指的修改不仅仅是修改了Text那么简单,包括拖放操作),照你的办法就只能保存整棵树了,我的意思是只改一个节点,还要保证能顺利读出.要注意的是,数据库和文本文件不同,你读出的第一个未必就是根节点,第二个也不见得是第一个的子节点.
    大家给出的方案只有myy的是可行的,但他多了2个变量,树一大了,也很可观.更好的办法寻求中...................希望有高手指点.
      

  18.   

    我有点不懂楼主的意思了,已经做到了“树的一个Node对应数据库中一条记录”,
    再要做到“数据库中动态修改节点后如何保存”,还很难吗?上面已经说了:
    treeview的添加,删除,拖放操作也不难,每次都通过被操作的TreeNode->Data取出
    id,pid等数据,组成sql语句更新数据库即可......何况我给出的表数据中节点其实只与父节点相关的,即使一次拖放一棵子树,
    也只需要改一条记录。
    >>但他多了2个变量,树一大了,也很可观...哪里多了2个变量?struct NODEDATA  中吗?你可以只放ID,PID嘛,其他的都是局部
    变量,只是临时的而已。
    不知道你的树有多大?我曾经做的是5万多节点,只是创建时稍慢(也与数据库有关)。如果你真的很在乎效率,那就不要一次建全部节点,而是每次在用户“展开节点”时,
    再建下级节点。