Taller práctico – Arbol TTreeView en Firemonkey (I)

Iniciamos el taller práctico abordando un componente que ya nos es conocido.Para la gran mayoría no es ningun personaje nuevo que entre en escena y todos, los que hemos pasado por las distintas versiones de Delphi, lo hemos conocido desde la perspectiva de la plataforma VCL. Os comento que va a estar en nuestro punto de mira durante esta entrada y algunas mas, aunque ahora mismo no se precisaros qué cantidad, y la idea al plantearme estas entradas era la de acercar a vuestro conocimientos algunas diferencias que pueden ser notables respecto a ambas versiones: el TTreeView de la VCL y el nuevo TTreeView de FireMonkey ( enfocado sobre FireMonkey por ser mas nuevo para nosotros).

Para los mas jovenes, empezaremos diciendo que un componente TTreeView va a ser una representación visual de la estructura clasica de arbol en programación, donde existen una serie de nodos (TTreeViewItem para FMX y TTreeNode para VCL) que establecen una relación «pertenecer a» que hace corresponder a cada uno de los nodos un unico vinculo de pertenencia. Aunque en este caso, la raiz de esta jerarquia pertenece al componente TTreeView y son los items TTreeViewItem o TTreeNode, lo que «cuelgan» bien del primero o bien de otro item. En terminos coloquiales podemos utilizar el simil «padre-hijo» para identificar que un nodo tiene un único padre. Esta estructura se ajusta a conceptos conocidos y usados por nosotros en el día a día, siendo el que me parece mas representativo o intuitivo, la estructura de ficheros en disco donde el disco hace las veces de nuestro TTreeView y las distintas carpetas serian el equivalente a los TTreeViewItem´(para ir por casa creo que nos vale la comparación).

Al decir, al inicio del párrafo anterior, que era una representación «visual», estamos diciendo basicamente que lo que tenemos es una «ventana», donde nuestro usuario visualizará la estructura de forma jerarquica y podrá navegar sobre los distintos nodos pulsando sobre ellos, expandiendolos o contrayendolos, de forma que acceda al contenido del nodo que a su vez serán otros nodos y así sucesivamente. Y cada uno de esos nodos (TTreeViewItem) consistirá básicamente en una etiqueta de texto.

Por otro lado, lo hemos definido como «componente», lo cual quiere decir que lo vamos a tener disponible en tiempo de diseño, desde nuestra paleta de componentes para soltarlo sobre cualquier contenedor válido que lo permita (un formulario, un panel, etc.). También podremos ajustar las propiedades publicadas que nos interesen o interactuar en el editor de diseño para crear la estructura que va a visualizar.

En la imagen inferior, veis la paleta de componentes, localizado sobre la pestaña «Standard» en FireMonkey mientras que si abrieramos un proyecto VCL, lo tendría,mos que buscar en la pestaña «Win32».

treeview1

Tampoco el editor en tiempo de diseño es igual en ambas plataformas. En el caso de la plataforma FireMonkey es mucho mas sencillo y presenta menos opciones que su editor hermano. En firemonkey el editor se reduce a crear un nodo, un nodo hijo y la posibilidad de que éste ascienda o descienda en la estructura (los 2 botones de las flechas), o bien que sea eliminado (el boton Delete). Sin embargo, en el caso del arbol en la VCL, podemos vincular el índice de la imagen asociada e incluso seleccionar la imagen en función de su estado (seleccionado o no seleccionado por ejemplo). Esto es porque el componente TTreeView permite que podamos enlazar una lista de imagenes (TImageList) y vincular el índice a distintas propiedades de los nodos. De momento en FireMonkey no es posible esto, pero en su descargo comentaré que trae algo muy práctico que van a ser los selectores checkbox asociados a los nodos, que yo personalmente he suspirado en su compañero VCL, sin tener que ir creando codigo para simularlos. Así que tenemos una da cal y otra de arena para compensar el balance.

