Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Redth committed Aug 15, 2023
2 parents a1d91a4 + 0e1f96c commit 976b760
Show file tree
Hide file tree
Showing 26 changed files with 358 additions and 339 deletions.
36 changes: 17 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ To create an adapter for the VirtualListView, you need to implement the followin
```csharp
public interface IVirtualListViewAdapter
{
int Sections { get; }
int GetNumberOfSections();

object Section(int sectionIndex);
object GetSection(int sectionIndex);

int ItemsForSection(int sectionIndex);
int GetNumberOfItemsInSection(int sectionIndex);

object Item(int sectionIndex, int itemIndex);
object GetItem(int sectionIndex, int itemIndex);

event EventHandler OnDataInvalidated;

Expand Down Expand Up @@ -120,10 +120,10 @@ public class SQLiteAdapter : VirtualListViewAdapterBase<object, ItemInfo>
int? cachedItemCount = null;

// No sections/grouping, so disregard the sectionIndex
public override int ItemsForSection(int sectionIndex)
public override int GetNumberOfItemsInSection(int sectionIndex)
=> cachedItemCount ??= Db.ExecuteScalar<int>("SELECT COUNT(Id) FROM Items");

public override string Item(int sectionIndex, int itemIndex)
public override string GetItem(int sectionIndex, int itemIndex)
=> Db.FindWithQuery<ItemInfo>("SELECT * FROM Items ORDER BY Id LIMIT 1 OFFSET ?", itemIndex);

public override void InvalidateData()
Expand Down Expand Up @@ -152,14 +152,14 @@ public class SQLiteSectionedAdapter : VirtualListViewAdapterBase<GroupInfo, Item

int? cachedNumberOfSections = null;

public int Sections
public int GetNumberOfSections()
=> cachedNumberOfSections ??= Db.ExecuteScalar<int>("SELECT DISTINCT COUNT(GroupId) FROM Items");

// No sections/grouping, so disregard the sectionIndex
public override int ItemsForSection(int sectionIndex)
public override int GetNumberOfItemsInSection(int sectionIndex)
=> cachedItemCount ??= Db.ExecuteScalar<int>("SELECT COUNT(Id) FROM Items");

public GroupInfo Section(int sectionIndex)
public GroupInfo GetSection(int sectionIndex)
{
if (cachedSectionSummaries.ContainsKey(sectionIndex))
return cachedSectionSummaries[sectionIndex];
Expand All @@ -181,7 +181,7 @@ public class SQLiteSectionedAdapter : VirtualListViewAdapterBase<GroupInfo, Item
return groupInfo;
}

public override string Item(int sectionIndex, int itemIndex)
public override string GetItem(int sectionIndex, int itemIndex)
=> Db.FindWithQuery<ItemInfo>("SELECT * FROM Items WHERE GroupId=? ORDER BY Id LIMIT 1 OFFSET ?", sectionIndex, itemIndex);

public override void InvalidateData()
Expand Down Expand Up @@ -226,7 +226,7 @@ public class MyItemTemplateSelector

public override DataTemplate SelectItemTemplate(IVirtualListViewAdapter adapter, int sectionIndex, int itemIndex)
{
var item = adapter.Item(sectionIndex, itemIndex);
var item = adapter.GetItem(sectionIndex, itemIndex);

if (item is Person)
return personTemplate;
Expand Down Expand Up @@ -269,7 +269,7 @@ You can access these properties from your templates. Here's an example of displ
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VirtualListViewSample.GenericViewCell">
<xct:VirtualViewCell>
<StackLayout
<VerticalStackLayout
Spacing="0"
BackgroundColor="{Binding Source={x:Reference self}, Path=IsSelected, Converter={StaticResource selectedColorConverter}}">

Expand All @@ -284,7 +284,7 @@ You can access these properties from your templates. Here's an example of displ
<Label Text="{Binding TrackName}" />
</Border>

</StackLayout>
</VerticalStackLayout>
</xct:VirtualViewCell>
</xct:VirtualViewCell>
```
Expand All @@ -293,12 +293,10 @@ Notice the `IsVisible="{DynamicResource IsNotFirstItemInSection}"` references a

## Selection

There are 3 selection modes: None, Single, and Multiple. Currently there is no bindable properties for selected items, but there is a `SelectedItemsChanged` event.

Only `Item` types are selectable.

In the future there will be bindable properties and maybe a way to cancel a selection event.
There are 3 selection modes: `None`, `Single`, and `Multiple`. Only `Item` types are selectable.

There are `SelectedItem` and `SelectedItems` bindable properties.
There's an `OnSelectedItemsChanged` event fired whenever these change.

## Refreshing

Expand Down Expand Up @@ -330,7 +328,7 @@ Scrolled notifications can be observed with `ScrolledCommand` which will pass a
Looking ahead, there are a few goals:

