Beispiel #1
0
        /// <summary>
        /// Sets the specified <see cref="Binding"/> instance for the object.
        /// </summary>
        /// <param name="obj">The target object.</param>
        /// <param name="binding">The <see cref="Binding"/> instance to be set.</param>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="binding"/> is <c>null</c>.</exception>
        public static void SetBinding(this IPairable obj, Binding binding)
        {
            var notifier = obj as INotifyPropertyChanged ?? obj.Pair as INotifyPropertyChanged;

            if (notifier != null)
            {
                notifier.SetBinding(binding);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Removes any bindings from the object that are currently targeting the property at the specified target path.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="targetPath">The path to the property that is to be cleared of any bindings.</param>
        public static void ClearBinding(this IPairable obj, string targetPath)
        {
            var notifier = obj as INotifyPropertyChanged ?? obj.Pair as INotifyPropertyChanged;

            if (notifier != null)
            {
                notifier.ClearBinding(targetPath);
            }
        }
Beispiel #3
0
        /// <summary>
        /// Removes every binding where the object is either the target or the source.
        /// </summary>
        /// <param name="obj">The object.</param>
        public static void ClearAllBindings(this IPairable obj)
        {
            var notifier = obj as INotifyPropertyChanged ?? obj.Pair as INotifyPropertyChanged;

            if (notifier != null)
            {
                notifier.ClearAllBindings();
            }
        }
Beispiel #4
0
        public static void OnPropertyChanged(this IPairable obj, [CallerMemberName] string propertyName = null)
        {
            var jObject = obj as Java.Lang.Object;

            if (obj is INotifyPropertyChanged && (jObject == null || jObject.Handle != IntPtr.Zero))
            {
                obj.RaiseEvent(nameof(INotifyPropertyChanged.PropertyChanged), new PropertyChangedEventArgs(propertyName));
            }
        }
        /// <summary>
        /// Returns the value of the property or field with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name of the property or field whose value is to be returned.</param>
        /// <param name="index">Optional index values for indexed properties.  This value should be <c>null</c> for non-indexed properties.</param>
        public static object GetValue(this IPairable obj, string name, object[] index)
        {
            if (obj == null)
            {
                return(null);
            }

            var type     = obj.GetType();
            var property = Device.Reflector.GetProperty(type, name);

            if (property != null)
            {
                return(property.GetValue(obj, index));
            }

            var field = Device.Reflector.GetField(type, name);

            if (field != null)
            {
                return(field.GetValue(obj));
            }

            var pairable = obj as IPairable;

            if (pairable == null || pairable.Pair == null)
            {
                return(null);
            }

            obj      = pairable.Pair;
            type     = obj.GetType();
            property = Device.Reflector.GetProperty(type, name);
            if (property != null)
            {
                return(property.GetValue(obj, index));
            }

            field = Device.Reflector.GetField(type, name);
            if (field != null)
            {
                return(field.GetValue(obj));
            }

            return(null);
        }
Beispiel #6
0
        internal static bool RaiseEvent(this IPairable obj, string eventName, EventArgs args)
        {
            var type = obj.GetType();
            var evt  = type.GetEvent(eventName, BindingFlags.Instance | BindingFlags.Public);

            if (evt != null)
            {
                var attribute = evt.GetCustomAttributes <EventDelegateAttribute>().FirstOrDefault();
                if (attribute != null)
                {
                    eventName = attribute.DelegateName;
                }

                FieldInfo info = null;
                do
                {
                    info = type.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
                    type = type.BaseType;
                }while (info == null && type != null);

                if (info != null)
                {
                    var del = info.GetValue(obj) as MulticastDelegate;
                    if (del != null)
                    {
                        var invocationList = del.GetInvocationList();
                        foreach (var method in invocationList)
                        {
                            method.Method.Invoke(method.Target, new object[] { obj.Pair ?? obj, EventArgs.Empty });
                        }
                        return(true);
                    }
                }
            }

            return(false);
        }
        /// <summary>
        /// Attempts to raise the event with the specified name and <see cref="EventArgs"/> on the current instance.
        /// </summary>
        /// <param name="obj">The current object.</param>
        /// <param name="eventName">The name of the event to be raised.</param>
        /// <param name="args">The <see cref="EventArgs"/> instance containing the event data.</param>
        /// <returns><c>true</c> if the event is successfully raised, otherwise <c>false</c>.</returns>
        public static bool RaiseEvent(this IPairable obj, string eventName, EventArgs args)
        {
            if (obj == null)
            {
                return(false);
            }
            var type = obj.GetType();
            var evt  = Device.Reflector.GetEvent(type, eventName);

            if (evt == null)
            {
                return(false);
            }
            var attribute = evt.GetCustomAttributes(typeof(EventDelegateAttribute), true).FirstOrDefault() as EventDelegateAttribute;

            if (attribute != null)
            {
                eventName = attribute.DelegateName;
            }

            FieldInfo info;

            do
            {
                info = Device.Reflector.GetField(type, eventName);
                type = Device.Reflector.GetBaseType(type);
            }while (info == null && type != null);

            if (info == null && obj.Pair != null)
            {
                obj  = obj.Pair;
                type = obj.GetType();
                evt  = Device.Reflector.GetEvent(type, eventName);
                if (evt == null)
                {
                    return(false);
                }
                attribute = evt.GetCustomAttributes(typeof(EventDelegateAttribute), true).FirstOrDefault() as EventDelegateAttribute;
                if (attribute != null)
                {
                    eventName = attribute.DelegateName;
                }

                do
                {
                    info = Device.Reflector.GetField(type, eventName);
                    type = Device.Reflector.GetBaseType(type);
                }while (info == null && type != null);
            }

            if (info == null)
            {
                return(false);
            }
            var del = info.GetValue(obj) as MulticastDelegate;

            if (del == null)
            {
                return(false);
            }
            var invocationList = del.GetInvocationList();

            foreach (var method in invocationList)
            {
                method.Raise(obj, args);
            }
            return(true);
        }
 /// <summary>
 /// Sets the value of the property or field with the specified <paramref name="name"/> when running on one of the specified <see cref="MobileTarget"/>s.
 /// </summary>
 /// <param name="obj">The object.</param>
 /// <param name="name">The name the property or field whose value is to be set.</param>
 /// <param name="value">The value to set to the property or field.</param>
 /// <param name="index">Optional index values for indexed properties.  This value should be <c>null</c> for non-indexed properties.</param>
 /// <param name="targets">The targets on which the code must be running for the value to be set.</param>
 public static void SetValue(this IPairable obj, string name, object value, object[] index, MobileTarget targets)
 {
     obj.SetValue(name, value, index, null, targets);
 }
 /// <summary>
 /// Returns the value of the property or field with the specified <paramref name="name"/>.
 /// </summary>
 /// <param name="obj">The object.</param>
 /// <param name="name">The name of the property or field whose value is to be returned.</param>
 public static object GetValue(this IPairable obj, string name)
 {
     return(obj.GetValue(name, null));
 }
        /// <summary>
        /// Sets the value of the property or field with the specified <paramref name="name"/> when running on one of the specified <see cref="MobileTarget"/>s.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name of the property or field whose value is to be set.</param>
        /// <param name="value">The value to set to the property or field.</param>
        /// <param name="index">Optional index values for indexed properties.  This value should be <c>null</c> for non-indexed properties.</param>
        /// <param name="converter">An optional converter to use on the value prior to setting the property or field.</param>
        /// <param name="targets">The targets on which the code must be running for the value to be set.</param>
        public static void SetValue(this IPairable obj, string name, object value, object[] index, IValueConverter converter, MobileTarget targets)
        {
            if (obj == null || !targets.HasFlag(iApp.Factory.Target))
            {
                return;
            }

            var type = obj.GetType();

            try
            {
                var property = Device.Reflector.GetProperty(type, name);
                if (property != null)
                {
                    if (converter != null)
                    {
                        value = converter.Convert(value, property.PropertyType, null);
                    }
                    else
                    {
                        value = Binding.GetConvertedValue(value, property.PropertyType);
                    }
                    property.SetValue(obj, value, index);
                    return;
                }

                var field = Device.Reflector.GetField(type, name);
                if (field != null)
                {
                    if (converter != null)
                    {
                        value = converter.Convert(value, field.FieldType, null);
                    }
                    else
                    {
                        value = Binding.GetConvertedValue(value, field.FieldType);
                    }
                    field.SetValue(obj, value);
                    return;
                }

                if (obj != null && obj.Pair != null)
                {
                    type     = obj.Pair.GetType();
                    property = Device.Reflector.GetProperty(type, name);
                    if (property != null)
                    {
                        if (converter != null)
                        {
                            value = converter.Convert(value, property.PropertyType, null);
                        }
                        else
                        {
                            value = Binding.GetConvertedValue(value, property.PropertyType);
                        }
                        property.SetValue(obj.Pair, value, index);
                        return;
                    }

                    field = Device.Reflector.GetField(type, name);
                    if (field != null)
                    {
                        if (converter != null)
                        {
                            value = converter.Convert(value, field.FieldType, null);
                        }
                        else
                        {
                            value = Binding.GetConvertedValue(value, field.FieldType);
                        }
                        field.SetValue(obj.Pair, value);
                    }
                }
            }
            catch (Exception e)
            {
                iApp.Log.Warn("Unable to set value of member on {0}.", e, obj.ToString());
            }
        }
 /// <summary>
 /// Sets the value of the property or field with the specified <paramref name="name"/> when running on one of the specified <see cref="MobileTarget"/>s.
 /// </summary>
 /// <param name="obj">The object.</param>
 /// <param name="name">The name of the property or field whose value is to be set.</param>
 /// <param name="value">The value to set to the property or field.</param>
 /// <param name="converter">An optional converter to use on the value prior to setting the property or field.</param>
 /// <param name="targets">The targets on which the code must be running for the value to be set.</param>
 public static void SetValue(this IPairable obj, string name, object value, IValueConverter converter, MobileTarget targets)
 {
     obj.SetValue(name, value, null, converter, targets);
 }
Beispiel #12
0
        /// <summary>
        /// Positions and sizes the children of a grid using the grid's columns and rows.
        /// </summary>
        /// <param name="grid">The grid object.</param>
        /// <param name="minimumSize">The minimum size of the area in which the children are placed.</param>
        /// <param name="maximumSize">The maximum size of the area in which the children are placed.</param>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="grid"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="minimumSize"/> or <paramref name="maximumSize"/> contains a negative value.</exception>
        /// <exception cref="ArgumentException">Thrown when <paramref name="minimumSize"/> has an infinite width or height -or- when the width of <paramref name="maximumSize"/> is infinite and the grid contains at least one star-sized column -or- when the height of <paramref name="maximumSize"/> is infinite and the grid contains at least one star-sized row.</exception>
        public static Size PerformLayout(this IGridBase grid, Size minimumSize, Size maximumSize)
        {
            if (grid == null)
            {
                throw new ArgumentNullException("grid");
            }

            #region ILayoutInstruction layout preprocessors

            var instruction = grid as ILayoutInstruction;
            if (instruction == null)
            {
                var pairable = grid as IPairable;
                if (pairable != null)
                {
                    instruction = pairable.Pair as ILayoutInstruction;
                }
            }

            if (instruction != null)
            {
                iApp.Factory.Instructor.Layout(instruction);

                IPairable pair     = null;
                var       pairable = grid as IPairable;
                if (pairable != null)
                {
                    pair = pairable.Pair;
                }

                var cell = grid as IGridCell ?? pair as IGridCell;
                if (cell != null)
                {
                    minimumSize.Height = cell.MinHeight;
                    maximumSize.Height = cell.MaxHeight;
                }
            }

            #endregion

            maximumSize.Width  = Math.Max(maximumSize.Width, minimumSize.Width);
            maximumSize.Height = Math.Max(maximumSize.Height, minimumSize.Height);

            if (double.IsInfinity(minimumSize.Width) || double.IsInfinity(minimumSize.Height))
            {
                throw new ArgumentException("Minimum size must not have an infinite width or height.", "minimumSize");
            }

            if (minimumSize.Width < 0 || minimumSize.Height < 0)
            {
                throw new ArgumentOutOfRangeException("minimumSize", "The given size must not contain any negative values.");
            }

            if (maximumSize.Width < 0 || maximumSize.Height < 0)
            {
                throw new ArgumentOutOfRangeException("maximumSize", "The given size must not contain any negative values.");
            }

            // if the grid has no rows or columns, provide one
            var rows = grid.Rows.Count == 0 ? new List <Row> {
                new Row(1, LayoutUnitType.Auto)
            } : grid.Rows.ToList();
            var columns = grid.Columns.Count == 0 ? new List <Column> {
                new Column(1, LayoutUnitType.Auto)
            } : grid.Columns.ToList();

            #region Build index dictionary

            var actualIndices = new Dictionary <IElement, IndexPair>();
            foreach (var element in grid.Children)
            {
                if (element.ColumnSpan < 1 || element.RowSpan < 1)
                {
                    throw new ArgumentOutOfRangeException("grid", "UI elements must not have negative column or row spans.");
                }

                actualIndices.Add(element, new IndexPair
                {
                    ColumnIndex = Math.Min(element.ColumnIndex, columns.Count - 1),
                    RowIndex    = Math.Min(element.RowIndex, rows.Count - 1)
                });
            }

            #endregion

            #region Find indices of auto controls

            if (actualIndices.Keys.Any(control => control.RowIndex < 0 || control.ColumnIndex < 0))
            {
                var columnHeights = new int[columns.Count];

                //Copy controls to an array in case the controls collection is modified
                var controls = new IElement[actualIndices.Count];
                actualIndices.Keys.CopyTo(controls, 0);

                foreach (var control in controls)
                {
                    var controlIndex = actualIndices[control];
                    var autoControl  = controlIndex.RowIndex == Element.AutoLayoutIndex || controlIndex.ColumnIndex == Element.AutoLayoutIndex;

                    if (controlIndex.RowIndex == Element.AutoLayoutIndex)
                    {
                        // If we have a column index, honor it
                        if (controlIndex.ColumnIndex >= 0)
                        {
                            controlIndex.RowIndex = columnHeights.Skip(controlIndex.ColumnIndex).Take(control.ColumnSpan).Max();
                            var obstructors = GetGridControls(actualIndices, controlIndex.RowIndex, controlIndex.ColumnIndex, control).ToList();
                            while (obstructors.Any())
                            {
                                // Try the next available row
                                controlIndex.RowIndex = obstructors.Max(p => p.Value.RowIndex + p.Key.RowSpan);
                                obstructors           = GetGridControls(actualIndices, controlIndex.RowIndex, controlIndex.ColumnIndex, control).ToList();
                            }
                        }
                        else
                        {
                            // Set a minimum row and drop to auto-column logic
                            controlIndex.RowIndex = columnHeights.Min();
                        }
                    }

                    // Place in specified row or one with an available span of columns
                    while (controlIndex.ColumnIndex == Element.AutoLayoutIndex)
                    {
                        controlIndex.ColumnIndex = GetColumnForRow(controlIndex.RowIndex, columns.Count, control, actualIndices);
                        if (controlIndex.ColumnIndex > Element.AutoLayoutIndex)
                        {
                            break;
                        }
                        var candidates = columnHeights.Where(r => r > controlIndex.RowIndex).ToList();
                        controlIndex.RowIndex = candidates.Any() ? candidates.Min() : rows.Count;
                    }

                    while (control.RowSpan + controlIndex.RowIndex > rows.Count)
                    {
                        rows.Add(Row.AutoSized);
                    }

                    actualIndices[control] = controlIndex;

                    // Record high water mark for each column the control occludes.
                    for (int i = 0; i < control.ColumnSpan && controlIndex.ColumnIndex + i < columnHeights.Length; i++)
                    {
                        int nextAvailableRow = controlIndex.RowIndex + control.RowSpan;

                        // Auto controls always leave a high water mark. Manually-placed controls only occlude
                        // completely filled columns so that auto controls may be placed above them.
                        if (autoControl || controlIndex.RowIndex == columnHeights[controlIndex.ColumnIndex + i])
                        {
                            //If a manual control blocks the next row, include it in the high water mark.
                            Dictionary <IElement, IndexPair> idxs;
                            while ((idxs = actualIndices.Where(v =>
                                                               v.Value.ColumnIndex <= controlIndex.ColumnIndex + i &&
                                                               v.Value.ColumnIndex + v.Key.ColumnSpan > controlIndex.ColumnIndex + i &&
                                                               v.Value.RowIndex <= nextAvailableRow &&
                                                               v.Value.RowIndex + v.Key.RowSpan > nextAvailableRow)
                                           .ToDictionary(p => p.Key, p => p.Value)).Any())
                            {
                                nextAvailableRow = idxs.Max(p => p.Value.RowIndex + p.Key.RowSpan);
                            }
                            columnHeights[controlIndex.ColumnIndex + i] = nextAvailableRow;
                        }
                    }
                }
            }

            #endregion

            var scale = iApp.Factory.GetDisplayScale();
            maximumSize.Height *= scale;
            minimumSize.Height *= scale;

            // total grid size minus grid padding
            double totalGridWidth  = double.IsInfinity(maximumSize.Width) ? double.MaxValue : maximumSize.Width;
            double totalGridHeight = double.IsInfinity(maximumSize.Height) ? double.MaxValue : maximumSize.Height;
            totalGridWidth  -= (grid.Padding.Left + grid.Padding.Right) * scale;
            totalGridHeight -= (grid.Padding.Top + grid.Padding.Bottom) * scale;

            // available grid size for autos
            double availableGridWidth  = totalGridWidth;
            double availableGridHeight = totalGridHeight;

            var rowSpaces    = new Space[rows.Count];
            var columnSpaces = new Space[columns.Count];
            var elementSizes = new Dictionary <IElement, Size>(actualIndices.Count);

            #region Star sizing with no size limit

            // if there are any star rows or columns and the maximum size is infinite,
            // treat the star with the smallest weight as an auto and use it to size the others.
            int baseStarRowIndex = -1;
            if (double.IsInfinity(maximumSize.Height))
            {
                double minWeight = double.MaxValue;
                for (int i = 0; i < rows.Count; i++)
                {
                    var row = rows[i];
                    if (row.UnitType == LayoutUnitType.Star && minWeight > row.Height)
                    {
                        minWeight        = row.Height;
                        baseStarRowIndex = i;
                    }
                }

                if (baseStarRowIndex >= 0)
                {
                    rows[baseStarRowIndex] = new Row(rows[baseStarRowIndex].Height, LayoutUnitType.Auto);
                }
            }

            int baseStarColumnIndex = -1;
            if (double.IsInfinity(maximumSize.Width))
            {
                double minWeight = double.MaxValue;
                for (int i = 0; i < columns.Count; i++)
                {
                    var column = columns[i];
                    if (column.UnitType == LayoutUnitType.Star && minWeight > column.Width)
                    {
                        minWeight           = column.Width;
                        baseStarColumnIndex = i;
                    }
                }

                if (baseStarColumnIndex >= 0)
                {
                    columns[baseStarColumnIndex] = new Column(columns[baseStarColumnIndex].Width, LayoutUnitType.Auto);
                }
            }

            #endregion

            #region Absolute sizing

            for (int rIndex = 0; rIndex < rows.Count; rIndex++)
            {
                Row row = rows[rIndex];
                if (row.UnitType == LayoutUnitType.Absolute)
                {
                    availableGridHeight   -= row.Height * scale;
                    rowSpaces[rIndex].Size = row.Height * scale;
                }
            }

            for (int cIndex = 0; cIndex < columns.Count; cIndex++)
            {
                Column column = columns[cIndex];
                if (column.UnitType == LayoutUnitType.Absolute)
                {
                    availableGridWidth       -= column.Width * scale;
                    columnSpaces[cIndex].Size = column.Width * scale;
                }
            }

            #endregion

            // loop through every child element, regardless of column/row indices or spans, and determine its desired size.
            // this is considered the 'measure' pass and is just to get an idea of how much space each element wants.
            foreach (var kvp in actualIndices)
            {
                var element   = kvp.Key;
                var indexPair = kvp.Value;

                if (element.Visibility == Visibility.Collapsed)
                {
                    elementSizes[element] = Size.Empty;
                    continue;
                }

                double maxElementWidth  = 0;
                double maxElementHeight = 0;

                bool inAutoColumn = false, inAutoRow = false, takeAll = false;
                int  lastColumn = Math.Min(columns.Count, indexPair.ColumnIndex + element.ColumnSpan);
                for (int i = indexPair.ColumnIndex; i < lastColumn; i++)
                {
                    var column = columns[i];
                    if (column.UnitType == LayoutUnitType.Absolute)
                    {
                        maxElementWidth += column.Width;
                    }
                    else if (!takeAll)
                    {
                        // if the element spans any auto or star column, its constraining width will include the
                        // entire available grid width minus any absolutes that the element is not a part of.
                        // this is because we don't know the final size of these columns yet, and we need to assume
                        // that they will take all of the grid's remaining available space.
                        takeAll          = true;
                        maxElementWidth += availableGridWidth;
                    }

                    if (column.UnitType == LayoutUnitType.Auto)
                    {
                        inAutoColumn = true;
                    }
                }

                takeAll = false;
                int lastRow = Math.Min(rows.Count, indexPair.RowIndex + element.RowSpan);
                for (int i = indexPair.RowIndex; i < lastRow; i++)
                {
                    var row = rows[i];
                    if (row.UnitType == LayoutUnitType.Absolute)
                    {
                        maxElementHeight += row.Height;
                    }
                    else if (!takeAll)
                    {
                        // if the element spans any auto row, its constraining height will include the
                        // entire available grid height minus any absolutes that the element is not a part of.
                        // this is because we don't know the final size of these rows yet, and we need to assume
                        // that they will take all of the grid's remaining available space.
                        takeAll           = true;
                        maxElementHeight += availableGridHeight;
                    }

                    if (row.UnitType == LayoutUnitType.Auto)
                    {
                        inAutoRow = true;
                    }
                }

                // if the element doesn't span any autos, we don't need to measure it in this pass
                if (!inAutoColumn && !inAutoRow)
                {
                    continue;
                }

                var desiredSize = element.Measure(new Size(Math.Max(0, maxElementWidth - (element.Margin.Left + element.Margin.Right) * scale),
                                                           Math.Max(0, maxElementHeight - (element.Margin.Top + element.Margin.Bottom) * scale)));

                elementSizes[element] = desiredSize;

                // if the element only spans one auto column, we can use it to determine the ultimate width for the column
                if (indexPair.ColumnIndex == (lastColumn - 1) && columns[indexPair.ColumnIndex].UnitType == LayoutUnitType.Auto)
                {
                    double totalWidth = Math.Max(desiredSize.Width + (element.Margin.Left + element.Margin.Right) * scale, 0);

                    var space = columnSpaces[indexPair.ColumnIndex];
                    if (space.Size < totalWidth)
                    {
                        space.Size = totalWidth;
                        columnSpaces[indexPair.ColumnIndex] = space;
                    }
                }

                // if the element only spans one auto row, we can use it to determine the ultimate height for the row
                if (indexPair.RowIndex == (lastRow - 1) && rows[indexPair.RowIndex].UnitType == LayoutUnitType.Auto)
                {
                    double totalHeight = Math.Max(desiredSize.Height + (element.Margin.Top + element.Margin.Bottom) * scale, 0);

                    var space = rowSpaces[indexPair.RowIndex];
                    if (space.Size < totalHeight)
                    {
                        space.Size = totalHeight;
                        rowSpaces[indexPair.RowIndex] = space;
                    }
                }
            }

            SizeColumns(grid, minimumSize.Width, availableGridWidth, columns, columnSpaces, baseStarColumnIndex, scale);
            SizeRows(grid, minimumSize.Height, availableGridHeight, rows, rowSpaces, baseStarRowIndex, scale);

            // this is considered the 'arrange' pass and is where we get the final size of each element before giving them an XY position.
            // at this point, all columns and rows should have a size that's pretty close to their final size.
            // some adjustments may be required if the second measurement of an element causes an auto row or column to be resized.
            foreach (var keyValue in actualIndices)
            {
                var element   = keyValue.Key;
                var indexPair = keyValue.Value;

                if (element.Visibility == Visibility.Collapsed)
                {
                    element.SetLocation(new Point(columnSpaces[indexPair.ColumnIndex].Origin, rowSpaces[indexPair.RowIndex].Origin), Size.Empty);
                    continue;
                }

                var marginLeft   = element.Margin.Left * scale;
                var marginTop    = element.Margin.Top * scale;
                var marginRight  = element.Margin.Right * scale;
                var marginBottom = element.Margin.Bottom * scale;

                Space rowSpace = rowSpaces[indexPair.RowIndex];
                for (int i = indexPair.RowIndex + 1; i < (indexPair.RowIndex + element.RowSpan) && i < rows.Count; i++)
                {
                    rowSpace.Size += rowSpaces[i].Size;
                }

                Space columnSpace = columnSpaces[indexPair.ColumnIndex];
                for (int i = indexPair.ColumnIndex + 1; i < (indexPair.ColumnIndex + element.ColumnSpan) && i < columns.Count; i++)
                {
                    columnSpace.Size += columnSpaces[i].Size;
                }

                var constraints = new Size(columnSpace.Size - (marginLeft + marginRight), rowSpace.Size - (marginTop + marginBottom));

                // labels are a bit special; as one dimension expands or contracts, the other dimension inversely contracts or expands.
                // because of this, if the column width is shorter than the label's desired width, the label will likely want more
                // vertical space than its desired height suggests.  to compensate for this, if the label spans any auto row,
                // we reset the vertical constraint before the label's final measurement and adjust any rows as needed.
                if (element is ILabel)
                {
                    int lastRow = Math.Min(rows.Count, indexPair.RowIndex + element.RowSpan);
                    for (int i = indexPair.RowIndex; i < lastRow; i++)
                    {
                        if (rows[i].UnitType == LayoutUnitType.Auto)
                        {
                            constraints.Height = double.MaxValue;
                            break;
                        }
                    }
                }

                constraints.Width  = Math.Max(constraints.Width, 0);
                constraints.Height = Math.Max(constraints.Height, 0);

                var finalSize      = element.Measure(constraints);
                var totalFinalSize = new Size(finalSize.Width + (marginLeft + marginRight), finalSize.Height + (marginTop + marginBottom));

                var desiredSize      = elementSizes.GetValueOrDefault(element);
                var totalDesiredSize = new Size(desiredSize.Width + (marginLeft + marginRight), desiredSize.Height + (marginTop + marginBottom));

                // if the final size is different from the desired size, see if any row or column needs adjustment.
                if (Math.Abs(totalFinalSize.Width - totalDesiredSize.Width) > 0.01)
                {
                    int trueColumnSpan = Math.Min(columns.Count - indexPair.ColumnIndex, element.ColumnSpan);
                    if (trueColumnSpan == 1 && columns[indexPair.ColumnIndex].UnitType == LayoutUnitType.Auto)
                    {
                        // if the final width is larger than the current size of the column, we need to expand the column.
                        // on the other hand, if this element is what determined the column's initial size and its final
                        // width is smaller, we need to shrink the column.
                        if (totalFinalSize.Width > columnSpace.Size || (totalDesiredSize.Width == columnSpace.Size &&
                                                                        !elementSizes.Any(e => e.Key != element && actualIndices[e.Key].ColumnIndex == indexPair.ColumnIndex &&
                                                                                          e.Value.Width + (marginLeft + marginRight) >= columnSpace.Size)))
                        {
                            columnSpace.Size = totalFinalSize.Width;
                            columnSpaces[indexPair.ColumnIndex] = columnSpace;
                            SizeColumns(grid, minimumSize.Width, availableGridWidth, columns, columnSpaces, baseStarColumnIndex, scale);

                            // SizeColumns may have shrunk the column if it was too large to fit in the grid
                            totalFinalSize.Width = columnSpaces[indexPair.ColumnIndex].Size;
                        }
                    }
                }

                if (Math.Abs(totalFinalSize.Height - totalDesiredSize.Height) > 0.01)
                {
                    int trueRowSpan = Math.Min(rows.Count - indexPair.RowIndex, element.RowSpan);
                    if (trueRowSpan == 1 && rows[indexPair.RowIndex].UnitType == LayoutUnitType.Auto)
                    {
                        // if the final height is larger than the current size of the row, we need to expand the row.
                        // on the other hand, if this element is what determined the row's initial size and its final
                        // height is smaller, we need to shrink the row.
                        if (totalFinalSize.Height > rowSpace.Size || (totalDesiredSize.Height == rowSpace.Size &&
                                                                      !elementSizes.Any(e => e.Key != element && actualIndices[e.Key].RowIndex == indexPair.RowIndex &&
                                                                                        e.Value.Height + (marginTop + marginBottom) >= rowSpace.Size)))
                        {
                            rowSpace.Size = totalFinalSize.Height;
                            rowSpaces[indexPair.RowIndex] = rowSpace;
                            SizeRows(grid, minimumSize.Height, availableGridHeight, rows, rowSpaces, baseStarRowIndex, scale);

                            // SizeRows may have shrunk the row if it was too large to fit in the grid
                            totalFinalSize.Height = rowSpaces[indexPair.RowIndex].Size;
                        }
                    }
                }

                finalSize = new Size(totalFinalSize.Width - (marginLeft + marginRight), totalFinalSize.Height - (marginTop + marginBottom));
                var availableArea = new Size(columnSpace.Size - (marginLeft + marginRight), rowSpace.Size - (marginTop + marginBottom));

                var image = element as IImage;
                if (image == null)
                {
                    var button = element as IButton;
                    if (button != null)
                    {
                        image = button.Image;
                    }
                }

                if (image != null)
                {
                    var dimensions = image.Dimensions;
                    var ratio      = dimensions.Width / dimensions.Height;
                    switch (image.Stretch)
                    {
                    case ContentStretch.Fill:
                    case ContentStretch.UniformToFill:
                        // in the case of UniformToFill, it is the platform's responsibility to clip the image
                        finalSize.Width  = availableArea.Width;
                        finalSize.Height = availableArea.Height;
                        break;

                    case ContentStretch.None:
                        // Stretch.None should still shrink the image if it's too large to fit in its space
                        if (finalSize.Width < dimensions.Width || finalSize.Height < dimensions.Height)
                        {
                            if (finalSize.Width > finalSize.Height * ratio)
                            {
                                finalSize.Width = finalSize.Height * ratio;
                            }
                            else if (finalSize.Width < finalSize.Height * ratio)
                            {
                                finalSize.Height = finalSize.Width / ratio;
                            }
                        }
                        break;

                    case ContentStretch.Uniform:
                        if (availableArea.Width > availableArea.Height * ratio)
                        {
                            finalSize.Width  = availableArea.Height * ratio;
                            finalSize.Height = availableArea.Height;
                        }
                        else if (availableArea.Width < availableArea.Height * ratio)
                        {
                            finalSize.Width  = availableArea.Width;
                            finalSize.Height = availableArea.Width / ratio;
                        }
                        break;
                    }
                }

                var location = new Point(columnSpace.Origin, rowSpace.Origin);

                switch (element.VerticalAlignment)
                {
                case VerticalAlignment.Stretch:
                    location.Y       = rowSpace.Origin + marginTop;
                    finalSize.Height = availableArea.Height;
                    break;

                case VerticalAlignment.Top:
                    location.Y       = rowSpace.Origin + marginTop;
                    finalSize.Height = Math.Min(finalSize.Height, availableArea.Height);
                    break;

                case VerticalAlignment.Bottom:
                    location.Y       = (rowSpace.Origin + rowSpace.Size) - (finalSize.Height + marginBottom);
                    finalSize.Height = Math.Min(finalSize.Height, availableArea.Height);
                    break;

                case VerticalAlignment.Center:
                    location.Y       = (availableArea.Height / 2f) - (finalSize.Height / 2f) + (rowSpace.Origin + marginTop);
                    finalSize.Height = Math.Min(finalSize.Height, availableArea.Height);
                    break;
                }

                switch (element.HorizontalAlignment)
                {
                case HorizontalAlignment.Stretch:
                    location.X      = columnSpace.Origin + marginLeft;
                    finalSize.Width = availableArea.Width;
                    break;

                case HorizontalAlignment.Left:
                    location.X      = columnSpace.Origin + marginLeft;
                    finalSize.Width = Math.Min(finalSize.Width, availableArea.Width);
                    break;

                case HorizontalAlignment.Right:
                    location.X      = (columnSpace.Origin + columnSpace.Size) - (finalSize.Width + marginRight);
                    finalSize.Width = Math.Min(finalSize.Width, availableArea.Width);
                    break;

                case HorizontalAlignment.Center:
                    location.X      = (availableArea.Width / 2f) - (finalSize.Width / 2f) + (columnSpace.Origin + marginLeft);
                    finalSize.Width = Math.Min(finalSize.Width, columnSpace.Size - marginLeft - marginRight);
                    break;
                }

                finalSize.Width  = Math.Max(finalSize.Width, 0);
                finalSize.Height = Math.Max(finalSize.Height, 0);

                elementSizes[element] = finalSize;
                element.SetLocation(location, finalSize);
            }

            // everything is laid out to the maximum size, so we know that we haven't exceeded it
            return(new Size(Math.Max(columnSpaces.Sum(c => c.Size) + scale * (grid.Padding.Left + grid.Padding.Right), minimumSize.Width),
                            Math.Max(rowSpaces.Sum(r => r.Size) + (grid.Padding.Top + grid.Padding.Bottom) * scale, minimumSize.Height)));
        }