Os muestro una imagen del editor en tiempo de diseño.

editor1

 

Con estas pinceladas queda mas o menos claro que podemos esperar de un TTreeView y a que nos puede ayudar.

Me pareció interesante escribir esta serie sobre el componente TTreeView en FireMonkey porque aunque estén ambos basados sobre el mismo paradigma de programación, la implementación que han hecho es distinta y puede conducir a confusión algunas de las funcionalidades  que podríamos esperar, sobretodo si hemos trabajado anteriormente con el TTreeView desde la VCL y tómamos a este como referencia para interactuar con el nuevo componente. Digamos simplente que hacen lo mismo pero de forma distinta y partiendo de supuestos distintos.

Lo primero a considerar  es la ascendencia de las clases implicadas.

¿Quereis verlo con una prueba muy sencilla?

Abrir un formulario en un nuevo proyecto de FireMonkey y dejais caer sobre el mismo un TTreeView. Luego pulsando sobre el editor en tiempo de diseño, creais dos o tres nodos. El paso siguiente es regresar al formulario y hacer click en el interior del arbol, sobre uno de los nodos (estamos en tiempo de diseño). Y… efectivamente. Os habeis dado cuenta que podeis interactuar en tiempo de diseño con los distintos items de la colección. Si hubierais hecho lo mismo en la VCL veriais que los distintos items son meramente el efecto del «pintado» que va haciendo nuestro TTreeView y no tienen entidad propia para esa labor pues no descienden de TComponent sino de TPersistent (la clase que sí permite que sean almacendado mediante persitencia los cambios en tiempo de diseño).

Y ademas otro punto (interesante). Una vez conscientes de que podemos seleccionar un item en tiempo de diseño, si escogemos un componente cualquiera (un TEdit por ejemplo) va tambien  a ser suceptible de ser alojado en su interior, de forma que queda acoplado como si fuera un contenedor (¡es que es un contenedor!) 🙂

Pero permitidme que os muestre un fragmento de los metodos y propiedades que se publican en ambos casos (un nodo TTreeNode y su hermano TTreeViewItem) para que podais apreciar diferencias notables a simple vista.

Esta es la interfaz publica que muestra un nodo en la VCL:

  TTreeNode = class(TPersistent)
  private
    ...
  public
    constructor Create(AOwner: TTreeNodes); virtual;
    destructor Destroy; override;
    function AlphaSort(ARecurse: Boolean = False): Boolean;
    procedure Assign(Source: TPersistent); override;
    procedure Collapse(Recurse: Boolean);
    procedure Delete;
    procedure DeleteChildren;
    function DisplayRect(TextOnly: Boolean): TRect;
    function EditText: Boolean;
    procedure EndEdit(Cancel: Boolean);
    procedure Expand(Recurse: Boolean);
    function getFirstChild: TTreeNode; {GetFirstChild conflicts with C++ macro}
    function GetHandle: HWND;
    function GetLastChild: TTreeNode;
    function GetNext: TTreeNode;
    function GetNextChild(Value: TTreeNode): TTreeNode;
    function getNextSibling: TTreeNode; {GetNextSibling conflicts with C++ macro}
    function GetNextVisible: TTreeNode;
    function GetPrev: TTreeNode;
    function GetPrevChild(Value: TTreeNode): TTreeNode;
    function getPrevSibling: TTreeNode; {GetPrevSibling conflicts with a C++ macro}
    function GetPrevVisible: TTreeNode;
    function HasAsParent(Value: TTreeNode): Boolean;
    function IndexOf(Value: TTreeNode): Integer;
    procedure MakeVisible;
    procedure MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode); virtual;
    function IsFirstNode: Boolean;
{$IFDEF CLR}
    function CustomSort(SortProc: TTVCompareProc; Data: TTag; ARecurse: Boolean = False): Boolean;
{$ELSE}
    function CustomSort(SortProc: TTVCompare; Data: NativeInt; ARecurse: Boolean = False): Boolean;
{$ENDIF}
    property AbsoluteIndex: Integer read GetAbsoluteIndex;
    property Count: Integer read GetCount;
    property Cut: Boolean read GetCut write SetCut;
    property Data: TCustomData read FData write SetData;
    property Deleting: Boolean read FDeleting;
    property Focused: Boolean read GetFocused write SetFocused;
    property DropTarget: Boolean read GetDropTarget write SetDropTarget;
    property Selected: Boolean read GetSelected write SetSelected;
    property Expanded: Boolean read GetExpanded write SetExpanded;
    property ExpandedImageIndex: TImageIndex read FExpandedImageIndex write SetExpandedImageIndex;
    property Handle: HWND read GetHandle;
    property HasChildren: Boolean read GetChildren write SetChildren;
    property ImageIndex: TImageIndex read FImageIndex write SetImageIndex;
    property Index: Integer read GetIndex;
    property IsVisible: Boolean read IsNodeVisible;
    property Item[Index: Integer]: TTreeNode read GetItem write SetItem; default;
    property ItemId: HTreeItem read FItemId;
    property Level: Integer read GetLevel;
    property OverlayIndex: Integer read FOverlayIndex write SetOverlayIndex;
    property Owner: TTreeNodes read FOwner;
    property Parent: TTreeNode read GetParent;
    property SelectedIndex: Integer read FSelectedIndex write SetSelectedIndex;
    property Enabled: Boolean read FEnabled write SetEnabled;
    property StateIndex: Integer read FStateIndex write SetStateIndex;
    property Text: string read FText write SetText;
    property TreeView: TCustomTreeView read GetTreeView;
  end;

