feat(desktop): size grid columns to their first content, then leave them to the user

Grid columns no longer stretch to fill the control. GridStyle sizes each
column once to its header and the first rows shown (capped at 400 px),
then the widths are the user's to resize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-24 16:31:27 -04:00
1 parent 3f5d402489
commit ebecd42433
12 files changed
+106 -33

No files matched your search

@@ -0,0 +1,48 @@
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.WinForms.Tests.Controls;
public class GridStyleTests
{
[Fact]
public void ColumnsSizeToFirstContentThenKeepTheUsersWidths() => ArchUnitsTests.RunSta(() =>
{
using var form = new Form { Width = 1200, Height = 400, ShowInTaskbar = false };
var grid = GridStyle.Apply(new DataGridView { Dock = DockStyle.Fill, AllowUserToAddRows = false });
var shortColumn = new DataGridViewTextBoxColumn { HeaderText = "Qty", Width = 300 };
var longColumn = new DataGridViewTextBoxColumn { HeaderText = "Name", Width = 40 };
var hugeColumn = new DataGridViewTextBoxColumn { HeaderText = "Notes", Width = 40 };
grid.Columns.AddRange(shortColumn, longColumn, hugeColumn);
form.Controls.Add(grid);
form.Show();
Pump(grid);
Assert.Equal(300, shortColumn.Width); // no rows yet: nothing is measured
grid.Rows.Add("1", "A fairly long drawing name", new string('W', 200));
Pump(grid);
Assert.True(shortColumn.Width < 300);
Assert.True(longColumn.Width > 40);
Assert.Equal(400, hugeColumn.Width);
Assert.All(grid.Columns.Cast<DataGridViewColumn>(), column =>
{
Assert.Equal(DataGridViewAutoSizeColumnMode.None, column.InheritedAutoSizeMode);
Assert.True(column.Resizable == DataGridViewTriState.True || column.Resizable == DataGridViewTriState.NotSet);
});
longColumn.Width = 77;
grid.Rows.Add("2", new string('X', 80), "");
Pump(grid);
Assert.Equal(77, longColumn.Width);
});
private static void Pump(Control control)
{
for (var i = 0; i < 5; i++)
{
control.Refresh();
Application.DoEvents();
}
}
}
+2 -2
View File
@@ -199,7 +199,7 @@ namespace OpenNest.Controls
//
// nestNameColumn
//
nestNameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
nestNameColumn.Width = 240;
nestNameColumn.HeaderText = "Name";
nestNameColumn.MinimumWidth = 160;
nestNameColumn.Name = "nestNameColumn";
@@ -366,7 +366,7 @@ namespace OpenNest.Controls
//
// nameColumn
//
nameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
nameColumn.Width = 240;
nameColumn.HeaderText = "Name";
nameColumn.MinimumWidth = 160;
nameColumn.Name = "nameColumn";
+32 -1
View File
@@ -6,11 +6,13 @@ namespace OpenNest.Controls;
/// <summary>
/// The shared look of every <see cref="DataGridView"/> in the desktop app, taken from the
/// database nest browser: flat light headers, faint grid lines, a soft blue selection and
/// fixed row heights. Behaviour (editing, selection mode, column sizing) stays with each grid.
/// fixed row heights. Columns size once to their first content and are then the user's to
/// resize. Other behaviour (editing, selection mode) stays with each grid.
/// </summary>
public static class GridStyle
{
public const int RowHeight = 22;
private const int MaxInitialColumnWidth = 400;
private static readonly Color HeaderBackColor = Color.FromArgb(245, 245, 245);
private static readonly Color HeaderForeColor = Color.FromArgb(90, 90, 90);
@@ -42,6 +44,35 @@ public static class GridStyle
grid.RowHeadersDefaultCellStyle.SelectionBackColor = SelectionBackColor;
grid.RowHeadersDefaultCellStyle.SelectionForeColor = Color.Black;
grid.RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
SizeColumnsOnceToContent(grid);
return grid;
}
/// <summary>
/// Widens each column to fit its header and the rows first shown, once, then leaves the
/// widths to the user. Columns never stretch to fill the grid.
/// </summary>
private static void SizeColumnsOnceToContent(DataGridView grid)
{
// The first paint with rows is the first time they are on screen to be measured. The
// empty new-row placeholder does not count: a grid that starts empty keeps its widths
// until it has content.
void OnPaint(object sender, PaintEventArgs e)
{
var newRowShown = grid.NewRowIndex >= 0 && grid.Rows[grid.NewRowIndex].Displayed;
if (grid.DisplayedRowCount(false) - (newRowShown ? 1 : 0) <= 0)
return;
grid.Paint -= OnPaint;
grid.BeginInvoke(() =>
{
if (grid.IsDisposed)
return;
grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells);
foreach (DataGridViewColumn column in grid.Columns)
column.Width = System.Math.Min(column.Width, MaxInitialColumnWidth);
});
}
grid.Paint += OnPaint;
}
}
+2 -3
View File
@@ -258,14 +258,13 @@ namespace OpenNest.Controls
Height = 200,
AllowUserToAddRows = true,
AllowUserToDeleteRows = true,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
RowHeadersWidth = 28,
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
MultiSelect = true,
Margin = new Padding(0, 4, 0, 0),
});
table.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = field.KeyHeader, FillWeight = 65 });
table.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = field.ValueHeader, FillWeight = 35 });
table.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = field.KeyHeader, Width = 260 });
table.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = field.ValueHeader, Width = 140 });
if (map != null)
{
+1 -1
View File
@@ -176,7 +176,7 @@ namespace OpenNest.Controls
releaseNotesColumn.HeaderText = "Notes";
releaseNotesColumn.Name = "releaseNotesColumn";
releaseNotesColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
releaseNotesColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
releaseNotesColumn.Width = 300;
//
// nestsGroup
//
+1 -1
View File
@@ -138,7 +138,7 @@ namespace OpenNest.Forms
//
// libraryCustomerColumn
//
libraryCustomerColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
libraryCustomerColumn.Width = 150;
libraryCustomerColumn.HeaderText = "Customer";
libraryCustomerColumn.MinimumWidth = 60;
libraryCustomerColumn.Name = "libraryCustomerColumn";
+7 -9
View File
@@ -270,7 +270,6 @@ namespace OpenNest.Forms
//
dgvParts.AllowUserToAddRows = false;
dgvParts.AllowUserToDeleteRows = false;
dgvParts.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
dgvParts.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { colItemNum, colFileName, colDescription, colMaterial, colThickness, colQty, colStatus });
dgvParts.Dock = System.Windows.Forms.DockStyle.Fill;
dgvParts.Name = "dgvParts";
@@ -288,7 +287,7 @@ namespace OpenNest.Forms
// colItemNum
//
colItemNum.DataPropertyName = "ItemNum";
colItemNum.FillWeight = 40F;
colItemNum.Width = 60;
colItemNum.HeaderText = "Item #";
colItemNum.Name = "colItemNum";
colItemNum.ReadOnly = true;
@@ -297,7 +296,7 @@ namespace OpenNest.Forms
// colFileName
//
colFileName.DataPropertyName = "FileName";
colFileName.FillWeight = 120F;
colFileName.Width = 180;
colFileName.HeaderText = "File Name";
colFileName.Name = "colFileName";
colFileName.ReadOnly = true;
@@ -306,7 +305,7 @@ namespace OpenNest.Forms
// colDescription
//
colDescription.DataPropertyName = "Description";
colDescription.FillWeight = 150F;
colDescription.Width = 220;
colDescription.HeaderText = "Description";
colDescription.Name = "colDescription";
colDescription.ReadOnly = true;
@@ -315,7 +314,7 @@ namespace OpenNest.Forms
// colMaterial
//
colMaterial.DataPropertyName = "Material";
colMaterial.FillWeight = 110F;
colMaterial.Width = 140;
colMaterial.HeaderText = "Material";
colMaterial.Name = "colMaterial";
colMaterial.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
@@ -327,7 +326,7 @@ namespace OpenNest.Forms
thicknessCellStyle.Format = "0.####";
colThickness.DataPropertyName = "Thickness";
colThickness.DefaultCellStyle = thicknessCellStyle;
colThickness.FillWeight = 70F;
colThickness.Width = 80;
colThickness.HeaderText = "Thickness";
colThickness.Name = "colThickness";
colThickness.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
@@ -338,7 +337,7 @@ namespace OpenNest.Forms
qtyCellStyle.DataSourceNullValue = null;
colQty.DataPropertyName = "Qty";
colQty.DefaultCellStyle = qtyCellStyle;
colQty.FillWeight = 50F;
colQty.Width = 60;
colQty.HeaderText = "Qty";
colQty.Name = "colQty";
colQty.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
@@ -346,7 +345,7 @@ namespace OpenNest.Forms
// colStatus
//
colStatus.DataPropertyName = "StatusText";
colStatus.FillWeight = 100F;
colStatus.Width = 140;
colStatus.HeaderText = "Status";
colStatus.Name = "colStatus";
colStatus.ReadOnly = true;
@@ -363,7 +362,6 @@ namespace OpenNest.Forms
//
dgvGroups.AllowUserToAddRows = false;
dgvGroups.AllowUserToDeleteRows = false;
dgvGroups.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
dgvGroups.Dock = System.Windows.Forms.DockStyle.Fill;
dgvGroups.Name = "dgvGroups";
dgvGroups.RowHeadersVisible = false;
+2 -4
View File
@@ -212,17 +212,15 @@ internal sealed class CuttingInspectionPanel : UserControl
warningGrid.Columns.Add(new DataGridViewTextBoxColumn
{
HeaderText = name,
Width = 48,
Width = name is "Issue" or "Status" ? 260 : 48,
SortMode = DataGridViewColumnSortMode.NotSortable,
AutoSizeMode = name == "Issue" ? DataGridViewAutoSizeColumnMode.Fill : DataGridViewAutoSizeColumnMode.None
});
foreach (var name in new[] { "Plate", "Part", "Status" })
partsGrid.Columns.Add(new DataGridViewTextBoxColumn
{
HeaderText = name,
Width = 48,
Width = name is "Issue" or "Status" ? 260 : 48,
SortMode = DataGridViewColumnSortMode.NotSortable,
AutoSizeMode = name == "Status" ? DataGridViewAutoSizeColumnMode.Fill : DataGridViewAutoSizeColumnMode.None
});
Padding = new Padding(10);
planButton.Click += (_, _) => StartCurrentMode();
+2 -2
View File
@@ -286,7 +286,7 @@ namespace OpenNest.Forms
//
// libraryCustomerColumn
//
libraryCustomerColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
libraryCustomerColumn.Width = 120;
libraryCustomerColumn.HeaderText = "Customer";
libraryCustomerColumn.MinimumWidth = 60;
libraryCustomerColumn.Name = "libraryCustomerColumn";
@@ -476,7 +476,7 @@ namespace OpenNest.Forms
//
// warningColumn
//
warningColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
warningColumn.Width = 200;
warningColumn.HeaderText = "Note";
warningColumn.MinimumWidth = 60;
warningColumn.Name = "warningColumn";
+6 -7
View File
@@ -190,7 +190,6 @@ namespace OpenNest.Forms
drawingGrid.AllowUserToAddRows = false;
drawingGrid.AllowUserToDeleteRows = false;
drawingGrid.AllowUserToResizeRows = false;
drawingGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
drawingGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
drawingGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { drawingColumn, materialColumn, thicknessColumn, quantityColumn, libraryColumn, noteColumn });
drawingGrid.Dock = System.Windows.Forms.DockStyle.Fill;
@@ -203,21 +202,21 @@ namespace OpenNest.Forms
//
// drawingColumn
//
drawingColumn.FillWeight = 130F;
drawingColumn.Width = 180;
drawingColumn.HeaderText = "Drawing";
drawingColumn.Name = "drawingColumn";
drawingColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// materialColumn
//
materialColumn.FillWeight = 80F;
materialColumn.Width = 120;
materialColumn.HeaderText = "Material";
materialColumn.Name = "materialColumn";
materialColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// thicknessColumn
//
thicknessColumn.FillWeight = 50F;
thicknessColumn.Width = 80;
thicknessColumn.HeaderText = "Thickness";
thicknessColumn.Name = "thicknessColumn";
thicknessColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
@@ -225,7 +224,7 @@ namespace OpenNest.Forms
// quantityColumn
//
quantityColumn.DefaultCellStyle = new System.Windows.Forms.DataGridViewCellStyle { Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleRight };
quantityColumn.FillWeight = 40F;
quantityColumn.Width = 60;
quantityColumn.HeaderText = "Qty";
quantityColumn.Name = "quantityColumn";
quantityColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
@@ -233,14 +232,14 @@ namespace OpenNest.Forms
//
// libraryColumn
//
libraryColumn.FillWeight = 120F;
libraryColumn.Width = 180;
libraryColumn.HeaderText = "Drawing library";
libraryColumn.Name = "libraryColumn";
libraryColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// noteColumn
//
noteColumn.FillWeight = 120F;
noteColumn.Width = 200;
noteColumn.HeaderText = "Note";
noteColumn.Name = "noteColumn";
noteColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
+1 -1
View File
@@ -46,7 +46,7 @@ namespace OpenNest.Forms
Name = "Name",
HeaderText = "Strategy",
ReadOnly = true,
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
Width = 220,
}
);
+2 -2
View File
@@ -209,7 +209,7 @@ namespace OpenNest.Forms
//
// setNameColumn
//
setNameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
setNameColumn.Width = 260;
setNameColumn.HeaderText = "Set or drawing";
setNameColumn.Name = "setNameColumn";
setNameColumn.ReadOnly = true;
@@ -434,7 +434,7 @@ namespace OpenNest.Forms
//
// statusColumn
//
statusColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
statusColumn.Width = 200;
statusColumn.HeaderText = "Status";
statusColumn.Name = "statusColumn";
statusColumn.ReadOnly = true;