diff --git a/OpenNest.WinForms.Tests/Forms/BomImportFormTests.cs b/OpenNest.WinForms.Tests/Forms/BomImportFormTests.cs index 6357dc7..9274ee0 100644 --- a/OpenNest.WinForms.Tests/Forms/BomImportFormTests.cs +++ b/OpenNest.WinForms.Tests/Forms/BomImportFormTests.cs @@ -76,6 +76,30 @@ public class BomImportFormTests }); } + [Theory] + [InlineData("colQty", "0")] + [InlineData("colQty", "abc")] + [InlineData("colThickness", "-1")] + [InlineData("colThickness", "abc")] + public void ProgrammaticCommitOfInvalidText_KeepsTheRowsValue(string column, string text) + { + RunSta(() => + { + using var form = Show(out var parts); + var part = Ready("PT01", qty: 2); + form.LoadRows(new[] { part }); + + parts.CurrentCell = parts.Rows[0].Cells[column]; + Assert.True(parts.BeginEdit(selectAll: true)); + parts.EditingControl!.Text = text; + Assert.True(parts.EndEdit()); + + Assert.Equal(2, part.Qty); + Assert.Equal(0.25, part.Thickness); + Assert.Equal(BomRowStatus.Ready, part.Status); + }); + } + [Fact] public void Edits_ChangeThePartShownInThatRow_AfterTheRowsAreReloadedInAnotherOrder() { @@ -131,7 +155,7 @@ public class BomImportFormTests form.LoadRows(new[] { first, second }); Assert.True(Edit(parts, 0, "colMaterial", " ")); - Assert.Null(first.Material); + Assert.Equal("", first.Material); Assert.Equal("Needs material", parts.Rows[0].Cells["colStatus"].Value); Assert.False(Edit(parts, 1, "colThickness", "abc")); @@ -156,12 +180,22 @@ public class BomImportFormTests return form; } + /// + /// Types into the cell and presses Enter, which + /// validates the cell the way the operator's commit does. Returns true + /// when the edit was accepted (the cell left edit mode). + /// private static bool Edit(DataGridView grid, int row, string column, string text) { grid.CurrentCell = grid.Rows[row].Cells[column]; Assert.True(grid.BeginEdit(selectAll: true)); grid.EditingControl!.Text = text; - return grid.EndEdit(); + var processEnterKey = typeof(DataGridView).GetMethod( + "ProcessEnterKey", + BindingFlags.Instance | BindingFlags.NonPublic + )!; + processEnterKey.Invoke(grid, new object[] { Keys.Enter }); + return !grid.IsCurrentCellInEditMode; } private static DataTable GroupsTable(BomImportForm form) => diff --git a/OpenNest/Forms/BomImportForm.Designer.cs b/OpenNest/Forms/BomImportForm.Designer.cs index 37fb528..b847ea1 100644 --- a/OpenNest/Forms/BomImportForm.Designer.cs +++ b/OpenNest/Forms/BomImportForm.Designer.cs @@ -326,6 +326,7 @@ namespace OpenNest.Forms // colThickness // thicknessCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleRight; + thicknessCellStyle.DataSourceNullValue = null; thicknessCellStyle.Format = "0.####"; colThickness.DataPropertyName = "Thickness"; colThickness.DefaultCellStyle = thicknessCellStyle; @@ -337,6 +338,7 @@ namespace OpenNest.Forms // colQty // qtyCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleRight; + qtyCellStyle.DataSourceNullValue = null; colQty.DataPropertyName = "Qty"; colQty.DefaultCellStyle = qtyCellStyle; colQty.FillWeight = 50F; diff --git a/OpenNest/Forms/BomImportForm.cs b/OpenNest/Forms/BomImportForm.cs index 88e7cd7..2d4077f 100644 --- a/OpenNest/Forms/BomImportForm.cs +++ b/OpenNest/Forms/BomImportForm.cs @@ -181,7 +181,7 @@ namespace OpenNest.Forms error = "Enter a whole number of 1 or more."; else if ( e.ColumnIndex == colThickness.Index - && !string.IsNullOrWhiteSpace(text) + && !string.IsNullOrEmpty(text) && !TryParseThickness(text, out _) ) error = "Enter a thickness greater than 0, or leave it blank."; @@ -191,27 +191,42 @@ namespace OpenNest.Forms e.Cancel = true; } + // CellValidating refuses bad text when the operator commits a cell. A + // programmatic EndEdit() skips validation, so parsing keeps the row's + // current value rather than storing text validation would refuse. The + // grid ignores a null parsed value, so a blank thickness is left to the + // column's DataSourceNullValue (null). private void DgvParts_CellParsing(object sender, DataGridViewCellParsingEventArgs e) { - var text = e.Value?.ToString(); + var text = e.Value?.ToString() ?? ""; + var part = PartAt(e.RowIndex); - if (e.ColumnIndex == colQty.Index && BomQuantity.TryParse(text, out var qty)) + if (e.ColumnIndex == colQty.Index) { - e.Value = qty; - e.ParsingApplied = true; + if (BomQuantity.TryParse(text, out var qty)) + Parsed(e, qty); + else if (part?.Qty is int current) + Parsed(e, current); } - else if (e.ColumnIndex == colThickness.Index) + else if (e.ColumnIndex == colThickness.Index && text.Length > 0) { - e.Value = TryParseThickness(text, out var thickness) ? thickness : null; - e.ParsingApplied = true; + if (TryParseThickness(text, out var thickness)) + Parsed(e, thickness); + else if (part?.Thickness is double current) + Parsed(e, current); } else if (e.ColumnIndex == colMaterial.Index) { - e.Value = string.IsNullOrWhiteSpace(text) ? null : text; - e.ParsingApplied = true; + Parsed(e, text.Trim()); } } + private static void Parsed(DataGridViewCellParsingEventArgs e, object value) + { + e.Value = value; + e.ParsingApplied = true; + } + private void DgvParts_CellEndEdit(object sender, DataGridViewCellEventArgs e) { dgvParts.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";