summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.WinRT/CellControl.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.WinRT/CellControl.cs')
-rw-r--r--Xamarin.Forms.Platform.WinRT/CellControl.cs342
1 files changed, 342 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WinRT/CellControl.cs b/Xamarin.Forms.Platform.WinRT/CellControl.cs
new file mode 100644
index 00000000..a47c9646
--- /dev/null
+++ b/Xamarin.Forms.Platform.WinRT/CellControl.cs
@@ -0,0 +1,342 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Windows.UI.Input;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+
+#if WINDOWS_UWP
+
+namespace Xamarin.Forms.Platform.UWP
+#else
+
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+ public class CellControl : ContentControl
+ {
+ public static readonly DependencyProperty CellProperty = DependencyProperty.Register("Cell", typeof(object), typeof(CellControl),
+ new PropertyMetadata(null, (o, e) => ((CellControl)o).SetSource((Cell)e.OldValue, (Cell)e.NewValue)));
+
+ public static readonly DependencyProperty IsGroupHeaderProperty = DependencyProperty.Register("IsGroupHeader", typeof(bool), typeof(CellControl), null);
+
+ internal static readonly BindableProperty MeasuredEstimateProperty = BindableProperty.Create("MeasuredEstimate", typeof(double), typeof(ListView), -1d);
+ readonly Lazy<ListView> _listView;
+ readonly PropertyChangedEventHandler _propertyChangedHandler;
+
+ IList<MenuItem> _contextActions;
+ Windows.UI.Xaml.DataTemplate _currentTemplate;
+ bool _isListViewRealized;
+ object _newValue;
+
+ public CellControl()
+ {
+ _listView = new Lazy<ListView>(GetListView);
+
+ DataContextChanged += OnDataContextChanged;
+
+ Unloaded += (sender, args) =>
+ {
+ Cell cell = Cell;
+ if (cell != null)
+ cell.SendDisappearing();
+ };
+
+ _propertyChangedHandler = OnCellPropertyChanged;
+ }
+
+ public Cell Cell
+ {
+ get { return (Cell)GetValue(CellProperty); }
+ set { SetValue(CellProperty, value); }
+ }
+
+ public bool IsGroupHeader
+ {
+ get { return (bool)GetValue(IsGroupHeaderProperty); }
+ set { SetValue(IsGroupHeaderProperty, value); }
+ }
+
+ protected FrameworkElement CellContent
+ {
+ get { return (FrameworkElement)Content; }
+ }
+
+ protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
+ {
+ ListView lv = _listView.Value;
+
+ // set the Cell now that we have a reference to the ListView, since it will have been skipped
+ // on DataContextChanged.
+ if (_newValue != null)
+ {
+ SetCell(_newValue);
+ _newValue = null;
+ }
+
+ if (Content == null)
+ {
+ if (lv != null)
+ {
+ if (lv.HasUnevenRows)
+ {
+ var estimate = (double)lv.GetValue(MeasuredEstimateProperty);
+ if (estimate > -1)
+ return new Windows.Foundation.Size(availableSize.Width, estimate);
+ }
+ else
+ {
+ double rowHeight = lv.RowHeight;
+ if (rowHeight > -1)
+ return new Windows.Foundation.Size(availableSize.Width, rowHeight);
+ }
+ }
+
+ return new Windows.Foundation.Size(0, 0);
+ }
+
+ // Children still need measure called on them
+ Windows.Foundation.Size result = base.MeasureOverride(availableSize);
+
+ if (lv != null)
+ {
+ lv.SetValue(MeasuredEstimateProperty, result.Height);
+ }
+
+ return result;
+ }
+
+ static string GetDisplayTextFromGroup(ListView lv, TemplatedItemsList<ItemsView<Cell>, Cell> group)
+ {
+ string displayBinding = null;
+
+ if (lv.GroupDisplayBinding != null)
+ displayBinding = group.Name;
+
+ if (lv.GroupShortNameBinding != null)
+ displayBinding = group.ShortName;
+
+ // TODO: what if they set both? should it default to the ShortName, like it will here?
+ // ShortNames binding did not appear to be functional before.
+ return displayBinding;
+ }
+
+ static TemplatedItemsList<ItemsView<Cell>, Cell> GetGroup(object newContext, ListView lv)
+ {
+ int groupIndex = lv.TemplatedItems.GetGlobalIndexOfGroup(newContext);
+ TemplatedItemsList<ItemsView<Cell>, Cell> group = lv.TemplatedItems.GetGroup(groupIndex);
+ return group;
+ }
+
+ ListView GetListView()
+ {
+ DependencyObject parent = VisualTreeHelper.GetParent(this);
+ while (parent != null)
+ {
+ var lv = parent as ListViewRenderer;
+ if (lv != null)
+ {
+ _isListViewRealized = true;
+ return lv.Element;
+ }
+
+ parent = VisualTreeHelper.GetParent(parent);
+ }
+
+ return null;
+ }
+
+ Windows.UI.Xaml.DataTemplate GetTemplate(Cell cell)
+ {
+ var renderer = Registrar.Registered.GetHandler<ICellRenderer>(cell.GetType());
+ return renderer.GetTemplate(cell);
+ }
+
+ void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "HasContextActions")
+ {
+ SetupContextMenu();
+ }
+ }
+
+ void OnClick(object sender, PointerRoutedEventArgs e)
+ {
+ PointerPoint point = e.GetCurrentPoint(CellContent);
+ if (point.Properties.PointerUpdateKind != PointerUpdateKind.RightButtonReleased)
+ return;
+
+ OpenContextMenu();
+ }
+
+ void OnContextActionsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var flyout = FlyoutBase.GetAttachedFlyout(CellContent) as MenuFlyout;
+ if (flyout != null)
+ {
+ flyout.Items.Clear();
+ SetupMenuItems(flyout);
+ }
+ }
+
+ void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ // We don't want to set the Cell until the ListView is realized, just in case the
+ // Cell has an ItemTemplate. Instead, we'll store the new data item, and it will be
+ // set on MeasureOverrideDelegate. However, if the parent is a TableView, we'll already
+ // have a complete Cell object to work with, so we can move ahead.
+ if (_isListViewRealized || args.NewValue is Cell)
+ SetCell(args.NewValue);
+ else if (args.NewValue != null)
+ _newValue = args.NewValue;
+ }
+
+ void OnLongTap(object sender, HoldingRoutedEventArgs e)
+ {
+ if (e.HoldingState == HoldingState.Started)
+ OpenContextMenu();
+ }
+
+ void OnOpenContext(object sender, RightTappedRoutedEventArgs e)
+ {
+ FlyoutBase.ShowAttachedFlyout(CellContent);
+ }
+
+ void OpenContextMenu()
+ {
+ if (FlyoutBase.GetAttachedFlyout(CellContent) == null)
+ {
+ var flyout = new MenuFlyout();
+ SetupMenuItems(flyout);
+
+ ((INotifyCollectionChanged)Cell.ContextActions).CollectionChanged += OnContextActionsChanged;
+
+ _contextActions = Cell.ContextActions;
+ FlyoutBase.SetAttachedFlyout(CellContent, flyout);
+ }
+
+ FlyoutBase.ShowAttachedFlyout(CellContent);
+ }
+
+ void SetCell(object newContext)
+ {
+ var cell = newContext as Cell;
+
+ if (ReferenceEquals(Cell?.BindingContext, newContext))
+ return;
+
+ // If there is a ListView, load the Cell content from the ItemTemplate.
+ // Otherwise, the given Cell is already a templated Cell from a TableView.
+ ListView lv = _listView.Value;
+ if (lv != null)
+ {
+ bool isGroupHeader = IsGroupHeader;
+ DataTemplate template = isGroupHeader ? lv.GroupHeaderTemplate : lv.ItemTemplate;
+
+ if (template is DataTemplateSelector)
+ {
+ template = ((DataTemplateSelector)template).SelectTemplate(newContext, lv);
+ }
+
+ if (template != null)
+ {
+ cell = template.CreateContent() as Cell;
+ cell.BindingContext = newContext;
+ }
+ else
+ {
+ string textContent = newContext.ToString();
+
+ if (isGroupHeader)
+ {
+ TemplatedItemsList<ItemsView<Cell>, Cell> group = GetGroup(newContext, lv);
+ textContent = GetDisplayTextFromGroup(lv, group);
+ }
+
+ cell = lv.CreateDefaultCell(textContent);
+ }
+
+ // A TableView cell should already have its parent,
+ // but we need to set the parent for a ListView cell.
+ cell.Parent = lv;
+
+ // This provides the Group Header styling (e.g., larger font, etc.) when the
+ // template is loaded later.
+ TemplatedItemsList<ItemsView<Cell>, Cell>.SetIsGroupHeader(cell, isGroupHeader);
+ }
+
+ Cell = cell;
+ }
+
+ void SetSource(Cell oldCell, Cell newCell)
+ {
+ if (oldCell != null)
+ {
+ oldCell.PropertyChanged -= _propertyChangedHandler;
+ oldCell.SendDisappearing();
+ }
+
+ if (newCell != null)
+ {
+ newCell.SendAppearing();
+
+ UpdateContent(newCell);
+ SetupContextMenu();
+
+ newCell.PropertyChanged += _propertyChangedHandler;
+ }
+ }
+
+ void SetupContextMenu()
+ {
+ if (CellContent == null || Cell == null)
+ return;
+
+ if (!Cell.HasContextActions)
+ {
+ CellContent.Holding -= OnLongTap;
+ CellContent.PointerReleased -= OnClick;
+ if (_contextActions != null)
+ {
+ ((INotifyCollectionChanged)_contextActions).CollectionChanged -= OnContextActionsChanged;
+ _contextActions = null;
+ }
+
+ FlyoutBase.SetAttachedFlyout(CellContent, null);
+ return;
+ }
+
+ CellContent.PointerReleased += OnClick;
+ CellContent.Holding += OnLongTap;
+ }
+
+ void SetupMenuItems(MenuFlyout flyout)
+ {
+ foreach (MenuItem item in Cell.ContextActions)
+ {
+ var flyoutItem = new MenuFlyoutItem();
+ flyoutItem.SetBinding(MenuFlyoutItem.TextProperty, "Text");
+ flyoutItem.Command = new MenuItemCommand(item);
+ flyoutItem.DataContext = item;
+
+ flyout.Items.Add(flyoutItem);
+ }
+ }
+
+ void UpdateContent(Cell newCell)
+ {
+ Windows.UI.Xaml.DataTemplate dt = GetTemplate(newCell);
+ if (dt != _currentTemplate || Content == null)
+ {
+ _currentTemplate = dt;
+ Content = dt.LoadContent();
+ }
+
+ ((FrameworkElement)Content).DataContext = newCell;
+ }
+ }
+} \ No newline at end of file