зеркало из https://github.com/DeGsoft/maui-linux.git
827 строки
24 KiB
C#
827 строки
24 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using global::Windows.Foundation;
|
|
using global::Windows.UI.Xaml;
|
|
using global::Windows.UI.Xaml.Automation.Peers;
|
|
using global::Windows.UI.Xaml.Controls;
|
|
using global::Windows.UI.Xaml.Data;
|
|
using global::Windows.UI.Xaml.Input;
|
|
using global::Windows.UI.Xaml.Media;
|
|
using WListView = global::Windows.UI.Xaml.Controls.ListView;
|
|
using WBinding = global::Windows.UI.Xaml.Data.Binding;
|
|
using WApp = global::Windows.UI.Xaml.Application;
|
|
using System.Maui.Internals;
|
|
using System.Maui.PlatformConfiguration.WindowsSpecific;
|
|
using Specifics = System.Maui.PlatformConfiguration.WindowsSpecific.ListView;
|
|
using System.Collections.ObjectModel;
|
|
using UwpScrollBarVisibility = global::Windows.UI.Xaml.Controls.ScrollBarVisibility;
|
|
using WSelectionChangedEventArgs = global::Windows.UI.Xaml.Controls.SelectionChangedEventArgs;
|
|
|
|
namespace System.Maui.Platform.UWP
|
|
{
|
|
public class ListViewRenderer : ViewRenderer<ListView, FrameworkElement>
|
|
{
|
|
ITemplatedItemsView<Cell> TemplatedItemsView => Element;
|
|
bool _collectionIsWrapped;
|
|
IList _collection = null;
|
|
bool _itemWasClicked;
|
|
bool _subscribedToItemClick;
|
|
bool _subscribedToTapped;
|
|
bool _disposed;
|
|
CollectionViewSource _collectionViewSource;
|
|
|
|
UwpScrollBarVisibility? _defaultHorizontalScrollVisibility;
|
|
UwpScrollBarVisibility? _defaultVerticalScrollVisibility;
|
|
|
|
protected WListView List { get; private set; }
|
|
|
|
protected class ListViewTransparent : WListView
|
|
{
|
|
public ListViewTransparent() : base() { }
|
|
|
|
// Container is not created when the item is null.
|
|
// To prevent this, base container preparationan receives an empty object.
|
|
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
|
|
=> base.PrepareContainerForItemOverride(element, item ?? new object());
|
|
}
|
|
|
|
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
|
{
|
|
base.OnElementChanged(e);
|
|
|
|
if (e.OldElement != null)
|
|
{
|
|
e.OldElement.ItemSelected -= OnElementItemSelected;
|
|
e.OldElement.ScrollToRequested -= OnElementScrollToRequested;
|
|
((ITemplatedItemsView<Cell>)e.OldElement).TemplatedItems.CollectionChanged -= OnCollectionChanged;
|
|
if (Control != null)
|
|
{
|
|
Control.Loaded -= ControlOnLoaded;
|
|
}
|
|
}
|
|
|
|
if (e.NewElement != null)
|
|
{
|
|
e.NewElement.ItemSelected += OnElementItemSelected;
|
|
e.NewElement.ScrollToRequested += OnElementScrollToRequested;
|
|
((ITemplatedItemsView<Cell>)e.NewElement).TemplatedItems.CollectionChanged += OnCollectionChanged;
|
|
|
|
if (List == null)
|
|
{
|
|
List = new ListViewTransparent
|
|
{
|
|
IsSynchronizedWithCurrentItem = false,
|
|
ItemTemplate = (global::Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["CellTemplate"],
|
|
HeaderTemplate = (global::Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["View"],
|
|
FooterTemplate = (global::Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["View"],
|
|
ItemContainerStyle = (global::Windows.UI.Xaml.Style)WApp.Current.Resources["FormsListViewItem"],
|
|
GroupStyleSelector = (GroupStyleSelector)WApp.Current.Resources["ListViewGroupSelector"]
|
|
};
|
|
|
|
List.SelectionChanged += OnControlSelectionChanged;
|
|
}
|
|
|
|
ReloadData();
|
|
|
|
UpdateGrouping();
|
|
UpdateHeader();
|
|
UpdateFooter();
|
|
UpdateSelectionMode();
|
|
UpdateWindowsSpecificSelectionMode();
|
|
ClearSizeEstimate();
|
|
UpdateVerticalScrollBarVisibility();
|
|
UpdateHorizontalScrollBarVisibility();
|
|
|
|
if (Control != null)
|
|
{
|
|
Control.Loaded += ControlOnLoaded;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ControlOnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
var scrollViewer = GetScrollViewer();
|
|
scrollViewer?.RegisterPropertyChangedCallback(ScrollViewer.VerticalOffsetProperty, (o, dp) =>
|
|
{
|
|
var args = new ScrolledEventArgs(0, _scrollViewer.VerticalOffset);
|
|
Element?.SendScrolled(args);
|
|
});
|
|
}
|
|
|
|
bool IsObservableCollection(object source)
|
|
{
|
|
return source is INotifyCollectionChanged && source is IList;
|
|
}
|
|
|
|
void ReloadData()
|
|
{
|
|
var isStillTheSameUnderlyingItemsSource = _collection != null && object.ReferenceEquals(_collection, Element?.ItemsSource);
|
|
|
|
if (Element?.ItemsSource == null)
|
|
{
|
|
_collection = null;
|
|
}
|
|
else
|
|
{
|
|
_collectionIsWrapped = !IsObservableCollection(Element.ItemsSource);
|
|
|
|
if (_collectionIsWrapped)
|
|
{
|
|
_collection = new ObservableCollection<object>();
|
|
foreach (var item in Element.ItemsSource)
|
|
_collection.Add(item);
|
|
}
|
|
else if(!object.ReferenceEquals(_collection, Element.ItemsSource))
|
|
{
|
|
_collection = (IList)Element.ItemsSource;
|
|
}
|
|
}
|
|
|
|
if (isStillTheSameUnderlyingItemsSource && _collectionViewSource != null)
|
|
return;
|
|
|
|
if (_collectionViewSource != null)
|
|
_collectionViewSource.Source = null;
|
|
|
|
_collectionViewSource = new CollectionViewSource
|
|
{
|
|
Source = _collection,
|
|
IsSourceGrouped = Element.IsGroupingEnabled
|
|
};
|
|
|
|
List.ItemsSource = _collectionViewSource.View;
|
|
}
|
|
|
|
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (_collectionIsWrapped && _collection != null)
|
|
{
|
|
switch (e.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
if (e.NewStartingIndex < 0)
|
|
goto case NotifyCollectionChangedAction.Reset;
|
|
|
|
// if a NewStartingIndex that's too high is passed in just add the items.
|
|
// I realize this is enforcing bad behavior but prior to this synchronization
|
|
// code being added it wouldn't cause the app to crash whereas now it does
|
|
// so this code accounts for that in order to ensure smooth sailing for the user
|
|
if (e.NewStartingIndex >= _collection.Count)
|
|
{
|
|
for (int i = 0; i < e.NewItems.Count; i++)
|
|
_collection.Add((e.NewItems[i] as BindableObject).BindingContext);
|
|
}
|
|
else
|
|
{
|
|
for (int i = e.NewItems.Count - 1; i >= 0; i--)
|
|
_collection.Insert(e.NewStartingIndex, (e.NewItems[i] as BindableObject).BindingContext);
|
|
}
|
|
|
|
break;
|
|
case NotifyCollectionChangedAction.Remove:
|
|
for (int i = e.OldItems.Count - 1; i >= 0; i--)
|
|
_collection.RemoveAt(e.OldStartingIndex);
|
|
break;
|
|
case NotifyCollectionChangedAction.Move:
|
|
{
|
|
var collection = (ObservableCollection<object>)_collection;
|
|
for (var i = 0; i < e.OldItems.Count; i++)
|
|
{
|
|
var oldi = e.OldStartingIndex;
|
|
var newi = e.NewStartingIndex;
|
|
|
|
if (e.NewStartingIndex < e.OldStartingIndex)
|
|
{
|
|
oldi += i;
|
|
newi += i;
|
|
}
|
|
|
|
collection.Move(oldi, newi);
|
|
}
|
|
}
|
|
break;
|
|
case NotifyCollectionChangedAction.Replace:
|
|
{
|
|
var collection = (ObservableCollection<object>)_collection;
|
|
var newi = e.NewStartingIndex;
|
|
for (var i = 0; i < e.NewItems.Count; i++)
|
|
{
|
|
newi += i;
|
|
collection[newi] = (e.NewItems[i] as BindableObject).BindingContext;
|
|
}
|
|
}
|
|
break;
|
|
case NotifyCollectionChangedAction.Reset:
|
|
default:
|
|
ClearSizeEstimate();
|
|
ReloadData();
|
|
break;
|
|
}
|
|
}
|
|
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
|
{
|
|
ClearSizeEstimate();
|
|
ReloadData();
|
|
}
|
|
|
|
if (Element.Dispatcher == null)
|
|
Device.BeginInvokeOnMainThread(() => List?.UpdateLayout());
|
|
else
|
|
Element.Dispatcher.BeginInvokeOnMainThread(() => List?.UpdateLayout());
|
|
}
|
|
|
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
base.OnElementPropertyChanged(sender, e);
|
|
|
|
if (e.PropertyName == ListView.IsGroupingEnabledProperty.PropertyName)
|
|
{
|
|
UpdateGrouping();
|
|
}
|
|
else if (e.PropertyName == ListView.HeaderProperty.PropertyName || e.PropertyName == "HeaderElement")
|
|
{
|
|
UpdateHeader();
|
|
}
|
|
else if (e.PropertyName == ListView.FooterProperty.PropertyName || e.PropertyName == "FooterElement")
|
|
{
|
|
UpdateFooter();
|
|
}
|
|
else if (e.PropertyName == ListView.RowHeightProperty.PropertyName)
|
|
{
|
|
ClearSizeEstimate();
|
|
}
|
|
else if (e.PropertyName == ListView.HasUnevenRowsProperty.PropertyName)
|
|
{
|
|
ClearSizeEstimate();
|
|
}
|
|
else if (e.PropertyName == ListView.ItemTemplateProperty.PropertyName)
|
|
{
|
|
ClearSizeEstimate();
|
|
}
|
|
else if (e.PropertyName == ListView.SelectionModeProperty.PropertyName)
|
|
{
|
|
UpdateSelectionMode();
|
|
}
|
|
else if (e.PropertyName == Specifics.SelectionModeProperty.PropertyName)
|
|
{
|
|
UpdateWindowsSpecificSelectionMode();
|
|
}
|
|
else if (e.PropertyName == ListView.VerticalScrollBarVisibilityProperty.PropertyName)
|
|
{
|
|
UpdateVerticalScrollBarVisibility();
|
|
}
|
|
else if (e.PropertyName == ListView.HorizontalScrollBarVisibilityProperty.PropertyName)
|
|
{
|
|
UpdateHorizontalScrollBarVisibility();
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_disposed = true;
|
|
|
|
if (disposing)
|
|
{
|
|
if (List != null)
|
|
{
|
|
foreach (ViewToRendererConverter.WrapperControl wrapperControl in FindDescendants<ViewToRendererConverter.WrapperControl>(List))
|
|
{
|
|
wrapperControl.CleanUp();
|
|
}
|
|
|
|
if (_subscribedToTapped)
|
|
{
|
|
_subscribedToTapped = false;
|
|
List.Tapped -= ListOnTapped;
|
|
}
|
|
if (_subscribedToItemClick)
|
|
{
|
|
_subscribedToItemClick = false;
|
|
List.ItemClick -= OnListItemClicked;
|
|
}
|
|
|
|
List.SelectionChanged -= OnControlSelectionChanged;
|
|
if (_collectionViewSource != null)
|
|
_collectionViewSource.Source = null;
|
|
|
|
List.DataContext = null;
|
|
|
|
// Leaving this here as a warning because setting this to null causes
|
|
// an AccessViolationException if you run Issue1975
|
|
// List.ItemsSource = null;
|
|
|
|
List = null;
|
|
}
|
|
|
|
if (_zoom != null)
|
|
{
|
|
_zoom.ViewChangeCompleted -= OnViewChangeCompleted;
|
|
_zoom = null;
|
|
}
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
static IEnumerable<T> FindDescendants<T>(DependencyObject dobj) where T : DependencyObject
|
|
{
|
|
int count = VisualTreeHelper.GetChildrenCount(dobj);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
DependencyObject element = VisualTreeHelper.GetChild(dobj, i);
|
|
if (element is T)
|
|
yield return (T)element;
|
|
|
|
foreach (T descendant in FindDescendants<T>(element))
|
|
yield return descendant;
|
|
}
|
|
}
|
|
|
|
sealed class BrushedElement
|
|
{
|
|
public BrushedElement(FrameworkElement element, WBinding brushBinding = null, Brush brush = null)
|
|
{
|
|
Element = element;
|
|
BrushBinding = brushBinding;
|
|
Brush = brush;
|
|
}
|
|
|
|
public Brush Brush { get; }
|
|
|
|
public WBinding BrushBinding { get; }
|
|
|
|
public FrameworkElement Element { get; }
|
|
|
|
public bool IsBound
|
|
{
|
|
get { return BrushBinding != null; }
|
|
}
|
|
}
|
|
|
|
SemanticZoom _zoom;
|
|
ScrollViewer _scrollViewer;
|
|
ContentControl _headerControl;
|
|
readonly List<BrushedElement> _highlightedElements = new List<BrushedElement>();
|
|
|
|
void ClearSizeEstimate()
|
|
{
|
|
Element.ClearValue(CellControl.MeasuredEstimateProperty);
|
|
}
|
|
|
|
void UpdateFooter()
|
|
{
|
|
List.Footer = Element.FooterElement;
|
|
}
|
|
|
|
void UpdateHeader()
|
|
{
|
|
List.Header = Element.HeaderElement;
|
|
}
|
|
|
|
void UpdateGrouping()
|
|
{
|
|
bool grouping = Element.IsGroupingEnabled;
|
|
|
|
if (_collectionViewSource != null)
|
|
_collectionViewSource.IsSourceGrouped = grouping;
|
|
|
|
var templatedItems = TemplatedItemsView.TemplatedItems;
|
|
if (grouping && templatedItems.ShortNames != null)
|
|
{
|
|
if (_zoom == null)
|
|
{
|
|
ScrollViewer.SetIsVerticalScrollChainingEnabled(List, false);
|
|
|
|
var grid = new GridView { ItemsSource = templatedItems.ShortNames, Style = (global::Windows.UI.Xaml.Style)WApp.Current.Resources["JumpListGrid"] };
|
|
|
|
ScrollViewer.SetIsHorizontalScrollChainingEnabled(grid, false);
|
|
|
|
_zoom = new SemanticZoom { IsZoomOutButtonEnabled = false, ZoomedOutView = grid };
|
|
|
|
// Since we reuse our ScrollTo, we have to wait until the change completes or ChangeView has odd behavior.
|
|
_zoom.ViewChangeCompleted += OnViewChangeCompleted;
|
|
|
|
// Specific order to let SNC unparent the ListView for us
|
|
SetNativeControl(_zoom);
|
|
_zoom.ZoomedInView = List;
|
|
}
|
|
else
|
|
{
|
|
_zoom.CanChangeViews = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_zoom != null)
|
|
_zoom.CanChangeViews = false;
|
|
else if (List != Control)
|
|
SetNativeControl(List);
|
|
}
|
|
}
|
|
|
|
void UpdateSelectionMode()
|
|
{
|
|
if (Element.SelectionMode == ListViewSelectionMode.None)
|
|
{
|
|
List.SelectionMode = global::Windows.UI.Xaml.Controls.ListViewSelectionMode.None;
|
|
List.SelectedIndex = -1;
|
|
Element.SelectedItem = null;
|
|
}
|
|
else if (Element.SelectionMode == ListViewSelectionMode.Single)
|
|
{
|
|
List.SelectionMode = global::Windows.UI.Xaml.Controls.ListViewSelectionMode.Single;
|
|
|
|
// UWP seems to reset the selected item when SelectionMode is set, make sure our items stays selected by doing this call
|
|
if (Element.SelectedItem != null)
|
|
OnElementItemSelected(null, new SelectedItemChangedEventArgs(Element.SelectedItem, TemplatedItemsView.TemplatedItems.GetGlobalIndexOfItem(Element.SelectedItem)));
|
|
}
|
|
}
|
|
|
|
void UpdateWindowsSpecificSelectionMode()
|
|
{
|
|
if (Element.OnThisPlatform().GetSelectionMode() == PlatformConfiguration.WindowsSpecific.ListViewSelectionMode.Accessible)
|
|
{
|
|
// Using Tapped will disable the ability to use the Enter key
|
|
List.IsItemClickEnabled = true;
|
|
if (!_subscribedToItemClick)
|
|
{
|
|
_subscribedToItemClick = true;
|
|
List.ItemClick += OnListItemClicked;
|
|
}
|
|
|
|
if (_subscribedToTapped)
|
|
{
|
|
_subscribedToTapped = false;
|
|
List.Tapped -= ListOnTapped;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// In order to support tapping on elements within a list item, we handle
|
|
// ListView.Tapped (which can be handled by child elements in the list items
|
|
// and prevented from bubbling up) rather than ListView.ItemClick
|
|
if (!_subscribedToTapped)
|
|
{
|
|
_subscribedToTapped = true;
|
|
List.Tapped += ListOnTapped;
|
|
}
|
|
|
|
List.IsItemClickEnabled = false;
|
|
if (_subscribedToItemClick)
|
|
{
|
|
_subscribedToItemClick = false;
|
|
List.ItemClick -= OnListItemClicked;
|
|
}
|
|
}
|
|
}
|
|
|
|
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, (UwpScrollBarVisibility)_defaultVerticalScrollVisibility);
|
|
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, (UwpScrollBarVisibility)_defaultHorizontalScrollVisibility);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async void OnViewChangeCompleted(object sender, SemanticZoomViewChangedEventArgs e)
|
|
{
|
|
if (e.IsSourceZoomedInView)
|
|
return;
|
|
|
|
// HACK: Technically more than one short name could be the same, this will potentially find the wrong one in that case
|
|
var item = (string)e.SourceItem.Item;
|
|
|
|
var templatedItems = TemplatedItemsView.TemplatedItems;
|
|
int index = templatedItems.ShortNames.IndexOf(item);
|
|
if (index == -1)
|
|
return;
|
|
|
|
var til = templatedItems.GetGroup(index);
|
|
if (til.Count == 0)
|
|
return; // FIXME
|
|
|
|
// Delay until after the SemanticZoom change _actually_ finishes, fixes tons of odd issues on Phone w/ virtualization.
|
|
if (Device.Idiom == TargetIdiom.Phone)
|
|
await Task.Delay(1);
|
|
|
|
IListProxy listProxy = til.ListProxy;
|
|
ScrollTo(listProxy.ProxiedEnumerable, listProxy[0], ScrollToPosition.Start, true, true);
|
|
}
|
|
|
|
bool ScrollToItemWithAnimation(ScrollViewer viewer, object item)
|
|
{
|
|
var selectorItem = List.ContainerFromItem(item) as global::Windows.UI.Xaml.Controls.Primitives.SelectorItem;
|
|
var transform = selectorItem?.TransformToVisual(viewer.Content as UIElement);
|
|
var position = transform?.TransformPoint(new global::Windows.Foundation.Point(0, 0));
|
|
if (!position.HasValue)
|
|
return false;
|
|
// scroll with animation
|
|
viewer.ChangeView(position.Value.X, position.Value.Y, null);
|
|
return true;
|
|
}
|
|
|
|
#pragma warning disable 1998 // considered for removal
|
|
async void ScrollTo(object group, object item, ScrollToPosition toPosition, bool shouldAnimate, bool includeGroup = false, bool previouslyFailed = false)
|
|
#pragma warning restore 1998
|
|
{
|
|
ScrollViewer viewer = GetScrollViewer();
|
|
if (viewer == null)
|
|
{
|
|
RoutedEventHandler loadedHandler = null;
|
|
loadedHandler = async (o, e) =>
|
|
{
|
|
List.Loaded -= loadedHandler;
|
|
|
|
// Here we try to avoid an exception, see explanation at bottom
|
|
await Dispatcher.RunIdleAsync(args => { ScrollTo(group, item, toPosition, shouldAnimate, includeGroup); });
|
|
};
|
|
List.Loaded += loadedHandler;
|
|
return;
|
|
}
|
|
var templatedItems = TemplatedItemsView.TemplatedItems;
|
|
Tuple<int, int> location = templatedItems.GetGroupAndIndexOfItem(group, item);
|
|
if (location.Item1 == -1 || location.Item2 == -1)
|
|
return;
|
|
|
|
object[] t = templatedItems.GetGroup(location.Item1).ItemsSource.Cast<object>().ToArray();
|
|
object c = t[location.Item2];
|
|
|
|
// scroll to desired item with animation
|
|
if (shouldAnimate && ScrollToItemWithAnimation(viewer, c))
|
|
return;
|
|
|
|
double viewportHeight = viewer.ViewportHeight;
|
|
|
|
var semanticLocation = new SemanticZoomLocation { Item = c };
|
|
|
|
// async scrolling
|
|
await Dispatcher.RunAsync(global::Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
|
{
|
|
switch (toPosition)
|
|
{
|
|
case ScrollToPosition.Start:
|
|
{
|
|
|
|
List.ScrollIntoView(c, ScrollIntoViewAlignment.Leading);
|
|
return;
|
|
}
|
|
|
|
case ScrollToPosition.MakeVisible:
|
|
{
|
|
List.ScrollIntoView(c, ScrollIntoViewAlignment.Default);
|
|
return;
|
|
}
|
|
|
|
case ScrollToPosition.End:
|
|
case ScrollToPosition.Center:
|
|
{
|
|
var content = (FrameworkElement)List.ItemTemplate.LoadContent();
|
|
content.DataContext = c;
|
|
content.Measure(new global::Windows.Foundation.Size(viewer.ActualWidth, double.PositiveInfinity));
|
|
|
|
double tHeight = content.DesiredSize.Height;
|
|
|
|
if (toPosition == ScrollToPosition.Center)
|
|
semanticLocation.Bounds = new Rect(0, viewportHeight / 2 - tHeight / 2, 0, 0);
|
|
else
|
|
semanticLocation.Bounds = new Rect(0, viewportHeight - tHeight, 0, 0);
|
|
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Waiting for loaded doesn't seem to be enough anymore; the ScrollViewer does not appear until after Loaded.
|
|
// Even if the ScrollViewer is present, an invoke at low priority fails (E_FAIL) presumably because the items are
|
|
// still loading. An invoke at idle sometimes work, but isn't reliable enough, so we'll just have to commit
|
|
// treason and use a blanket catch for the E_FAIL and try again.
|
|
try
|
|
{
|
|
List.MakeVisible(semanticLocation);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
if (previouslyFailed)
|
|
return;
|
|
|
|
Task.Delay(1).ContinueWith(ct => { ScrollTo(group, item, toPosition, shouldAnimate, includeGroup, true); }, TaskScheduler.FromCurrentSynchronizationContext()).WatchForError();
|
|
}
|
|
}
|
|
|
|
void OnElementScrollToRequested(object sender, ScrollToRequestedEventArgs e)
|
|
{
|
|
var scrollArgs = (ITemplatedItemsListScrollToRequestedEventArgs)e;
|
|
ScrollTo(scrollArgs.Group, scrollArgs.Item, e.Position, e.ShouldAnimate);
|
|
}
|
|
|
|
T GetFirstDescendant<T>(DependencyObject element) where T : FrameworkElement
|
|
{
|
|
int count = VisualTreeHelper.GetChildrenCount(element);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
DependencyObject child = VisualTreeHelper.GetChild(element, i);
|
|
|
|
T target = child as T ?? GetFirstDescendant<T>(child);
|
|
if (target != null)
|
|
return target;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
ContentControl GetHeaderControl()
|
|
{
|
|
if (_headerControl == null)
|
|
{
|
|
ScrollViewer viewer = GetScrollViewer();
|
|
if (viewer == null)
|
|
return null;
|
|
|
|
var presenter = GetFirstDescendant<ItemsPresenter>(viewer);
|
|
if (presenter == null)
|
|
return null;
|
|
|
|
_headerControl = GetFirstDescendant<ContentControl>(presenter);
|
|
}
|
|
|
|
return _headerControl;
|
|
}
|
|
|
|
ScrollViewer GetScrollViewer()
|
|
{
|
|
if (_scrollViewer == null)
|
|
{
|
|
_scrollViewer = List.GetFirstDescendant<ScrollViewer>();
|
|
}
|
|
|
|
return _scrollViewer;
|
|
}
|
|
|
|
void ListOnTapped(object sender, TappedRoutedEventArgs args)
|
|
{
|
|
var orig = args.OriginalSource as DependencyObject;
|
|
int index = -1;
|
|
|
|
// Work our way up the tree until we find the actual list item
|
|
// the user tapped on
|
|
|
|
while (orig != null && orig != List)
|
|
{
|
|
var lv = orig as ListViewItem;
|
|
if (lv != null)
|
|
{
|
|
index = TemplatedItemsView.TemplatedItems.GetGlobalIndexOfItem(lv.Content);
|
|
break;
|
|
}
|
|
|
|
orig = VisualTreeHelper.GetParent(orig);
|
|
}
|
|
|
|
if (index > -1)
|
|
{
|
|
OnListItemClicked(index);
|
|
}
|
|
}
|
|
|
|
void OnElementItemSelected(object sender, SelectedItemChangedEventArgs e)
|
|
{
|
|
if (Element == null)
|
|
return;
|
|
|
|
if (_deferSelection)
|
|
{
|
|
// If we get more than one of these, that's okay; we only want the latest one
|
|
_deferredSelectedItemChangedEvent = new Tuple<object, SelectedItemChangedEventArgs>(sender, e);
|
|
return;
|
|
}
|
|
|
|
if (e.SelectedItem == null)
|
|
{
|
|
List.SelectedIndex = -1;
|
|
return;
|
|
}
|
|
var templatedItems = TemplatedItemsView.TemplatedItems;
|
|
var index = 0;
|
|
if (Element.IsGroupingEnabled)
|
|
{
|
|
int selectedItemIndex = templatedItems.GetGlobalIndexOfItem(e.SelectedItem);
|
|
var leftOver = 0;
|
|
int groupIndex = templatedItems.GetGroupIndexFromGlobal(selectedItemIndex, out leftOver);
|
|
|
|
index = selectedItemIndex - (groupIndex + 1);
|
|
}
|
|
else
|
|
{
|
|
index = templatedItems.GetGlobalIndexOfItem(e.SelectedItem);
|
|
}
|
|
|
|
List.SelectedIndex = index;
|
|
}
|
|
|
|
void OnListItemClicked(int index)
|
|
{
|
|
Element.NotifyRowTapped(index);
|
|
_itemWasClicked = true;
|
|
}
|
|
|
|
void OnListItemClicked(object sender, ItemClickEventArgs e)
|
|
{
|
|
if (e.ClickedItem != null && TemplatedItemsView?.TemplatedItems != null)
|
|
{
|
|
var templatedItems = TemplatedItemsView.TemplatedItems;
|
|
var selectedItemIndex = templatedItems.GetGlobalIndexOfItem(e.ClickedItem);
|
|
|
|
OnListItemClicked(selectedItemIndex);
|
|
}
|
|
}
|
|
|
|
void OnControlSelectionChanged(object sender, WSelectionChangedEventArgs e)
|
|
{
|
|
bool areEqual = false;
|
|
|
|
if (Element.SelectedItem != null && Element.SelectedItem.GetType().GetTypeInfo().IsValueType)
|
|
areEqual = Element.SelectedItem.Equals(List.SelectedItem);
|
|
else
|
|
areEqual = Element.SelectedItem == List.SelectedItem;
|
|
|
|
if (!areEqual)
|
|
{
|
|
if (_itemWasClicked)
|
|
List.SelectedItem = Element.SelectedItem;
|
|
else
|
|
((IElementController)Element).SetValueFromRenderer(ListView.SelectedItemProperty, List.SelectedItem);
|
|
}
|
|
|
|
_itemWasClicked = false;
|
|
}
|
|
|
|
FrameworkElement FindElement(object cell)
|
|
{
|
|
foreach (CellControl selector in FindDescendants<CellControl>(List))
|
|
{
|
|
if (ReferenceEquals(cell, selector.DataContext))
|
|
return selector;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool _deferSelection = false;
|
|
Tuple<object, SelectedItemChangedEventArgs> _deferredSelectedItemChangedEvent;
|
|
|
|
protected override AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
if (List == null)
|
|
return new FrameworkElementAutomationPeer(this);
|
|
|
|
var automationPeer = new ListViewAutomationPeer(List);
|
|
// skip this renderer from automationPeer tree to avoid infinity loop
|
|
automationPeer.SetParent(new FrameworkElementAutomationPeer(Parent as FrameworkElement));
|
|
return automationPeer;
|
|
}
|
|
|
|
}
|
|
}
|