Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Conexión con bases de datos (https://www.clubdelphi.com/foros/forumdisplay.php?f=2)
-   -   ¿Cómo manejar un VirtualTreeView? (https://www.clubdelphi.com/foros/showthread.php?t=85458)

jesconsa 20-03-2014 00:59:07

¿Cómo manejar un VirtualTreeView?
 
Hola. Necesito usar un TreeView asociado/conectado a una base de datos, editando el Tree el cambio debe quedar reflejado en la base de datos y viceversa. Las mejores opciones que veo son el JvDBTreeView de las componentes Jedi y el VirtualTreeView que es mas farragoso. El problema de la primera es que no veo a nadie que las haya usado y el ejemplo es bastante criptico. El VirtualTreeView es bastante farragoso si quiero hacer cualquier cosa. Cual me aconsejais?..Habeis trabajado con alguno?....

Muchas gracias
Saludos

Jesus

Casimiro Notevi 20-03-2014 03:24:32

Por favor, recuerda poner títulos descriptivos a tus preguntas, gracias.

pacopenin 20-03-2014 09:56:14

Cita:

Empezado por jesconsa (Mensaje 474046)
Hola. Necesito usar un TreeView asociado/conectado a una base de datos, editando el Tree el cambio debe quedar reflejado en la base de datos y viceversa. Las mejores opciones que veo son el JvDBTreeView de las componentes Jedi y el VirtualTreeView que es mas farragoso. El problema de la primera es que no veo a nadie que las haya usado y el ejemplo es bastante criptico. El VirtualTreeView es bastante farragoso si quiero hacer cualquier cosa. Cual me aconsejais?..Habeis trabajado con alguno?....
Jesus

Hola Jesús. Yo trabajo con JvDBTreeView, aunque le tengo desactivadas las opciones de arrastrar y soltar ya que no he conseguido que funcionen bien. Le he modificado algo el procedimiento de añadir nodos ya que uso un procedimiento almacenado de firebird. Por lo demás hace su papel aunque de apariencia es mucho más soso que el otro que pones, que también usé alguna vez pero que a mi también se me hace muy complicado manejar. Por sencillez el primero. Por posibilidades y apariencia, el segundo.

Un saludo.

jesconsa 20-03-2014 10:30:07

Tree y DB
 
Ok Casimiro. Gracias pacopenin. Como hiciste para usar el JvDBTreeView?, algun ejemplo en el cual te basaste?, donde aprendiste a utilizarlo? algun tutorial?.....

Muchas gracias
Saludos

Jesus

pacopenin 20-03-2014 13:02:38

No hay mucho que explicar. Te adjunto una captura de pantalla :



Es un árbol de plantillas de texto organizados por carpetas. El campo TIPO identifica si es una carpeta(1) o una plantilla(3) y lo utilizo para el icono dentro del árbol. Del resto no se que más comentar.

jesconsa 20-03-2014 14:32:52

Tree y DB
 
Gracias por la captura pacopenin. Entiendo lo que haces, No puedo creer que sea asi de sencillo. Pero hay algo que no entiendo...Esto esta bien para una relacion Master/Padre Detail/Hijo ...pero si quiero mas niveles?...como se haria?....No veo ninguna propiedad que de a pensar que se puedan obtener arboles de mas de dos niveles (un nivel de padres y otro de hijos)....

Gracias
Saludos

Jesus

pacopenin 20-03-2014 15:05:35

Puede tener los niveles que quieras. Si añado un nodo (11) y pongo como padre 10 me lo pondrá dentro de la carpeta "Otra carpeta". Normalmente yo bloqueo que no se puedan borrar los dos primeros nodos, para que siempre tenga que estar seleccionado un nodo que haga de padre, y en su defecto el 0 que es el valor de la propiedad StartMasterValue. Para añadir un nodo utilizo lo siguiente :

Código Delphi [-]
procedure TFCarpetas.btNewFolderClick(Sender: TObject);
var
  carpeta : String;
  New : TJvDBTreeNode;
  Node: TTreeNode;
begin
  carpeta := '';
  if InputQuery(buscaTraduccion('330'), buscaTraduccion('331'), carpeta) then
    begin
       node := Arbol.Selected;
       New := Arbol.MyAddChildNode(node, True, 0, carpeta, 1);
    end
end;

Como ves, se llama a la función MyAddChildNode que he modificado de la original añadiendo dos o tres parámetros más.

A ver si te sirve.

jesconsa 20-03-2014 16:46:55

Tree + DB
 
Ah ok ok, entiendo.....pero a la hora de añadir mas nodos/niveles , ya que se tiene todo en el mismo Dataset, se puede formar una buena no?....

Muchas gracias!
Saludos

Jesus

jesconsa 22-03-2014 15:36:49

Tirar la toalla con VirtualTreeView
 
Hola. Estoy tratando de "domar" al Virtual TreeView pero es un hueso duro de roer, tanto que estoy a punto de tirar la toalla. He visto todos los enlaces, todos los ejemplos y hay cosas que no acabo de entender. El VirtualTreeView esta hecho como sabeis para que se creen nodos raiz y a partir de ahi todos los demas. Los datos de dichos nodos van aparte y se pueden crear en el evento OnInit, cuando se crea el arbol o cuando quieras, mostrando cada nodo su texto mediante OnGetText. Todo esto esta bien hasta que llegamos a la parte de seguir añadiendo nodos a los nodos raiz.....Algo asi no funciona:

Código Delphi [-]
 try
    Nodo := Form1.VST1.GetFirst;
    while Assigned(Nodo) do
    begin
      Form1.VST1.Selected[Nodo] := True;

  //for I := 1 to Form1.FDTable10.RecordCount do
   //   begin

         N2:=Form1.VST1.GetNodeData(Nodo);
         S1:=N2.Id;
         Form1.FDTable11.First;
         while not Form1.FDTable11.Eof do
           begin
              S2:=Form1.FDTable11['Parent'];
              if S2=S1 then begin
                  N:=nil;
                  //N.NCaption := Form1.FDTable11.FieldByName('Cities').asString;
                  //N.Chk:= Form1.FDTable11.FieldByName('Selected').AsBoolean;
                  N.Id:= StrToInt(Form1.FDTable11['Id']);
                  añade_nodo(Form1.VST1,Nodo,N);
                  Initialize(N^);
              end;
              Form1.FDTable11.Next;
           end;
      end;


       Nodo := Form1.VST1.GetNextSibling(Nodo);
    //end;
  finally
    Form1.VST1.EndUpdate;
  end;

Lo que voy haciendo en este codigo es lo siguiente: Tengo 3 tabalas cada una para un nivel del arbol . Lo he hecho asi para que en tiempo de ejecucion sea muy facil añadir nodos (registros) a las tablas. En el codigo busco los nodos hijo de un padre determinado comparando los campos 'ID' de cada registro de la tabla de registros hijo con un 'Id' determinado de la tabla de registros raiz. Pues bien, cuando detecta que un hijo pertenece a un padre no me deja coger dicho registro de la tabla de hijos. En añade_nodo tengo todo para crear el nodo hijo (AddCHild, etc) pero no llega a hacerlo por este error.


Código Delphi [-]

N.NCaption := Form1.FDTable11.FieldByName('Cities').asString;

Aqui es donde da un error...He tratado de coger dicho dato con un Dataurce o otro Dataset pero no hay manera. Cualquier ayuda es bienvenida y agradecida.

Muchas gracias

Saludos

Casimiro Notevi 22-03-2014 15:52:56

Por favor, no abras otro hilo para tratar el mismo tema, gracias.
Los he unido :)

jesconsa 22-03-2014 16:44:37

VirtualTreeView
 
Ok, disculpa Casimiro.

Gracias
Saludos

Casimiro Notevi 22-03-2014 16:51:41

Cita:

Empezado por jesconsa (Mensaje 474203)
Saludos

^\||/
Le he cambiado el título para que sea más fácil de identificar por quien pueda ayudar.
Si prefieres otro título, lo dices :)

