2010-01-08 2 views
2

У меня есть дерево, связанное с деревом объектов. Когда я удаляю объект из дерева объектов, он корректно удаляется из древовидного представления, но поведение по умолчанию для древовидной структуры состоит в том, чтобы перетащить выбранный элемент до родительского узла удаленного элемента. Как я могу изменить это, чтобы вместо этого перейти к следующему элементу?WPF treeview itemselected перемещается неправильно при удалении элемента

EDIT:

Я обновил свой код с предложением Aviad в. Вот мой код ..

public class ModifiedTreeView : TreeView 
{ 
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (e.Action == NotifyCollectionChangedAction.Remove) 
     { 
      if (e.OldStartingIndex - 1 > 0) 
      { 
       ModifiedTreeViewItem item = 
        this.ItemContainerGenerator.ContainerFromIndex(
        e.OldStartingIndex - 2) as ModifiedTreeViewItem; 

       item.IsSelected = true; 
      } 
     } 
    } 

    protected override DependencyObject GetContainerForItemOverride() 
    { 
     return new ModifiedTreeViewItem(); 
    } 

    protected override bool IsItemItsOwnContainerOverride(object item) 
    { 
     return item is ModifiedTreeViewItem; 
    } 
} 

public class ModifiedTreeViewItem : TreeViewItem 
{ 
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (e.Action == NotifyCollectionChangedAction.Remove) 
     { 
      if (e.OldStartingIndex > 0) 
      { 
       ModifiedTreeViewItem item = 
        this.ItemContainerGenerator.ContainerFromIndex(
        e.OldStartingIndex - 1) as ModifiedTreeViewItem; 

       item.IsSelected = true; 
      } 
     } 
    } 

    protected override DependencyObject GetContainerForItemOverride() 
    { 
     return new ModifiedTreeViewItem(); 
    } 

    protected override bool IsItemItsOwnContainerOverride(object item) 
    { 
     return item is ModifiedTreeViewItem; 
    } 
} 

Код выше не работает, если я не отлаживать его, или каким-то образом замедлить OnItemsChanged метод. Например, если я помещаю thread.sleep (500) в нижней части метода OnItemsChanged, он работает, иначе это не так. Любая идея, что я делаю неправильно? Это действительно странно.

+0

Вместо 'item.IsSelected = истина;' 'попробовать this.SelectedItem = this.ItemContainerGenerator.ItemFromContainer (пункт);' –

+0

Свойство SelectedItem только для чтения, и не доступен вообще в типе TreeViewItem , Спасибо за вашу помощь. Я думаю, что ваше первое предложение, вероятно, правильно. У меня такое чувство, что это может быть ошибка WPF. – 2010-01-13 16:16:43

ответ

1

поведение вы упоминаете контролируется виртуальный метод в Selector класса под названием OnItemsChanged (ссылка: Selector.OnItemsChanged Method) - Для того, чтобы изменить его, вы должны получить от TreeView и переопределить эту функцию. Вы можете использовать отражатель, чтобы основывать свою реализацию на существующей реализации, хотя это довольно просто.

Вот код для TreeView переопределения TreeView.OnItemsChanged извлекали с помощью отражателя:

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
{ 
    switch (e.Action) 
    { 
     case NotifyCollectionChangedAction.Add: 
     case NotifyCollectionChangedAction.Move: 
      break; 

     case NotifyCollectionChangedAction.Remove: 
     case NotifyCollectionChangedAction.Reset: 
      if ((this.SelectedItem == null) || this.IsSelectedContainerHookedUp) 
      { 
       break; 
      } 
      this.SelectFirstItem(); 
      return; 

     case NotifyCollectionChangedAction.Replace: 
     { 
      object selectedItem = this.SelectedItem; 
      if ((selectedItem == null) || !selectedItem.Equals(e.OldItems[0])) 
      { 
       break; 
      } 
      this.ChangeSelection(selectedItem, this._selectedContainer, false); 
      return; 
     } 
     default: 
      throw new NotSupportedException(SR.Get("UnexpectedCollectionChangeAction", new object[] { e.Action })); 
    } 
} 

