/// /// Port of WPF UniformGrid control to UWP. In the Microsoft UWP examples on GitHub, see the XamlUIBasics project. They have a custom WrapPanel included in the project. /// I took that code and combined it with a reverse compile of the the WPF UniformGrid control to get the code below. /// January 20, 2016 /// RickApps.com /// namespace UniformGridForUWP { using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; /// /// Provides a way to arrange content in a grid where all the cells in the grid have the same size. /// public class UniformGrid : Panel { /// /// A value indicating whether a dependency property change handler /// should ignore the next change notification. This is used to reset /// the value of properties without performing any of the actions in /// their change handlers. /// private bool _ignorePropertyChange; private int _rows; private int _columns; #region public int Rows /// /// Gets or sets the number of rows that are in the grid. /// /// The number of rows that are in the grid. The default is 0. public int Rows { get { return (int)GetValue(RowsProperty); } set { SetValue(RowsProperty, value); } } public static readonly DependencyProperty RowsProperty = DependencyProperty.Register( "Rows", typeof(int), typeof(UniformGrid), new PropertyMetadata(0, OnRowsColumnsChanged)); #endregion #region public int Columns /// /// Gets or sets the number of columns that are in the grid. /// /// The number of columns that are in the grid. The default is 0. public int Columns { get { return (int)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register( "Columns", typeof(int), typeof(UniformGrid), new PropertyMetadata(0, OnRowsColumnsChanged)); #endregion #region public int FirstColumn /// /// Gets or sets the number of leading blank cells in the first row of the grid. /// /// The number of empty cells that are in the first row of the grid. The default is 0. public int FirstColumn { get { return (int)GetValue(FirstColumnProperty); } set { SetValue(FirstColumnProperty, value); } } public static readonly DependencyProperty FirstColumnProperty = DependencyProperty.Register( "FirstColumn", typeof(int), typeof(UniformGrid), new PropertyMetadata(0, OnFirstColumnChanged)); #endregion /// /// Validity check on row or column. For now, just check that it is positive. This code could be much simplified. /// /// /// private static void OnRowsColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UniformGrid source = (UniformGrid)d; int value = (int)e.NewValue; // Ignore the change if requested if (source._ignorePropertyChange) { source._ignorePropertyChange = false; return; } if (value < 0) { // Reset the property to its original state before throwing source._ignorePropertyChange = true; source.SetValue(e.Property, (int)e.OldValue); string message = string.Format( CultureInfo.InvariantCulture, "Properties.Resources.UniformGrid_RowsColumnsChanged_InvalidValue", value); throw new ArgumentException(message, "value"); } // The length properties affect measuring. source.InvalidateMeasure(); } /// /// Check that the offset is positive. This seems to be the only check in the WPF uniform grid. /// Might also make the offset be less than the number of items included in the grid. /// /// /// private static void OnFirstColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UniformGrid source = (UniformGrid)d; int value = (int)e.NewValue; // Ignore the change if requested if (source._ignorePropertyChange) { source._ignorePropertyChange = false; return; } if (value < 0) { // Reset the property to its original state before throwing source._ignorePropertyChange = true; source.SetValue(e.Property, (int)e.OldValue); string message = string.Format( CultureInfo.InvariantCulture, "Properties.Resources.UniformGrid_OnFirstColumnChanged_InvalidValue", value); throw new ArgumentException(message, "value"); } // The length properties affect measuring. source.InvalidateMeasure(); } /// /// Initializes a new instance of the UniformGrid. /// public UniformGrid() { } /// /// Computes the desired size of the System.Windows.Controls.Primitives.UniformGrid by measuring all of the child elements. /// /// The System.Windows.Size of the available area for the grid. /// The desired System.Windows.Size based on the child content of the grid and the constraint parameter. [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] protected override Size MeasureOverride(Size constraint) { this.UpdateComputedValues(); Size availableSize = new Size(constraint.Width / (double)this._columns, constraint.Height / (double)this._rows); double num = 0.0; double num2 = 0.0; int i = 0; int count = base.Children.Count; while (i < count) { UIElement element = base.Children[i]; element.Measure(availableSize); Size desiredSize = element.DesiredSize; if (num < desiredSize.Width) { num = desiredSize.Width; } if (num2 < desiredSize.Height) { num2 = desiredSize.Height; } i++; } return new Size(num * (double)this._columns, num2 * (double)this._rows); } /// /// Defines the layout of the System.Windows.Controls.Primitives.UniformGrid by distributing space evenly among all of the child elements. /// /// The System.Windows.Size of the area for the grid to use. /// The actual System.Windows.Size of the grid that is rendered to display the child elements that are visible. protected override Size ArrangeOverride(Size finalSize) { Rect finalRect = new Rect(0.0, 0.0, finalSize.Width / (double)this._columns, finalSize.Height / (double)this._rows); double width = finalRect.Width; double num = finalSize.Width - 1.0; finalRect.X += finalRect.Width * (double)this.FirstColumn; foreach (UIElement element in base.Children) { element.Arrange(finalRect); if (element.Visibility != Visibility.Collapsed) { finalRect.X += width; if (finalRect.X >= num) { finalRect.Y += finalRect.Height; finalRect.X = 0.0; } } } return finalSize; } /// /// If no row count or column count is specified, use the smallest square number that will contain all the items /// as our dimensions. Otherwise, calculate the number of rows or columns needed to contain all items. /// private void UpdateComputedValues() { this._columns = this.Columns; this._rows = this.Rows; if (this.FirstColumn >= this._columns) { this.FirstColumn = 0; } if (this._rows == 0 || this._columns == 0) { int num = 0; int i = 0; int count = base.Children.Count; while (i < count) { if (base.Children[i].Visibility != Visibility.Collapsed) { num++; } i++; } if (num == 0) { num = 1; } if (this._rows == 0) { if (this._columns > 0) { this._rows = (num + this.FirstColumn + (this._columns - 1)) / this._columns; return; } this._rows = (int)Math.Sqrt((double)num); if (this._rows * this._rows < num) { this._rows++; } this._columns = this._rows; return; } else if (this._columns == 0) { this._columns = (num + (this._rows - 1)) / this._rows; } } } } }