зеркало из https://github.com/DeGsoft/maui-linux.git
482 строки
12 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|