using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using Xamarin.Forms.Internals; namespace Xamarin.Forms { public partial class Grid : Layout { public static readonly BindableProperty RowProperty = BindableProperty.CreateAttached("Row", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0); public static readonly BindableProperty RowSpanProperty = BindableProperty.CreateAttached("RowSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1); public static readonly BindableProperty ColumnProperty = BindableProperty.CreateAttached("Column", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0); public static readonly BindableProperty ColumnSpanProperty = BindableProperty.CreateAttached("ColumnSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1); public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create("RowSpacing", typeof(double), typeof(Grid), 6d, propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged)); public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create("ColumnSpacing", typeof(double), typeof(Grid), 6d, propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged)); public static readonly BindableProperty ColumnDefinitionsProperty = BindableProperty.Create("ColumnDefinitions", typeof(ColumnDefinitionCollection), typeof(Grid), null, validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) => { if (oldvalue != null) ((ColumnDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged; if (newvalue != null) ((ColumnDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; }, defaultValueCreator: bindable => { var colDef = new ColumnDefinitionCollection(); colDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; return colDef; }); public static readonly BindableProperty RowDefinitionsProperty = BindableProperty.Create("RowDefinitions", typeof(RowDefinitionCollection), typeof(Grid), null, validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) => { if (oldvalue != null) ((RowDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged; if (newvalue != null) ((RowDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; }, defaultValueCreator: bindable => { var rowDef = new RowDefinitionCollection(); rowDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; return rowDef; }); readonly GridElementCollection _children; public Grid() { _children = new GridElementCollection(InternalChildren, this) { Parent = this }; } public new IGridList Children { get { return _children; } } public ColumnDefinitionCollection ColumnDefinitions { get { return (ColumnDefinitionCollection)GetValue(ColumnDefinitionsProperty); } set { SetValue(ColumnDefinitionsProperty, value); } } public double ColumnSpacing { get { return (double)GetValue(ColumnSpacingProperty); } set { SetValue(ColumnSpacingProperty, value); } } public RowDefinitionCollection RowDefinitions { get { return (RowDefinitionCollection)GetValue(RowDefinitionsProperty); } set { SetValue(RowDefinitionsProperty, value); } } public double RowSpacing { get { return (double)GetValue(RowSpacingProperty); } set { SetValue(RowSpacingProperty, value); } } public static int GetColumn(BindableObject bindable) { return (int)bindable.GetValue(ColumnProperty); } public static int GetColumnSpan(BindableObject bindable) { return (int)bindable.GetValue(ColumnSpanProperty); } public static int GetRow(BindableObject bindable) { return (int)bindable.GetValue(RowProperty); } public static int GetRowSpan(BindableObject bindable) { return (int)bindable.GetValue(RowSpanProperty); } public static void SetColumn(BindableObject bindable, int value) { bindable.SetValue(ColumnProperty, value); } public static void SetColumnSpan(BindableObject bindable, int value) { bindable.SetValue(ColumnSpanProperty, value); } public static void SetRow(BindableObject bindable, int value) { bindable.SetValue(RowProperty, value); } public static void SetRowSpan(BindableObject bindable, int value) { bindable.SetValue(RowSpanProperty, value); } protected override void OnAdded(View view) { base.OnAdded(view); view.PropertyChanged += OnItemPropertyChanged; } protected override void OnBindingContextChanged() { UpdateInheritedBindingContexts(); base.OnBindingContextChanged(); } protected override void OnRemoved(View view) { base.OnRemoved(view); view.PropertyChanged -= OnItemPropertyChanged; } internal override void ComputeConstraintForView(View view) { LayoutOptions vOptions = view.VerticalOptions; LayoutOptions hOptions = view.HorizontalOptions; var result = LayoutConstraint.None; if (_rows == null || _columns == null) EnsureRowsColumnsInitialized(); if (vOptions.Alignment == LayoutAlignment.Fill) { int row = GetRow(view); int rowSpan = GetRowSpan(view); List rowDefinitions = _rows; var canFix = true; for (int i = row; i < row + rowSpan && i < rowDefinitions.Count; i++) { GridLength height = rowDefinitions[i].Height; if (height.IsAuto) { canFix = false; break; } if ((Constraint & LayoutConstraint.VerticallyFixed) == 0 && height.IsStar) { canFix = false; break; } } if (canFix) result |= LayoutConstraint.VerticallyFixed; } if (hOptions.Alignment == LayoutAlignment.Fill) { int col = GetColumn(view); int colSpan = GetColumnSpan(view); List columnDefinitions = _columns; var canFix = true; for (int i = col; i < col + colSpan && i < columnDefinitions.Count; i++) { GridLength width = columnDefinitions[i].Width; if (width.IsAuto) { canFix = false; break; } if ((Constraint & LayoutConstraint.HorizontallyFixed) == 0 && width.IsStar) { canFix = false; break; } } if (canFix) result |= LayoutConstraint.HorizontallyFixed; } view.ComputedConstraint = result; } internal override void InvalidateMeasureInternal(InvalidationTrigger trigger) { base.InvalidateMeasureInternal(trigger); _columns = null; _rows = null; } void OnDefinitionChanged(object sender, EventArgs args) { ComputeConstrainsForChildren(); UpdateInheritedBindingContexts(); InvalidateLayout(); } void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == ColumnProperty.PropertyName || e.PropertyName == ColumnSpanProperty.PropertyName || e.PropertyName == RowProperty.PropertyName || e.PropertyName == RowSpanProperty.PropertyName) { var child = sender as View; if (child != null) { ComputeConstraintForView(child); } InvalidateLayout(); } } void UpdateInheritedBindingContexts() { object bindingContext = BindingContext; RowDefinitionCollection rowDefs = RowDefinitions; if (rowDefs != null) { for (var i = 0; i < rowDefs.Count; i++) { RowDefinition rowdef = rowDefs[i]; SetInheritedBindingContext(rowdef, bindingContext); } } ColumnDefinitionCollection colDefs = ColumnDefinitions; if (colDefs != null) { for (var i = 0; i < colDefs.Count; i++) { ColumnDefinition coldef = colDefs[i]; SetInheritedBindingContext(coldef, bindingContext); } } } public interface IGridList : IList where T : View { void Add(View view, int left, int top); void Add(View view, int left, int right, int top, int bottom); void AddHorizontal(IEnumerable views); void AddHorizontal(View view); void AddVertical(IEnumerable views); void AddVertical(View view); } class GridElementCollection : ElementCollection, IGridList { public GridElementCollection(ObservableCollection inner, Grid parent) : base(inner) { Parent = parent; } internal Grid Parent { get; set; } public void Add(View view, int left, int top) { if (left < 0) throw new ArgumentOutOfRangeException("left"); if (top < 0) throw new ArgumentOutOfRangeException("top"); Add(view, left, left + 1, top, top + 1); } public void Add(View view, int left, int right, int top, int bottom) { if (left < 0) throw new ArgumentOutOfRangeException("left"); if (top < 0) throw new ArgumentOutOfRangeException("top"); if (left >= right) throw new ArgumentOutOfRangeException("right"); if (top >= bottom) throw new ArgumentOutOfRangeException("bottom"); if (view == null) throw new ArgumentNullException("view"); SetRow(view, top); SetRowSpan(view, bottom - top); SetColumn(view, left); SetColumnSpan(view, right - left); Add(view); } public void AddHorizontal(IEnumerable views) { if (views == null) throw new ArgumentNullException("views"); views.ForEach(AddHorizontal); } public void AddHorizontal(View view) { if (view == null) throw new ArgumentNullException("view"); int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1; lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1); int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1; lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1); Add(view, lastCol + 1, lastCol + 2, 0, Math.Max(1, lastRow)); } public void AddVertical(IEnumerable views) { if (views == null) throw new ArgumentNullException("views"); views.ForEach(AddVertical); } public void AddVertical(View view) { if (view == null) throw new ArgumentNullException("view"); int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1; lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1); int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1; lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1); Add(view, 0, Math.Max(1, lastCol), lastRow + 1, lastRow + 2); } } } }