Wpf - Control TreeView Directorios

Descripción general

Este apunte estudia el control Wpf TreeView para cargar dinámicamente los directorios de un disco.

[TOC] Tabla de Contenidos


↑↑↑

Introducción

Voy a usar un control Wpf, TreeView para cargar dinámicamente los directorios de un disco. Hay que emplear el evento [changed] y estudiar la forma de devolver el valor de [selectedPath], o lo que es lo mismo la cadena con el directorio completo desde el directorio raíz cuando se selecciona un directorio dentro del árbol.


↑↑↑

Xaml

El XAML es muy sencillo y sólo un detalle interesante es el momento: La forma en que suscribimos el evento Ampliado de TreeViewItem. Tenga en cuenta que este es el TreeViewItem y no el TreeView, pero debido a que el evento se propaga hacia arriba, somos capaces de simplemente capturar en un solo lugar para todo el TreeView, en lugar de tener que suscribirse a él para cada elemento lo añadimos al árbol. Este evento se llama cada vez que un elemento se expande, lo que tenemos que ser conscientes de que cargar sus elementos secundarios bajo demanda.

<UserControl x:Class="UC_TreeViewDirectory"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:WPF_Explorer_Tree_Traduccion"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <Grid>
     <TreeView x:Name="trvArbolDirectoriosDisco"
                  Background="AliceBlue"
                  TreeViewItem.Expanded="TreeViewItem_Expanded"
                  SelectedItemChanged="trvArbolDirectoriosDisco_SelectedItemChanged"
                  Margin="10"
                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                  HorizontalContentAlignment="Left"/>
    </Grid>
    
</UserControl>


↑↑↑

Imagen de como queda el control

Código subyacente

