Merge pull request #980 from StephenMcConnel/bug-18638
authorZoltan Varga <vargaz@gmail.com>
Fri, 5 Dec 2014 19:09:00 +0000 (14:09 -0500)
committerZoltan Varga <vargaz@gmail.com>
Fri, 5 Dec 2014 19:09:00 +0000 (14:09 -0500)
Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)

mcs/class/Managed.Windows.Forms/System.Windows.Forms.Layout/TableLayout.cs
mcs/class/Managed.Windows.Forms/System.Windows.Forms/TableLayoutPanel.cs
mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/.gitattributes
mcs/class/Managed.Windows.Forms/Test/System.Windows.Forms/TableLayoutTest.cs

index 98b16b4bcdd4e49da73a96fabaaff4b416814b51..7d306658c6f9160878ae8576090f865f71914cb8 100644 (file)
@@ -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)
index e288bf879a9f2eb396f403cbfdf52d537a0e3523..4ba18553c3c8ab04c9dc75a8da72af3a9e22e0c0 100644 (file)
@@ -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);
+               }
+
+               /// <summary>
+               /// Adjust the widths of the columns underlying a span if necessary.
+               /// </summary>
+               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<int> adjusting = new List<int>();
+                               List<float> adjusting_widths = new List<float>();
+                               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;
+                                       }
+                               }
+                       }
+               }
+
+               /// <summary>
+               /// Adjust the heights of the rows underlying a span if necessary.
+               /// </summary>
+               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<int> adjusting = new List<int>();
+                               List<float> adjusting_heights = new List<float>();
+                               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
                
index ace0846b8466030f772aa3d0ab7521e6ca6a9edc..7dca390e2f11401fe0188459ad08ff2c11d7d3a4 100644 (file)
@@ -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
index feb073922441ea307c0c971b81baeae7fb95cfb6..2d5ad06b0673fbed2ca2a782cd72f48c87889d18 100644 (file)
@@ -1672,6 +1672,55 @@ namespace MonoTests.System.Windows.Forms
                                tlp.LayoutSettings = tls; // Should not throw an exception\r
                        }\r
                }\r
+\r
+               [Test]\r
+               public void XamarinBug18638 ()\r
+               {\r
+                       // Spanning items should not have their entire width assigned to the first column in the span.\r
+                       TableLayoutPanel tlp = new TableLayoutPanel ();\r
+                       tlp.SuspendLayout ();\r
+                       tlp.Size = new Size(291, 100);\r
+                       tlp.AutoSize = true;\r
+                       tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Absolute, 60));\r
+                       tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Percent, 100));\r
+                       tlp.ColumnStyles.Add (new ColumnStyle (SizeType.Absolute, 45));\r
+                       tlp.ColumnCount = 3;\r
+                       tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize));\r
+                       var label1 = new Label {AutoSize = true, Text = @"This line spans all three columns in the table!"};\r
+                       tlp.Controls.Add (label1, 0, 0);\r
+                       tlp.SetColumnSpan (label1, 3);\r
+                       tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize));\r
+                       tlp.RowCount = 1;\r
+                       var label2 = new Label {AutoSize = true, Text = @"This line spans columns two and three."};\r
+                       tlp.Controls.Add (label2, 1, 1);\r
+                       tlp.SetColumnSpan (label2, 2);\r
+                       tlp.RowCount = 2;\r
+                       AddTableRow (tlp, "First Row", "This is a test");\r
+                       AddTableRow (tlp, "Row 2", "This is another test");\r
+                       tlp.ResumeLayout ();\r
+\r
+                       var widths = tlp.GetColumnWidths ();\r
+                       Assert.AreEqual (4, tlp.RowCount, "X18638-1");\r
+                       Assert.AreEqual (3, tlp.ColumnCount, "X18638-2");\r
+                       Assert.AreEqual (60, widths[0], "X18638-3");\r
+                       Assert.Greater (label2.Width, widths[1], "X18638-5");\r
+                       Assert.AreEqual (45, widths[2], "X18638-4");\r
+               }\r
+\r
+               private void AddTableRow(TableLayoutPanel tlp, string label, string text)\r
+               {\r
+                       tlp.SuspendLayout ();\r
+                       int row = tlp.RowCount;\r
+                       tlp.RowStyles.Add (new RowStyle (SizeType.AutoSize));\r
+                       var first = new Label {AutoSize = true, Dock = DockStyle.Fill, Text = label};\r
+                       tlp.Controls.Add (first, 0, row);\r
+                       var second = new TextBox {AutoSize = true, Text = text, Dock = DockStyle.Fill, Multiline = true};\r
+                       tlp.Controls.Add (second, 1, row);\r
+                       var third = new Button {Text = @"DEL", Dock = DockStyle.Fill};\r
+                       tlp.Controls.Add (third, 2, row);\r
+                       tlp.RowCount = row + 1;\r
+                       tlp.ResumeLayout ();\r
+               }\r
        }\r
 }\r
 #endif\r