Un datasource hierarchique pour binder vos treeview / menu
ASP.net 2.0 possède les contrôles Menu et Treeview. Ces contrôles sont généralement méconnus par les développeurs ASP.net pour plusieurs raisons :
- On a rarement besoin de ce genre de contrôle ;
- Les exemples d'utilisations sont toujours simples, association avec un XmlDataSource ou SiteMapDataSource, ce qui correspond rarement à la réalité, dès que l'on veut faire quelque chose de précis avec ces contrôles cela se complique et les exemples sont rares;
- Le code HTML généré est horrible.
Au niveau du code HTML, les CSS Friendly Control Adapters améliorent grandement les choses, la démo du contrôle treeview est particulièrement bluffante : le code HTML généré passe de 49ko à 18ko tout simplement en utilisant HTML correctement avec une bonne sémantique.
Pour le second point, les exemples que l'on trouve utilisent généralement un SiteMapDataSource : du coup on se retrouve avec un exemple du genre :
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
Mais dans la vraie vie, on a des besoins bien plus complexes. Comment faire pour, par exemple, afficher une arborescence de dossier ? La plupart des exemples vont rajouter des noeuds directement au niveau de la propriété Nodes du treeview :
' sample from : http://aspnet.4guysfromrolla.com/articles/083006-1.aspx
Private Const VirtualImageRoot = "~/Images/Public/"
Private Sub PopulateTree()
'Populate the tree based on the subfolders of the specified VirtualImageRoot
Dim rootFolder As New DirectoryInfo(Server.MapPath(VirtualImageRoot))
Dim root As TreeNode = AddNodeAndDescendents(rootFolder, Nothing)
'Add the root to the TreeView
PictureTree.Nodes.Add(root)
End Sub
Private Function AddNodeAndDescendents(ByVal folder As DirectoryInfo, ByVal parentNode As TreeNode) As TreeNode
'Add the TreeNode, displaying the folder's name and storing the full path to the folder as the value...
Dim virtualFolderPath As String
If parentNode Is Nothing Then
virtualFolderPath = VirtualImageRoot
Else
virtualFolderPath = parentNode.Value & folder.Name & "/"
End If
Dim node As New TreeNode(folder.Name, virtualFolderPath)
'Recurse through this folder's subfolders
Dim subFolders As DirectoryInfo() = folder.GetDirectories()
For Each subFolder As DirectoryInfo In subFolders
Dim child As TreeNode = AddNodeAndDescendents(subFolder, node)
node.ChildNodes.Add(child)
Next
Return node 'Return the new TreeNode
End Function
Cette solution ne me convient pas ! Ce code n'a rien à faire dans la couche d'affichage, il devrait être dans la couche logique. Le plus simple serait de faire comme les classiques contrôles, c'est à dire de pouvoir assigner un objet à la propriété DataSource du treeview. Mais que lui passer comme DataSource ? Il faudrait une datasource hiérarchique ! C'est exactement le but des interfaces IHierarchicalEnumerable et IHierarchyData.
Voici d'ailleurs comment elles sont définies :
public interface IHierarchicalEnumerable : IEnumerable
{
// Methods
IHierarchyData GetHierarchyData(object enumeratedItem);
}
public interface IHierarchyData
{
// Methods
IHierarchicalEnumerable GetChildren();
IHierarchyData GetParent();
// Properties
bool HasChildren { get; }
object Item { get; }
string Path { get; }
string Type { get; }
}
Un objet implémentant l'interface IHierarchicalEnumerable doit retourner des objets implémentant IHierarchyData qui eux même peuvent returner des enfants de type IHierarchicalEnumerable.
On peut alors binder notre treeview avec notre objet implémentant IHierarchicalEnumerable ! Vous pouvez retrouver sur aspfr.com un exemple d'utilisation de cette interface qui vous permettra d'afficher le contenu d'un répertoire dans un treeview :
L'interface IHierarchicalEnumerable - bindez vos menu/treeview avec votre propre source de donnée
Cet exemple vous permettra d'écrire :
protected void Page_Load(object sender, EventArgs e)
{
// on bind notre treeview avec les fichiers/dossier contenu dans le dossier d:/www/
tv1.DataSource = new FileSystem(@"d:\www\", true);
tv1.DataBind();
}
<asp:TreeView ID="tv1" runat="server" ImageSet="XPFileExplorer" NodeIndent="15"
ExpandDepth="1" EnableViewState="false">
<ParentNodeStyle Font-Bold="False" />
<HoverNodeStyle Font-Underline="True" ForeColor="#6666AA" />
<SelectedNodeStyle BackColor="#B5B5B5" Font-Underline="False" HorizontalPadding="0px"
VerticalPadding="0px" />
<NodeStyle Font-Names="Tahoma" Font-Size="8pt" ForeColor="Black" HorizontalPadding="2px"
NodeSpacing="0px" VerticalPadding="2px" />
<DataBindings>
<asp:TreeNodeBinding TextField="Name" />
</DataBindings>
</asp:TreeView>
Ou alors en utilisant directement FileSystemDataSource, contrôle héritant de HierarchicalDataSourceControl
<asp:TreeView ID="tv1" runat="server" ImageSet="XPFileExplorer" NodeIndent="15"
ExpandDepth="1" DataSourceID="fsds1" EnableViewState="false">
<ParentNodeStyle Font-Bold="False" />
<HoverNodeStyle Font-Underline="True" ForeColor="#6666AA" />
<SelectedNodeStyle BackColor="#B5B5B5" Font-Underline="False" HorizontalPadding="0px"
VerticalPadding="0px" />
<NodeStyle Font-Names="Tahoma" Font-Size="8pt" ForeColor="Black" HorizontalPadding="2px"
NodeSpacing="0px" VerticalPadding="2px" />
<DataBindings>
<asp:TreeNodeBinding TextField="Name" />
</DataBindings>
</asp:TreeView>
<test:FileSystemDataSource runat="server" id="fsds1" rootPath="d:/www/" />
Vous pouvez retrouver un autre exemple d'utilisation de l'interface IHierarchicalEnumerable ici : Create a Hierarchical Data Source for the TreeView and Menu control.