From: Zoltan Varga Date: Fri, 5 Dec 2014 19:09:00 +0000 (-0500) Subject: Merge pull request #980 from StephenMcConnel/bug-18638 X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=2b19bdc99c0a18b514eddfdeb4a6d2ad8d8b0a18;hp=c49994e9042b4ed6620cd0a795821b669f2e7d45;p=mono.git Merge pull request #980 from StephenMcConnel/bug-18638 Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638) --- diff --git a/mcs/class/Managed.Windows.Forms/System.Windows.Forms.Layout/TableLayout.cs b/mcs/class/Managed.Windows.Forms/System.Windows.Forms.Layout/TableLayout.cs index 98b16b4bcdd..7d306658c6f 100644 --- a/mcs/class/Managed.Windows.Forms/System.Windows.Forms.Layout/TableLayout.cs +++ b/mcs/class/Managed.Windows.Forms/System.Windows.Forms.Layout/TableLayout.cs @@ -368,7 +368,20 @@ namespace System.Windows.Forms.Layout } if (total_width > 0) - panel.column_widths[col_styles.Count - 1] += total_width; + { + // Find the last column that isn't an Absolute SizeType, and give it + // all this free space. (Absolute sized columns need to retain their + // absolute width if at all possible!) + int col = col_styles.Count - 1; + for (; col >= 0; --col) + { + if (col_styles[col].SizeType != SizeType.Absolute) + break; + } + if (col < 0) + col = col_styles.Count - 1; + panel.column_widths[col] += total_width; + } // Figure up all the row heights int total_height = parentDisplayRectangle.Height - (border_width * (rows + 1)); @@ -464,7 +477,20 @@ namespace System.Windows.Forms.Layout } if (total_height > 0) - panel.row_heights[row_styles.Count - 1] += total_height; + { + // Find the last row that isn't an Absolute SizeType, and give it + // all this free space. (Absolute sized rows need to retain their + // absolute height if at all possible!) + int row = row_styles.Count - 1; + for (; row >= 0; --row) + { + if (row_styles[row].SizeType != SizeType.Absolute) + break; + } + if (row < 0) + row = row_styles.Count - 1; + panel.row_heights[row] += total_height; + } } private void LayoutControls (TableLayoutPanel panel) diff --git a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TableLayoutPanel.cs b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TableLayoutPanel.cs index e288bf879a9..4ba18553c3c 100644 --- a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TableLayoutPanel.cs +++ b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TableLayoutPanel.cs @@ -28,6 +28,7 @@ using System; using System.Drawing; +using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows.Forms.Layout; @@ -514,29 +515,67 @@ namespace System.Windows.Forms int actual_cols = actual_positions.GetLength (0); int actual_rows = actual_positions.GetLength (1); + // Find the largest column-span/row-span values. A table entry that spans more than one + // column (row) should not be treated as though it's width (height) all belongs to the + // first column (row), but should be spread out across all the columns (rows) that are + // spanned. So we need to keep track of the widths (heights) of spans as well as + // individual columns (rows). + int max_colspan = 1, max_rowspan = 1; + foreach (Control c in Controls) + { + max_colspan = Math.Max(max_colspan, GetColumnSpan(c)); + max_rowspan = Math.Max(max_rowspan, GetRowSpan(c)); + } + // Figure out how wide the owner needs to be int[] column_widths = new int[actual_cols]; + // Keep track of widths for spans as well as columns. column_span_widths[i,j] stores + // the maximum width for items column i than have a span of j+1 (ie, covers columns + // i through i+j). + int[,] column_span_widths = new int[actual_cols, max_colspan]; + int[] biggest = new int[max_colspan]; float total_column_percentage = 0f; - // Figure out how tall each column wants to be + // Figure out how wide each column wants to be for (int i = 0; i < actual_cols; i++) { if (i < ColumnStyles.Count && ColumnStyles[i].SizeType == SizeType.Percent) total_column_percentage += ColumnStyles[i].Width; - - int biggest = 0; + int absolute_width = -1; + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType == SizeType.Absolute) + absolute_width = (int)ColumnStyles[i].Width; // use the absolute width if it's absolute! + + for (int s = 0; s < max_colspan; ++s) + biggest[s] = 0; for (int j = 0; j < actual_rows; j++) { Control c = actual_positions[i, j]; if (c != null) { - if (!c.AutoSize) - biggest = Math.Max (biggest, c.ExplicitBounds.Width + c.Margin.Horizontal + Padding.Horizontal); + int colspan = GetColumnSpan (c); + if (colspan == 0) + continue; + if (colspan == 1 && absolute_width > -1) + biggest[0] = absolute_width; // use the absolute width if the column has absolute width assigned! + else if (!c.AutoSize) + biggest[colspan-1] = Math.Max (biggest[colspan-1], c.ExplicitBounds.Width + c.Margin.Horizontal + Padding.Horizontal); else - biggest = Math.Max (biggest, c.PreferredSize.Width + c.Margin.Horizontal + Padding.Horizontal); + biggest[colspan-1] = Math.Max (biggest[colspan-1], c.PreferredSize.Width + c.Margin.Horizontal + Padding.Horizontal); + } + else if (absolute_width > -1) { + biggest[0] = absolute_width; } } - column_widths[i] = biggest; + for (int s = 0; s < max_colspan; ++s) + column_span_widths[i,s] = biggest[s]; + } + + for (int i = 0; i < actual_cols; ++i) { + for (int s = 1; s < max_colspan; ++s) { + if (column_span_widths[i,s] > 0) + AdjustWidthsForSpans (column_span_widths, i, s); + } + column_widths[i] = column_span_widths[i,0]; } // Because percentage based rows divy up the remaining space, @@ -552,30 +591,55 @@ namespace System.Windows.Forms non_percent_total_width += column_widths[i]; } + int border_width = GetCellBorderWidth (CellBorderStyle); + int needed_width = non_percent_total_width + percent_total_width + (border_width * (actual_cols + 1)); // Figure out how tall the owner needs to be int[] row_heights = new int[actual_rows]; + int[,] row_span_heights = new int[actual_rows, max_rowspan]; + biggest = new int[max_rowspan]; float total_row_percentage = 0f; // Figure out how tall each row wants to be for (int j = 0; j < actual_rows; j++) { if (j < RowStyles.Count && RowStyles[j].SizeType == SizeType.Percent) total_row_percentage += RowStyles[j].Height; + int absolute_height = -1; + if (j < RowStyles.Count && RowStyles[j].SizeType == SizeType.Absolute) + absolute_height = (int)RowStyles[j].Height; // use the absolute height if it's absolute! - int biggest = 0; + for (int s = 0; s < max_rowspan; ++s) + biggest[s] = 0; for (int i = 0; i < actual_cols; i++) { Control c = actual_positions[i, j]; if (c != null) { - if (!c.AutoSize) - biggest = Math.Max (biggest, c.ExplicitBounds.Height + c.Margin.Vertical + Padding.Vertical); + int rowspan = GetRowSpan (c); + if (rowspan == 0) + continue; + if (rowspan == 1 && absolute_height > -1) + biggest[0] = absolute_height; // use the absolute height if the row has absolute height assigned! + else if (!c.AutoSize) + biggest[rowspan-1] = Math.Max (biggest[rowspan-1], c.ExplicitBounds.Height + c.Margin.Vertical + Padding.Vertical); else - biggest = Math.Max (biggest, c.PreferredSize.Height + c.Margin.Vertical + Padding.Vertical); + biggest[rowspan-1] = Math.Max (biggest[rowspan-1], c.PreferredSize.Height + c.Margin.Vertical + Padding.Vertical); + } + else if (absolute_height > -1) { + biggest[0] = absolute_height; } } - row_heights[j] = biggest; + for (int s = 0; s < max_rowspan; ++s) + row_span_heights[j,s] = biggest[s]; + } + + for (int j = 0; j < actual_rows; ++j) { + for (int s = 1; s < max_rowspan; ++s) { + if (row_span_heights[j,s] > 0) + AdjustHeightsForSpans (row_span_heights, j, s); + } + row_heights[j] = row_span_heights[j,0]; } // Because percentage based rows divy up the remaining space, @@ -591,8 +655,111 @@ namespace System.Windows.Forms non_percent_total_height += row_heights[j]; } - int border_width = GetCellBorderWidth (CellBorderStyle); - return new Size (non_percent_total_width + percent_total_width + (border_width * (actual_cols + 1)), non_percent_total_height + percent_total_height + (border_width * (actual_rows + 1))); + int needed_height = non_percent_total_height + percent_total_height + (border_width * (actual_rows + 1)); + + return new Size (needed_width, needed_height); + } + + /// + /// Adjust the widths of the columns underlying a span if necessary. + /// + private void AdjustWidthsForSpans (int[,] widths, int col, int span) + { + // Get the combined width of the columns underlying the span. + int existing_width = 0; + for (int i = col; i <= col+span; ++i) + existing_width += widths[i,0]; + if (widths[col,span] > existing_width) + { + // We need to expand one or more of the underlying columns to fit the span, + // preferably ones that are not Absolute style. + int excess = widths[col,span] - existing_width; + int remaining = excess; + List adjusting = new List(); + List adjusting_widths = new List(); + for (int i = col; i <= col+span; ++i) { + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType != SizeType.Absolute) { + adjusting.Add(i); + adjusting_widths.Add((float)widths[i,0]); + } + } + if (adjusting.Count == 0) { + // if every column is Absolute, spread the gain across every column + for (int i = col; i <= col+span; ++i) { + adjusting.Add(i); + adjusting_widths.Add((float)widths[i,0]); + } + } + float original_total = 0f; + foreach (var w in adjusting_widths) + original_total += w; + // Divide up the needed additional width proportionally. + for (int i = 0; i < adjusting.Count; ++i) { + var idx = adjusting[i]; + var percent = adjusting_widths[i] / original_total; + var adjust = (int)(percent * excess); + widths[idx,0] += adjust; + remaining -= adjust; + } + // Any remaining fragment (1 or 2 pixels?) is divided evenly. + while (remaining > 0) { + for (int i = 0; i < adjusting.Count && remaining > 0; ++i) { + ++widths[adjusting[i],0]; + --remaining; + } + } + } + } + + /// + /// Adjust the heights of the rows underlying a span if necessary. + /// + private void AdjustHeightsForSpans (int[,] heights, int row, int span) + { + // Get the combined height of the rows underlying the span. + int existing_height = 0; + for (int i = row; i <= row+span; ++i) + existing_height += heights[i,0]; + if (heights[row,span] > existing_height) + { + // We need to expand one or more of the underlying rows to fit the span, + // preferably ones that are not Absolute style. + int excess = heights[row,span] - existing_height; + int remaining = excess; + List adjusting = new List(); + List adjusting_heights = new List(); + for (int i = row; i <= row+span; ++i) { + if (i < RowStyles.Count && RowStyles[i].SizeType != SizeType.Absolute) { + adjusting.Add(i); + adjusting_heights.Add((float)heights[i,0]); + } + } + if (adjusting.Count == 0) { + // if every row is Absolute, spread the gain across every row + for (int i = row; i <= row+span; ++i) { + adjusting.Add(i); + adjusting_heights.Add((float)heights[i,0]); + } + } + float original_total = 0f; + foreach (var w in adjusting_heights) + original_total += w; + // Divide up the needed additional height proportionally. + for (int i = 0; i < adjusting.Count; ++i) { + var idx = adjusting[i]; + var percent = adjusting_heights[i] / original_total; + var adjust = (int)(percent * excess); + heights[idx,0] += adjust; + remaining -= adjust; + } + // Any remaining fragment (1 or 2 pixels?) is divided evenly. + while (remaining > 0) { + for (int i = 0; i < adjusting.Count && remaining > 0; ++i) { + ++heights[adjusting[i],0]; + --remaining; + } + } + } } #endregion diff --git a/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/.gitattributes b/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/.gitattributes index ace0846b846..7dca390e2f1 100644 --- a/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/.gitattributes +++ b/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/.gitattributes @@ -16,7 +16,7 @@ /SendKeysTest.cs -crlf /SplitContainerTests.cs -crlf /StatusStripTest.cs -crlf -/TableLayoutTest.cs -crlf +/TableLayoutTest.cs -crlf -whitespace /TimerTest.cs -crlf /ToolStripContainerTest.cs -crlf /ToolStripContentPanelTest.cs -crlf diff --git a/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/TableLayoutTest.cs b/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/TableLayoutTest.cs index feb07392244..2d5ad06b067 100644 --- a/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/TableLayoutTest.cs +++ b/mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/TableLayoutTest.cs @@ -1672,6 +1672,55 @@ namespace MonoTests.System.Windows.Forms tlp.LayoutSettings = tls; // Should not throw an exception } } + + [Test] + public void XamarinBug18638 () + { + // Spanning items should not have their entire width assigned to the first column in the span. + TableLayoutPanel tlp = new TableLayoutPanel (); + tlp.SuspendLayout (); + tlp.Size = new Size(291, 100); + tlp.AutoSize = true; + tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Absolute, 60)); + tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Percent, 100)); + tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Absolute, 45)); + tlp.ColumnCount = 3; + tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize)); + var label1 = new Label {AutoSize = true, Text = @"This line spans all three columns in the table!"}; + tlp.Controls.Add (label1, 0, 0); + tlp.SetColumnSpan (label1, 3); + tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize)); + tlp.RowCount = 1; + var label2 = new Label {AutoSize = true, Text = @"This line spans columns two and three."}; + tlp.Controls.Add (label2, 1, 1); + tlp.SetColumnSpan (label2, 2); + tlp.RowCount = 2; + AddTableRow (tlp, "First Row", "This is a test"); + AddTableRow (tlp, "Row 2", "This is another test"); + tlp.ResumeLayout (); + + var widths = tlp.GetColumnWidths (); + Assert.AreEqual (4, tlp.RowCount, "X18638-1"); + Assert.AreEqual (3, tlp.ColumnCount, "X18638-2"); + Assert.AreEqual (60, widths[0], "X18638-3"); + Assert.Greater (label2.Width, widths[1], "X18638-5"); + Assert.AreEqual (45, widths[2], "X18638-4"); + } + + private void AddTableRow(TableLayoutPanel tlp, string label, string text) + { + tlp.SuspendLayout (); + int row = tlp.RowCount; + tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize)); + var first = new Label {AutoSize = true, Dock = DockStyle.Fill, Text = label}; + tlp.Controls.Add (first, 0, row); + var second = new TextBox {AutoSize = true, Text = text, Dock = DockStyle.Fill, Multiline = true}; + tlp.Controls.Add (second, 1, row); + var third = new Button {Text = @"DEL", Dock = DockStyle.Fill}; + tlp.Controls.Add (third, 2, row); + tlp.RowCount = row + 1; + tlp.ResumeLayout (); + } } } #endif