1. Even Rows - by default every cell is assumed uneven and measured every time the context changes or the cell is recycled. Adding an option to assume each template type is the same size will make performance even better, but will be an explicit opt-in
2. Bindable properties for item selection
2. Supporting "size of content" constraints

Some current non-goals but considerations for even later:
- Grid / Column support
Expand Down
37 changes: 37 additions & 0 deletions Sample/VirtualListViewSample/BindableSelectedItemPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VirtualListViewSample.BindableSelectedItemPage"
xmlns:local="clr-namespace:VirtualListViewSample"
xmlns:vlv="clr-namespace:Microsoft.Maui.Controls;assembly=VirtualListView"
x:DataType="local:BindableSelectedItemViewModel"
Title="BindableSelectedItemPage">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="*,Auto" Padding="20">
<vlv:VirtualListView
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2"
x:Name="vlv"
Adapter="{Binding Adapter}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Single">
<vlv:VirtualListView.ItemTemplate>
<DataTemplate>
<vlv:VirtualViewCell SelectedBackground="DarkBlue" UnselectedBackground="LightBlue">
<Border
Margin="10,0,0,0"
Padding="4"
Background="Transparent"
StrokeShape="{RoundRectangle CornerRadius=10}">
<Label Margin="10,6,10,6" Text="{Binding .}" />
</Border>
</vlv:VirtualViewCell>
</DataTemplate>
</vlv:VirtualListView.ItemTemplate>
</vlv:VirtualListView>

<Entry x:Name="entryItem" Grid.Row="1" Grid.Column="0" Placeholder="Item" />
<Button Grid.Row="1" Grid.Column="1" Text="Select/Deselect" Clicked="Button_Clicked" />
</Grid>
</ContentPage>
75 changes: 75 additions & 0 deletions Sample/VirtualListViewSample/BindableSelectedItemPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Maui.Adapters;
using System.Collections.ObjectModel;

namespace VirtualListViewSample;

public partial class BindableSelectedItemViewModel : ObservableObject
{
public BindableSelectedItemViewModel(IDispatcher dispatcher)
{
Dispatcher = dispatcher;

for (int i = 0; i < 10; i++)
{
Items.Add($"Item: {i}");
}

Adapter = new ObservableCollectionAdapter<string>(Items);
}

protected IDispatcher Dispatcher { get; }

[ObservableProperty]
ItemPosition? selectedItem;

[ObservableProperty]
ObservableCollectionAdapter<string> adapter;

public ObservableCollection<string> Items = new();

public void OnAppearing()
{
Task.Delay(1000).ContinueWith(t =>
{
Dispatcher.Dispatch(() =>
{
Items.Add("Item 11");
Items.Add("Item 12");
});
});
}
}

public partial class BindableSelectedItemPage : ContentPage
{
public BindableSelectedItemPage()
{
InitializeComponent();

ViewModel = new BindableSelectedItemViewModel(Dispatcher);

BindingContext = ViewModel;
}

public readonly BindableSelectedItemViewModel ViewModel;

private void Button_Clicked(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(entryItem.Text))
{
var index = ViewModel.Items.IndexOf(entryItem.Text);

if (index == ViewModel.SelectedItem?.ItemIndex)
ViewModel.SelectedItem = null;
else if (index >= 0)
ViewModel.SelectedItem = new ItemPosition(0, index);
}
}

private void vlv_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
{
var selection = string.Join(", ", e.NewSelection.Select(i => i.ItemIndex));
System.Diagnostics.Debug.WriteLine($"SelectedItemsChanged: {selection}");
}
}
2 changes: 2 additions & 0 deletions Sample/VirtualListViewSample/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<Button Text="Observable Collection Demo" Clicked="Button_Clicked_1" />

<Button Text="Sectioned Adapter Demo" Clicked="Button_Clicked_2" />

<Button Text="Bindable Selected Item Demo" Clicked="Button_Clicked_4" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
5 changes: 5 additions & 0 deletions Sample/VirtualListViewSample/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ private void Button_Clicked_2(object sender, EventArgs e)
{
Navigation.PushAsync(new SectionedAdapterPage());
}

private void Button_Clicked_4(object sender, EventArgs e)
{
Navigation.PushAsync(new BindableSelectedItemPage());
}
}
14 changes: 5 additions & 9 deletions Sample/VirtualListViewSample/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.ComponentModel;

namespace VirtualListViewSample;