pacopenin 23-03-2014 14:40:17

Hola Jesús. Hace muchos años que no uso VST, pero mirando un proyecto antiguo te mando lo que hacía para cargar un árbol de un sólo nivel. No llegué a explorar mucho más allá. A ver si te sirve.

Código SQL [-]
procedure TFListados.iniciaArbol;
var
  Data1 : PEntryData;
  Node1: PVirtualNode;
  NewNode : PVirtualNode;
begin

     VST.BeginUpdate;
     VST.Clear;

     VST.RootNodeCount := 0;

     Node1 := VST.AddChild(VST.RootNode);

     Data1 := PEntryData(VST.GetNodeData(Node1));
     Data1.Titulo := 'Listados';

     Dat.cdInf.close;
     Dat.cdInf.Params.ParamByName('GRUPO').asInteger := Grupo;
     Dat.cdInf.open;

     VST.Selected[VST.TopNode] := true;
     VST.FocusedNode := VST.TopNode;

     while NOT Dat.cdInf.EOF do
       begin
          NewNode := VST.AddChild(VST.FocusedNode);
          with PEntryData(VST.GetNodeData(NewNode))^ do
            begin
               Titulo  := Dat.cdInfNOMBRE.value;
               Informe := Dat.cdInfINFORME.value;
               Id      := Dat.cdInfUSER_FLAG.value;
               Tipo    := Dat.cdInfTIPO.value;
            end;
          Dat.cdInf.Next;
       end;

     VST.EndUpdate;

     VST.FullExpand;
