diff --git a/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml b/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml
new file mode 100644
index 0000000..d3f2519
--- /dev/null
+++ b/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml.cs b/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml.cs
new file mode 100644
index 0000000..c6bc8c5
--- /dev/null
+++ b/Sample/VirtualListViewSample/BindableSelectedItemPage.xaml.cs
@@ -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(Items);
+ }
+
+ protected IDispatcher Dispatcher { get; }
+
+ [ObservableProperty]
+ ItemPosition? selectedItem;
+
+ [ObservableProperty]
+ ObservableCollectionAdapter adapter;
+
+ public ObservableCollection 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}");
+ }
+}
\ No newline at end of file
diff --git a/Sample/VirtualListViewSample/MainPage.xaml b/Sample/VirtualListViewSample/MainPage.xaml
index 8f9c24f..62f13b1 100644
--- a/Sample/VirtualListViewSample/MainPage.xaml
+++ b/Sample/VirtualListViewSample/MainPage.xaml
@@ -14,6 +14,8 @@
+
+
diff --git a/Sample/VirtualListViewSample/MainPage.xaml.cs b/Sample/VirtualListViewSample/MainPage.xaml.cs
index 52e9cde..a50abae 100644
--- a/Sample/VirtualListViewSample/MainPage.xaml.cs
+++ b/Sample/VirtualListViewSample/MainPage.xaml.cs
@@ -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());
+ }
}
diff --git a/Sample/VirtualListViewSample/MainViewModel.cs b/Sample/VirtualListViewSample/MainViewModel.cs
index 9571469..653fc72 100644
--- a/Sample/VirtualListViewSample/MainViewModel.cs
+++ b/Sample/VirtualListViewSample/MainViewModel.cs
@@ -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]
diff --git a/Sample/VirtualListViewSample/MusicLibraryPage.xaml b/Sample/VirtualListViewSample/MusicLibraryPage.xaml
index 1996cbc..45ad698 100644
--- a/Sample/VirtualListViewSample/MusicLibraryPage.xaml
+++ b/Sample/VirtualListViewSample/MusicLibraryPage.xaml
@@ -23,7 +23,7 @@
RefreshCommand="{Binding RefreshCommand}"
Adapter="{Binding Adapter}"
SelectionMode="Multiple"
- SelectedItemsChanged="VirtualListView_SelectedItemsChanged"
+ OnSelectedItemsChanged="VirtualListView_SelectedItemsChanged"
ItemTemplateSelector="{StaticResource itemTemplateSelector}">
diff --git a/Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs b/Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs
index 59f3153..503918a 100644
--- a/Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs
+++ b/Sample/VirtualListViewSample/MusicLibraryPage.xaml.cs
@@ -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));
+
});
});
}
diff --git a/Sample/VirtualListViewSample/ObservableCollectionPage.xaml b/Sample/VirtualListViewSample/ObservableCollectionPage.xaml
index 92dbd36..d09a2a3 100644
--- a/Sample/VirtualListViewSample/ObservableCollectionPage.xaml
+++ b/Sample/VirtualListViewSample/ObservableCollectionPage.xaml
@@ -11,7 +11,7 @@
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2"
x:Name="vlv"
- SelectedItemsChanged="vlv_SelectedItemsChanged"
+ OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Multiple">
diff --git a/Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs b/Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs
index e98ad45..7c6d15a 100644
--- a/Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs
+++ b/Sample/VirtualListViewSample/ObservableCollectionPage.xaml.cs
@@ -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);
+ }
}
}
\ No newline at end of file
diff --git a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
index e25ace7..3e7ed68 100644
--- a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
+++ b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
@@ -11,7 +11,7 @@
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="3"
x:Name="vlv"
- SelectedItemsChanged="vlv_SelectedItemsChanged"
+ OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Single">
diff --git a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
index 96fd53d..12e3944 100644
--- a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
+++ b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
@@ -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();
+ }
+
}
}
diff --git a/Sample/VirtualListViewSample/VirtualListViewSample.csproj b/Sample/VirtualListViewSample/VirtualListViewSample.csproj
index adc1bec..7de473d 100644
--- a/Sample/VirtualListViewSample/VirtualListViewSample.csproj
+++ b/Sample/VirtualListViewSample/VirtualListViewSample.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
index 42e98c8..31eeca0 100644
--- a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
@@ -13,8 +13,6 @@ public CvDataSource(VirtualListViewHandler handler)
VirtualListViewHandler Handler { get; }
- public Func IsSelectedHandler { get; set; }
-
readonly ReusableIdManager itemIdManager = new ReusableIdManager("Item");
readonly ReusableIdManager globalIdManager = new ReusableIdManager("Global");
readonly ReusableIdManager sectionHeaderIdManager = new ReusableIdManager("SectionHeader", new NSString("SectionHeader"));
@@ -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)
{
@@ -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)
diff --git a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
index c16b915..9495bdf 100644
--- a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
@@ -38,9 +38,9 @@ void HandleSelection(UICollectionView collectionView, NSIndexPath indexPath, boo
selectedCell.PositionInfo.ItemIndex);
if (selected)
- Handler?.VirtualView?.SelectItems(itemPos);
+ Handler?.VirtualView?.SelectItem(itemPos);
else
- Handler?.VirtualView?.DeselectItems(itemPos);
+ Handler?.VirtualView?.DeselectItem(itemPos);
}
}
diff --git a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
index 8b82d37..b7d4b6e 100644
--- a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
@@ -62,12 +62,10 @@ protected override void ConnectHandler(UICollectionView nativeView)
PositionalViewSelector = new PositionalViewSelector(VirtualView);
dataSource = new CvDataSource(this);
- dataSource.IsSelectedHandler = (realSection, realIndex) =>
- VirtualView?.IsItemSelected(realSection, realIndex) ?? false;
-
+
cvdelegate = new CvDelegate(this, collectionView);
cvdelegate.ScrollHandler = (x, y) =>
- VirtualView?.Scrolled(new ScrolledEventArgs(x, y));
+ VirtualView?.Scrolled(x, y);
collectionView.DataSource = dataSource;
collectionView.Delegate = cvdelegate;
@@ -117,44 +115,27 @@ public static void MapSelectionMode(VirtualListViewHandler handler, IVirtualList
public static void MapInvalidateData(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
=> handler?.InvalidateData();
- public static void MapSelectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object parameter)
+ void PlatformUpdateItemSelection(ItemPosition itemPosition, bool selected)
{
- if (parameter is ItemPosition[] items)
- {
- UpdateSelection(handler, items, true);
- }
- }
+ var realIndex = PositionalViewSelector?.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex) ?? -1;
- public static void MapDeselectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object parameter)
- {
- if (parameter is ItemPosition[] items)
- {
- UpdateSelection(handler, items, false);
- }
- }
+ if (realIndex < 0)
+ return;
- static void UpdateSelection(VirtualListViewHandler handler, ItemPosition[] itemPositions, bool selected)
- {
- foreach (var itemPosition in itemPositions)
- {
- var realIndex = handler.PositionalViewSelector.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex);
+ var cell = collectionView.CellForItem(NSIndexPath.FromItemSection(realIndex, 0));
- var cell = handler.collectionView.CellForItem(NSIndexPath.FromItemSection(realIndex, 0));
+ if (cell is CvCell cvcell)
+ {
+ cvcell.PositionInfo.IsSelected = selected;
- if (cell is CvCell cvcell)
+ if (cvcell.VirtualView is IPositionInfo positionInfo)
{
- cvcell.PositionInfo.IsSelected = selected;
-
- if (cvcell.VirtualView is IPositionInfo positionInfo)
- {
- handler.collectionView.InvokeOnMainThread(() =>
- {
- positionInfo.IsSelected = selected;
- });
- }
+ collectionView.InvokeOnMainThread(() =>
+ {
+ positionInfo.IsSelected = selected;
+ });
}
}
-
}
public static void MapOrientation(VirtualListViewHandler handler, IVirtualListView virtualListView)
diff --git a/VirtualListView/Controls/VirtualListView.cs b/VirtualListView/Controls/VirtualListView.cs
index 75fefbc..2ea1f3b 100644
--- a/VirtualListView/Controls/VirtualListView.cs
+++ b/VirtualListView/Controls/VirtualListView.cs
@@ -3,7 +3,7 @@
namespace Microsoft.Maui.Controls;
-public partial class VirtualListView : View, IVirtualListView, IVirtualListViewSelector, IVisualTreeElement
+public partial class VirtualListView : View, IVirtualListView, IVirtualListViewSelector
{
public static readonly BindableProperty PositionInfoProperty = BindableProperty.CreateAttached(
nameof(PositionInfo),
@@ -109,7 +109,7 @@ public Maui.SelectionMode SelectionMode
public static readonly BindableProperty SelectionModeProperty =
BindableProperty.Create(nameof(SelectionMode), typeof(Maui.SelectionMode), typeof(VirtualListView), Maui.SelectionMode.None);
- public event EventHandler SelectedItemsChanged;
+ public event EventHandler OnSelectedItemsChanged;
public event EventHandler OnRefresh;
@@ -183,70 +183,114 @@ public View EmptyView
IView IVirtualListView.EmptyView => EmptyView;
-
- //public View RefreshView
- //{
- // get => (View)GetValue(RefreshViewProperty);
- // set => SetValue(RefreshViewProperty, value);
- //}
-
- //public static readonly BindableProperty RefreshViewProperty =
- // BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null,
- // propertyChanged: (bobj, oldValue, newValue) =>
- // {
- // if (bobj is VirtualListView virtualListView)
- // {
- // if (oldValue is IView oldView)
- // virtualListView.RemoveLogicalChild(oldView);
-
- // if (newValue is IView newView)
- // virtualListView.AddLogicalChild(newView);
- // }
- // });
-
- //IView IVirtualListView.RefreshView => RefreshView;
-
-
-
-
public IVirtualListViewSelector ViewSelector => this;
public IView Header => GlobalHeader;
public IView Footer => GlobalFooter;
-
-
- public event EventHandler DataInvalidated;
-
-
public event EventHandler OnScrolled;
- void IVirtualListView.Scrolled(ScrolledEventArgs args)
+ public void Scrolled(double x, double y)
{
+ var args = new ScrolledEventArgs(x, y);
+
if (ScrolledCommand != null && ScrolledCommand.CanExecute(args))
- {
ScrolledCommand.Execute(args);
- }
OnScrolled?.Invoke(this, args);
}
+ public static readonly BindableProperty ScrolledCommandProperty =
+ BindableProperty.Create(nameof(ScrolledCommand), typeof(ICommand), typeof(VirtualListView), default);
+
public ICommand ScrolledCommand
{
get => (ICommand)GetValue(ScrolledCommandProperty);
set => SetValue(ScrolledCommandProperty, value);
}
- public IReadOnlyList SelectedItems => throw new NotImplementedException();
+ public static readonly BindableProperty SelectedItemsProperty =
+ BindableProperty.Create(nameof(SelectedItems), typeof(IList), typeof(VirtualListView), Array.Empty(),
+ propertyChanged: (bindableObj, oldValue, newValue) =>
+ {
+ if (bindableObj is VirtualListView vlv
+ && oldValue is IList oldSelection
+ && newValue is IList newSelection)
+ {
+ vlv.RaiseSelectedItemsChanged(oldSelection.ToArray(), newSelection.ToArray());
+ }
+ });
+ public IList SelectedItems
+ {
+ get => (IList)GetValue(SelectedItemsProperty);
+ set => SetValue(SelectedItemsProperty, value ?? Array.Empty());
+ }
- public static readonly BindableProperty ScrolledCommandProperty =
- BindableProperty.Create(nameof(ScrolledCommandProperty), typeof(ICommand), typeof(VirtualListView), default);
+ public static readonly BindableProperty SelectedItemProperty =
+ BindableProperty.Create(nameof(SelectedItem), typeof(ItemPosition?), typeof(VirtualListView), default,
+ propertyChanged: (bindableObj, oldValue, newValue) =>
+ {
+ if (bindableObj is VirtualListView vlv)
+ {
+ if (newValue is null || newValue is not ItemPosition)
+ vlv.SelectedItems = null;
+ else if (newValue is ItemPosition p)
+ vlv.SelectedItems = new[] { p };
+ }
+ });
+
+ public ItemPosition? SelectedItem
+ {
+ get => (ItemPosition?)GetValue(SelectedItemProperty);
+ set => SetValue(SelectedItemProperty, value);
+ }
+
+
+ public void DeselectItem(ItemPosition itemPosition)
+ {
+ if (SelectionMode == Maui.SelectionMode.Single)
+ {
+ if (SelectedItem.HasValue && SelectedItem.Value.Equals(itemPosition))
+ {
+ SelectedItem = null;
+ }
+ }
+ else if (SelectionMode == Maui.SelectionMode.Multiple)
+ {
+ var current = SelectedItems.ToList();
+ if (current.Contains(itemPosition))
+ {
+ current.Remove(itemPosition);
+ SelectedItems = current.ToArray();
+ }
+ }
+ }
- public void InvalidateData()
+ public void SelectItem(ItemPosition itemPosition)
{
- (Handler as VirtualListViewHandler)?.InvalidateData();
+ if (SelectionMode == Maui.SelectionMode.Single)
+ {
+ if (!SelectedItem.HasValue || !SelectedItem.Value.Equals(itemPosition))
+ {
+ SelectedItem = itemPosition;
+ }
+ }
+ else if (SelectionMode == Maui.SelectionMode.Multiple)
+ {
+ var current = SelectedItems;
+ if (!current.Contains(itemPosition))
+ {
+ SelectedItems = current.Append(itemPosition).ToArray();
+ }
+ }
+ }
- DataInvalidated?.Invoke(this, new EventArgs());
+ public void ClearSelectedItems()
+ {
+ if (SelectionMode == Maui.SelectionMode.Multiple)
+ SelectedItems = null;
+ else
+ SelectedItem = null;
}
public bool SectionHasHeader(int sectionIndex)
@@ -256,8 +300,9 @@ public bool SectionHasFooter(int sectionIndex)
=> SectionFooterTemplateSelector != null || SectionFooterTemplate != null;
public IView CreateView(PositionInfo position, object data)
- => position.Kind switch {
- PositionKind.Item =>
+ => position.Kind switch
+ {
+ PositionKind.Item =>
ItemTemplateSelector?.SelectTemplate(data, position.SectionIndex, position.ItemIndex)?.CreateContent() as View
?? ItemTemplate?.CreateContent() as View,
PositionKind.SectionHeader =>
@@ -309,18 +354,7 @@ public void ViewDetached(PositionInfo position, IView view)
public void ViewAttached(PositionInfo position, IView view)
=> this.AddLogicalChild(view);
- public bool IsItemSelected(int sectionIndex, int itemIndex)
- => (Handler as VirtualListViewHandler).IsItemSelected(sectionIndex, itemIndex);
-
- public void OnSelectedItemsChanged(SelectedItemsChangedEventArgs args)
- => this.SelectedItemsChanged?.Invoke(this, args);
-
- public void SelectItems(params ItemPosition[] paths)
- => (Handler as VirtualListViewHandler).SelectItems(paths);
-
- public void DeselectItems(params ItemPosition[] paths)
- => (Handler as VirtualListViewHandler).DeselectItems(paths);
-
- public void ClearSelection()
- => (Handler as VirtualListViewHandler).ClearSelection();
+ void RaiseSelectedItemsChanged(ItemPosition[] previousSelection, ItemPosition[] newSelection)
+ => this.OnSelectedItemsChanged?.Invoke(this, new SelectedItemsChangedEventArgs(previousSelection, newSelection));
+
}
diff --git a/VirtualListView/IVirtualListView.cs b/VirtualListView/IVirtualListView.cs
index 73e4fb0..6dbc2e8 100644
--- a/VirtualListView/IVirtualListView.cs
+++ b/VirtualListView/IVirtualListView.cs
@@ -12,10 +12,17 @@ public interface IVirtualListView : IView
IView Footer { get; }
- event EventHandler SelectedItemsChanged;
- void OnSelectedItemsChanged(SelectedItemsChangedEventArgs eventArgs);
+ event EventHandler OnScrolled;
- event EventHandler DataInvalidated;
+ void Scrolled(double x, double y);
+
+ SelectionMode SelectionMode { get; }
+
+ IList SelectedItems { get; set; }
+
+ ItemPosition? SelectedItem { get; set; }
+
+ event EventHandler OnSelectedItemsChanged;
Color RefreshAccentColor { get; }
@@ -23,27 +30,15 @@ public interface IVirtualListView : IView
bool IsRefreshEnabled { get; }
- void Scrolled(ScrolledEventArgs args);
-
- SelectionMode SelectionMode { get; }
-
- IReadOnlyList SelectedItems { get; }
-
ListOrientation Orientation { get; }
IView EmptyView { get; }
- // IView RefreshView { get; }
-
- bool IsItemSelected(int sectionIndex, int itemIndex);
-
- void SelectItems(params ItemPosition[] paths);
-
- void DeselectItems(params ItemPosition[] paths);
+ void SelectItem(ItemPosition path);
- void ClearSelection();
+ void DeselectItem(ItemPosition path);
- //void InvalidateData();
+ void ClearSelectedItems();
}
public enum ListOrientation
diff --git a/VirtualListView/ItemPosition.cs b/VirtualListView/ItemPosition.cs
index eec2397..68022f9 100644
--- a/VirtualListView/ItemPosition.cs
+++ b/VirtualListView/ItemPosition.cs
@@ -1,6 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace Microsoft.Maui;
-public struct ItemPosition
+public struct ItemPosition : IEquatable
{
public ItemPosition(int sectionIndex = 0, int itemIndex = 0)
{
@@ -10,4 +12,7 @@ public ItemPosition(int sectionIndex = 0, int itemIndex = 0)
public int SectionIndex { get; }
public int ItemIndex { get; }
+
+ public bool Equals(ItemPosition other)
+ => SectionIndex == other.SectionIndex && ItemIndex == other.ItemIndex;
}
\ No newline at end of file
diff --git a/VirtualListView/Platforms/Android/RvAdapter.cs b/VirtualListView/Platforms/Android/RvAdapter.cs
index 45774dd..696ec37 100644
--- a/VirtualListView/Platforms/Android/RvAdapter.cs
+++ b/VirtualListView/Platforms/Android/RvAdapter.cs
@@ -57,7 +57,7 @@ public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int positi
// The template selector doesn't infer selected properly
// so we need to ask the listview which tracks selections about the state
info.IsSelected = info.Kind == PositionKind.Item
- && (handler?.VirtualView?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false);
+ && (handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false);
if (holder is RvItemHolder itemHolder)
{
@@ -134,9 +134,9 @@ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int
positionInfo.IsSelected = rvh.PositionInfo.IsSelected;
if (rvh.PositionInfo.IsSelected)
- handler.VirtualView?.SelectItems(p);
+ handler?.VirtualView?.SelectItem(p);
else
- handler.VirtualView?.DeselectItems(p);
+ handler?.VirtualView?.DeselectItem(p);
});
viewHolder.ItemView.SetOnClickListener(clickListener);
diff --git a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
index 4b754ca..8d30f5f 100644
--- a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
+++ b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
@@ -53,7 +53,7 @@ protected override void ConnectHandler(FrameLayout nativeView)
var x = Context.FromPixels(dx);
var y = Context.FromPixels(dy);
- VirtualView?.Scrolled(new ScrolledEventArgs(x, y));
+ VirtualView?.Scrolled(x, y);
}));
recyclerView.SetLayoutManager(layoutManager);
@@ -94,33 +94,18 @@ public static void MapSelectionMode(VirtualListViewHandler handler, IVirtualList
public static void MapInvalidateData(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
=> handler.InvalidateData();
- public static void MapSelectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
+ void PlatformUpdateItemSelection(ItemPosition itemPosition, bool selected)
{
- if (parameter is ItemPosition[] itemPositions)
- UpdateSelection(handler, itemPositions, true);
- }
+ var position = PositionalViewSelector.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex);
- public static void MapDeselectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
- {
- if (parameter is ItemPosition[] itemPositions)
- UpdateSelection(handler, itemPositions, false);
- }
+ var vh = recyclerView.FindViewHolderForAdapterPosition(position);
- static void UpdateSelection(VirtualListViewHandler handler, ItemPosition[] itemPositions, bool selected)
- {
- foreach (var itemPosition in itemPositions)
+ if (vh is RvItemHolder rvh)
{
- var position = handler.PositionalViewSelector.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex);
-
- var vh = handler.recyclerView.FindViewHolderForAdapterPosition(position);
-
- if (vh is RvItemHolder rvh)
- {
- rvh.PositionInfo.IsSelected = selected;
+ rvh.PositionInfo.IsSelected = selected;
- if (rvh.ViewContainer?.VirtualView is IPositionInfo viewPositionInfo)
- viewPositionInfo.IsSelected = selected;
- }
+ if (rvh.ViewContainer?.VirtualView is IPositionInfo viewPositionInfo)
+ viewPositionInfo.IsSelected = selected;
}
}
diff --git a/VirtualListView/Platforms/Windows/IrElementContainer.cs b/VirtualListView/Platforms/Windows/IrElementContainer.cs
index b0192fa..78b2ad4 100644
--- a/VirtualListView/Platforms/Windows/IrElementContainer.cs
+++ b/VirtualListView/Platforms/Windows/IrElementContainer.cs
@@ -90,9 +90,9 @@ protected override void OnTapped(TappedRoutedEventArgs e)
var itemPos = new ItemPosition(PositionInfo?.SectionIndex ?? 0, PositionInfo?.ItemIndex ?? 0);
if (PositionInfo?.IsSelected ?? false)
- PositionalViewSelector?.VirtualListView?.SelectItems(itemPos);
+ PositionalViewSelector?.VirtualListView?.SelectItem(itemPos);
else
- PositionalViewSelector?.VirtualListView?.DeselectItems(itemPos);
+ PositionalViewSelector?.VirtualListView?.DeselectItem(itemPos);
}
protected override IEnumerable GetChildrenInTabFocusOrder()
diff --git a/VirtualListView/Platforms/Windows/IrElementFactory.cs b/VirtualListView/Platforms/Windows/IrElementFactory.cs
index 0e3b71a..0f87a29 100644
--- a/VirtualListView/Platforms/Windows/IrElementFactory.cs
+++ b/VirtualListView/Platforms/Windows/IrElementFactory.cs
@@ -13,16 +13,18 @@ namespace Microsoft.Maui;
internal class IrElementFactory : IElementFactory, IDisposable
{
- public IrElementFactory(IMauiContext context, PositionalViewSelector positionalViewSelector)
+ public IrElementFactory(IMauiContext context, PositionalViewSelector positionalViewSelector, VirtualListViewHandler handler)
{
MauiContext = context;
PositionalViewSelector = positionalViewSelector;
+ Handler = handler;
}
readonly object lockObj = new object();
protected readonly IMauiContext MauiContext;
protected readonly PositionalViewSelector PositionalViewSelector;
+ protected readonly VirtualListViewHandler Handler;
internal record IrRecycledElement(string reuseId, UIElement element);
@@ -77,10 +79,18 @@ public UIElement GetElement(UI.Xaml.ElementFactoryGetArgs args)
var container = GetRecycledElement(reuseId)
?? new IrElementContainer(MauiContext, reuseId, PositionalViewSelector);
+ // The template selector doesn't infer selected properly
+ // so we need to ask the listview which tracks selections about the state
+ info.IsSelected = info.Kind == PositionKind.Item
+ && (Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false);
+
+
var view = container.VirtualView ?? PositionalViewSelector.ViewSelector?.CreateView(info, data);
container.Update(info, view);
+
+
container.IsRecycled = false;
PositionalViewSelector.ViewSelector?.RecycleView(info, data, view);
diff --git a/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs b/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
index fd981f3..d4ea75d 100644
--- a/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
+++ b/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
@@ -52,7 +52,7 @@ private void ItemsRepeaterScrollHost_Loaded(object sender, UI.Xaml.RoutedEventAr
itemsRepeaterScrollHost.Loaded -= ItemsRepeaterScrollHost_Loaded;
scrollViewer?.RegisterPropertyChangedCallback(ScrollViewer.VerticalOffsetProperty, (o, dp) =>
- VirtualView.Scrolled(new ScrolledEventArgs(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset)));
+ VirtualView.Scrolled(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset));
}
protected override void ConnectHandler(WGrid nativeView)
@@ -60,7 +60,7 @@ protected override void ConnectHandler(WGrid nativeView)
base.ConnectHandler(nativeView);
PositionalViewSelector = new PositionalViewSelector(VirtualView);
- elementFactory = new IrElementFactory(MauiContext, PositionalViewSelector);
+ elementFactory = new IrElementFactory(MauiContext, PositionalViewSelector, this);
itemsRepeater.ItemTemplate = elementFactory;
irSource = new IrSource(MauiContext, PositionalViewSelector, VirtualView);
@@ -98,40 +98,18 @@ public static void MapViewSelector(VirtualListViewHandler handler, IVirtualListV
public static void MapSelectionMode(VirtualListViewHandler handler, IVirtualListView virtualListView)
{ }
- public static void MapInvalidateData(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
- => handler?.InvalidateData();
-
- public static void MapSelectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
+ void PlatformUpdateItemSelection(ItemPosition itemPosition, bool selected)
{
- if (parameter is ItemPosition[] items && items != null && items.Length > 0)
- {
- UpdateSelection(handler, items, true);
- }
- }
+ var position = PositionalViewSelector.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex);
- public static void MapDeselectItems(VirtualListViewHandler handler, IVirtualListView virtualListView, object? parameter)
- {
- if (parameter is ItemPosition[] items && items != null && items.Length > 0)
- {
- UpdateSelection(handler, items, false);
- }
- }
+ var elem = itemsRepeater.TryGetElement(position);
- static void UpdateSelection(VirtualListViewHandler handler, ItemPosition[] itemPositions, bool selected)
- {
- foreach (var itemPosition in itemPositions)
+ if (elem is IrElementContainer contentControl)
{
- var position = handler.PositionalViewSelector.GetPosition(itemPosition.SectionIndex, itemPosition.ItemIndex);
-
- var elem = handler.itemsRepeater.TryGetElement(position);
-
- if (elem is IrElementContainer contentControl)
- {
- contentControl.PositionInfo.IsSelected = selected;
+ contentControl.PositionInfo.IsSelected = selected;
- if (contentControl?.VirtualView is IPositionInfo viewPositionInfo)
- viewPositionInfo.IsSelected = selected;
- }
+ if (contentControl?.VirtualView is IPositionInfo viewPositionInfo)
+ viewPositionInfo.IsSelected = selected;
}
}
diff --git a/VirtualListView/PositionInfoExtensions.cs b/VirtualListView/PositionInfoExtensions.cs
index 2933644..a152af3 100644
--- a/VirtualListView/PositionInfoExtensions.cs
+++ b/VirtualListView/PositionInfoExtensions.cs
@@ -18,3 +18,4 @@ public static void Update(this IPositionInfo positionInfo, IPositionInfo newPosi
}
}
}
+
diff --git a/VirtualListView/VirtualListViewHandler.cs b/VirtualListView/VirtualListViewHandler.cs
index f531fa2..31ebc0c 100644
--- a/VirtualListView/VirtualListViewHandler.cs
+++ b/VirtualListView/VirtualListViewHandler.cs
@@ -4,7 +4,7 @@ namespace Microsoft.Maui;
public partial class VirtualListViewHandler
{
- public static PropertyMapper VirtualListViewMapper = new PropertyMapper(VirtualListViewHandler.ViewMapper)
+ public static new IPropertyMapper ViewMapper = new PropertyMapper(Handlers.ViewHandler.ViewMapper)
{
[nameof(IVirtualListView.Adapter)] = MapAdapter,
[nameof(IVirtualListView.Header)] = MapHeader,
@@ -15,20 +15,14 @@ public partial class VirtualListViewHandler
[nameof(IVirtualListView.RefreshAccentColor)] = MapRefreshAccentColor,
[nameof(IVirtualListView.IsRefreshEnabled)] = MapIsRefreshEnabled,
[nameof(IVirtualListView.EmptyView)] = MapEmptyView,
+ [nameof(IVirtualListView.SelectedItems)] = MapSelectedItems,
};
- public static CommandMapper VirtualListViewCommandMapper = new(VirtualListViewHandler.ViewCommandMapper)
+ public static CommandMapper CommandMapper = new(ViewCommandMapper)
{
- [nameof(IVirtualListView.SelectItems)] = MapSelectItems,
- [nameof(IVirtualListView.DeselectItems)] = MapDeselectItems,
};
- public VirtualListViewHandler() : base(VirtualListViewMapper, VirtualListViewCommandMapper)
- {
-
- }
-
- public VirtualListViewHandler(PropertyMapper mapper = null, CommandMapper commandMapper = null) : base(mapper ?? VirtualListViewMapper, commandMapper ?? VirtualListViewCommandMapper)
+ public VirtualListViewHandler() : base(ViewMapper, CommandMapper)
{
}
@@ -67,21 +61,6 @@ void Adapter_OnDataInvalidated(object sender, EventArgs e)
InvalidateData();
}
- readonly object selectedItemsLocker = new object();
- readonly List selectedItems = new List();
-
- public IReadOnlyList SelectedItems
- {
- get
- {
- if (VirtualView.SelectionMode == SelectionMode.None)
- return new List();
-
- lock (selectedItemsLocker)
- return selectedItems.AsReadOnly();
- }
- }
-
public bool IsItemSelected(int sectionIndex, int itemIndex)
{
if (VirtualView is null)
@@ -90,110 +69,38 @@ public bool IsItemSelected(int sectionIndex, int itemIndex)
if (VirtualView.SelectionMode == SelectionMode.None)
return false;
- lock (selectedItemsLocker)
- return selectedItems.Contains(new ItemPosition(sectionIndex, itemIndex));
+ return previousSelections.Contains(new ItemPosition(sectionIndex, itemIndex));
}
- public void SelectItems(params ItemPosition[] paths)
- {
- if (VirtualView is null)
- return;
-
- // Can't select any items in none mode
- if (VirtualView.SelectionMode == SelectionMode.None)
- return;
-
- // Can't select multiple items in single mode
- if (VirtualView.SelectionMode == SelectionMode.Single && selectedItems.Count > 0)
- return;
-
- // Keep track of previous selection state
- var prev = new List(selectedItems);
+ ItemPosition[] previousSelections = Array.Empty();
- lock (selectedItemsLocker)
- {
- var toAdd = new List();
-
- foreach (var path in paths)
- {
- // Check if the item is already selected
- if (selectedItems.Contains(path))
- continue;
-
- toAdd.Add(path);
- }
-
- foreach (var path in toAdd)
- selectedItems.Add(path);
-
- Invoke(nameof(SelectItems), toAdd.ToArray());
- }
-
- // Raise event
- VirtualView.OnSelectedItemsChanged(new SelectedItemsChangedEventArgs(prev, selectedItems));
- }
-
-
-
- public void DeselectItems(params ItemPosition[] paths)
+ public static void MapSelectedItems(VirtualListViewHandler handler, IVirtualListView virtualListView)
{
- if (VirtualView is null)
+ if (handler is null)
return;
- // Nothing to deselect in none mode
- if (VirtualView.SelectionMode == SelectionMode.None)
- return;
-
- // Nothing to deselect in single mode if we have no items selected
- if (VirtualView.SelectionMode == SelectionMode.Single && selectedItems.Count <= 0)
- return;
+ var newSelections = virtualListView?.SelectedItems ?? Array.Empty();
- var prev = new List(selectedItems);
+ if (virtualListView.SelectionMode == SelectionMode.None)
+ newSelections = Array.Empty();
+ else if (virtualListView.SelectionMode == SelectionMode.Single && newSelections.Count > 1)
+ newSelections = newSelections.Take(1).ToArray();
- lock (selectedItemsLocker)
+ // First deselect any previously selected items that aren't in the new set
+ foreach (var itemPosition in handler.previousSelections)
{
- var toRemove = new List();
-
- foreach (var path in paths)
- {
- // If our selection doesn't contain the requested item, we can't deselect it
- if (!selectedItems.Contains(path))
- continue;
-
- toRemove.Add(path);
- }
-
- foreach (var path in toRemove)
- selectedItems.Remove(path);
-
- Invoke(nameof(DeselectItems), toRemove.ToArray());
+ if (!newSelections.Contains(itemPosition))
+ handler.PlatformUpdateItemSelection(itemPosition, false);
}
- // Raise event
- VirtualView.OnSelectedItemsChanged(new SelectedItemsChangedEventArgs(prev, selectedItems));
- }
-
- public void ClearSelection()
- {
- if (VirtualView is null)
- return;
-
- if (VirtualView.SelectionMode == SelectionMode.None)
- return;
-
- var prev = new List(selectedItems);
-
- lock (selectedItemsLocker)
+ // Set all the new state selected to true
+ foreach (var itemPosition in newSelections)
{
- var toRemove = selectedItems.ToArray();
- selectedItems.Clear();
-
- UpdateSelection(this, toRemove, false);
-
- Invoke(nameof(ClearSelection), toRemove);
+ if (!handler.previousSelections.Contains(itemPosition))
+ handler.PlatformUpdateItemSelection(itemPosition, true);
}
- // Raise event
- //VirtualView.OnSelectedItemsChanged(new SelectedItemsChangedEventArgs(prev, selectedItems));
+ // Keep track of the new state for next time it changes
+ handler.previousSelections = newSelections.ToArray();
}
}
\ No newline at end of file