From 8239498ebf1312938fb82003ed85277812d3a124 Mon Sep 17 00:00:00 2001 From: Zachary Burnham Date: Tue, 20 Oct 2026 09:41:55 -0400 Subject: [PATCH] LT-22848: Never focus the slice of a deleted object after a delete Deleting an Example from its row menu in the Avalonia detail view crashed FieldWorks. That menu runs on the hidden WinForms DataTree, whose rebuild after the delete is postponed, so the deleted object's slice was still listed when the next slice to focus was chosen. The hidden tree was never shown, so focusing that slice built its view, which looked the deleted object up and threw. SelectFirstPossibleSlice now skips a slice whose object has been deleted. A DataTreeTests case reproduces the crash on a tree that is never shown and passes with the fix. Co-Authored-By: Claude Opus 5.5 --- .../Controls/DetailControls/DataTree.cs | 7 +++- .../DetailControlsTests/DataTreeTests.cs | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Src/Common/Controls/DetailControls/DataTree.cs b/Src/Common/Controls/DetailControls/DataTree.cs index c281a5935d..16da10365b 100644 --- a/Src/Common/Controls/DetailControls/DataTree.cs +++ b/Src/Common/Controls/DetailControls/DataTree.cs @@ -4789,7 +4789,12 @@ internal void SelectFirstPossibleSlice(List closeSlices) { foreach (var slice in closeSlices) { - if (!slice.IsDisposed && slice.ContainingDataTree == this && slice.TakeFocus(false)) + if (slice.IsDisposed || slice.ContainingDataTree != this + || (slice.Object != null && !slice.Object.IsValidObject)) + { + continue; + } + if (slice.TakeFocus(false)) break; } } diff --git a/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs b/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs index 60d747bd6e..633dc0cb16 100644 --- a/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs +++ b/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs @@ -391,6 +391,42 @@ public void NestedCollapsedPart() Assert.That((m_dtree.Controls[0] as Slice).Label, Is.EqualTo("Header")); } + // LT-22848: a delete postpones the rebuild, so the deleted object's slice is still + // listed. Focusing it in a tree never shown builds a view that looks the object up. + [Test] + public void SelectFirstPossibleSlice_SkipsTheSliceOfADeletedObject_InATreeNeverShown() + { + var senseFactory = Cache.ServiceLocator.GetInstance(); + var deletedSense = senseFactory.Create(); + m_entry.SensesOS.Add(deletedSense); + deletedSense.Gloss.SetAnalysisDefaultWritingSystem("cat"); + deletedSense.ScientificName = TsStringUtils.MakeString("Felis catus", Cache.DefaultAnalWs); + var survivingSense = senseFactory.Create(); + m_entry.SensesOS.Add(survivingSense); + survivingSense.Gloss.SetAnalysisDefaultWritingSystem("dog"); + m_dtree.Initialize(Cache, false, m_layouts, m_parts); + m_dtree.ShowObject(m_entry, "OptSensesEty", null, m_entry, false); + Slice deletedSlice = null; + var closeSlices = new List(); + foreach (var slice in m_dtree.Slices) + { + if (slice is StringSlice && slice.Object == deletedSense) + deletedSlice = slice; + } + Assert.That(deletedSlice, Is.Not.Null, "precondition: the sense's ScientificName shows in a string slice"); + closeSlices.Add(deletedSlice); + closeSlices.AddRange(m_dtree.Slices); + + m_entry.SensesOS.Remove(deletedSense); + Assert.That(deletedSlice.IsDisposed, Is.False, + "precondition: inside the open unit of work the tree has not rebuilt yet"); + + Assert.That(() => m_dtree.SelectFirstPossibleSlice(closeSlices), Throws.Nothing, + "choosing the next slice must not build the deleted object's view"); + Assert.That(m_dtree.CurrentSlice, Is.Not.SameAs(deletedSlice), + "the deleted object's slice never becomes current"); + } + [Test] public void GetGuidForJumpToTool_UsesRootObject_WhenNoCurrentSlice() {