Skip to content

Commit 976b760

Browse files
committed
2 parents a1d91a4 + 0e1f96c commit 976b760

26 files changed

+358
-339
lines changed

README.md

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ To create an adapter for the VirtualListView, you need to implement the followin
5454
```csharp
5555
public interface IVirtualListViewAdapter
5656
{
57-
int Sections { get; }
57+
int GetNumberOfSections();
5858

59-
object Section(int sectionIndex);
59+
object GetSection(int sectionIndex);
6060

61-
int ItemsForSection(int sectionIndex);
61+
int GetNumberOfItemsInSection(int sectionIndex);
6262

63-
object Item(int sectionIndex, int itemIndex);
63+
object GetItem(int sectionIndex, int itemIndex);
6464

6565
event EventHandler OnDataInvalidated;
6666

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

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

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

129129
public override void InvalidateData()
@@ -152,14 +152,14 @@ public class SQLiteSectionedAdapter : VirtualListViewAdapterBase<GroupInfo, Item
152152

153153
int? cachedNumberOfSections = null;
154154

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

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

162-
public GroupInfo Section(int sectionIndex)
162+
public GroupInfo GetSection(int sectionIndex)
163163
{
164164
if (cachedSectionSummaries.ContainsKey(sectionIndex))
165165
return cachedSectionSummaries[sectionIndex];
@@ -181,7 +181,7 @@ public class SQLiteSectionedAdapter : VirtualListViewAdapterBase<GroupInfo, Item
181181
return groupInfo;
182182
}
183183

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

187187
public override void InvalidateData()
@@ -226,7 +226,7 @@ public class MyItemTemplateSelector
226226

227227
public override DataTemplate SelectItemTemplate(IVirtualListViewAdapter adapter, int sectionIndex, int itemIndex)
228228
{
229-
var item = adapter.Item(sectionIndex, itemIndex);
229+
var item = adapter.GetItem(sectionIndex, itemIndex);
230230

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

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

287-
</StackLayout>
287+
</VerticalStackLayout>
288288
</xct:VirtualViewCell>
289289
</xct:VirtualViewCell>
290290
```
@@ -293,12 +293,10 @@ Notice the `IsVisible="{DynamicResource IsNotFirstItemInSection}"` references a
293293

294294
## Selection
295295

296-
There are 3 selection modes: None, Single, and Multiple. Currently there is no bindable properties for selected items, but there is a `SelectedItemsChanged` event.
297-
298-
Only `Item` types are selectable.
299-
300-
In the future there will be bindable properties and maybe a way to cancel a selection event.
296+
There are 3 selection modes: `None`, `Single`, and `Multiple`. Only `Item` types are selectable.
301297

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

303301
## Refreshing
304302

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

332330
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
333-
2. Bindable properties for item selection
331+
2. Supporting "size of content" constraints
334332

335333
Some current non-goals but considerations for even later:
336334
- Grid / Column support
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage
3+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
x:Class="VirtualListViewSample.BindableSelectedItemPage"
6+
xmlns:local="clr-namespace:VirtualListViewSample"
7+
xmlns:vlv="clr-namespace:Microsoft.Maui.Controls;assembly=VirtualListView"
8+
x:DataType="local:BindableSelectedItemViewModel"
9+
Title="BindableSelectedItemPage">
10+
<Grid RowDefinitions="*,Auto" ColumnDefinitions="*,Auto" Padding="20">
11+
<vlv:VirtualListView
12+
Grid.Row="0"
13+
Grid.Column="0" Grid.ColumnSpan="2"
14+
x:Name="vlv"
15+
Adapter="{Binding Adapter}"
16+
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
17+
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
18+
SelectionMode="Single">
19+
<vlv:VirtualListView.ItemTemplate>
20+
<DataTemplate>
21+
<vlv:VirtualViewCell SelectedBackground="DarkBlue" UnselectedBackground="LightBlue">
22+
<Border
23+
Margin="10,0,0,0"
24+
Padding="4"
25+
Background="Transparent"
26+
StrokeShape="{RoundRectangle CornerRadius=10}">
27+
<Label Margin="10,6,10,6" Text="{Binding .}" />
28+
</Border>
29+
</vlv:VirtualViewCell>
30+
</DataTemplate>
31+
</vlv:VirtualListView.ItemTemplate>
32+
</vlv:VirtualListView>
33+
34+
<Entry x:Name="entryItem" Grid.Row="1" Grid.Column="0" Placeholder="Item" />
35+
<Button Grid.Row="1" Grid.Column="1" Text="Select/Deselect" Clicked="Button_Clicked" />
36+
</Grid>
37+
</ContentPage>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using Microsoft.Maui.Adapters;
3+
using System.Collections.ObjectModel;
4+
5+
namespace VirtualListViewSample;
6+
7+
public partial class BindableSelectedItemViewModel : ObservableObject
8+
{
9+
public BindableSelectedItemViewModel(IDispatcher dispatcher)
10+
{
11+
Dispatcher = dispatcher;
12+
13+
for (int i = 0; i < 10; i++)
14+
{
15+
Items.Add($"Item: {i}");
16+
}
17+
18+
Adapter = new ObservableCollectionAdapter<string>(Items);
19+
}
20+
21+
protected IDispatcher Dispatcher { get; }
22+
23+
[ObservableProperty]
24+
ItemPosition? selectedItem;
25+
26+
[ObservableProperty]
27+
ObservableCollectionAdapter<string> adapter;
28+
29+
public ObservableCollection<string> Items = new();
30+
31+
public void OnAppearing()
32+
{
33+
Task.Delay(1000).ContinueWith(t =>
34+
{
35+
Dispatcher.Dispatch(() =>
36+
{
37+
Items.Add("Item 11");
38+
Items.Add("Item 12");
39+
});
40+
});
41+
}
42+
}
43+
44+
public partial class BindableSelectedItemPage : ContentPage
45+
{
46+
public BindableSelectedItemPage()
47+
{
48+
InitializeComponent();
49+
50+
ViewModel = new BindableSelectedItemViewModel(Dispatcher);
51+
52+
BindingContext = ViewModel;
53+
}
54+
55+
public readonly BindableSelectedItemViewModel ViewModel;
56+
57+
private void Button_Clicked(object sender, EventArgs e)
58+
{
59+
if (!string.IsNullOrEmpty(entryItem.Text))
60+
{
61+
var index = ViewModel.Items.IndexOf(entryItem.Text);
62+
63+
if (index == ViewModel.SelectedItem?.ItemIndex)
64+
ViewModel.SelectedItem = null;
65+
else if (index >= 0)
66+
ViewModel.SelectedItem = new ItemPosition(0, index);
67+
}
68+
}
69+
70+
private void vlv_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
71+
{
72+
var selection = string.Join(", ", e.NewSelection.Select(i => i.ItemIndex));
73+
System.Diagnostics.Debug.WriteLine($"SelectedItemsChanged: {selection}");
74+
}
75+
}

