Permalink
Switch branches/tags
2.2.0 2.3.0 2.3.1 2.3.2 2.3.3-XFSv25 2.3.3-a11y 2.3.3-a11y2 2.3.3-hf1 2.3.3-nuance 2.3.3 2.3.4-2 2.3.4-paul 2.3.4 2.4.0 2.5.0 2.5.1 3.0.0 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 4.0-pre 15-5 1733_waitfors 4248-alternative 51509 52487_VSDbg 52487 59412 59813 59896 60140 60337 60382 60524 AndreiMisiukevich-fix_4388 Bz42620 CssGIncremental FusedLayout GH1939 ImagePerf Issue2266-uitest-fix Issue2266 ListviewFastrenderer VisualElementPackager XamlC-fixXStatic activa-relativelayout-responsive add-disable-frameworktask add-flexnative alanivt alias android-support-25 androidpackref azure-pipelines baseline binding-convert-culture bindingfixes build-updates buzz-visual buzz bz32871 check_ci check_ci0 checknetfreamework ci_test ckjs-scratch cktest components cp4016 create-nuget-fix create-nuget css_border_color css_borderRadius css_extended_properties cssvendorprefix customtabs_nuspec cv-android-single-select cv-ios-snap cvflags cycle9 debugger59347 debugtype_full delay_for_bitmap_test desired-api disable-frameworktask drop-shadow-platform-specific editor-fr embedding-webchromeclient enable-linker f100-designmode feature-carouselview feature/1678-read-only-entry fix-15 fix-1356 fix-1426 fix-1585-title fix-2580 fix-2664-2 fix-2664 fix-2740 fix-2789 fix-2837 fix-2859 fix-2951 fix-3111 fix-3275 fix-3353 fix-3408-dispose fix-3408 fix-3458 fix-3622-2 fix-3622 fix-3717 fix-3763 fix-3972 fix-4125 fix-4314 fix-4356 fix-4600 fix-41138 fix-addfakepcl fix-bugzilla31171 fix-bugzilla38189 fix-bugzilla39908 fix-bugzilla39936 fix-bugzilla40485 fix-bugzilla40514 fix-bugzilla42301 fix-bugzilla43955 fix-bugzilla45067 fix-bugzilla46632 fix-bugzilla47430 fix-bugzilla49304 fix-bugzilla51173-2 fix-bugzilla51825 fix-bugzilla52299 fix-bugzilla54645 fix-bugzilla55674 fix-bugzilla57787 fix-bugzilla59404 fix-bugzilla59457 fix-bugzilla60033 fix-bugzilla60056 fix-bugzilla60204 fix-bugzilla60691 fix-bz59220 fix-dismisspage-crash fix-g2630 fix-gh1342 fix-gh2279 fix-gh2617 fix-gh2829 fix-gh2936 fix-gh3008 fix-gh3273 fix-gh3333 fix-gh4069 fix-gh4600 fix-grid-measure fix-index-check-order fix-issue2297 fix-issue3413-k fix-issue3413 fix-loadurlport fix-missingref fix-nuspec-pcl fix-platformresume fix-pr-762 fix-provision fix-safearea fix-slider fix-tbitemsios fix-tests fix-toolbaritems fix-uwp-3d-rotation fix_GestureDetector_ctor fix_android_scale fix_build_for_4024 fix_gh1788 fix_gh2169 fix_gh2216 fix_gh2627 fix_gh2633 fix_gh2753 fix_gh2918 fix_gh3082 fix_gh3191 fix_gh3520 fix_gh3781 fix_gh3873 fix_gh4102 fix_gh4348 fix_gh4446 fix_gh4524 fix_gh4553 fix_gh4563 fix_gh4563_2 fix_gh4619 fix_gh4620 fix_gh4625 fix_tabbedpage_android_icon_crash fix_test2499 fix_uitest_gesture fix_zindex_uitest fix3808 fix_4130 fixes-3675 fixmsbuildtests flex-build-tests flexlayout-core flexlayout font-icon-source font-image-source fr_entry fr_parts g1598 g3344 gh1872 gh1939 gh1942 gh2687 gh3344 gh3579 gh3603 gh4138 gh4373 gh4484 gh4597 github-1426 gtk-merge handle-testing ignore-slidertest implicitctrliface0 inheritble-properties internalconverter ios-datatemplates issue-2376-v0 ivt jsuarezruiz-feature/1709-rounded-boxview-II kingc-housecleaning kzu-xaml-loader kzu/issdkstyle kzu/live-reload kzu/relative-submodule kzu/xaml-loader legistek-gh2691-xmlnsdefinitionattribute-public listview-bg-test listview-bg-test3 live-reload loader-tests mac macOS-IVTOFF macOS master mattleibow/view-visual-update mattleibow/view-visual menuitem_fixes merged-implicit-style-support msbuild_quirk mubold-master mxbuild_sdk0 mxbuild namedsizeconverterperf nuspec_supports paymicro-fix-gh4187-picker-dialog pdb4uap picker-font-enhancements picker-resize-selection-changed pickerfocus platrend popover pr-1128 pr/2512 previewer-test previewer project-capabilities provision-15-7 pull-175 ravinderjangra-ListView reblend remove-23-support remove-iplatform remove-perf-calls-from-core-builds remove-wp remove-xf001 remove_BackButtonTitle_check remove_latest_sdk_setting remove_tag_from_imagebutton resilientXaml-temp revert-1014-quickfix scrollto-uwp sdk sdkstylexamltests shell-3.4.0 shell-shellcontenttab shell-squash shell shuffletests span_crash_uwp_text_decorations srtnfd static-binding stepper-onplatform-fix stepper-same-size-buttons strongname stubbard test2ck toolbox-embedding toolbox-support tooltips tvOS tweak uapsym uitest_1583 uitests-collectionview update-android-packages update-provisioning-xcode10 update-provisioning update-readme update-testcloudagent update-uitest update-yaml useDefaultItems uwp-3188 uwp-placeholder-alignment uwp-platform uwp-provider-cleanup uwp-ps-searchbar uwp-ui-tests visual-master visual-preview7-bug visual-shell visual_entry visual vsbug vsts-sln wae-pages wildcard-xaml-items wkwebview wpf xfcore-alpha yaml-builds yaml zsv-hitcents _52487 __52487 _59813 __59813 _59896 _60140
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
776 lines (663 sloc) 21.8 KB
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using WListView = Windows.UI.Xaml.Controls.ListView;
using WBinding = Windows.UI.Xaml.Data.Binding;
using WApp = Windows.UI.Xaml.Application;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
using Specifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.ListView;
using System.Collections.ObjectModel;
using UwpScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility;
namespace Xamarin.Forms.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 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 (e.NewElement != null)
{
e.NewElement.ItemSelected += OnElementItemSelected;
e.NewElement.ScrollToRequested += OnElementScrollToRequested;
((ITemplatedItemsView<Cell>)e.NewElement).TemplatedItems.CollectionChanged += OnCollectionChanged;
if (List == null)
{
List = new WListView
{
IsSynchronizedWithCurrentItem = false,
ItemTemplate = (Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["CellTemplate"],
HeaderTemplate = (Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["View"],
FooterTemplate = (Windows.UI.Xaml.DataTemplate)WApp.Current.Resources["View"],
ItemContainerStyle = (Windows.UI.Xaml.Style)WApp.Current.Resources["FormsListViewItem"],
GroupStyleSelector = (GroupStyleSelector)WApp.Current.Resources["ListViewGroupSelector"]
};
List.SelectionChanged += OnControlSelectionChanged;
}
ReloadData();
if (Element.SelectedItem != null)
OnElementItemSelected(null, new SelectedItemChangedEventArgs(Element.SelectedItem));
UpdateGrouping();
UpdateHeader();
UpdateFooter();
UpdateSelectionMode();
UpdateWindowsSpecificSelectionMode();
ClearSizeEstimate();
UpdateVerticalScrollBarVisibility();
UpdateHorizontalScrollBarVisibility();
}
}
bool IsObservableCollection(object source)
{
var type = source.GetType();
return type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ObservableCollection<>);
}
void ReloadData()
{
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
{
_collection = (IList)Element.ItemsSource;
}
}
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();
}
Device.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 == Specifics.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 = (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 = Windows.UI.Xaml.Controls.ListViewSelectionMode.None;
List.SelectedIndex = -1;
Element.SelectedItem = null;
}
else if (Element.SelectionMode == ListViewSelectionMode.Single)
{
List.SelectionMode = Windows.UI.Xaml.Controls.ListViewSelectionMode.Single;
}
}
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 Windows.UI.Xaml.Controls.Primitives.SelectorItem;
var transform = selectorItem?.TransformToVisual(viewer.Content as UIElement);
var position = transform?.TransformPoint(new 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(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 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, cell: null);
_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, SelectionChangedEventArgs e)
{
if (Element.SelectedItem != List.SelectedItem)
{
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()
{
return List == null
? new FrameworkElementAutomationPeer(this)
: new ListViewAutomationPeer(List);
}
}
}