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