Sample/VirtualListViewSample/MainPage.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<Button Text="Observable Collection Demo" Clicked="Button_Clicked_1" />
1515

1616
<Button Text="Sectioned Adapter Demo" Clicked="Button_Clicked_2" />
17+
18+
<Button Text="Bindable Selected Item Demo" Clicked="Button_Clicked_4" />
1719
</VerticalStackLayout>
1820
</ScrollView>
1921
</ContentPage>

Sample/VirtualListViewSample/MainPage.xaml.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ private void Button_Clicked_2(object sender, EventArgs e)
2323
{
2424
Navigation.PushAsync(new SectionedAdapterPage());
2525
}
26+
27+
private void Button_Clicked_4(object sender, EventArgs e)
28+
{
29+
Navigation.PushAsync(new BindableSelectedItemPage());
30+
}
2631
}

Sample/VirtualListViewSample/MainViewModel.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
1-
using CommunityToolkit.Mvvm.Input;
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using CommunityToolkit.Mvvm.Input;
23
using System.ComponentModel;
34

45
namespace VirtualListViewSample;
56

6-
public partial class MainViewModel : INotifyPropertyChanged
7+
public partial class MainViewModel : ObservableObject
78
{
89
public MainViewModel()
910
{
1011
Adapter = new MusicDataAdapter();
1112
}
1213

13-
public MusicDataAdapter Adapter { get; set; }
14-
15-
public void NotifyPropertyChanged(string propertyName)
16-
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
17-
18-
public event PropertyChangedEventHandler PropertyChanged;
14+
[ObservableProperty]
15+
MusicDataAdapter adapter;
1916

2017
[RelayCommand]
2118
async Task Refresh()
2219
{
2320
await Task.Delay(3000);
24-
NotifyPropertyChanged(nameof(Adapter));
2521
}
2622

2723
[RelayCommand]

Sample/VirtualListViewSample/MusicLibraryPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
RefreshCommand="{Binding RefreshCommand}"
2424
Adapter="{Binding Adapter}"
2525
SelectionMode="Multiple"
26-
SelectedItemsChanged="VirtualListView_SelectedItemsChanged"
26+
OnSelectedItemsChanged="VirtualListView_SelectedItemsChanged"
2727
ItemTemplateSelector="{StaticResource itemTemplateSelector}">
2828

2929
<vlv:VirtualListView.SectionHeaderTemplate>

Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public MusicLibraryPage()
1717
{
1818
Dispatcher.Dispatch(() =>
1919
{
20-
vlv.SelectItems(new ItemPosition(0, 2), new ItemPosition(0, 4));
20+
vlv.SelectItem(new ItemPosition(0, 2));
21+
vlv.SelectItem(new ItemPosition(0, 4));
22+
2123
});
2224
});
2325
}

Sample/VirtualListViewSample/ObservableCollectionPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
Grid.Row="0"
1212
Grid.Column="0" Grid.ColumnSpan="2"
1313
x:Name="vlv"
14-
SelectedItemsChanged="vlv_SelectedItemsChanged"
14+
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
1515
SelectionMode="Multiple">
1616
<vlv:VirtualListView.EmptyView>
1717
<Grid>

Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ private void Button_Clicked(object sender, EventArgs e)
4747

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

52-
if (item != null)
53+
if (e.NewSelection.Any())
5354
{
54-
Items.RemoveAt(item.Value.ItemIndex);
55-
}
55+
var toDelete = e.NewSelection.First();
56+
57+
vlv.ClearSelectedItems();
5658

57-
(sender as IVirtualListView).ClearSelection();
59+
var item = Adapter.GetItem(toDelete.SectionIndex, toDelete.ItemIndex);
60+
61+
Items.Remove(item);
62+
}
5863
}
5964
}

0 commit comments

Comments
 (0)