File: TableView\TableView.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Xaml.Diagnostics;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls
{
	/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="Type[@FullName='Microsoft.Maui.Controls.TableView']/Docs/*" />
	[ContentProperty(nameof(Root))]
	public class TableView : View, ITableViewController, IElementConfiguration<TableView>, IVisualTreeElement
	{
		/// <summary>Bindable property for <see cref="RowHeight"/>.</summary>
		public static readonly BindableProperty RowHeightProperty = BindableProperty.Create(nameof(RowHeight), typeof(int), typeof(TableView), -1);
 
		/// <summary>Bindable property for <see cref="HasUnevenRows"/>.</summary>
		public static readonly BindableProperty HasUnevenRowsProperty = BindableProperty.Create(nameof(HasUnevenRows), typeof(bool), typeof(TableView), false);
 
		readonly Lazy<PlatformConfigurationRegistry<TableView>> _platformConfigurationRegistry;
 
		readonly TableSectionModel _tableModel;
 
		TableIntent _intent = TableIntent.Data;
 
		TableModel _model;
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='.ctor'][1]/Docs/*" />
		public TableView() : this(null)
		{
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='.ctor'][2]/Docs/*" />
		public TableView(TableRoot root)
		{
#pragma warning disable CS0618 // Type or member is obsolete
			VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
#pragma warning restore CS0618 // Type or member is obsolete
			Model = _tableModel = new TableSectionModel(this, root);
			_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<TableView>>(() => new PlatformConfigurationRegistry<TableView>(this));
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='HasUnevenRows']/Docs/*" />
		public bool HasUnevenRows
		{
			get { return (bool)GetValue(HasUnevenRowsProperty); }
			set { SetValue(HasUnevenRowsProperty, value); }
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='Intent']/Docs/*" />
		public TableIntent Intent
		{
			get { return _intent; }
			set
			{
				if (_intent == value)
					return;
 
				OnPropertyChanging();
				_intent = value;
				OnPropertyChanged();
			}
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='Root']/Docs/*" />
		public TableRoot Root
		{
			get { return _tableModel.Root; }
			set
			{
				if (_tableModel.Root != null)
				{
					_tableModel.Root.SectionCollectionChanged -= OnSectionCollectionChanged;
					_tableModel.Root.PropertyChanged -= OnTableModelRootPropertyChanged;
					VisualDiagnostics.OnChildRemoved(this, _tableModel.Root, 0);
				}
				_tableModel.Root = value ?? new TableRoot();
				VisualDiagnostics.OnChildAdded(this, _tableModel.Root);
				SetInheritedBindingContext(_tableModel.Root, BindingContext);
 
				Root.SelectMany(r => r).ForEach(cell => cell.Parent = this);
				_tableModel.Root.SectionCollectionChanged += OnSectionCollectionChanged;
				_tableModel.Root.PropertyChanged += OnTableModelRootPropertyChanged;
				OnModelChanged();
			}
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='RowHeight']/Docs/*" />
		public int RowHeight
		{
			get { return (int)GetValue(RowHeightProperty); }
			set { SetValue(RowHeightProperty, value); }
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/TableView.xml" path="//Member[@MemberName='Model']/Docs/*" />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public TableModel Model
		{
			get { return _model; }
			set
			{
				_model = value;
				OnModelChanged();
			}
		}
 
		ITableModel ITableViewController.Model
		{
			get
			{
				return Model;
			}
		}
 
		protected override void OnBindingContextChanged()
		{
			base.OnBindingContextChanged();
			if (Root != null)
				SetInheritedBindingContext(Root, BindingContext);
		}
 
		protected virtual void OnModelChanged()
		{
			foreach (Cell cell in Root.SelectMany(r => r))
				cell.Parent = this;
 
			ModelChanged?.Invoke(this, EventArgs.Empty);
		}
 
		[Obsolete("Use MeasureOverride instead")]
		protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
		{
			var minimumSize = new Size(40, 40);
			var scaled = DeviceDisplay.MainDisplayInfo.GetScaledScreenSize();
			double width = Math.Min(scaled.Width, scaled.Height);
			var request = new Size(width, Math.Max(scaled.Width, scaled.Height));
 
			return new SizeRequest(request, minimumSize);
		}
 
		[EditorBrowsable(EditorBrowsableState.Never)]
		public event EventHandler ModelChanged;
 
		/// <inheritdoc/>
		public IPlatformElementConfiguration<T, TableView> On<T>() where T : IConfigPlatform
		{
			return _platformConfigurationRegistry.Value.On<T>();
		}
 
		void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			OnModelChanged();
		}
 
		void OnSectionCollectionChanged(object sender, ChildCollectionChangedEventArgs childCollectionChangedEventArgs)
		{
			childCollectionChangedEventArgs.Args.NewItems?.Cast<Cell>().ForEach(cell => cell.Parent = this);
			OnModelChanged();
		}
 
		void OnTableModelRootPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == TableSectionBase.TitleProperty.PropertyName)
				OnModelChanged();
		}
 
		IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => new List<Maui.IVisualTreeElement>() { this.Root };
 
		internal class TableSectionModel : TableModel
		{
			static readonly BindableProperty PathProperty = BindableProperty.Create("Path", typeof(Tuple<int, int>), typeof(Cell), null);
 
			readonly TableView _parent;
			TableRoot _root;
 
			public TableSectionModel(TableView tableParent, TableRoot tableRoot)
			{
				_parent = tableParent;
				Root = tableRoot ?? new TableRoot();
			}
 
			public TableRoot Root
			{
				get { return _root; }
				set
				{
					if (_root == value)
						return;
 
					RemoveEvents(_root);
					_root = value;
					ApplyEvents(_root);
				}
			}
 
			public override Cell GetCell(int section, int row)
			{
				var cell = (Cell)GetItem(section, row);
				SetPath(cell, new Tuple<int, int>(section, row));
				return cell;
			}
 
			public override object GetItem(int section, int row)
			{
				return _root[section][row];
			}
 
			public override int GetRowCount(int section)
			{
				return _root[section].Count;
			}
 
			public override int GetSectionCount()
			{
				return _root.Count;
			}
 
			public override string GetSectionTitle(int section)
			{
				return _root[section].Title;
			}
 
			public override Color GetSectionTextColor(int section)
			{
				return _root[section].TextColor;
			}
 
			protected override void OnRowSelected(object item)
			{
				base.OnRowSelected(item);
 
				((Cell)item).OnTapped();
			}
 
			internal static Tuple<int, int> GetPath(Cell item)
			{
				if (item == null)
					throw new ArgumentNullException(nameof(item));
 
				return (Tuple<int, int>)item.GetValue(PathProperty);
			}
 
			void ApplyEvents(TableRoot tableRoot)
			{
				tableRoot.CollectionChanged += _parent.CollectionChanged;
				tableRoot.SectionCollectionChanged += _parent.OnSectionCollectionChanged;
			}
 
			void RemoveEvents(TableRoot tableRoot)
			{
				if (tableRoot == null)
					return;
 
				tableRoot.CollectionChanged -= _parent.CollectionChanged;
				tableRoot.SectionCollectionChanged -= _parent.OnSectionCollectionChanged;
			}
 
			static void SetPath(Cell item, Tuple<int, int> index)
			{
				if (item == null)
					return;
 
				item.SetValue(PathProperty, index);
			}
		}
	}
}