end;

jesconsa 23-03-2014 21:10:25

Como manejar el VirtualTreeView
 
Gracias pacopenin. Ya lo habia resuelto con bucles como:

Código Delphi [-]
Form1.FDTable10.First;
  for X := 1 to Form1.FDTable10.RecordCount do
      begin
        N.NCaption:= Form1.FDTable10.Fieldbyname('Areas').AsString;
    .........
    .........

A la vez que creo los nodos con AddChild les añado los datos, no lo hago en el evento OnInit porque asi me resulta mas facil. Como ves utilizo una tabla asociada directamente con la tabla de la base de datos (se puede hacer directamente asi en la componente Table de FireDac). El problema lo tengo ahora en que los cambios que haga en el VirtualTreeView se graben en la base de datos, por ejemplo pulsar sobre un checkbox de un nodo, etc. Con los Datasources asociados a Grids era facil porque un cambio en el grid automaticamente (activando la propiedad correspondiente) se actualizaba la base, pero aqui no hay enlace con Datasources,..la tabla directa a la base. Alguna idea?

Muchas gracias
Saludos

pacopenin 24-03-2014 10:29:19

Pues deberás guardar el id del registro correspondiente en cada nodo (como hago en mi ejemplo) y cuando detectes una modificación, recuperar dicho registro y realizar la edición correspondiente. No se me ocurre otro modo.

jesconsa 25-03-2014 17:05:54

VirtualTreeView
 
Hola pacopenin. Lo que he hecho es un poco al reves,..actualizo la base de datos con:

Código Delphi [-]
 dbMain.ExecSQL('update Areas set Selected=:Selected where Id=:Id', [Data.Chk,Data.Id]);

..y luego vuelvo a pintar el arbol, lo hago asi para poder activar una casilla y todos sus hijos, el repintado global me quita de historias...Solo una cosa negativa,..al repintar el arbol , éste esta reducido/colapsado,..si guardo el Nodo para poder expandirlo despues de pintar el arbol no lo hace bien, supongo que el puntero de ese nodo que guardo ya ha cambiado y no sirve de nada...Y tambien hice lo del "repintado" del arbol porque si no no me ponia actualizado el checkbox de los hijos de ese nodo (al checkearlos/señalarlos recursivamente)...espero haberme explicado bien....