En código subyacente, comenzamos añadiendo al control TreeView (en el constructor del control, justo después de la inicialización general,) las unidades de disco (o CD-ROM que tiene el equipo.

La función [CreateTreeItem] se encarga de crear un nodo [TreeViewItem] y carga en el nuevo nodo la información del nodo. Observa dos cosas. Que no se cuelga del árbol, solo se crea el nodo, y que en la propiedad [Tag] guardo, normalmente el nombre objeto [DirectoryInfo] que contiene el directorio que voy a almacenar [Pe. C:\uno\dos\tres], y (solo una por disco) un objeto [DriveInfo] que contiene el directorio raíz de la unidad de disco [Pe. C:\]. El valor de esta propiedad se usa para recuperar el valor [SelectedItem] o lo que es lo mismo, una cadena con la ruta completa desde el directorio raíz del directorio seleccionado en el árbol. La función [CreateTreeItem] también añade a cada nodo un nodo hijo del tipo [_NodoNothing] que es en realidad un nodo [Object] con valor [Nothing]

Para añadir el nodo al árbol (TreeView), se utiliza el evento Ampliado [TreeViewItem.Expanded]. Este evento se genera cada vez que un elemento TreeView se expande, así que lo primero que hacemos es comprobar si en ese nodo ya se han cargado los directorios del disco, para ello miramos si los elementos hijos contienen un solo nodo y además es un objeto del tipo [_NodoNothing], si es así, significa que esta vaciao, que ahora debemos cargar el contenido real y sustituir el elemento marcador [_NodoNothing], con la lista de directorios hijos del nodo.

Observa que el bucle donde añadimos cada carpeta hija está en un bloque [try – catch], ya que algunos directorios, (por lo general) por razones de seguridad, pueden no ser accesibles. De esta forma podemos interceptar la excepción y reflejar esto en la interfaz de una manera u otra.

Imports System.IO

''' <summary>
'''  Control de usuario que muestra en forma de árbol los directorios de un disco
''' </summary>
<Serializable>
Public Class UC_TreeViewDirectory
    Implements System.ComponentModel.INotifyPropertyChanged


#Region "Evento PropertyChanged [Versión 2014-02-13]"

    '-----------------------------------------------------------------------
    ' Declaración del evento usando un EventHandler genérico 
    ' !! Observación !!! Se produce un error al serializar eventos Genéricos
    <NonSerializedAttribute()>
    Public Event PropertyChanged As _
              System.ComponentModel.PropertyChangedEventHandler _
              Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged


    '----------------------------------------------------------------------
    ''' <summary>Función que dispara el evento [PropertyChanged]</summary>
    Protected Overloads Sub OnPropertyChanged(ByVal e As ComponentModel.PropertyChangedEventArgs)
        If e Is Nothing Then
            Throw New ArgumentException("No se admiten valores Nothing")
        End If
        Call OnPropertyChanged(e.PropertyName)
    End Sub

    '----------------------------------------------------------------------
    ''' <summary>Función que dispara el evento [PropertyChanged]</summary>
    ''' <param name=nombreDeLaPropiedadChanged">"
    '''  <para>Se espera una cadena de texto con uno de los siguientes valores:</para>
    '''  <para> a) Una cadena vacía, en este caso la función averiguara el 
    '''            nombre de la propiedad a través de su 
    '''            atributo [CallerMemberName]]</para>
    '''  <para> b) Una cadena de texto con el nombre de 
    '''             la propiedad (Ejemplo, Apellidos") </para>"
    ''' </param>
    ''' <remarks>
    ''' <code>
    '''     system.componentmodel.inotifypropertychanged(VS.95).aspx
    '''          http://msdn.microsoft.com/es-es/library/
    '''     Bibliografía [CallerMemberName>]
    '''          http://msdn.microsoft.com/es-es/library/hh534540.aspx
    ''' </code>
    '''</remarks>
    Protected Overloads Sub OnPropertyChanged(
        <System.Runtime.CompilerServices.CallerMemberNameAttribute>
                   Optional ByVal nombreDeLaPropiedadChanged As String = Nothing)
        ' ----------------------------------
        ' Evitar problemas tontos
        If String.IsNullOrWhiteSpace(nombreDeLaPropiedadChanged) = False Then
            ' Disparar el evento
            RaiseEvent PropertyChanged(
                      Me,
                      New System.ComponentModel.PropertyChangedEventArgs(nombreDeLaPropiedadChanged))
        End If
    End Sub
    '
    ' /Eof - PropertyChanged
    '-----------------------------------------------------------------------

#End Region


    ''' <summary>
    '''  representa a un nodo [TreeViewItem] nulo 
    '''  Se usa para indicar que este nodo (del árbol) no tiene nodos hijos
    ''' </summary>
    Private _NodoNothing As Object = Nothing

    ''' <summary>
    ''' Contiene la cadena completa desde el nodo raíz del nodo seleccionado del árbol
    ''' </summary>
    Private _selectedItem As String = String.Empty

    ''' <summary>
    ''' Contiene la cadena completa desde el nodo raíz del nodo seleccionado del árbol
    ''' </summary>
    ''' <remarks>Utiliza [INotifyPropertyChanged] para que funcione [binding] en una ventana</remarks>
    Public Property ZSelectedItem() As String
        Get
            Return _selectedItem
        End Get
        Set(value As String)
            If String.Equals(_selectedItem, value) = False Then
                _selectedItem = value
                ' Disparar el evento [Changed]
                Call OnPropertyChanged()
            End If
        End Set
    End Property



    ''' <summary>
    ''' Constructor, se cargan directamente las unidades de disco
    ''' </summary>
    Public Sub New()
        InitializeComponent()
        Dim drives As DriveInfo() = DriveInfo.GetDrives()
        For Each driveInfo__1 As DriveInfo In drives
            trvArbolDirectoriosDisco.Items.Add(CreateTreeItem(driveInfo__1))
            ' aquí no uso añadir hijos [AddTreeItem], porque estoy añadiendo los nodos root del árbol
        Next
    End Sub


    ''' <summary>
    '''    cuando se expande un nodo se dispara el evento expanded
    '''    aquí se cargan los nodos hijos (los directorios hijos)
    '''    de ese directorio expandido
    ''' </summary>
    Private Sub TreeViewItem_Expanded(sender As Object, e As RoutedEventArgs)

        Dim itemPadre As TreeViewItem = TryCast(e.Source, TreeViewItem)
        If (itemPadre.Items.Count = 1) AndAlso (itemPadre.Items(0) Is _NodoNothing) Then
            itemPadre.Items.Clear()

            Dim diretorioExpandido As DirectoryInfo = Nothing

            If TypeOf itemPadre.Tag Is DriveInfo Then
                diretorioExpandido = TryCast(itemPadre.Tag, DriveInfo).RootDirectory
            End If

            If TypeOf itemPadre.Tag Is DirectoryInfo Then
                diretorioExpandido = TryCast(itemPadre.Tag, DirectoryInfo)
            End If

            Try
                For Each nodoHijo As DirectoryInfo In diretorioExpandido.GetDirectories()
                    Call AddTreeItem(itemPadre, nodoHijo)
                Next
            Catch ex As Exception
                ' no hacer nada
                ' en este caso significa que no hay acceso a es directorio
                ' normalmente por temas de seguridad de acceso
            End Try

        End If
    End Sub


    ''' <summary>
    ''' Auxiliar que da de alta un nodo hijo
    ''' </summary>
    Private Sub AddTreeItem(ByVal padre As TreeViewItem, ByVal nodoHijo As Object)

        ' esta función no realiza control de errores  ni de parámetros de entrada
        ' porque es responsabilidad de la función llamadora
        '-------------------------------
        If TypeOf (nodoHijo) Is DirectoryInfo Then
            ' A) Tratar el caso de Directorios que es lo mas normal
            ' Excluir directorios ocultos
            Dim objDirInfo As DirectoryInfo = CType(nodoHijo, DirectoryInfo)
            If (objDirInfo.Attributes And FileAttributes.Hidden) <> FileAttributes.Hidden Then
                padre.Items.Add(CreateTreeItem(objDirInfo))
            End If
        Else
            'B) Tratar el caso de las unidades de disco 
            If TypeOf (nodoHijo) Is DriveInfo Then
                padre.Items.Add(CreateTreeItem(nodoHijo))
            End If
        End If
    End Sub

   
    ''' <summary>
    '''  auxiliar crea un nodo hijo
    ''' </summary>
    Private Function CreateTreeItem(ByVal directorio As Object) As TreeViewItem
        '-------------------------------------
        ' Una observación Importante
        ' A) En la propiedad [Tag] guardo :
        '  - Normalmente el nombre objeto [DirectoryInfo] que contiene el directorio 
        '    que voy a almacenar [Pe. C:\uno\dos\tres ]
        '  - pocas veces (solo una por disco) un objeto [DriveInfo] que contiene 
        '    el directorio raíz de la unidad de disco [Pe.  C:\]
        ' B) El valor de esta propiedad se usa para recuperar el valor [SelectedItem] o lo que es lo mismo,
        '    una cadena con la ruta completa desde el directorio raíz del directorio seleccionado en el árbol.
        '-------------------------------------
        Dim item As New TreeViewItem()
        item.Header = directorio.ToString()
        item.Tag = directorio
        item.Items.Add(_NodoNothing)
        Return item
    End Function


    ''' <summary>
    '''   Evento selected item changed
    '''   se dispara cuando cambia el nodo seleccionado
    ''' </summary>
    Private Sub trvArbolDirectoriosDisco_SelectedItemChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Object))

        Try
            Dim arbol As TreeView = DirectCast(sender, TreeView)
            Dim nodoSeleccionado As TreeViewItem = DirectCast(arbol.SelectedItem, TreeViewItem)

            ZSelectedItem = String.Empty

            If nodoSeleccionado Is Nothing Then
                Return
            End If
        
            '-------------------------------------
            ' Una observación Importante
            ' A) En la propiedad [Tag] guardo :
            '  - Normalmente el nombre objeto [DirectoryInfo] que contiene el directorio 
            '    que voy a almacenar [Pe. C:\uno\dos\tres ]
            '  - pocas veces (solo una por disco) un objeto [DriveInfo] que contiene 
            '    el directorio raíz de la unidad de disco [Pe.  C:\]
            ' B) El valor de esta propiedad se usa para recuperar el valor [SelectedItem] o lo que es lo mismo,
            '    una cadena con la ruta completa desde el directorio raíz del directorio seleccionado en el árbol.
            '-------------------------------------
            If TypeOf (nodoSeleccionado.Tag) Is DirectoryInfo Then
                ' A) Tratar el caso de Directorios que es lo mas normal
                Dim di As DirectoryInfo = CType(nodoSeleccionado.Tag, DirectoryInfo)
                ZSelectedItem = di.FullName
            Else
                'B) Tratar el caso de las unidades de disco 
                If TypeOf (nodoSeleccionado.Tag) Is DriveInfo Then
                    Dim di As DriveInfo = CType(nodoSeleccionado.Tag, DriveInfo)
                    ZSelectedItem = di.Name
                End If
            End If

        Catch ex As Exception
            ' no trato el error
            ZSelectedItem = String.Empty
        End Try

    End Sub

End Class

↑↑↑

A.2.Enlaces

[Para saber mas]
[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]
© 1997 - - La Güeb de Joaquín
Joaquín Medina Serrano
Ésta página es española

Codificación
Fecha de creación
Última actualización
[HTML5 Desarrollado usando CSS3]