El problema a resolver es serializar /Deserializar el contenido de un control TreView
El problema de serializar un árbol es relativamente complejo, la parte fácil es volcar la información del árbol a un soporte, la parte difícil es recomponer el árbol a partir de la información guardada.
Recuerda que un árbol es una estructura que contiene nodos, y cada uno de los nodos a su vez contiene nodos hijos, formando una estructura que se asemeja a un árbol
El problema que se plantea es generar una estructura de datos que represente cada nodo del árbol y que se pueda guardar en el disco en un fichero, y a su vez que permita recuperar el árbol usando esa información
Buscando por internet, se encuentran varias soluciones posibles, por ejemplo,
En este documento he estudiado otra posibilidad, que consiste en guardar la información del árbol en un fichero de texto puro, guardando la información de cada nodo del árbol en formato CSV.
Una de las formas es utilizar una estructura de datos que consiste en asignar a cada nodo tres valores, un valor ID único que lo identifica, el valor del ID del nodo padre y los datos que queremos aguardar del nodo, en este ejemplo solo guardaremos la propiedad Text del nodo, es decir el valor de texto que aparece en el control TreeView en cada uno de sus nodos
'********************************* ' | ID | ' ID |Padre| Text '--------------------------------- ' 1| 0|1.- Animales ' 2| 1|1.1- Mamíferos ' 3| 2|1.1.1- Perros ' 4| 3|1.1.1.1- Pastores ' 5| 3|1.1.1.2- Chiguaguas ' 6| 3|1.1.1.3- Compañía ' 7| 1|1.2-Serpientes
ID debe contener un numero único que identifica a cada nodo.
ID Padre: debe contener el ID del nodo padre del cual "Cuelga" este nodo
Text: Es el valor de la propiedad Text del objeto TreeNode. Si queremos guardar mas información del nodo, deberemos crear una columna adicional por cada dato que queramos guardar
Para manejar esta información utilizo una estructura, que llamare [EstructruraNodoSerialiar] que contiene la información de cada nodo, y también contiene una propiedad llamada [CSV] que se encarga de volcar los datos de la estructura en una cadena CSV para guardarla en un fichero de texto, y al revés, cuando recibe una cadena que contiene la información en formato CSV, es capaz de leerla y cargar los datos en la estructura.
El proceso de serializar la información es el siguiente
El proceso de Deserializar es el siguiente
Observacion: El problema que presenta este enfoque es que se leen varias veces lo nodos para darlos de alta, por lo que he habilitado un "semáforo" en la estructura que indica si ese "nodo" se ha creado a no.
La estructura [EstructruraNodoSerialiar] tiene la siguiente interface:
Public Structure EstructruraNodoSerialiar ''' <summary>El id UNICO del nodo</summary> Public Property ID() As Integer ''' <summary>El id del nodo padre</summary> Public Property IDPadre() As Integer ''' <summary>El valor de la propiedad Text del nodo</summary> Public Property Text() As String ''' <summary> ''' Se emplea en el proceso de carga del árbol, ''' y es un valor lógico que indica si el nodo se ''' ha creado en el árbol (valor true) o no ''' </summary> Public Property Grabado() As Boolean ''' <summary> ''' Constructor de la estructra ''' </summary> ''' <param name="id">El id UNICO del nodo</param> ''' <param name="idPadre"> El id del nodo padre</param> ''' <param name="text"> ''' El valor de la propiedad Text del nodo</param> Public Sub New(ByVal id As Integer, _ ByVal idPadre As Integer, _ ByVal text As String) ''' <summary> ''' Devuelve los datos de la estructura en un ''' formato legible por el usuario ''' </summary> ''' <returns> ''' Una cadena con la información de la estructura ''' por ejemplo ID = 5; ID Padre = 2; Text = pajaros; Grabado = False ''' </returns> Public Overrides Function ToString() As String ''' <summary> ''' Obtiene / establece los valores de la estructura ''' en una cadena con formato CSV ''' </summary> Public Property CSV() As String
La propiedad CSV tiene el siguiente código:
''' <summary> ''' Obtiene / establece los valores de la estructura ''' en una cadena con formato CSV ''' </summary> Public Property CSV() As String Get Dim provider As IFormatProvider provider = System.Globalization.CultureInfo.CurrentCulture 'La sintaxis de un elemento de formato es ' {index[,alignment][:formatString]}, Dim formato As String = "{0,5:0}|{1,5:0}|{2,-20}" Dim salida As String '---------------------- ' montar la cadena de salida con el salto de linea al final salida = String.Format( _ provider, formato, ID, IDPadre, Text) & _ Environment.NewLine '---------------------- Return salida End Get Set(ByVal value As String) If String.IsNullOrEmpty(value) Then Throw New ArgumentException( _ "El valor de la cadena no puede ser nulo o vacio", "CSV") End If Dim matrizElementos() As String Dim matrizSeparadores() As Char = {"|"c} Try matrizElementos = _ value.Split(matrizSeparadores, _ StringSplitOptions.RemoveEmptyEntries) Me.ID = Integer.Parse(matrizElementos(0)) Me.IDPadre = Integer.Parse(matrizElementos(1)) Me.Text = matrizElementos(2) Me.Grabado = False Catch ex As Exception Throw End Try End Set End Property
Si observas detenidamente, el código se encarga de recorrer todos y cada uno de los nodos del árbol, asignando a cada uno de ellos un numero único que es el ID del nodo, y a continuación se encarga de recorrer los nodos hijos. Por cada nodo, se carga la estructura con la información y se genera una cadena en formato CSV que es la que se escribe en un fichero de texto, el resultado de ese fichero es algo parecido a esto...
'******************************** ' | ID | ' ID |Padre| Text '-------------------------------- ' 1| 0|1.- Animales ' 2| 1|1.1- Mamíferos ' 3| 2|1.1.1- Perros ' 4| 3|1.1.1.1- Pastores ' 5| 3|1.1.1.2- Chiguaguas ' 6| 3|1.1.1.3- Compañía ' 7| 1|1.2-Serpientes ' 8| 1|1.3-Aves ' 9| 8|1.3.1- Voladoras ' 10| 8|1.3.2- Caminantes ' 11| 1|1.4-Peces ' 12| 11|1.4.1- Luchadores ' 13| 12|1.4.1.1- Azules ' 14| 12|1.4.1.2- Colorados ' 15| 11|1.4.2- Gupis ' 16| 11|1.4.3- Truchas ' 17| 0|2.- Hongos ' 18| 0|3.- Plantas ' 19| 18|3.1- Hierbas ' 20| 18|3.2- Arbustos ' 21| 18|3.3- Arboles ' 22| 0|4.- Minerales ' 23| 22|4.1- Piedras
EL proceso de grabar en disco la información tiene el siguiente código:
#Region "Save" ''' <summary> ''' Guarda un arbol en un fichero en formato CSV ''' </summary> ''' <param name="unArbol">el arbol que hay que guardar</param> ''' <param name="nombreDeFichero"> ''' El nombre completo del fichero</param> Public Sub Save( _ ByVal unArbol As System.Windows.Forms.TreeView, _ ByVal nombreDeFichero As String) '------------------------------------- ' para evitar errores tontos ' si el directorio no existe crearlo If IO.Directory.Exists( _ IO.Path.GetDirectoryName(nombreDeFichero)) = False Then IO.Directory.CreateDirectory( _ IO.Path.GetDirectoryName(nombreDeFichero)) End If '------------------------------------- ' Recorrer los nodos raices del arbol Using SW As New System.IO.StreamWriter( _ nombreDeFichero, False, codificacion) For Each TreeNode As TreeNode In unArbol.Nodes AuxSaveRecorrerNodosHijos (0, TreeNode, SW) Next End Using End Sub ''' <summary> ''' Funcion recursiva que recorre los nodos hijos de cada nodo ''' </summary> ''' <param name="IDNodoPadre"> ''' El ID del nodo padre</param> ''' <param name="nodo"> ''' El Objeto [TreeNode] que se esta estudiando </param> ''' <param name="SW"> ''' El objeto [StreamWriter] usado para grabar ''' la informacion en el disco ''' </param> Private Sub AuxSaveRecorrerNodosHijos ( _ ByVal IDNodoPadre As Integer, _ ByVal nodo As TreeNode, _ ByRef SW As System.IO.StreamWriter) '-------------------------------- ' el contador para generar un numero correlativo de nodos Static contadorIDNodo As Integer = 0 ' Calcular y asigbar el ID unico a este nodo contadorIDNodo += 1 Dim IDEsteNodoHijo As Integer = contadorIDNodo ' almacenar los valores del nodo Dim estruNodo As New EstructruraNodoSerialiar( _ IDEsteNodoHijo, IDNodoPadre, nodo.Text) ' montar la cadena de salida con el salto de linea al final #If DEBUG Then ' para control de depuracion Debug.Write(estruNodo.CSV) #End If ' escribir el nodo en el fichero SW.Write(estruNodo.CSV) ' recorrer los nodos hijos For Each unNodo As TreeNode In nodo.Nodes saveRecorrerNodosHijos(IDEsteNodoHijo, unNodo, SW) Next End Sub #End Region
Para leer la información el primer paso es recuperar la información del fichero de texto y cargarla en la memoria. El proceso se realiza en la función que se muestra a continuación, que lee el fichero y carga una lista genérica que contiene una lista de estructuras [EstructruraNodoSerialiar]. Cada una de las estructuras de la lista, contiene la información necesaria y suficiente para reconstruir el nodo del árbol. El proceso de reconstrucción se realiza en otras funciones
''' <summary> ''' Esta función lee el fichero de texto y carga ''' la información en una lista genérica ''' </summary> ''' <param name="nombreDeFichero"> ''' El nombre del fichero de texto donde está ''' la información del árbol serializado ''' </param> ''' <returns> ''' Devuelve una lista genérica que contiene estructuras ''' de datos del tipo [EstructruraNodoSerialiar] de manera ''' que cada una de las estructuras contiene la información ''' de un nodo del árbol. En otro proceso, usando esa ''' información, se reconstruye el árbol ''' </returns> Private Function AuxLoadCargarLista( _ ByVal nombreDeFichero As String) _ As List(Of EstructruraNodoSerialiar) '----------------------------------------------- ' control de la existencia del fichero en el disco If IO.File.Exists(nombreDeFichero) = False Then Throw New System.IO.FileNotFoundException( _ "El fichero no existe en el disco", nombreDeFichero) End If '---------------------------------------- Dim listaNodos As New List(Of EstructruraNodoSerialiar) Dim estructuraUnNodo As EstructruraNodoSerialiar = Nothing #If DEBUG Then Debug.WriteLine("*********************************") Debug.WriteLine(" | ID | ") Debug.WriteLine(" ID |Padre| Text") Debug.WriteLine("---------------------------------") #End If '---------------------------------------- ' cargar la lista leyendo el fichero de texto Using SR As New System.IO.StreamReader( _ nombreDeFichero, codificacion, True) ' leer la linea del fichero Dim input As String input = SR.ReadLine() While Not input Is Nothing #If DEBUG Then Debug.WriteLine(input) #End If ' escribir el nodo en un fichero estructuraUnNodo.CSV = input listaNodos.Add(estructuraUnNodo) input = SR.ReadLine() End While SR.Close() End Using Return listaNodos End Function
El proceso de carga del fichero lo realiza la siguiente función:
''' <summary> ''' Deserializa un árbol guardado en un fichero de texto ''' </summary> ''' <param name="outputArbolSalida"> ''' El árbol que se va a cargar con la información ''' que existe en el fichero de texto</param> ''' <param name="nombreDeFichero"> ''' El nombre del fichero en el disco que contiene ''' la información del árbol</param> Public Sub Load( _ ByRef outputArbolSalida As System.Windows.Forms.TreeView, _ ByVal nombreDeFichero As String) Try '---------------------------------------- ' PASO UNO cargar el texto en una lista '---------------------------------------- Dim listaNodos As New List(Of EstructruraNodoSerialiar) listaNodos = AuxLoadCargarLista(nombreDeFichero) '---------------------------------------- ' PASO DOS cargar el arbol a partir de la lista ' busco el nodo raiz (o los nodos) lo grabo ' y despues busco sus hijos '--------------------------------------- Call AuxLoadCargaNodosRaiz(outputArbolSalida, listaNodos) Catch ex As Exception Throw End Try End Sub
El proceso [AuxLoadCargarLista] que se encarga de cargar en una lista genérica la información del disco ya lo hemos visto
El proceso [AuxLoadCargaNodosRaiz] se encarga de recorrer la lista buscando los nodos raíces (aquellos que en su campo [IDPadre =0]. Cuando encuentra uno, se cuelga del árbol, se marca como colgado (grabado=true) y después se buscan en el árbol los nodos hijos que pueda tener
Nota
El proceso de búsqueda de nodos se hace mediante unos bucles que recorren toda la lista de nodos. Para hacer esta búsqueda más eficaz, lo primero que se comprueba es que el nodo no este [Grabado=False], es decir, que el nodo NO se haya colgado del árbol. Si un nodo se cuelga del árbol quiere decir que ya ha sido tratado antes por esta función, y no solo se ha colgado del árbol el nodo, sino que también se han buscado (y tratado) ya todos sus hijos, por lo que estén nodo no hay que volver a tratarlo, y podemos saltárnoslo.
''' <summary> ''' Lee en la lista de nodos los nodos raíces, ''' los cuelga del árbol y a continuación busca ''' los nodos hijos de ese nodo raíz ''' </summary> ''' <param name="outputArbolSalida"> ''' El árbol que se va a cargar con la información ''' que existe en la lista genérica</param> ''' <param name="listaNodos"> ''' La lista genérica que contiene las estructuras ''' con la información de cada nodo</param> Private Sub AuxLoadCargaNodosRaiz( _ ByRef outputArbolSalida As System.Windows.Forms.TreeView, _ ByRef listaNodos As List(Of EstructruraNodoSerialiar)) Dim estrucNodoPadre As EstructruraNodoSerialiar = Nothing '---------------------------------------- 'busco el nodo raíz (o los nodos) lo grabo 'busco y después busco sus hijos '---------------------------------------- outputArbolSalida.Nodes.Clear() Dim nuevoNodoRaiz As TreeNode For indexLista As Integer = 0 To listaNodos.Count - 1 estrucNodoPadre = listaNodos.Item(indexLista) ' si no esta grabado If estrucNodoPadre.Grabado = False Then ' si es un nodo raiz If estrucNodoPadre.IDPadre = 0 Then nuevoNodoRaiz = New TreeNode(estrucNodoPadre.Text) outputArbolSalida.Nodes.Add(nuevoNodoRaiz) #If DEBUG Then ' control de depuracion Debug.Write("Alta Raiz " & estrucNodoPadre.ToString) #End If ' marcarlo como colgado del arbol estrucNodoPadre.Grabado = True ' buscar y grabar sus hijos AuxLoadCargaNodosHijos( _ nuevoNodoRaiz, estrucNodoPadre, listaNodos) End If End If Next End Sub
El proceso [AuxLoadCargaNodosHijos] se encarga de colgar el nodo padre que se recibe del árbol (si aun no está colgado) y a continuación, de forma recursiva, buscar los nodos hijos de este nodo.
''' <summary> ''' Busca los nodos hijos de un nodo en la lista de nodos, ''' cuando encuentra uno, lo cuelga del árbol, ''' lo marca como colgado del árbol, ''' y de forma recursiva busca sus hijos ''' </summary> ''' <param name="nodoPadre"> ''' El objeto nodo padre, es decir el nodo del cual ''' estamos buscando sus hijos</param> ''' <param name="estrucNodoPadre"> ''' La estructura que contiene la información del nodo padre. ''' Se pasa la estructuras porque necesito conocer ''' los ID de los nodos</param> ''' <param name="listaNodos"> ''' La lista genérica que contiene las estructuras ''' con la información de cada nodo</param> Private Sub AuxLoadCargaNodosHijos( _ ByRef nodoPadre As TreeNode, _ ByRef estrucNodoPadre As EstructruraNodoSerialiar, _ ByRef listaNodos As List(Of EstructruraNodoSerialiar)) ' a partir de ese nodo buscar los hijos 'Dim NodoSalida As TreeNode = Nothing For indexLista As Integer = estrucNodoPadre.IDPadre + 1 _ To listaNodos.Count – 1 Dim estrucNodoHijo As EstructruraNodoSerialiar = Nothing estrucNodoHijo = listaNodos.Item(indexLista) ' si no esta grabado Grabarlo como hijo de su padre If estrucNodoHijo.Grabado = False Then If estrucNodoHijo.IDPadre = estrucNodoPadre.ID Then Dim nuevoNodoHijo As New TreeNode(estrucNodoHijo.Text) nodoPadre.Nodes.Add(nuevoNodoHijo) ' control de depuracion #If DEBUG Then Debug.Write("Alta Nodo " & estrucNodoHijo.ToString) #End If ' marcarlo como grabado estrucNodoHijo.Grabado = True ' buscar sus hijos ' llamada recursiva a esta funcion AuxLoadCargaNodosHijos( _ nuevoNodoHijo, estrucNodoHijo, listaNodos) End If End If Next End Sub
El uso de llamadas recursivas a las funciones permite, con el uso de muy pocas líneas de código, crear los nodos del árbol, sin conocer la profundidad que pueda llegar a tener el contenido del árbol
Puedes descargarte un fichero ZIP que contiene el código descrito en este documento. Esta escrito en (.NET Framework 3.5) - Visual Basic .NET (versión 9.0) - 2010
© 1997 - - La Güeb de Joaquín | |||||
Joaquin Medina Serrano
|
|||||
|