public partial class MainViewModel : INotifyPropertyChanged
public partial class MainViewModel : ObservableObject
{
public MainViewModel()
{
Adapter = new MusicDataAdapter();
}

public MusicDataAdapter Adapter { get; set; }

public void NotifyPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

public event PropertyChangedEventHandler PropertyChanged;
[ObservableProperty]
MusicDataAdapter adapter;

[RelayCommand]
async Task Refresh()
{
await Task.Delay(3000);
NotifyPropertyChanged(nameof(Adapter));
}

[RelayCommand]
Expand Down
2 changes: 1 addition & 1 deletion Sample/VirtualListViewSample/MusicLibraryPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
RefreshCommand="{Binding RefreshCommand}"
Adapter="{Binding Adapter}"
SelectionMode="Multiple"
SelectedItemsChanged="VirtualListView_SelectedItemsChanged"
OnSelectedItemsChanged="VirtualListView_SelectedItemsChanged"
ItemTemplateSelector="{StaticResource itemTemplateSelector}">

<vlv:VirtualListView.SectionHeaderTemplate>
Expand Down
4 changes: 3 additions & 1 deletion Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public MusicLibraryPage()
{
Dispatcher.Dispatch(() =>
{
vlv.SelectItems(new ItemPosition(0, 2), new ItemPosition(0, 4));
vlv.SelectItem(new ItemPosition(0, 2));
vlv.SelectItem(new ItemPosition(0, 4));

});
});
}
Expand Down
2 changes: 1 addition & 1 deletion Sample/VirtualListViewSample/ObservableCollectionPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2"
x:Name="vlv"
SelectedItemsChanged="vlv_SelectedItemsChanged"
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Multiple">
<vlv:VirtualListView.EmptyView>
<Grid>
Expand Down
15 changes: 10 additions & 5 deletions Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ private void Button_Clicked(object sender, EventArgs e)

private void vlv_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
{
var item = e.NewSelection?.FirstOrDefault();
var selection = string.Join(", ", e.NewSelection.Select(i => i.ItemIndex));
System.Diagnostics.Debug.WriteLine($"SelectedItemsChanged: {selection}");

if (item != null)
if (e.NewSelection.Any())
{
Items.RemoveAt(item.Value.ItemIndex);
}
var toDelete = e.NewSelection.First();

vlv.ClearSelectedItems();

(sender as IVirtualListView).ClearSelection();
var item = Adapter.GetItem(toDelete.SectionIndex, toDelete.ItemIndex);

Items.Remove(item);
}
}
}
2 changes: 1 addition & 1 deletion Sample/VirtualListViewSample/SectionedAdapterPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="3"
x:Name="vlv"
SelectedItemsChanged="vlv_SelectedItemsChanged"
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Single">
<vlv:VirtualListView.EmptyView>
<Label Text="Empty!" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
Expand Down
12 changes: 6 additions & 6 deletions Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ private void Button_Clicked(object sender, EventArgs e)

private void vlv_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
{
var item = e.NewSelection?.FirstOrDefault();

if (item != null)
if (e.NewSelection.Any())
{
Adapter.RemoveItem(item.Value.SectionIndex, item.Value.ItemIndex);
}
var item = e.NewSelection.First();

(sender as IVirtualListView)?.ClearSelection();
Adapter.RemoveItem(item.SectionIndex, item.ItemIndex);
vlv.ClearSelectedItems();
}

}
}
2 changes: 1 addition & 1 deletion Sample/VirtualListViewSample/VirtualListViewSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="LiteDb" Version="5.0.16" />
<PackageReference Include="LiteDb" Version="5.0.17" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="chinook.litedb" />
Expand Down
8 changes: 3 additions & 5 deletions VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public CvDataSource(VirtualListViewHandler handler)

VirtualListViewHandler Handler { get; }

public Func<int, int, bool> IsSelectedHandler { get; set; }

readonly ReusableIdManager itemIdManager = new ReusableIdManager("Item");
readonly ReusableIdManager globalIdManager = new ReusableIdManager("Global");
readonly ReusableIdManager sectionHeaderIdManager = new ReusableIdManager("SectionHeader", new NSString("SectionHeader"));
Expand Down Expand Up @@ -52,7 +50,7 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS
if (info.SectionIndex < 0 || info.ItemIndex < 0)
info.IsSelected = false;
else
info.IsSelected = IsSelectedHandler?.Invoke(info.SectionIndex, info.ItemIndex) ?? false;
info.IsSelected = Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false;

if (cell.NeedsView)
{
Expand All @@ -79,9 +77,9 @@ void TapCellHandler(CvCell cell)
cell.PositionInfo.IsSelected = !cell.PositionInfo.IsSelected;

if (cell.PositionInfo.IsSelected)
Handler.VirtualView?.SelectItems(p);
Handler?.VirtualView?.SelectItem(p);
else
Handler.VirtualView?.DeselectItems(p);
Handler?.VirtualView?.DeselectItem(p);
}

public override nint GetItemsCount(UICollectionView collectionView, nint section)
Expand Down
Loading

0 comments on commit 976b760

Please sign in to comment.