fix(ui): traverse nested active controls when restoring focus
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
[Collection("Fill operation lifetime")]
|
||||
public class EditNestFocusTraversalTests
|
||||
{
|
||||
[Fact]
|
||||
public void NestedActiveContainersReturnTheirLeaf() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
var outer = new UserControl();
|
||||
var inner = new UserControl();
|
||||
var leaf = new TextBox();
|
||||
form.Controls.Add(outer);
|
||||
outer.Controls.Add(inner);
|
||||
inner.Controls.Add(leaf);
|
||||
inner.ActiveControl = leaf;
|
||||
outer.ActiveControl = inner;
|
||||
form.ActiveControl = outer;
|
||||
|
||||
Assert.Same(outer, form.ActiveControl);
|
||||
Assert.Same(inner, outer.ActiveControl);
|
||||
Assert.Same(leaf, inner.ActiveControl);
|
||||
Assert.Same(leaf, GetFocusedControl(form));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void OrdinaryActiveControlIsReturnedUnchanged() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
var leaf = new TextBox();
|
||||
form.Controls.Add(leaf);
|
||||
form.ActiveControl = leaf;
|
||||
|
||||
Assert.Same(leaf, form.ActiveControl);
|
||||
Assert.Same(leaf, GetFocusedControl(form));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void NoActiveControlReturnsTheForm() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
form.ActiveControl = null;
|
||||
|
||||
Assert.Null(form.ActiveControl);
|
||||
Assert.Same(form, GetFocusedControl(form));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void NestedContainerWithoutActiveControlIsReturned() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
var outer = new UserControl();
|
||||
var inner = new UserControl();
|
||||
form.Controls.Add(outer);
|
||||
outer.Controls.Add(inner);
|
||||
inner.ActiveControl = null;
|
||||
outer.ActiveControl = inner;
|
||||
form.ActiveControl = outer;
|
||||
|
||||
Assert.Same(outer, form.ActiveControl);
|
||||
Assert.Same(inner, outer.ActiveControl);
|
||||
Assert.Null(inner.ActiveControl);
|
||||
Assert.Same(inner, GetFocusedControl(form));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void PlateListRefreshRestoresTheOriginalNestedLeaf() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
form.Width = 1100;
|
||||
form.Height = 600;
|
||||
form.Show();
|
||||
var outer = new UserControl();
|
||||
var inner = new UserControl { Dock = DockStyle.Fill };
|
||||
var original = new TextBox { Top = 10 };
|
||||
var replacement = new TextBox { Top = 50 };
|
||||
inner.Controls.AddRange(new Control[] { original, replacement });
|
||||
outer.Controls.Add(inner);
|
||||
form.ShowSidePanel(outer, "Focus regression");
|
||||
var plates = PlateList(form);
|
||||
Assert.True(original.Focus());
|
||||
Assert.True(form.ContainsFocus);
|
||||
Assert.Same(original, inner.ActiveControl);
|
||||
|
||||
// Force a different leaf to become active during the real refresh. Merely
|
||||
// focusing the old outer container would now restore this replacement.
|
||||
var focusWasDisplaced = false;
|
||||
plates.SelectedIndexChanged += (_, _) =>
|
||||
{
|
||||
Assert.True(replacement.Focus());
|
||||
focusWasDisplaced = true;
|
||||
};
|
||||
|
||||
form.UpdatePlateList();
|
||||
|
||||
Assert.True(focusWasDisplaced);
|
||||
Assert.True(original.Focused);
|
||||
Assert.False(replacement.Focused);
|
||||
Assert.Same(original, GetFocusedControl(form));
|
||||
Assert.Equal(form.Nest.Plates.Count, plates.Items.Count);
|
||||
Assert.Equal(form.PlateManager.CurrentIndex, Assert.Single(plates.SelectedIndices.Cast<int>()));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void PlateListRefreshKeepsFocusOnThePlateList() => RunSta(() =>
|
||||
{
|
||||
using var form = CreateForm();
|
||||
form.Show();
|
||||
var plates = PlateList(form);
|
||||
Assert.True(plates.Focus());
|
||||
|
||||
form.UpdatePlateList();
|
||||
|
||||
Assert.True(plates.Focused);
|
||||
Assert.Same(plates, GetFocusedControl(form));
|
||||
Assert.Equal(form.Nest.Plates.Count, plates.Items.Count);
|
||||
});
|
||||
|
||||
private static EditNestForm CreateForm()
|
||||
{
|
||||
var form = new EditNestForm(new Nest("focus traversal"));
|
||||
form.PlateView.SetOverlapAutoCheck(null);
|
||||
return form;
|
||||
}
|
||||
|
||||
private static Control GetFocusedControl(EditNestForm form)
|
||||
{
|
||||
var method = typeof(EditNestForm).GetMethod("GetFocusedControl", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.NotNull(method);
|
||||
return Assert.IsAssignableFrom<Control>(method.Invoke(form, null));
|
||||
}
|
||||
|
||||
private static ListView PlateList(EditNestForm form) =>
|
||||
Assert.IsType<ListView>(Assert.Single(form.Controls.Find("platesListView", true)));
|
||||
|
||||
private static void RunSta(System.Action testBody)
|
||||
{
|
||||
Exception? testFailure = null;
|
||||
var staThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
testBody();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
testFailure = error;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
Assert.True(staThread.Join(TimeSpan.FromSeconds(60)), "The focus traversal STA test did not complete.");
|
||||
if (testFailure != null)
|
||||
ExceptionDispatchInfo.Capture(testFailure).Throw();
|
||||
}
|
||||
}
|
||||
@@ -317,9 +317,9 @@ namespace OpenNest.Forms
|
||||
|
||||
private Control GetFocusedControl()
|
||||
{
|
||||
var ctrl = this;
|
||||
Control ctrl = this;
|
||||
while (ctrl is ContainerControl container && container.ActiveControl != null)
|
||||
return container.ActiveControl;
|
||||
ctrl = container.ActiveControl;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user