using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; namespace ytLive.Helpers; /// /// A ListBox that keeps selection and keyboard focus coherent when the /// selected item is deleted. Focus lands on the item now at the deleted /// position (the previous item when the last one was removed) or leaves /// the box entirely when it becomes empty. Drag-to-reorder is untouched. /// public class FocusPreservingListBox : ListBox { /// /// Predicate deciding which items the delete-focus fallback may land on /// (e.g. skip hidden scenes). Null means every item is eligible. /// public Func? IsSelectable { get; set; } protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (e.Action != NotifyCollectionChangedAction.Remove) return; var hadFocus = IsKeyboardFocusWithin || IsKeyboardFocused; if (!hadFocus) return; if (Mouse.Captured == this) return; if (Items.Count == 0) { SelectedItem = null; MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); return; } var targetIndex = FindSelectableIndex(Math.Clamp(e.OldStartingIndex, 0, Items.Count - 1)); if (targetIndex < 0) { SelectedItem = null; return; } SelectedIndex = targetIndex; var item = Items[targetIndex]; Dispatcher.BeginInvoke(DispatcherPriority.Loaded, () => { ScrollIntoView(item); if (ItemContainerGenerator.ContainerFromItem(item) is ListBoxItem container) container.Focus(); }); } private int FindSelectableIndex(int start) { for (var i = start; i >= 0; i--) if (IsItemSelectable(i)) return i; for (var i = start + 1; i < Items.Count; i++) if (IsItemSelectable(i)) return i; return -1; } private bool IsItemSelectable(int index) { if (index < 0 || index >= Items.Count) return false; var item = Items[index]; return item != null && (IsSelectable?.Invoke(item) ?? true); } }