fix(ui): keep an add-new row in Auto Nest stock options

This commit is contained in:
aj
2026-09-29 12:43:55 -04:00
parent 7d179c696a
commit 64d0e50df3
3 changed files with 117 additions and 4 deletions
@@ -0,0 +1,109 @@
using System.Collections;
using System.ComponentModel;
using System.Runtime.ExceptionServices;
using System.Windows.Forms;
using OpenNest.Forms;
namespace OpenNest.WinForms.Tests.Forms;
public class AutoNestFormStockOptionsTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void StockOptionsKeepAnAddRowAfterAddingAPlate(bool loadSavedOptions)
{
RunSta(() =>
{
using var form = new AutoNestForm(new Nest());
if (loadSavedOptions)
form.LoadPlateOptions(new List<PlateOption>
{
new() { Width = 48, Length = 96, Cost = 25 },
}, 0.5);
form.OptimizePlateSize = true;
var grid = GetGrid(form);
var initialCount = form.GetPlateOptions().Count;
AssertNewRow(grid);
var rows = Assert.IsAssignableFrom<IBindingList>(grid.DataSource);
Assert.True(rows.AllowNew);
var added = rows.AddNew()!;
SetValue(added, "Size", "84 x 168");
SetValue(added, "Cost", 123.5);
((ICancelAddNew)rows).EndNew(rows.Count - 1);
var options = form.GetPlateOptions();
Assert.Equal(initialCount + 1, options.Count);
var option = options.Last();
Assert.Equal(84, option.Width);
Assert.Equal(168, option.Length);
Assert.Equal(123.5, option.Cost);
AssertNewRow(grid);
});
}
[Fact]
public void BlankAndCancelledRowsDoNotBecomeStockOptions()
{
RunSta(() =>
{
using var form = new AutoNestForm(new Nest());
var saved = new List<PlateOption>
{
new() { Width = 48, Length = 96, Cost = 25 },
};
form.LoadPlateOptions(saved, 0.5);
var grid = GetGrid(form);
var rows = Assert.IsAssignableFrom<IBindingList>(grid.DataSource);
var addNew = Assert.IsAssignableFrom<ICancelAddNew>(rows);
rows.AddNew();
Assert.Single(form.GetPlateOptions());
addNew.CancelNew(rows.Count - 1);
Assert.Single((IEnumerable)rows);
rows.Clear();
Assert.Empty(form.GetPlateOptions());
AssertNewRow(grid);
Assert.Single(saved);
Assert.Equal(48, saved[0].Width);
Assert.Equal(96, saved[0].Length);
Assert.Equal(25, saved[0].Cost);
});
}
private static DataGridView GetGrid(AutoNestForm form)
{
// Binding needs a context even when the test does not show the form.
form.BindingContext = new BindingContext();
var grid = Assert.IsType<DataGridView>(form.Controls.Find("plateGrid", true).Single());
grid.CreateControl();
return grid;
}
private static void AssertNewRow(DataGridView grid)
{
Assert.True(grid.AllowUserToAddRows);
Assert.True(grid.NewRowIndex >= 0);
Assert.True(grid.Rows[grid.NewRowIndex].IsNewRow);
}
private static void SetValue(object row, string property, object value) =>
TypeDescriptor.GetProperties(row)[property]!.SetValue(row, value);
private static void RunSta(Action action)
{
Exception? error = null;
var thread = new Thread(() =>
{
try { action(); }
catch (Exception ex) { error = ex; }
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Assert.True(thread.Join(TimeSpan.FromSeconds(15)));
if (error != null)
ExceptionDispatchInfo.Capture(error).Throw();
}
}
+7 -3
View File
@@ -195,8 +195,9 @@ namespace OpenNest.Forms
public List<PlateOption> GetPlateOptions()
{
plateGrid.EndEdit();
var result = new List<PlateOption>();
var gridItems = plateGrid.DataSource as List<PlateOptionItem>;
var gridItems = plateGrid.DataSource as BindingList<PlateOptionItem>;
if (gridItems == null)
return result;
@@ -231,7 +232,7 @@ namespace OpenNest.Forms
Cost = o.Cost,
})
.ToList();
plateGrid.DataSource = items;
plateGrid.DataSource = new BindingList<PlateOptionItem>(items);
optimizePlateSizeBox.Checked = true;
}
SalvageRate = salvageRate;
@@ -239,7 +240,8 @@ namespace OpenNest.Forms
private void LoadDefaultPlateOptions()
{
var items = new List<PlateOptionItem>
// A bound DataGridView needs IBindingList.AddNew support for its blank last row.
var items = new BindingList<PlateOptionItem>
{
new() { Size = "48 x 96", Cost = 0 },
new() { Size = "48 x 120", Cost = 0 },
@@ -385,6 +387,8 @@ namespace OpenNest.Forms
private class PlateOptionItem
{
public PlateOptionItem() { }
public string Size { get; set; }
public double Cost { get; set; }
}
+1 -1
View File
@@ -14,7 +14,7 @@
Every engine selected in **Nest > Auto Nest** uses the pipeline. Part-First and its sorting controls are removed. Use interactive Fill Area/remnant tools for leftover space on occupied sheets; these are not whole-job nesting.
Auto Nest starts on empty/new sheets and does not change existing populated plates. Stock Options offers sheet sizes and salvage settings; minimum salvage size is part of that section, not a Part-First option. The maximum remains 100 physical sheets per run.
Auto Nest starts on empty/new sheets and does not change existing populated plates. Stock Options offers sheet sizes and salvage settings; minimum salvage size is part of that section, not a Part-First option. When enabled, its grid keeps a blank last row for adding another size (`W x L`) and cost, including after loading saved options. Unused blank rows are not offered as stock. The maximum remains 100 physical sheets per run.
Progress is owned and modal so the input drawings cannot be edited while a worker uses them. Stop or closing progress cancels and discards the whole proposal. The dialog waits for the worker to finish; no engine has an Accept-early button on this path. A plug-in that ignores the cancellation token cannot commit its late result.