Muchas gracias
Saludos

Jesus

Chris 27-03-2014 19:10:24

Antes de empezar, entre los dos componentes te recomiendo que utilices el VirtualTreeView. TJvDBTreeView es poco efeciente y muy poco dinámico.

Para trabajar con el VirtualTreeView [VTV] debes estar muy familiarizado con los punteros. Si tienes un buen entendimiento de los punteros se te hará mucho más fácil trabajar con el VTV.

Lo primero que debes de definir es el tipo de datos de cada Nodo. El tipo de datos debe ser un puntero a un registro. El registro puede contener las propiedades que quieras.

Para explicartelo haré un mini tutorial. Utilizaré dos tablas virtuales. Una llamada clientes y otra llamada contactos. Cada cliente será represetando por un nodo en el VTV. Su respectivos contactos serán representados por subnodos.

Empezaremos por definir el registro de datos que utilizaremos para los nodos:

Código Delphi [-]
type
    TVTVNodeType = (ntClient, ntContact);
    TMyNodeData = record
        NodeType: TVTVNodeType; // Tipo de nodo
        ID: Variant;            // contendrá el id en la DB.
        Name: String;           // nombre del cliente o contacto
        HasContacts: Boolean;   // True cuando el cliente tiene contactos
    end;
    PMyNodeData = ^TMyNodeData;

Luego, escribiré un par de funciones que me serviraran para luego ubicar nodos en el árbol.

Código Delphi [-]
function get_node_of(Tree: TBaseVirtualTree,
                     NodeType: TVTVNodeType,
                     ID: Variant,
                     ParentNode: PVirtualNode = nil);
var
    NodeData: PMyNodeData;
    CurrentNode: PVirtualNode;                  
begin
    // esta función devuelve el nodo que cumpla con
    // las condiciones establecidas en los parámentros.
    result := nil;
    if ParentNode = nil then
        ParentNode := Tree.RootNode;

    CurrentNode = Tree.GetFirstChild(ParentNode)
    with Tree do
        while (CurrentNode <> nil)
        begin
            if not assigned(GetNodeData(CurrentNode)) then
            begin
                // El nodo no tiene datos asociados. Seguir con el siguiente.
                CurrentNode := Tree.GetNext(CurrentNode);
                continue;
            end;

            NodeData = GetNodeData(CurrentNode);
            if (NodeData.NodeType = NodeType) and (NodeData.ID = ID) then
            begin
                result := CurrentNode;
                break;
            end
            else
                CurrentNode := Tree.GetNext(CurrentNode);
        end;
end;

Ahora haremos la primera etapa. Mostrar todos los registros de clientes en el evento OnShow del formulario. Crearemos un nodo para cada registro de cliente.

Código Delphi [-]
procedure TForm1.OnShow(Sender: TObject);
var
    I: Integer;
    NewNodeData: PMyNodeData;
    NewNode: PVirtualNode;
begin
    // VTView es el nombre del componente VirtualTreeView en el formulario
    VTView.NodeDataSize := SizeOf(TMyNodeData);

    // Por simplicidad, no pondré un código de petición SQL
    // a la base de datos. Pero para que el código siguiente
    // funcione, debemos hacer una petición SQL que me devuelva
    // 3 campos. El ID y NOMBRE del cliente. El 3er campo será
    // un Boolean que cuando sea True indique si el cliente tiene
    // contactos asociados.

    // ...
    // select id, nombre, has_contacts from clients ...
    // ...

    for I := 0 to Dataset.RecorCount -1 do
    begin
        NewNode := VTView.AddChild(nil, NewNodeData);
        NewNodeData.NodeType    := ntClient;
        NewNodeData.ID          := Dataset.Records[i].FieldByName('id').Value;
        NewNodeData.Name        := Dataset.Records[i].FieldByName('name').Value;
        NewNodeData.HasContacts := Dataset.Records[i].FieldByName('has_contacts').Value;

        if NewNodeData.HasContacts then
          Include(NewNode.States, vsHasChildren);
    end;
