jueves, 26 de septiembre de 2019

TTreeView con nodos y punteros TTreeNode.Data

En la entrada anterior vimos las operaciones básicas de un TreeView solo con ítems, ahora veremos con nodos, que ya sirve de algo, con punteros a objetos, más precisamente a un registro, que es apuntado por TreeNode.Data siendo Data del tipo Pointer y, siendo Pointer un puntero sin tipo. En el programa de ejemplo se muestra como agregar un nodo raíz, un nodo hijo, el índice absoluto, modificar los datos de un registro de un nodo, borrar un nodo, mostrar los datos del registro apuntado por un nodo. Para el caso, los nodos raíz, no pueden apuntar a un objeto, solo a hojas (nodos), para el caso que se necesite que lo nodos de nivel cero tengan un registro (apunten a un registro), ya que no se puede, lo que se hace es se crea un único nodo raíz y se lo oculta, de esta forma todos los nodos parten del nivel 1 y son hijos de ese único nodo padre. En este ejemplo se parte de dos nodos raíces a los cuales el usuario le agrega nodos hijos, nietos, etc. y todos tienen el mismo tipo de registro, lo cual no es obligatorio; en este ejemplo un nodo raíz se llama "profesores" y el otro "alumnos", los nodos de ambos apuntan el mismo tipo de registro pero bien se puede crear un registro para profesores y otro para alumnos.


Como se ve en la imagen, se utilizan: un TTreeView, 5 TEdit, 4 TLabel y 5 botones.


Le agregamos 2 elementos al árbol, nodos de nivel 0 (cero), todas la operaciones que se hagan, serán sobre los nodos que "cuelguen" de estos dos.

type
PRegistro=^TRegistro;

TRegistro=record
  documento:string;
  nacionalidad:string;
  estadocivil:string;
end;


Definimos un registro y un puntero a dicho registro.

Agregar nodo:

procedure TForm1.BAgregarClick(Sender: TObject);
var
  RegPtr:PRegistro;
begin
  New(RegPtr);
  RegPtr^.documento:=edDocumento.Text;
  RegPtr^.nacionalidad:=edNacionalidad.Text;
  RegPtr^.estadocivil:=edEstadoCivil.Text;
  if (tv.Items.Count=0) or (tv.Selected=nil) then
    tv.Items.Add (nil, edNombre.Text)
  else
   tv.Items.AddChildObject(tv.Selected, edNombre.Text, RegPtr);
end;


Declaramos una variable local del tipo PRegistro (puntero al registro) y con New la instanciamos. Es decir, tenemos una nueva dirección de memoria y espacio para nuestro nuevo registro, luego le asignamos los datos. Ahora a ese registro que está "flotando" en la memoria RAM, hay que enlazarlo al árbol, y esto se hace asignando a la propiedad Data del nodo, la dirección de memoria donde se aloja el registro, eso lo hacemos mediante AddChildObject (nodopadre, textoamostrar, punteroalregistro). La parte True del if es para agregar un elemento raíz, si se agrega sin haber seleccionado un nodo, creará un ítem nivel 0.

Borrar un nodo:

procedure TForm1.BBorrarNodoClick(Sender: TObject);
begin
  if tv.Selected=nil then Exit;
  tv.Selected.Delete;
  BorroEdit; //Procedimiento que borrar los TEdit.
end;


Siempre averiguar si hay algún nodo seleccionado para evitar errores.

Modificar un nodo:

procedure TForm1.BModificarClick(Sender: TObject);
begin
  if tv.Selected=nil then Exit;
  edNombre.Text:=tv.Selected.Text;
  edDocumento.Text:=PRegistro(tv.Selected.Data)^.documento;
  edNacionalidad.Text:=PRegistro(tv.Selected.Data)^.nacionalidad;
  edEstadoCivil.Text:=PRegistro(tv.Selected.Data)^.estadocivil;
end;


En esta primera parte se carga el registro apuntado por el nodo en los TEdit. Como vemos no es necesario crear una variable local del tipo puntero a TRegsitro, se utiliza PRegistro(puntero)^.campo.

procedure TForm1.BActualizarClick(Sender: TObject);
begin
  if tv.Selected=nil then Exit;
  if tv.Selected.Level < 1 then Exit; 

  tv.Selected.Text:=edNombre.Text;      
  PRegistro(tv.Selected.Data)^.documento:=edDocumento.Text;  
  PRegistro(tv.Selected.Data)^.nacionalidad:=edNacionalidad.Text; 
  PRegistro(tv.Selected.Data)^.estadocivil:=edEstadoCivil.Text; 
end;

Cuando puslamos el botón Actualizar, lo que está en los TEdit, lo asignamos al registro; previamente hacemos las validaciones mínimas del caso, ya que solo es un ejemplo.

Ver el registro apuntado por un nodo:

procedure TForm1.BVerClick(Sender: TObject);
begin
  if tv.Items.Count < 1 then Exit;
  if tv.Selected=nil then exit;
  if (tv.Selected.Data < > nil) then
  begin
    lNombre2.Caption:=tv.Selected.Text;
    lDocumento.Caption:=PRegistro(tv.Selected.Data)^.documento;
    lNacionalidad.Caption:=PRegistro(tv.Selected.Data)^.nacionalidad;
    lEstadoCivil.Caption:=PRegistro(tv.Selected.Data)^.estadocivil;
  end;
end;


Mostramos el contenido del registro en los TLabel. Como siempre, validar antes de ejecutar la acción.

procedure TForm1.tvChange(Sender: TObject; Node: TTreeNode);
begin
  if (not(Assigned(Node)) or (Node.Level < 1)) then Exit;
  edNombre.Text:=tv.Selected.Text;
  edDocumento.Text:=PRegistro(tv.Selected.Data)^.documento;  

  edNacionalidad.Text:=PRegistro(tv.Selected.Data)^.nacionalidad;
  edEstadoCivil.Text:=PRegistro(tv.Selected.Data)^.estadocivil;
end;



En este caso cargamos los TEdit con el registro del nodo al cual se le ha hecho click y atención, no usamos el evento OnClick sino el evento OnChange que se lanza o dispara cuando cambia el nodo seleccionado. Es importante la validación previa, si el Node, que viene como parámetro, no está asignado o es de nivel 0, entonces "huímos".
Al implementar este método ya no sería necesario el botón modificar que carga los TEdit.

Ver el índice absoluto de un nodo:

procedure TForm1.BVerIndiceClick(Sender: TObject);
begin
  if tv.Selected=nil then exit;
  Edit4.Text := IntToStr(tv.Selected.AbsoluteIndex);
end;


Cada nodo tiene un índice absoluto dentro de un árbol, independientemente de su nivel (level), vendría a ser un identificador único.

Video:



Código fuente del proyecto: TTreeView_Nodos.7z

No hay comentarios:

Publicar un comentario