Оригинальный ответ
В своем первоначальном ответе я догадался, что вы можете столкнуться с ошибкой в WPF, и дал общий обходной путь для такой ситуации, который должен был заменить item.IsSelected = true;
на:
Disptacher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
item.IsSelected = true;
}));
Я объяснил, что причина, по которой этот способ обходится без дела в 90% случаев, заключается в том, что он задерживает выбор до тех пор, пока почти все текущие операции не завершат обработку.
Когда я действительно попробовал код, который вы разместили в своем другом вопросе, я обнаружил, что это действительно ошибка в WPF, но нашел более прямой и надежный обходной путь. Я объясню, как я диагностировал проблему, а затем опишу обходной путь.
Диагностика
Я добавил обработчик SelectedItemChanged с точкой останова и посмотрел на трассировку стека. Это сделало очевидным, в чем проблема. Вот выбранные части трассировки стека:
...
System.Windows.Controls.TreeView.ChangeSelection
...
System.Windows.Controls.TreeViewItem.OnGotFocus
...
System.Windows.Input.FocusManager.SetFocusedElement
System.Windows.Input.KeyboardNavigation.UpdateFocusedElement
System.Windows.FrameworkElement.OnGotKeyboardFocus
System.Windows.Input.KeyboardFocusChangedEventArgs.InvokeEventHandler
...
System.Windows.Input.InputManager.ProcessStagingArea
System.Windows.Input.InputManager.ProcessInput
System.Windows.Input.KeyboardDevice.ChangeFocus
System.Windows.Input.KeyboardDevice.TryChangeFocus
System.Windows.Input.KeyboardDevice.Focus
System.Windows.Input.KeyboardDevice.ReevaluateFocusCallback
...
Как видите, KeyboardDevice
имеет ReevaluateFocusCallback
закрытый или внутренний метод, который меняет фокус на родителя удаленного TreeViewItem
. Это вызывает событие GotFocus
, которое вызывает выбор родительского элемента. Все это происходит в фоновом режиме после того, как ваш обработчик события возвращается.
Решение
Обычно в этом случае я бы сказал вам, просто вручную .Focus()
TreeViewItem
, который вы выбираете. Здесь это сложно, потому что в TreeView
нет простого способа перейти от произвольного элемента данных к соответствующему контейнеру (на каждом уровне есть отдельные ItemContainerGenerators
).
Так что я думаю, что ваше лучшее решение - это заставить focus к родительскому узлу (именно там, где вы не хотите, чтобы он заканчивался), затем установить IsSelected в дочернем узле. данные. Таким образом, менеджер ввода никогда не решит, что ему нужно переместить фокус самостоятельно: он обнаружит, что фокус уже установлен на допустимый IInputElement
.
Вот код для этого:
if(child != null)
{
SomeObject parent = child.Parent;
// Find the currently focused element in the TreeView's focus scope
DependencyObject focused =
FocusManager.GetFocusedElement(
FocusManager.GetFocusScope(tv)) as DependencyObject;
// Scan up the VisualTree to find the TreeViewItem for the parent
var parentContainer = (
from element in GetVisualAncestorsOfType<FrameworkElement>(focused)
where (element is TreeViewItem && element.DataContext == parent)
|| element is TreeView
select element
).FirstOrDefault();
parent.Children.Remove(child);
if(parent.Children.Count > 0)
{
// Before selecting child, first focus parent's container
if(parentContainer!=null) parentContainer.Focus();
parent.Children[0].IsSelected = true;
}
}
Для этого также требуется этот вспомогательный метод:
private IEnumerable<T> GetVisualAncestorsOfType<T>(DependencyObject obj) where T:DependencyObject
{
for(; obj!=null; obj = VisualTreeHelper.GetParent(obj))
if(obj is T)
yield return (T)obj;
}
Это должно быть более надежно, чем использование Dispatcher.BeginInvoke
, потому что оно будет обходить эту конкретную проблему, не делая никаких предположений об упорядочении входной очереди, приоритетах диспетчера и т. Д.