end;

Ya hemos agregado los nodos. VirtualTreeView te permite crear nodos y árboles muy elaborados. O sencillos si así lo prefieres. Para este ejemplo usaremos nodos sencillos. Sin mucho adorno. VTV se encargará de pintarlos y no nosotros. Pero VTV necesita saber que cuál es el texto que mostrará en cada nodo. Así que nos lo debe preguntar en el evento "OnGetText". En este evento nuestro código devolverá la propiedad "name" de nodo.

Código Delphi [-]
procedure TForm1.VTVIewGetText(Sender: TBaseVirtualTree;
                               Node: PVirtualNode;
                               Column: Integer; TextType: TVSTTextType;
                               var Text: WideString);
var
    NodeData: PMyNodeData
begin
    NodeData := Sender.GetNodeData(Node);
    if (NodeData <> nil)
        Text := NodeData.Name
    else
        Text := '';
end;

Cuando el usuario expanda un Node de clientes que tiene contactos, debemos cargar desde la DB los registros de contactos. Esto lo haremos en el evento OnInitChildren.

Código Delphi [-]
procedure TForm1.VTVIewInitChildren(Sender: TBaseVirtualTree;
                                    Node: PVirtualNode;
                                    var ChildCount: Cardinal);
var
    I: Integer;
    ClientNodeData, NewNodeData: PMyNodeData;
    NewNode: PVirtualNode;
begin
    ChildCount := 0;
    ClientNodeData := Sender.GetNodeData(Node);
    // Hacer una petición a la DB con los datos de los contactos
    // asociados al cliente seleccionado.
    // PSEUDO-CÓDIGO:
    Dataset := Database.query('select * from contacts where client = ' + ClientNodeData.ID);

    for I := 0 to Dataset.RecorCount -1 do
    begin
        NewNode := VTView.AddChild(Node, NewNodeData);
        NewNodeData.NodeType    := ntContact;
        NewNodeData.ID          := Dataset.Records[i].FieldByName('id').Value;
        NewNodeData.Name        := Dataset.Records[i].FieldByName('name').Value;
        NewNodeData.HasContacts := False;
       
       Inc(ChildCount);
    end;   
end;

A cómo ves todo se trata de manejar los punteros. Utilizado Drag-and-Drop, podríamos mover un contacto desde un cliente a otro cliente. Podríamos eliminar un registro de la DB cuando el usuario elimine un Nodo del árbol y muchas otras funcionalidades que están fuera del alcance de esta respuesta.

VirtualTreeView te ofrece una funcionalidad inigualable. Todo es cuestión de entender cómo opera el componente. VirtualTreeView es sólo como una base. Este componente hace preguntas para todo. Lo que tu código debe implementar son respuestas a esas preguntas. Preguntas que normalmente el componente te las hace en los eventos.

Saludos.

pacopenin 27-03-2014 19:40:13

Estupendo tutorial. Voy recordando como funcionaba y me dan ganas de volver a utilizarlo. En su momento llegué a muchos puntos muertos ya que no encontraba ejemplos de como usarlo y depurar su funcionamiento era una locura por lo que comentas: pregunta para todo. Lo que si recuerdo es haber implementado grids con él y era rapidísimo y muy muy vistoso. Recientemente he descubierto que HeidySQL lo usa y le eché un vistazo, pero sin demasiado éxito. Otro punto a favor es que funciona en Lázarus.

Con respecto al otro componente, TJvDBTreeView, tienes toda la razón. Lo uso por la inmediatez y porque solo lo utilizo para mostrar la información clasificada por carpetas, pero sin usar las opciones de edición ni drag&drop ya que no funcionan bien.

pacopenin 27-03-2014 20:32:20

Acabo de encontrar un tutorial dentro de la wiki de Lazarus que sin mirarlo en profundidad parece que cubre bastantes aspectos.

Ejemplos de VirtualTreeView


La franja horaria es GMT +2. Ahora son las 22:40:44.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi