/// <summary> /// Calculates the width of the complete row /// </summary> /// <param name="row">The row.</param> /// <param name="labelWidth">Width of the first label.</param> /// <param name="availableWidth">Total available width.</param> /// <returns>Total width of the row including all spacing</returns> protected virtual double GetTotalRowWidth(ControlRow row, double labelWidth, double availableWidth) { if (row.Elements.Count == 1 && row.Elements[0].IsFullSpanControl) return availableWidth; var totalWidth = labelWidth + LabelControlLeftSpacing; // TODO: Should we support shrinking here? for (var counter = 1; counter < row.Elements.Count; counter++) // Calculating all but the first one (which is already handled with the labelWidth parameter) { var element = row.Elements[counter]; totalWidth += element.IsConsideredLabelElement ? LabelControlLeftSpacing : EditControlLeftSpacing; totalWidth += element.DesiredSize.Width; //if (counter < row.Elements.Count - 1) // totalWidth += element.IsConsideredLabelElement ? EditControlLeftSpacing : LabelControlLeftSpacing; } return totalWidth; }
/// <summary> /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the <see cref="T:System.Windows.FrameworkElement" />-derived class. /// </summary> /// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param> /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns> protected override Size MeasureOverride(Size availableSize) { base.MeasureOverride(availableSize); InvalidateVisual(); var resultingHeight = 0d; var resultingWidth = 0d; // Before we do anything else, we allow each control to measure itself to the size they would ideally like to be var measureWidth = double.IsNaN(availableSize.Width) ? 10000d : availableSize.Width; var veryLarge = new Size(measureWidth, 100000d); // Giving the control a very large size so we see how much it really wants to use foreach (UIElement child in Children) child.Measure(veryLarge); // Arranging all controls into flowing rows (at this point assuming infinite horizontal space) _currentRows = new List<ControlRow>(); var currentRow = AddNewRow(_currentRows); foreach (var child in Children) { var element = child as FrameworkElement; if (element == null) continue; var lineBreak = SimpleView.GetLineBreak(element); if (lineBreak && currentRow.Elements.Count > 0) currentRow = AddNewRow(_currentRows); var spanWidth = SimpleView.GetSpanFullWidth(element); if (spanWidth) { if (currentRow.Elements.Count > 0) currentRow = AddNewRow(_currentRows); currentRow.Elements.Add(new ControlRowElement {Element = element, IsFullSpanControl = true}); continue; } var label = SimpleView.GetLabel(element); var isStandAloneEditControl = SimpleView.GetIsStandAloneEditControl(element); if (label != null || isStandAloneEditControl) { var individualFont = SimpleView.GetLabelFontFamily(element); var font = individualFont ?? LabelFontFamily; var individualStyle = SimpleView.GetLabelFontStyle(element); var style = individualStyle == FontStyles.Normal ? LabelFontStyle : individualStyle; var individualWeight = SimpleView.GetLabelFontWeight(element); var weight = individualWeight == FontWeights.Normal ? LabelFontWeight : individualWeight; var individualSize = SimpleView.GetLabelFontSize(element); var size = individualSize > 0d ? individualSize : LabelFontSize; var individualBrush = SimpleView.GetLabelForegroundBrush(element); var brush = individualBrush ?? LabelForegroundBrush; currentRow.Elements.Add(new ControlRowElement { Label = label ?? string.Empty, IsAutoGeneratedLabel = true, IsConsideredLabelElement = true, HadOriginalLineBreak = currentRow.Elements.Count == 0, DesiredSize = CalculateLabelSize(label ?? string.Empty, element), FontFamily = font, FontStyle = style, FontWeight = weight, FontSize = size, ForegroundBrush = brush }); } var isLabelElement = false; if (currentRow.Elements.Count%2 == 0) // Could be a label if (element is Label || element is TextBlock) isLabelElement = true; currentRow.Elements.Add(new ControlRowElement { Element = element, IsAutoGeneratedLabel = false, IsConsideredLabelElement = isLabelElement, HadOriginalLineBreak = currentRow.Elements.Count == 0, DesiredSize = isLabelElement ? element.DesiredSize : GetControlSize(element.DesiredSize) }); } // See which items need to be wrapped to the next row (since we do not really have infinite horizontal space) var rowCounter = 0; while (true) { if (rowCounter >= _currentRows.Count) break; var widestLabelWidth = GetWidestFirstLabel(_currentRows); var row = _currentRows[rowCounter]; var rowWidth = GetTotalRowWidth(row, widestLabelWidth, availableSize.Width); if (rowWidth > availableSize.Width) { // This row is too wide... we need to do something about it var fittingItemCount = GetNumberOfElementsThatFitInRow(row, widestLabelWidth, availableSize.Width); if (fittingItemCount < row.Elements.Count) { // We insert a new row into the current flow var insertedRow = new ControlRow(); var elementsToMove = row.Elements.Count - fittingItemCount; for (var moveCounter = 0; moveCounter < elementsToMove; moveCounter++) { var moveElement = row.Elements[fittingItemCount]; // it will always be the next item since we are removing them as we go row.Elements.RemoveAt(fittingItemCount); if (moveCounter == 0 || !moveElement.IsFakeLabel) insertedRow.Elements.Add(moveElement); } if (row.Elements.Count > 1 && row.Elements[row.Elements.Count - 1].IsConsideredLabelElement) { var elementToMove = row.Elements[row.Elements.Count - 1]; row.Elements.RemoveAt(row.Elements.Count - 1); insertedRow.Elements.Insert(0, elementToMove); } _currentRows.Insert(rowCounter + 1, insertedRow); rowCounter = 0; // We need to start over continue; } } rowCounter++; } // Ready to calculate the actual control positions var finalLabelWidth = GetWidestFirstLabel(_currentRows); var currentY = VerticalSpacing; foreach (var row in _currentRows) { if (row.Elements.Count == 1 && row.Elements[0].IsFullSpanControl) { // TODO: Maybe we want some special margins for this case row.Elements[0].DesiredPosition = new Point(0d, currentY); row.Elements[0].DesiredSize = new Size(availableSize.Width, row.Elements[0].Element.DesiredSize.Height); currentY += row.Elements[0].Element.DesiredSize.Height + VerticalSpacing; continue; } var currentX = 0d; var elementCounter = 0; var tallestElement = 0d; var mustUseVerticalLabelOffsetForLabelsInRow = false; foreach (var element in row.Elements) { if (element.IsConsideredLabelElement) { currentX += LabelControlLeftSpacing; if (elementCounter == 0) // We can still decide whether labels must be offset or not if (row.Elements.Count > 1) { var nextElement = row.Elements[1]; if (nextElement != null) if (nextElement.DesiredSize.Height >= element.DesiredSize.Height + VerticalLabelControlOffset) mustUseVerticalLabelOffsetForLabelsInRow = true; // If the next label is taller than the current label, we add an offset. } var controlY = mustUseVerticalLabelOffsetForLabelsInRow ? currentY + VerticalLabelControlOffset : currentY; element.DesiredPosition = new Point(currentX, controlY); element.DesiredSize = new Size(element.DesiredSize.Width, element.DesiredSize.Height); if (elementCounter == 0) element.MaxLabelWidth = finalLabelWidth; } else { currentX += EditControlLeftSpacing; element.DesiredPosition = new Point(currentX, currentY); element.DesiredSize = new Size(element.DesiredSize.Width, element.DesiredSize.Height); } currentX += elementCounter == 0 ? finalLabelWidth : element.DesiredSize.Width; tallestElement = Math.Max(element.DesiredSize.Height, tallestElement); elementCounter++; } currentY += tallestElement + VerticalSpacing; resultingWidth = Math.Max(resultingWidth, currentX); resultingHeight = currentY; } return new Size(resultingWidth, resultingHeight); }
/// <summary>Returns how many of the specified row's elements fit in the available horizontal space</summary> /// <param name="row">The row.</param> /// <param name="labelWidth">Width of the label.</param> /// <param name="availableWidth">Total available width.</param> /// <returns>Number of elements that fit in the row.</returns> protected virtual int GetNumberOfElementsThatFitInRow(ControlRow row, double labelWidth, double availableWidth) { if (row.Elements.Count == 1 && row.Elements[0].IsFullSpanControl) return 1; var fitCount = 1; var totalWidth = labelWidth + LabelControlLeftSpacing; // TODO: Should we support shrinking here? for (var counter = 1; counter < row.Elements.Count; counter++) // Calculating all but the first two (which we will always leave on the first row, even if that forces scrolling) { var element = row.Elements[counter]; totalWidth += element.IsConsideredLabelElement ? LabelControlLeftSpacing : EditControlLeftSpacing; totalWidth += element.DesiredSize.Width; if (totalWidth < availableWidth) fitCount++; // This control still fits on the row else { // This one doesn't fit into this row anymore. // Maybe checkboxes are special if (row.Elements[counter].Element is CheckBox && SpecialCheckBoxBehaviorActive) { // If this isn't the first special captioned checkbox in the group, we need to break the whole group var labelIndex = counter - 1; while (labelIndex > 0) { if (row.Elements[labelIndex].IsConsideredLabelElement) // This would make for a label that is not the first element in the row, and thus we break the whole group of checkboxes over to the next row { fitCount = labelIndex; if (fitCount < 2) fitCount = 2; // Even if 2 controls don't fit, we leave them on that row anyway. Worst case, things have to scroll return fitCount; } labelIndex--; } // Looks like we have not found an inline label yet, so we just need to break however many checkboxes we have if (counter > 1) // We insert another fake label if the control to the left is a checkbox and not a label if (!row.Elements[counter - 1].IsConsideredLabelElement && row.Elements[counter - 1].Element is CheckBox) { InsertFakeLabel(row.Elements, row.Elements[counter].Element, fitCount); if (fitCount < 2) fitCount = 2; // Even if 2 controls don't fit, we leave them on that row anyway. Worst case, things have to scroll return fitCount; } if (fitCount == 1) // We can really only fit one control space-wise, but we still need to keep a label and control on the same line { if (row.Elements.Count > 2 && !row.Elements[2].IsConsideredLabelElement) // If there are any elements after the second one, we need to add another fake label InsertFakeLabel(row.Elements, row.Elements[counter].Element, 2); return 2; } } // Maybe radio buttons are special if (row.Elements[counter].Element is RadioButton && SpecialRadioButtonBehaviorActive) { // If this isn't the first special captioned radio button in the group, we need to break the whole group var labelIndex = counter - 1; while (labelIndex > 0) { if (row.Elements[labelIndex].IsConsideredLabelElement) // This would make for a label that is not the first element in the row, and thus we break the whole group of radio buttons over to the next row { fitCount = labelIndex; if (fitCount < 2) fitCount = 2; // Even if 2 controls don't fit, we leave them on that row anyway. Worst case, things have to scroll return fitCount; } labelIndex--; } // Looks like we have not found an inline label yet, so we just need to break however many radio buttons we have if (counter > 1) // We insert another fake label if the control to the left is a radio button and not a label if (!row.Elements[counter - 1].IsConsideredLabelElement && row.Elements[counter - 1].Element is RadioButton) { InsertFakeLabel(row.Elements, row.Elements[counter].Element, fitCount); if (fitCount < 2) fitCount = 2; // Even if 2 controls don't fit, we leave them on that row anyway. Worst case, things have to scroll return fitCount; } if (fitCount == 1) // We can really only fit one control space-wise, but we still need to keep a label and control on the same line { if (row.Elements.Count > 2 && !row.Elements[2].IsConsideredLabelElement) // If there are any elements after the second one, we need to add another fake label InsertFakeLabel(row.Elements, row.Elements[counter].Element, 2); return 2; } } // If the current control is the edit control, we also consider the label to not have fit if (!element.IsConsideredLabelElement) fitCount--; break; } } if (fitCount < 2) fitCount = 2; // Even if 2 controls don't fit, we leave them on that row anyway. Worst case, things have to scroll return fitCount; }