В качестве альтернативы, вы можете подключить в накопительную NotifyCollectionChanged случае с одного из кода за классов и явно изменить текущее выделение до события достигает TreeView (я не уверен в этом решении, хотя, поскольку я не уверен в порядке, в котором вызываются делегаты событий - TreeView может обработать событие, прежде чем вы это сделаете, но это может сработать).

1

Оригинальный ответ

В моем оригинальном ответе я догадалась, что вы можете быть встретив ошибку в 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).

Так что я думаю, что лучшее решение, чтобы заставить фокус к родительскому узлу (только там, где вы не хотите, чтобы в конечном итоге), а затем установите 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, потому что он будет работать вокруг этой конкретной проблемы без делая какие-либо предположения об упорядочении очереди ввода, приоритетах диспетчера и т. д.

+0

http://stackoverflow.com/questions/2053993/wpf-treeview-selecteditem-moves-incorrectly-why-doesnt-this-work Вот ссылка на полный код. Я вынул унаследованный treeview/treeviewitem, потому что он, похоже, имеет тот же эффект. Любое понимание было бы действительно полезно. – 2010-01-13 16:11:44

+0

О, кстати, я пробовал исправить, это работает большую часть времени. Время от времени он все же переходит к родительскому узлу, хотя этого не может быть. Спасибо в любом случае :) – 2010-01-13 16:12:53

+0

Спасибо, что разместили свой код. Легко было найти проблему, которая была связана с фокусом. Я добавил обновление к моему ответу, объясняющее, что происходит и как его исправить. –

1

Это работает для меня (спасибо исследований, приведенных выше)

protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (e.Action == NotifyCollectionChangedAction.Remove) 
     { 
      Focus(); 
     } 
    } 
0

Согласно ответу, предоставленной @Kirill Я думаю, что правильный ответ на этот конкретный вопрос будет следующий код добавляется в класс, производный от В виде дерева.

protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
{  
    if (e.Action == NotifyCollectionChangedAction.Remove && SelectedItem != null) 
    { 
     var index = Items.IndexOf(SelectedItem); 
     if (index + 1 < Items.Count) 
     { 
      var item = Items.GetItemAt(index + 1) as TreeViewItem; 
      if (item != null) 
      { 
       item.IsSelected = true; 
      } 
     } 
    } 
} 
0

На основании приведенных выше ответов, вот решение, которое работало для меня (это зафиксировала различные другие проблемы, такие как потеря фокуса после выбора элемента с помощью модели и т.д.)

Примечание OnSelected переопределить (прокрутить весь путь вниз), который на самом деле сделал трюк.

Это было скомпилировано в VS2015 для Net 3.5.

using System.Windows; 
using System.Windows.Controls; 
using System.Collections.Specialized; 

namespace WPF 
{ 
    public partial class TreeViewEx : TreeView 
    { 
     #region Overrides 

     protected override DependencyObject GetContainerForItemOverride() 
     { 
      return new TreeViewItemEx(); 
     } 
     protected override bool IsItemItsOwnContainerOverride(object item) 
     { 
      return item is TreeViewItemEx; 
     } 

     #endregion 
    } 
    public partial class TreeViewItemEx : TreeViewItem 
    { 
     #region Overrides 

     protected override DependencyObject GetContainerForItemOverride() 
     { 
      return new TreeViewItemEx(); 
     } 

     protected override bool IsItemItsOwnContainerOverride(object item) 
     { 
      return item is TreeViewItemEx; 
     } 
     protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
     { 
      switch (e.Action) 
      { 
       case NotifyCollectionChangedAction.Remove: 
        if (HasItems) 
        { 
         int newIndex = e.OldStartingIndex; 
         if (newIndex >= Items.Count) 
          newIndex = Items.Count - 1; 
         TreeViewItemEx item = ItemContainerGenerator.ContainerFromIndex(newIndex) as TreeViewItemEx; 
         item.IsSelected = true; 
        } 
        else 
         base.OnItemsChanged(e); 
        break; 
       default: 
        base.OnItemsChanged(e); 
       break; 
      } 
     } 
     protected override void OnSelected(RoutedEventArgs e) 
     { 
      base.OnSelected(e); 
      Focus(); 
     } 

     #endregion 
    } 
} 
Смежные вопросы