En el caso de la existente para FireMonkey (lineas mas abajo) se aprecia perfectamente que es mucho mas reducida en cantidad.

  TTreeViewItem = class(TTextControl, IItemsContainer)
  private
  ...
  public
    constructor Create(AOwner: TComponent); override;
    procedure Paint; override;
    procedure AddObject(AObject: TFmxObject); override;
    procedure RemoveObject(AObject: TFmxObject); override;
    procedure Sort(Compare: TFmxObjectSortCompare); override;
    function ItemByPoint(const X, Y: Single): TTreeViewItem;
    function ItemByIndex(const Idx: Integer): TTreeViewItem;
    property Count: Integer read GetCount;
    property GlobalIndex: Integer read FGlobalIndex write FGlobalIndex;
    function TreeView: TCustomTreeView;
    function Level: Integer;
    function ParentItem: TTreeViewItem;
    property Items[Index: Integer]: TTreeViewItem read GetTreeItem; default;
  published
    property IsChecked: Boolean read FIsChecked write SetIsChecked;
    property IsExpanded: Boolean read FIsExpanded write SetIsExpanded;
    property IsSelected: Boolean read FIsSelected write SetIsSelected;
    property AutoTranslate default True;
    property Font;
    property StyleLookup;
    property Text;
    property TextAlign default TTextAlign.taLeading;
  end;

Una de las cosas que se destaca a primera vista es que no se han especificado expresamente las rutinas para movernos en el interior de los nodos, bien hacia los nodos hermanos (situados en el mismo nivel) bien hacia el interior de los niveles correspondientes a nodos hijos. Os he remarcado con color amarillo algunos de esos metodos que no existen (al menos de momento). Lo cual, no significa en algun modo que no seamos capaces de recorrer con programación toda la estructura de nodos. Pero es significativo y bueno saberlo de cara a la migración de código existente, que hagan uso de estos métodos en el contexto del desarrollo, ya que vamos a tener que hacer la sustitución de los mismos (quizás pueda ser interesante anticiparnos y  crear un descendiente que añada lo que necesitamos para compatibilizar la migración. Es una idea que se me ocurre). Igualmente, os he marcado en color fucsia dos metodos que hacen referencia al indice de las imagenes (hay mas en el interfaz pero solo os resalto dos de ellos). Igualmente, como comentabamos, no existe actualmente posiblidad de ligar en tiempo de diseño nuestro arbol a un contenedor de imagenes de forma que podamos seleccionar cualquiera de ellas para los distintos estados. Eso también deberia ser tenido en cuenta si vais a porta codigo a FireMonkey.

