maui-linux/System.Maui.Platform.UAP/CollectionView/ItemsViewRenderer.cs

482 строки
12 KiB
C#

using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading.Tasks;
using global::Windows.UI.Xaml;
using global::Windows.UI.Xaml.Controls;
using global::Windows.UI.Xaml.Data;
using global::Windows.UI.Xaml.Controls.Primitives;
using global::Windows.Foundation;
using System.Maui.Internals;
using UwpScrollBarVisibility = global::Windows.UI.Xaml.Controls.ScrollBarVisibility;
using UWPApp = global::Windows.UI.Xaml.Application;
using UWPDataTemplate = global::Windows.UI.Xaml.DataTemplate;
namespace System.Maui.Platform.UWP
{
public abstract class ItemsViewRenderer<TItemsView> : ViewRenderer<TItemsView, ListViewBase>
where TItemsView : ItemsView
{
protected CollectionViewSource CollectionViewSource;
UwpScrollBarVisibility? _defaultHorizontalScrollVisibility;
UwpScrollBarVisibility? _defaultVerticalScrollVisibility;
FrameworkElement _emptyView;
View _formsEmptyView;
ScrollViewer _scrollViewer;
internal double _previousHorizontalOffset;
internal double _previousVerticalOffset;
protected ListViewBase ListViewBase { get; private set; }
protected UWPDataTemplate ViewTemplate => (UWPDataTemplate)UWPApp.Current.Resources["View"];
protected UWPDataTemplate ItemsViewTemplate => (UWPDataTemplate)UWPApp.Current.Resources["ItemsViewDefaultTemplate"];
protected ItemsViewRenderer()
{
AutoPackage = false;
}
protected TItemsView ItemsView => Element;
protected ItemsControl ItemsControl { get; private set; }
protected override void OnElementChanged(ElementChangedEventArgs<TItemsView> args)
{
base.OnElementChanged(args);
TearDownOldElement(args.OldElement);
SetUpNewElement(args.NewElement);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs changedProperty)
{
base.OnElementPropertyChanged(sender, changedProperty);
if (changedProperty.Is(System.Maui.ItemsView.ItemsSourceProperty))
{
UpdateItemsSource();
}
else if (changedProperty.Is(System.Maui.ItemsView.ItemTemplateProperty))
{
UpdateItemTemplate();
}
else if (changedProperty.Is(System.Maui.ItemsView.HorizontalScrollBarVisibilityProperty))
{
UpdateHorizontalScrollBarVisibility();
}
else if (changedProperty.Is(System.Maui.ItemsView.VerticalScrollBarVisibilityProperty))
{
UpdateVerticalScrollBarVisibility();
}
else if (changedProperty.IsOneOf(System.Maui.ItemsView.EmptyViewProperty,
System.Maui.ItemsView.EmptyViewTemplateProperty))
{
UpdateEmptyView();
}
}
protected abstract ListViewBase SelectListViewBase();
protected abstract void HandleLayoutPropertyChanged(PropertyChangedEventArgs property);
protected abstract IItemsLayout Layout { get; }
protected virtual void UpdateItemsSource()
{
if (ListViewBase == null)
{
return;
}
CleanUpCollectionViewSource();
if (Element.ItemsSource == null)
{
return;
}
CollectionViewSource = CreateCollectionViewSource();
if (CollectionViewSource?.Source is INotifyCollectionChanged incc)
{
incc.CollectionChanged += ItemsChanged;
}
ListViewBase.ItemsSource = CollectionViewSource.View;
UpdateEmptyViewVisibility();
}
protected virtual void CleanUpCollectionViewSource()
{
if (CollectionViewSource != null)
{
if (CollectionViewSource.Source is ObservableItemTemplateCollection observableItemTemplateCollection)
{
observableItemTemplateCollection.CleanUp();
}
}
if (Element?.ItemsSource == null)
{
if (CollectionViewSource?.Source is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsChanged;
}
if (CollectionViewSource != null)
{
CollectionViewSource.Source = null;
}
CollectionViewSource = null;
ListViewBase.ItemsSource = null;
return;
}
}
protected virtual CollectionViewSource CreateCollectionViewSource()
{
var itemsSource = Element.ItemsSource;
var itemTemplate = Element.ItemTemplate;
if (itemTemplate != null)
{
return new CollectionViewSource
{
Source = TemplatedItemSourceFactory.Create(itemsSource, itemTemplate, Element),
IsSourceGrouped = false
};
}
return new CollectionViewSource
{
Source = itemsSource,
IsSourceGrouped = false
};
}
void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateEmptyViewVisibility();
}
protected virtual void UpdateItemTemplate()
{
if (Element == null || ListViewBase == null)
{
return;
}
ListViewBase.ItemTemplate = Element.ItemTemplate == null ? null : ItemsViewTemplate;
UpdateItemsSource();
}
void LayoutPropertyChanged(object sender, PropertyChangedEventArgs property)
{
HandleLayoutPropertyChanged(property);
}
protected virtual void SetUpNewElement(ItemsView newElement)
{
if (newElement == null)
{
return;
}
if (ListViewBase == null)
{
ListViewBase = SelectListViewBase();
ListViewBase.IsSynchronizedWithCurrentItem = false;
FindScrollViewer(ListViewBase);
Layout.PropertyChanged += LayoutPropertyChanged;
SetNativeControl(ListViewBase);
}
UpdateItemTemplate();
UpdateItemsSource();
UpdateVerticalScrollBarVisibility();
UpdateHorizontalScrollBarVisibility();
UpdateEmptyView();
// Listen for ScrollTo requests
newElement.ScrollToRequested += ScrollToRequested;
}
protected virtual void TearDownOldElement(ItemsView oldElement)
{
if (oldElement == null)
{
return;
}
if (Layout != null)
{
// Stop tracking the old layout
Layout.PropertyChanged -= LayoutPropertyChanged;
}
// Stop listening for ScrollTo requests
oldElement.ScrollToRequested -= ScrollToRequested;
if (CollectionViewSource != null)
{
CleanUpCollectionViewSource();
}
if (ListViewBase != null)
{
ListViewBase.ItemsSource = null;
}
if (_scrollViewer != null)
{
_scrollViewer.ViewChanged -= OnScrollViewChanged;
}
}
void UpdateVerticalScrollBarVisibility()
{
if (_defaultVerticalScrollVisibility == null)
_defaultVerticalScrollVisibility = ScrollViewer.GetVerticalScrollBarVisibility(Control);
switch (Element.VerticalScrollBarVisibility)
{
case (ScrollBarVisibility.Always):
ScrollViewer.SetVerticalScrollBarVisibility(Control, UwpScrollBarVisibility.Visible);
break;
case (ScrollBarVisibility.Never):
ScrollViewer.SetVerticalScrollBarVisibility(Control, UwpScrollBarVisibility.Hidden);
break;
case (ScrollBarVisibility.Default):
ScrollViewer.SetVerticalScrollBarVisibility(Control, _defaultVerticalScrollVisibility.Value);
break;
}
}
void UpdateHorizontalScrollBarVisibility()
{
if (_defaultHorizontalScrollVisibility == null)
_defaultHorizontalScrollVisibility = ScrollViewer.GetHorizontalScrollBarVisibility(Control);
switch (Element.HorizontalScrollBarVisibility)
{
case (ScrollBarVisibility.Always):
ScrollViewer.SetHorizontalScrollBarVisibility(Control, UwpScrollBarVisibility.Visible);
break;
case (ScrollBarVisibility.Never):
ScrollViewer.SetHorizontalScrollBarVisibility(Control, UwpScrollBarVisibility.Hidden);
break;
case (ScrollBarVisibility.Default):
ScrollViewer.SetHorizontalScrollBarVisibility(Control, _defaultHorizontalScrollVisibility.Value);
break;
}
}
protected virtual void FindScrollViewer(ListViewBase listView)
{
var scrollViewer = listView.GetFirstDescendant<ScrollViewer>();
if (scrollViewer != null)
{
_scrollViewer = scrollViewer;
_scrollViewer.ViewChanged += OnScrollViewChanged;
return;
}
void ListViewLoaded(object sender, RoutedEventArgs e)
{
var lv = (ListViewBase)sender;
lv.Loaded -= ListViewLoaded;
FindScrollViewer(listView);
}
listView.Loaded += ListViewLoaded;
}
protected virtual async Task ScrollTo(ScrollToRequestEventArgs args)
{
if (!(Control is ListViewBase list))
{
return;
}
var item = FindBoundItem(args);
if (item == null)
{
// Item wasn't found in the list, so there's nothing to scroll to
return;
}
if (args.IsAnimated)
{
await ScrollHelpers.AnimateToItemAsync(list, item, args.ScrollToPosition);
}
else
{
await ScrollHelpers.JumpToItemAsync(list, item, args.ScrollToPosition);
}
}
async void ScrollToRequested(object sender, ScrollToRequestEventArgs args)
{
await ScrollTo(args);
}
object FindBoundItem(ScrollToRequestEventArgs args)
{
if (args.Mode == ScrollToMode.Position)
{
if (args.Index >= CollectionViewSource.View.Count)
{
return null;
}
return CollectionViewSource.View[args.Index];
}
if (Element.ItemTemplate == null)
{
return args.Item;
}
for (int n = 0; n < CollectionViewSource.View.Count; n++)
{
if (CollectionViewSource.View[n] is ItemTemplateContext pair)
{
if (pair.Item == args.Item)
{
return CollectionViewSource.View[n];
}
}
}
return null;
}
protected virtual void UpdateEmptyView()
{
if (Element == null || ListViewBase == null)
{
return;
}
var emptyView = Element.EmptyView;
if (emptyView == null)
{
return;
}
switch (emptyView)
{
case string text:
_emptyView = new TextBlock
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Text = text
};
break;
case View view:
_emptyView = RealizeEmptyView(view);
break;
default:
_emptyView = RealizeEmptyViewTemplate(emptyView, Element.EmptyViewTemplate);
break;
}
(ListViewBase as IEmptyView)?.SetEmptyView(_emptyView, _formsEmptyView);
UpdateEmptyViewVisibility();
}
FrameworkElement RealizeEmptyViewTemplate(object bindingContext, DataTemplate emptyViewTemplate)
{
if (emptyViewTemplate == null)
{
return new TextBlock
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Text = bindingContext.ToString()
};
}
var template = emptyViewTemplate.SelectDataTemplate(bindingContext, null);
var view = template.CreateContent() as View;
view.BindingContext = bindingContext;
return RealizeEmptyView(view);
}
FrameworkElement RealizeEmptyView(View view)
{
_formsEmptyView = view;
return view.GetOrCreateRenderer().ContainerElement;
}
protected virtual void UpdateEmptyViewVisibility()
{
if (_emptyView != null && ListViewBase is IEmptyView emptyView)
{
emptyView.EmptyViewVisibility = (CollectionViewSource?.View?.Count ?? 0) == 0
? Visibility.Visible
: Visibility.Collapsed;
if (emptyView.EmptyViewVisibility == Visibility.Visible)
{
if (ActualWidth >= 0 && ActualHeight >= 0)
{
_formsEmptyView?.Layout(new Rectangle(0, 0, ActualWidth, ActualHeight));
}
}
}
}
internal void HandleScroll(ScrollViewer scrollViewer)
{
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
HorizontalOffset = scrollViewer.HorizontalOffset,
HorizontalDelta = scrollViewer.HorizontalOffset - _previousHorizontalOffset,
VerticalOffset = scrollViewer.VerticalOffset,
VerticalDelta = scrollViewer.VerticalOffset - _previousVerticalOffset,
};
_previousHorizontalOffset = scrollViewer.HorizontalOffset;
_previousVerticalOffset = scrollViewer.VerticalOffset;
var layoutOrientaton = ItemsLayoutOrientation.Vertical;
bool goingNext = true;
switch (Layout)
{
case LinearItemsLayout linearItemsLayout:
layoutOrientaton = linearItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal ? ItemsLayoutOrientation.Horizontal : ItemsLayoutOrientation.Vertical;
goingNext = itemsViewScrolledEventArgs.HorizontalDelta > 0;
break;
case GridItemsLayout gridItemsLayout:
layoutOrientaton = gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal ? ItemsLayoutOrientation.Horizontal : ItemsLayoutOrientation.Vertical;
goingNext = itemsViewScrolledEventArgs.VerticalDelta > 0;
break;
default:
break;
}
var visibleIndexes = ListViewBase.GetVisibleIndexes(layoutOrientaton, goingNext);
itemsViewScrolledEventArgs.FirstVisibleItemIndex = visibleIndexes.firstVisibleItemIndex;
itemsViewScrolledEventArgs.CenterItemIndex = visibleIndexes.centerItemIndex;
itemsViewScrolledEventArgs.LastVisibleItemIndex = visibleIndexes.lastVisibleItemIndex;
Element.SendScrolled(itemsViewScrolledEventArgs);
}
void OnScrollViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
HandleScroll(_scrollViewer);
}
}
}