Développez votre paradis de Maui avec le .NET MAUI TreeView

Êtes-vous à la recherche d’un moyen pratique de présenter des informations dans un format plat ou hiérarchique, garantissant un rendu rapide des éléments, une personnalisation flexible et la possibilité d’étendre ou de réduire les nœuds de données selon vos besoins ? Qu’il s’agisse de structures de fichiers complexes ou même d’un moyen de faciliter la navigation dans les applications mobiles, .NET MAUI TreeView peut vous faire gagner beaucoup de temps.
Cela fait un moment que je n’ai pas démonté mon sapin de Noël, mais ce faisant, j’ai pensé qu’il fallait vous présenter une nouvelle espèce qui a fait son apparition sur le marché .NET MAUI l’été dernier. Prêt à rencontrer le Arborescence .NET MAUI? Croyez-moi, démarrer avec cette vue arborescente est beaucoup plus facile que de démonter toutes les décorations de l’autre arbre. 😅 Suivez-moi.
Attentes
À quoi s’attendre du TreeView de Progress Interface utilisateur Telerik pour .NET MAUI? Pour le dire en seulement deux mots : la richesse des fonctionnalités. Liaison de données, virtualisation de l’interface utilisateur, chargement à la demande, expansion/réduction des nœuds, sélection simple/multiple, prise en charge des cases à cocher, commandes, API de style, modèles d’éléments personnalisés, etc.
Qu’attendre de cet article de blog ? Pour examiner le .NET MAUI TreeView ensemble, mais procédez étape par étape. Alors, choisissons un point de départ simple et construisons à partir de là.
Point de départ
En supposant que vous disposez déjà d’une nouvelle application .NET MAUI et que le Telerik.UI.pour.Maui NuGetapporter le composant TreeView à cette application est aussi bref que ce qui suit.
XAML :
<telerik:RadTreeView x:Name="treeView" />
C# :
RadTreeView treeView = new RadTreeView();
Facile-arbre! Cependant, il ne s’agit que de racines : pas de branches, pas de feuilles. 😅
Et tout cela parce que j’ai promis de procéder étape par étape. Passons à la section suivante où nous allons lentement construire pour atteindre ce résultat :
Après tout, c’est l’un des scénarios les plus courants auxquels nous sommes habitués dans notre vie quotidienne : la boîte de réception de votre client de messagerie préféré, les fichiers de projet de votre IDE préféré ou tout autre type d’explorateur de fichiers.
Dévoilement des capacités
Tout comme je l’ai mentionné dans la section précédente, un arbre sans branches ni feuilles n’a pas beaucoup de sens. Je propose donc d’apporter quelques données aux nôtres et de les faire fleurir.
Liaison de données et virtualisation de l’interface utilisateur
Le composant TreeView de Telerik UI pour .NET MAUI est spécialement conçu pour simplifier liaison de donnéesque la structure des données soit linéaire ou hiérarchique.
De plus, son Virtualisation de l’interface utilisateur permet une visualisation plus rapide des données et un affichage sans effort des éléments TreeView lors du défilement, quelle que soit la taille de l’ensemble de données.
En regardant la capture d’écran ci-dessus (notre objectif 🎯), vous remarquerez peut-être que j’ai choisi une structure de données hiérarchique. J’ai également créé deux modèles décrivant les nœuds File et Folder de notre arborescence et le ViewModel qui contiendra la collection de dossiers. Découvrez-les ci-dessous.
Modèle de nœud de fichier :
public class FileNode : NotifyPropertyChangedBase
{
private string name;
private string icon;
private string dateModified;
private string size;
public string Name
{
get => this.name;
set => this.UpdateValue(ref this.name, value);
}
public string Icon
{
get => this.icon;
set => this.UpdateValue(ref this.icon, value);
}
public string DateModified
{
get => this.dateModified;
set => this.UpdateValue(ref this.dateModified, value);
}
public string Size
{
get => this.size;
set => this.UpdateValue(ref this.size, value);
}
internal FolderNode Parent { get; set; }
}
Modèle de nœud de dossier :
public class FolderNode : FileNode
{
public FolderNode()
{
this.Children = new ObservableCollection<FileNode>();
}
public ObservableCollection<FileNode> Children { get; private set; }
}
Et le ViewModel :
public class MainPageViewModel : NotifyPropertyChangedBase
{
private ObservableCollection<FolderNode> folders;
public MainPageViewModel()
{
this.folders = this.GetFolderSource();
}
public ObservableCollection<FolderNode> Folders
{
get { return this.folders; }
set { this.UpdateValue(ref this.folders, value); }
}
private ObservableCollection<FolderNode> GetFolderSource()
{
FolderNode desktopFolder = new FolderNode() { Name = "Desktop", Icon = "\ue82a" };
desktopFolder.Children.Add(new FileNode() { Name = "Blog post draft.docx", Icon = "\ue898", DateModified = "Yesterday, 17:20", Size = "907 KB", Parent = desktopFolder });
desktopFolder.Children.Add(new FileNode() { Name = "Screenshot 20230805.png", Icon = "\ue852", DateModified = "Yesterday, 11:10", Size = "920 KB", Parent = desktopFolder });
desktopFolder.Children.Add(new FileNode() { Name = "Screenshot 20230806.png", Icon = "\ue852", DateModified = "Yesterday, 12:13", Size = "805 KB", Parent = desktopFolder });
desktopFolder.Children.Add(new FileNode() { Name = "Screenshot 20230807.png", Icon = "\ue852", DateModified = "Yesterday, 11:48", Size = "667 KB", Parent = desktopFolder });
desktopFolder.Children.Add(new FileNode() { Name = "Screen Recording 20230721.mov", Icon = "\ue874", DateModified = "Yesterday, 11:54", Size = "735 KB", Parent = desktopFolder });
FolderNode documentsFolder = new FolderNode() { Name = "Documents", Icon = "\ue82a" };
documentsFolder.Children.Add(new FileNode() { Name = "Document.docx", Icon = "\ue898", DateModified = "Yesterday, 12:02", Size = "722 KB", Parent = documentsFolder });
documentsFolder.Children.Add(new FileNode() { Name = "Book.xlsx", Icon = "\ue896", DateModified = "Today, 09:08", Size = "901 KB", Parent = documentsFolder });
documentsFolder.Children.Add(new FileNode() { Name = "Presentation.pptx", Icon = "\ue897", DateModified = "Today, 10:25", Size = "2.1 MB", Parent = documentsFolder });
documentsFolder.Children.Add(new FileNode() { Name = "Invoice_details.pdf", Icon = "\ue899", DateModified = "Yesterday, 18:40", Size = "310 KB", Parent = documentsFolder });
FolderNode resourcesFolder = new FolderNode() { Name = "Resources", Icon = "\ue82a" };
resourcesFolder.Children.Add(new FolderNode() { Name = "Fonts", Icon = "\ue82a", Parent = resourcesFolder });
FolderNode imagesFolder = new FolderNode() { Name = "Images", Icon = "\ue82a", Parent = resourcesFolder };
imagesFolder.Children.Add(new FileNode() { Name = "american_pancakes.png", Icon = "\ue852", DateModified = "Today, 09:01", Size = "20 KB", Parent = imagesFolder });
imagesFolder.Children.Add(new FileNode() { Name = "belgian_chocolate.png", Icon = "\ue852", DateModified = "Today, 09:02", Size = "20 bytes", Parent = imagesFolder });
imagesFolder.Children.Add(new FileNode() { Name = "blueberry_waffle.png", Icon = "\ue852", DateModified = "Today, 09:03", Size = "20 KB", Parent = imagesFolder });
imagesFolder.Children.Add(new FileNode() { Name = "tiramisu.png", Icon = "\ue852", DateModified = "Today, 09:04", Size = "20 KB", Parent = imagesFolder });
resourcesFolder.Children.Add(imagesFolder);
resourcesFolder.Children.Add(new FileNode() { Name = "appicon.svg", Icon = "\ue852", DateModified = "Yesterday, 10:32", Size = "513 bytes", Parent = resourcesFolder });
resourcesFolder.Children.Add(new FileNode() { Name = "logo.svg", Icon = "\ue852", DateModified = "Yesterday, 10:38", Size = "790 bytes", Parent = resourcesFolder });
FolderNode videosFolder = new FolderNode() { Name = "Videos", Icon = "\ue82a", Parent = resourcesFolder };
FolderNode mauiFolder = new FolderNode() { Name = "MAUI", Icon = "\ue82a", Parent = videosFolder };
mauiFolder.Children.Add(new FileNode() { Name = "Getting Started with MAUI.mov", Icon = "\ue874", DateModified = "31 March 2023, 15:04", Size = "324 MB", Parent = mauiFolder });
mauiFolder.Children.Add(new FileNode() { Name = "MAUI Webinar.mov", Icon = "\ue874", DateModified = "20 October 2022, 16:20", Size = "737 MB", Parent = mauiFolder });
mauiFolder.Children.Add(new FileNode() { Name = "Building a Newsletter App.mov", Icon = "\ue874", DateModified = "6 September 2022, 14:34", Size = "922 MB", Parent = mauiFolder });
videosFolder.Children.Add(mauiFolder);
FolderNode recordingsFolder = new FolderNode() { Name = "Recordings", Icon = "\ue82a", Parent = videosFolder };
recordingsFolder.Children.Add(new FileNode() { Name = "Screen Recording 20220721.mov", Icon = "\ue874", DateModified = "21 July 2022, 17:20", Size = "24,8 MB", Parent = recordingsFolder });
recordingsFolder.Children.Add(new FileNode() { Name = "Screen Recording 20220807.mov", Icon = "\ue874", DateModified = "7 August 2022, 14:34", Size = "19,3 MB", Parent = recordingsFolder });
videosFolder.Children.Add(recordingsFolder);
return new ObservableCollection<FolderNode>()
{
desktopFolder,
documentsFolder,
resourcesFolder,
videosFolder
};
}
}
Depuis que j’ai décidé de travailler avec une structure de données hiérarchique, il me reste encore une chose à faire. Je dois appliquer un TreeViewDescriptor qui fournit simplement une description d’une source d’éléments hiérarchiques. Dans notre cas, je dois définir deux descripteurs pour chaque type de donnée (fichier et dossier) via le paramètre du descripteur Type de cible et spécifiez le nom de la propriété qui sera affichée pour l’élément de données respectif via le descripteur AfficherCheminMembre.
Ce qui reste à notre arbre pour faire pousser des branches et des feuilles, c’est d’y lier la collection Folders :
<telerik:RadTreeView x:Name="treeView" ItemsSource="{Binding Folders}">
<telerik:TreeViewDescriptor TargetType="{x:Type local:FolderNode}"
ItemsSourcePath="Children"
DisplayMemberPath="Name" />
<telerik:TreeViewDescriptor TargetType="{x:Type local:FileNode}"
DisplayMemberPath="Name" />
</telerik:RadTreeView>
Devons-nous vérifier nos progrès ?
Je sais, je sais, cet explorateur de fichiers n’est pas encore génial, mais nous allons changer cela dans la section suivante.
Styles et modèles
Le .NET MAUI TreeView introduit une fonctionnalité de style conditionnel qui permet aux utilisateurs d’appliquer des modes à des articles individuels en fonction de conditions spécifiques. Cette fonctionnalité permet une personnalisation dynamique, permettant à chaque élément d’avoir sa propre apparence unique basée sur des critères prédéfinis. Il existe un superbe exemple exécutable démontrant le Sélecteur de style d’élément du TreeView dans le Application de démonstration du navigateur SDK. Ne manquez pas cette occasion, assurez-vous de la vérifier.
Le composant d’arborescence expose également un Modèle d’élément propriété à l’aide de laquelle l’apparence de chaque nœud TreeView peut être personnalisée. Cela vous permet d’insérer sans effort des icônes spécifiques dans les nœuds, par exemple, améliorant ainsi les options de personnalisation pour votre scénario spécifique.
Ajoutons un ItemTemplate pour nos fichiers et un pour nos dossiers.
<DataTemplate x:Key="FileTemplate">
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="8"
RowDefinitions="Auto, Auto"
Padding="0, 4">
<Label Grid.RowSpan="2"
Text="{Binding Icon}"
TextColor="{Binding Name, Converter={StaticResource FileTypeToIconColorConverter}}"
FontFamily="TelerikFontExamples"
FontSize="24"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontSize="14"
VerticalOptions="Center" />
<HorizontalStackLayout Grid.Row="1"
Grid.Column="1"
VerticalOptions="Center"
Opacity="0.38">
<Label Text="{Binding DateModified}"
FontSize="12" />
<Label Text=""
FontFamily="TelerikFontExamples"
FontSize="12"
VerticalTextAlignment="Center" />
<Label Text="{Binding Size}"
FontSize="12" />
</HorizontalStackLayout>
</Grid>
</DataTemplate>
<DataTemplate x:Key="FolderTemplate">
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="4">
<Label Text="{Binding Icon}"
TextColor="{Binding TextColor, Source={x:RelativeSource AncestorType={x:Type telerik:TreeViewItemView}}}"
FontFamily="TelerikFontExamples"
FontSize="16"
VerticalTextAlignment="Center" />
<Label Grid.Column="1"
FontSize="16"
Text="{Binding Name}"
VerticalTextAlignment="Center"
Margin="{OnPlatform Default=0, MacCatalyst="0, 2, 0, 0", WinUI='0, -4, 0, 0'}" />
</Grid>
</DataTemplate>
Maintenant, utilisons-les dans nos TreeViewDescriptors :
<telerik:RadTreeView x:Name="treeView" ItemsSource="{Binding Folders}">
<telerik:TreeViewDescriptor TargetType="{x:Type local:FolderNode}"
ItemsSourcePath="Children"
ItemTemplate="{StaticResource FolderTemplate}" />
<telerik:TreeViewDescriptor TargetType="{x:Type local:FileNode}"
ItemTemplate="{StaticResource FileTemplate}" />
</telerik:RadTreeView>
Je peux vous garantir que nous avons obtenu le résultat dès la toute première image de cet article de blog, mais laissez-moi vous le prouver en exécutant également notre exemple sur un appareil mobile (Android à gauche, iOS à droite) :
Beau. Montons un peu plus de niveau.
Sélection et cases à cocher
En fonction de votre scénario d’entreprise, sélection d’un ou plusieurs nœuds peut être autorisé. Le nôtre est simple et ne doit pas nécessairement permettre une sélection multiple, mais nous pouvons l’activer simplement en réglant le Mode de selection propriété de notre TreeView à Plusieurs. Ou nous pouvons également désactiver la sélection en définissant la même propriété sur Aucun.
Vous pouvez également afficher Case à cocher éléments et permettent la sélection d’éléments spécifiques à partir de ItemsSource. C’est particulièrement intéressant, car les éléments sélectionnés sont automatiquement ajoutés à la collection CheckedItems, ce qui donne la liberté d’effectuer diverses actions, telles que la sélection ou la manipulation de ces éléments à votre guise.
Curieux d’essayer quelque chose avec moi ? Je dis que nous affichons ces cases à cocher et créons un nouveau dossier avec les éléments cochés en cliquant sur un bouton. Pour que les cases à cocher s’affichent, nous devons définir le Mode CheckBox propriété:
<telerik:RadTreeView x:Name="treeView"
ItemsSource="{Binding Folders}"
CheckBoxMode="Recursive">
<telerik:TreeViewDescriptor TargetType="{x:Type local:FolderNode}"
ItemsSourcePath="Children"
ItemTemplate="{StaticResource FolderTemplate}" />
<telerik:TreeViewDescriptor TargetType="{x:Type local:FileNode}"
ItemTemplate="{StaticResource FileTemplate}" />
</telerik:RadTreeView>
Je l’ai défini sur récursif car je souhaite que mon nœud de dossier parent propage son état vérifié aux nœuds de fichiers enfants. La prochaine chose que je veux est un bouton qui sera chargé de créer un dossier avec les éléments cochés (qu’il s’agisse de fichiers ou de dossiers entiers).
<Grid>
<telerik:RadTreeView x:Name="treeView"
ItemsSource="{Binding Folders}"
CheckBoxMode="Recursive">
<telerik:TreeViewDescriptor TargetType="{x:Type local:FolderNode}"
ItemsSourcePath="Children"
ItemTemplate="{StaticResource FolderTemplate}" />
<telerik:TreeViewDescriptor TargetType="{x:Type local:FileNode}"
ItemTemplate="{StaticResource FileTemplate}" />
</telerik:RadTreeView>
<telerik:RadButton x:Name="createFolderWithSelectionBtn"
Style="{StaticResource FABStyle}"
Command="{Binding CreateFolderWithSelectionCommand}"
VerticalOptions="End"
HorizontalOptions="End"
Margin="0, 0, 16, 16" />
</Grid>
Jusqu’ici, tout va bien, mais il nous faut un peu plus que cela. Respirez brièvement et passez à la section suivante, où nous ferons en sorte que tout fonctionne.
Commandes
Comme vous pouvez le deviner, nous devons ajouter des options personnalisées pour notre bouton afin de créer de nouveaux dossiers avec les éléments de l’arborescence cochés. Nous pouvons volontiers le faire en tirant parti de prise en charge des commandes de l’interface utilisateur Telerik pour .NET MAUI TreeView. La liste de ces éléments comprend l’expansion, la réduction, la vérification, la décoche et le défilement.
Dans notre exemple, nous profiterons de ItemCheckedCommand, ItemUncheckedCommand et, bien sûr, une commande personnalisée pour créer un dossier avec la sélection des nœuds. Améliorons le MainPageViewModel (la méthode GetFoldersSource sera ignorée car il n’y a aucun changement) :
public class MainPageViewModel : NotifyPropertyChangedBase
{
private ObservableCollection<FolderNode> folders;
private FolderNode newFolderItem;
private int counter = 1;
private readonly Command createFolderWithSelectionCommand;
private readonly Command itemCheckedCommand;
private readonly Command itemUncheckedCommand;
public MainPageViewModel()
{
this.folders = this.GetFolderSource();
this.newFolderItem = new FolderNode() { Name = "New folder", Icon = "\ue829" };
this.createFolderWithSelectionCommand = new Command(this.CreateFolderWithSelection, this.CanCreateFolderWithSelection);
this.itemCheckedCommand = new Command(this.ItemChecked);
this.itemUncheckedCommand = new Command(this.ItemUnchecked);
}
public ObservableCollection<FolderNode> Folders
{
get { return this.folders; }
set { this.UpdateValue(ref this.folders, value); }
}
public ICommand CreateFolderWithSelectionCommand => this.createFolderWithSelectionCommand;
public ICommand ItemCheckedCommand => this.itemCheckedCommand;
public ICommand ItemUncheckedCommand => this.itemUncheckedCommand;
private ObservableCollection<FolderNode> GetFolderSource()
{
//Skipped this part as there is no change
}
private void CreateFolderWithSelection(object obj)
{
foreach (var item in this.newFolderItem.Children)
{
if (item is FolderNode folderNode)
{
if (folderNode.Parent != null)
{
int parentFolderIndex = this.Folders.IndexOf(folderNode.Parent);
this.Folders[parentFolderIndex]?.Children.Remove(folderNode);
}
else
{
this.Folders.Remove(folderNode);
}
}
else if (item is FileNode fileNode)
{
int parentFolderIndex = this.Folders.IndexOf(fileNode.Parent);
this.Folders[parentFolderIndex]?.Children.Remove(fileNode);
}
}
this.Folders.Insert(0, this.newFolderItem);
this.counter++;
this.newFolderItem = new FolderNode() { Name = $"New folder ({this.counter})", Icon = "\ue829" };
((Command)this.CreateFolderWithSelectionCommand).ChangeCanExecute();
}
private bool CanCreateFolderWithSelection(object arg)
{
return this.newFolderItem.Children.Count > 0;
}
private void ItemChecked(object obj)
{
if (obj is FolderNode folderNode)
{
newFolderItem.Children.Add(folderNode);
}
else if (obj is FileNode fildeNode)
{
newFolderItem.Children.Add(fildeNode);
}
((Command)this.CreateFolderWithSelectionCommand).ChangeCanExecute();
}
private void ItemUnchecked(object obj)
{
if (obj is FolderNode folderNode)
{
newFolderItem.Children.Remove(folderNode);
}
else if (obj is FileNode fileNode)
{
newFolderItem.Children.Remove(fileNode);
}
((Command)this.CreateFolderWithSelectionCommand).ChangeCanExecute();
}
}
Prêt à le voir en action ? Je suis! Et cette fois, je vais vous montrer WinUI. 🤘
Je suis satisfait de notre exemple et j’espère que vous l’êtes aussi. 😊
Et comme ce n’est qu’un des nombreux scénarios possibles qui peuvent être réalisés à l’aide du .NET MAUI TreeView, n’oubliez pas de prêter une attention particulière à son documentation en ligneaussi.
Prenez le volant
Si vous êtes arrivé à ce point de mon blog, un immense merci de ma part de l’avoir lu ! Et avec cela, je pense que c’est à votre tour de prendre le volant et de porter le TreeView à des niveaux encore plus élevés.
Branchez le .NET MAUI TreeView dans votre application MAUI et dites-nous quelle impression cela a fait. Et lorsque vous le faites, assurez-vous de partager votre opinion et vos idées soit dans la section commentaires ci-dessous, soit en visitant l’interface utilisateur Telerik pour .NET MAUI. Portail de commentaires.
Si vous développez déjà activement des applications MAUI exceptionnelles avec Telerik UI pour MAUI, vous savez déjà comment obtenir les dernières nouveautés. Si par hasard vous êtes un nouvel explorateur, il vous suffit de vous inscrire à notre essai gratuit de 30 jours, qui vous donne accès aux composants ainsi qu’à notre légendaire support technique sans frais. Rendez-vous simplement sur la page de présentation de l’interface utilisateur Telerik pour .NET MAUI ou cliquez sur le bouton ci-dessous pour vous inscrire pour un essai dès aujourd’hui !
Source link