Y vuelvo a repetir. El hecho de que actualmente no existan estas funcionalidades en tiempo de diseño no significa que no puedan ser implementadas por nosotros mismos mediante código o bien ser adquiridas a través de la solución de un tercero que si que las contemple. Y lo mejor es verlo con una imagen. Para finalizar esta serie pensaba que seria interesante ver un caso práctico donde se pudieran comentar algunos conceptos y que a su vez, sirva de pretexto para compartir ideas y conocimientos, pero para que veáis que no hay problema en lo comentado anteriormente, os adelanto un video de ese posible código (no aseguro que sea el final ya que estoy cambiandolo sobre la marcha, corrigiendo algunos errores, para aprender más cosas sobre el uso del componente, evaluando distintas formas de hacerlo).

Ah. No hagais demasiado caso a la rejilla situada en el panel izquierdo ya que es meramente informativa (realmente puede ser suprimida pero la dejé porque las ventanas dialogo se enlazan mediante livebindings con el dataset asociado.

.

Hablemos de su uso en tiempo de ejecución

Hablar de todo esto y no comentar lo mas básico que puede ser crear un nodo en tiempo de ejecución es un poco un contrasentido. Para verlo, simplente con un proyecto abierto y teniendo un componente TTreeView en nuestro formulario, añadid cualquier control que podamos enlazar unas lineas de codigo (un boton por ejemplo en su evento click). Supongamos que nuestro Arbol se represente con el nombre «Arbol» 🙂  (derrochemos imaginación)

var
FNodo: TTreeViewItem;


FNodo:= TTreeViewItem.Create(Self);
FNodo.Parent:= Arbol;

En este caso le decimos que el propietario es el formulario y que asocie como padre del nodo creado a nuestra instancia (Arbol). Y suponiendo que una vez hecho esto hubieramos querido crear un hijo en su interior, repetiriamos la operacion entregando como Parent del nuevo nodo el Nodo anterior. Básicamente es eso. Es similar a lo que haciamos en la VCL pero teniendo en cuenta que en aquel caso, al no ser propiamente un componente y no necesitar un propietario, el parametro de la creación era funcionalmente el parent del nodo. Ahora, nuestro TTreeViewItem es por definicion descendiente de la clase TComponent y necesita tomar un propietario que se responsabilize de las llamadas a destruirse, que es el parametro en la constuccion. Y adicionalmente recibe de forma expresa el parent que establecerá la relacion de pertenencia. Veremos en la siguiente entrada la relacion con los metodos AddObject y RemoveObject, que estan directamente relacionadas también.

Primer test para plantearnos cosas…

Dado que no tenemos disponibles metodos de navegación (salvo que estos se definieran en algun otro momento en una clase ayudante que colabore en ello), podemos pensar (y estamos en lo cierto) que existen índices que nos permiten recorrer las  matrices que contienen distintos nodos. De no ser así, pensemosssss… , ¿como se podria pintar a si mismo?  🙂  Me sorprendo a mi mismo con pensamientos como éste.  😉

Como cualquier acceso a una matriz al final necesita de un índice y debe existir un recuento que nos devuelva la cantidad total (nuestro Count) para poder establecer un bucle de recorrido habitualmente desde cero (0) hasta Count -1 (que serían los rangos validos de acceso), podemos buscar que indices tenemos disponibles en las clases que nos ocupan y vemos que aparece «GlobalCount» y «Count». Son metodos de nuestro componente TTreeView.

Para ver la diferencia entre ambos os incluyo este primer test donde aparecen definidos dos arboles que se generan con el mismo código (los dos procedimientos CicloParaCount y CicloParaGlobalCount son identicos salvo y generan la misma cantidad de ciclos (5) salvo que se apoyan cada uno en la evaluación de una de las dos funciones. El detalle que no se aprecia en la imagen es que he añadido una etiqueta (TLabel) inicialmente vinculada al formulario pero que es enlazada al arbol en cada uno de los ciclos.

Este es el código:

 

testcount1

Formulario que evalua la diferencia entre GlobalCount y Count

 

unit UMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Layouts, FMX.TreeView;

type
  TMain = class(TForm)
    Arbol1: TTreeView;
    bnTest: TButton;
    lbMiEstiqueta1: TLabel;
    Arbol2: TTreeView;
    Label1: TLabel;
    Label2: TLabel;
    lbMiEtiqueta2: TLabel;
    procedure bnTestClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function NuevoNodo: TTreeViewItem;
    procedure CicloParaGlobalCount;
    procedure CicloParaCount;
  end;

var
  Main: TMain;

implementation

{$R *.fmx}

procedure TMain.bnTestClick(Sender: TObject);
begin
   Arbol1.Clear;
   Arbol2.Clear;
   CicloParaCount;
   CicloParaGlobalCount;
end;

procedure TMain.CicloParaCount;
var
  FNodo, FSubNodo: TTreeViewItem;
  i: Integer;
begin
   i:= 1;
   FNodo:= NuevoNodo;
   FNodo.Parent:= Arbol2;
   FNodo.Text:= 'El nodo ' + IntToStr(i);
   {Creamos un subitem en el primero}
   FSubNodo:= NuevoNodo;
   FSubNodo.Parent:= FNodo;
   FSubNodo.Text:= 'Un subnodo asociado al nodo '+  IntToStr(i);
   {Añadimos una etiqueta}
   lbMiEtiqueta2.Parent:= Arbol2;    Inc(i);

   repeat
     FNodo:= NuevoNodo;
     FNodo.Parent:= Arbol2;
     FNodo.Text:= 'El nodo ' + IntToStr(i);
     Inc(i);
   until Arbol2.Count = 5;
   Arbol2.ExpandAll;
end;

procedure TMain.CicloParaGlobalCount;
var
  FNodo, FSubNodo: TTreeViewItem;
  i: Integer;
begin
   i:= 1;
   FNodo:= NuevoNodo;
   FNodo.Parent:= Arbol1;
   FNodo.Text:= 'El nodo ' + IntToStr(i);
   {Creamos un subitem en el primero}
   FSubNodo:= NuevoNodo;
   FSubNodo.Parent:= FNodo;
   FSubNodo.Text:= 'Un subnodo asociado al nodo '+  IntToStr(i);
   {Añadimos una etiqueta}
   lbMiEstiqueta1.Parent:= Arbol1;    Inc(i);

   repeat
     FNodo:= NuevoNodo;
     FNodo.Parent:= Arbol1;
     FNodo.Text:= 'El nodo ' + IntToStr(i);
     Inc(i);
   until Arbol1.GlobalCount = 5;
   Arbol1.ExpandAll;
end;

function TMain.NuevoNodo: TTreeViewItem;
begin
  Result:= TTreeViewItem.Create(Self);
end;

end.

Podeis descargar el codigo fuente y ejecutarlo pero por la misma imagen adjunta se aprecia. GlobalCount crea 5 nodos (hermanos) mientras que Count solo 4. El motivo es porque al ser un contenedor va a admitir que convivan elementos distintos del item propiamente y el conteo tiene en cuenta la etiqueta. Pero si os parece podemos buscar ayuda en Delphi para corroborarlo. En el modulo FMX.TreeView, que es donde se especifican las clases, existe el procedimiento UpdateGlobalIndexes que se responsabiliza de mantener siempre correcto el valor de los indices globales.

 

procedure TCustomTreeView.UpdateGlobalIndexes;
...
var
  i: Integer;
begin
  FGlobalList.Clear;
  GlobalIdx := 0;
  for i := 0 to Count - 1 do
    AlignItem(ItemByIndex(i)); //not all the items are of type TTreeViewItem, so some may return nil   FGlobalCount := GlobalIdx;
end;

Fijaros tambien en la anotación ya que no es casual y si al final vemos que se esta haciendo en ItemByIndex( ) observariamos la existencia de una condicion que filtra que solo se recorran nodos descendientes de la clase TTreeViewItem.

Por esa razón en nuestro test si que recorre los cinco ciclos y Count no lo hace. Me he dado cuenta posteriormente que olvidé incrementar el valor de la variable i antes del bucle pero esto no afecta a los efectos del comentario, que es la diferenciacion entre ambos contadores y como ya tenía el codigo en el servidor lo dejé tal cual.

Podeis hacer la prueba vosotros mismo. Al final de esta entrada os he adjuntado el codigo fuente de los dos ejemplos.

Lo bueno de conocer estos detalles es que podemos evitar dar como buenos supuestos que eran ciertos para la VCL, donde si existia un índice que permtia recorrer linealmente la estructura. En el siguiente ejemplo vereis el porque del comentario

Vamos a ver otro pequeño test para ir conociendo algunos detalles del componente.

En este caso, la idea es mostrar como trabaja la propiedad Checked (IsChecked) del Item y como podemos manipularla a traves del nodo seleccionado.

Veamos.

Segundo test para plantearnos cosas…

En este caso con muy poco codigo podemos ver algunas cosas interesantes.

testvarios1
Formulario con varios checks

unit UMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Layouts, FMX.TreeView,
  Data.Bind.EngExt, Fmx.Bind.DBEngExt, System.Rtti, System.Bindings.Outputs,
  Fmx.Bind.Editors, Data.Bind.Components, FMX.Edit;

type
  TMain = class(TForm)
    Arbol: TTreeView;
    Item1: TTreeViewItem;
    Item2: TTreeViewItem;
    Item3: TTreeViewItem;
    SubItem1_1: TTreeViewItem;
    SubItem1_2: TTreeViewItem;
    chbItem: TCheckBox;
    SubItem2_1: TTreeViewItem;
    SubItem3_1: TTreeViewItem;
    gboxPlusMinus: TGroupBox;
    rbtExp: TRadioButton;
    rbtCol: TRadioButton;
    Nivel: TLabel;
    lbNivel: TLabel;
    Label1: TLabel;
    lbGlobalIndex: TLabel;
    Label2: TLabel;
    lbIndex: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure ArbolChange(Sender: TObject);
    procedure ArbolChangeCheck(Sender: TObject);
    procedure rbtExpChange(Sender: TObject);
    procedure rbtColChange(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Main: TMain;

implementation

{$R *.fmx}

procedure TMain.ArbolChange(Sender: TObject);
begin
   chbItem.IsChecked:=  TTreeView(Sender).Selected.IsChecked;
   chbItem.Text:=       TTreeView(Sender).Selected.Text;
   lbNivel.Text:=       IntToStr(TTreeView(Sender).Selected.Level);
   lbGlobalIndex.Text:= IntToStr(TTreeView(Sender).Selected.GlobalIndex);
   lbIndex.Text:=       IntToStr(TTreeView(Sender).Selected.Index);
end;

procedure TMain.ArbolChangeCheck(Sender: TObject);
begin
   chbItem.IsChecked:= TTreeViewItem(Sender).IsChecked;
end;

procedure TMain.FormCreate(Sender: TObject);
begin
   Arbol.ShowScrollBars:= True;
   Arbol.ShowCheckboxes:= True;
   Arbol.AlternatingRowBackground:= True;
   Arbol.Selected:= Item1;
end;

procedure TMain.rbtColChange(Sender: TObject);
begin
     Arbol.Selected.IsExpanded:= not rbtCol.IsChecked
end;

procedure TMain.rbtExpChange(Sender: TObject);
begin
     Arbol.Selected.IsExpanded:= rbtExp.IsChecked
end;

end.

Si ejecutais el segundo ejemplo, vereis que aparece una ventana que contiene un arbol, que a su vez tiene creados en tiempo de diseño una serie de items.

Durante la creación del formulario, le decimos que muestre las barras de desplazamiento, que muestr un checkbox en cada item, y que pinte con colores alternados filas pares e impares. La ultima sentencia simplemente asigna el item seleccionado en el arbol.

procedure TMain.FormCreate(Sender: TObject);
begin
   Arbol.ShowScrollBars:= True;
   Arbol.ShowCheckboxes:= True;
   Arbol.AlternatingRowBackground:= True;
   Arbol.Selected:= Item1;
end;

Hay dos eventos que nos pueden interesar especialmente: OnChange( )ChangeCheck( ), ambos nos pueden ayudar a obtener información en funcion del nodo que el usuario está manipulando. En el primer evento nos centramos para obtener informacion sobre el estado del check, el texto del nodo y las propiedades Level, GlobalIndex e Index. Son los sitios adecuados para transmitir esa información a otros elementos de nuestra interfaz.

Y se ha añadido, en sentido contario (desde un control a nuestro arbol) una asignación para que al hacer check se expanda o colapse el nodo seleccionado. Lo podeis probar con los dos TCheckBox que existen en el interior del componente TGroupBox.

Hay un detalle curioso. El valor del indice GlobalIndex va a estar en función de que nuestros nodos esten expandidos o colapsados. Es decir, que inicia el recuento en cero y solo considera los nodos visibles. Así que llevad precaución si lo utilizais porque el nombre puede no ser adecuado… 🙂  Yo hubiera elegido quizás GlobalVisibleIndex que da mas información sobre que información captura el índice.

Respecto a Index, indexa desde cero (0) y cuenta solo desde el padre inmediatamente anterior. Por ejemplo: El SubItem3_1 devuelve indice cero (0) porque es el primer nodo de Item3

En la siguiente entrada, la idea es crear un pequeño test que podamos ver como navegar en un arbol y comentaremos alguna posible opción con el tema de las imagenes.

Descargar código fuente
Para despedirme y hasta que podamos leernos en una nueva entrada os invito a los que no lo conoceis, hagais un visita al Grupo de Delphi Solidario en Facebook, que ya va camino de los 300 miembros. Estamos trabajando intensamente para que nuestra Comunidad sea mejor día a día: con muchos mas recursos y mas ayuda.

Soy consciente de que no es algo que podamos lograr de la noche a la mañana pero con vuestro apoyo creo que no es algo inviable. Lo peor que nos puede pasar no es equivocarnos sino no hacer nada para mejorarla. Así que os invito a participar activamente en todos los foros, webs y blogs de la Comunidad. Sin excepciones.

Ahh  Y para aquellos que se acercan a Delphi por primera vez simplente comentarles que no tengan miedo a preguntar. Cuando alguien parte desde cero siempre tiene el miedo a que los demas puedan valorarnos en funcion de nuestras dudas. El camino siempre es preguntar, preguntar y preguntar. Nuestra comunidad lleva años resolviendo las dudas generosamente desde foros, webs y blogs personales. Así que ¡ánimo!.

En fin. Espero y deseo que os haya podido ser de ayuda.

Hasta la siguiente entrada.

 

Una respuesta a “Taller práctico – Arbol TTreeView en Firemonkey (I)

Add yours

Deja un comentario

Blog de WordPress.com.

Subir ↑

Marina Casado

Escritora y Doctora en Literatura Española. Periodista cultural. Madrid, España

Sigo aqui

Mi rincon del cuadrilatero, ahi donde al creer que me he rendido, aun sigo peleando.

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

https://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi, C++ Builder and related technologies...

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphic Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general