I am building a custom file explorer for my mobile app in Maui on .NET 9. So far, folder/file creation and loading are recursive (code allows for infinite nesting). However, the issue I'm encountering now is making the XAML itself recursive without just nesting more and more collection views.
XAML - just have the collection view nested a few times already:
<CollectionView x:Name="FileCollectionView"
ItemsSource="{Binding FileItems}"
SelectionMode="None"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToColorConverter}}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" />
</StackLayout.GestureRecognizers>
<!-- Folder/File Name and Buttons -->
<Grid ColumnDefinitions="*,Auto,Auto" Padding="0">
<!-- Folder/File Name -->
<Label Text="{Binding DisplayName}" FontSize="Medium" LineBreakMode="TailTruncation" VerticalOptions="Center" Grid.Column="0" />
</Grid>
<!-- Nested CollectionView for files inside folders -->
<CollectionView
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10"
BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToColorConverter}}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" />
</StackLayout.GestureRecognizers>
<!-- File Name -->
<Label Text="{Binding DisplayName}" MaxLines="1" LineBreakMode="TailTruncation" VerticalOptions="Center" />
<!-- Nested CollectionView for files inside subfolders -->
<CollectionView
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10"
BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToColorConverter}}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" />
</StackLayout.GestureRecognizers>
<!-- File Name -->
<Label Text="{Binding DisplayName}" LineBreakMode="TailTruncation" MaxLines="1" VerticalOptions="Center" />
<CollectionView
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10"
BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToColorConverter}}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" />
</StackLayout.GestureRecognizers>
<!-- File Name -->
<Label Text="{Binding DisplayName}" LineBreakMode="TailTruncation" MaxLines="1" VerticalOptions="Center" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
And this is the code behind that loads the files (seems to work perfectly fine when I inspect the directory on my phone):
private void LoadFilesAndFolders()
{
if (Directory.Exists(_myReSEEptImagesFolder))
{
var items = Directory.GetFileSystemEntries(_myReSEEptImagesFolder);
foreach (var item in items)
{
var fileItem = new FileItem
{
Name = Path.GetFileName(item),
Parent = null,
Path = item,
IsFolder = Directory.Exists(item),
Items = new ObservableCollection<FileItem>(),
OnTappedCallback = OpenPath
};
if (fileItem.IsFolder)
{
LoadSubfolderItems(fileItem);
}
FileItems.Add(fileItem);
}
}
}
// Recursive function to load subfolders
private void LoadSubfolderItems(FileItem parentFolder)
{
var subItems = Directory.GetFileSystemEntries(parentFolder.Path);
foreach (var subItem in subItems)
{
var subFileItem = new FileItem
{
Name = Path.GetFileName(subItem),
Parent = parentFolder,
Path = subItem,
IsFolder = Directory.Exists(subItem),
Items = new ObservableCollection<FileItem>(),
OnTappedCallback = OpenPath
};
// If it's a folder, recursively load its children
if (subFileItem.IsFolder)
{
LoadSubfolderItems(subFileItem);
}
parentFolder.Items.Add(subFileItem);
}
}
Here is the FileItem
model class:
public class FileItem : INotifyPropertyChanged
{
public FileItem? Parent;
private bool _isSelected;
private string _name;
private bool _isFolder;
private ObservableCollection<FileItem> _items;
private bool _isExpanded;
private string _path;
public event PropertyChangedEventHandler PropertyChanged;
public string DisplayName => IsFolder
? (Parent != null ? $"└─