我有一个绑定到对象树的树视图。当我从对象树中删除一个对象时,它会从树视图中正确删除,但树视图的默认行为是将selecteditem跳转到已删除项的父节点。如何改变这一点,以便它跳转到下一个项目?
编辑:
我用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 :(得分:1)
您提到的行为由名为Selector
的{{1}}类中的虚拟方法控制(参考:Selector.OnItemsChanged Method
) - 为了修改它,您应该从{{1并覆盖该函数。您可以使用反射器将您的实现基于现有实现,尽管它非常简单。
以下是使用反射器提取的树视图覆盖OnItemsChanged
的代码:
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
可能会在你做之前处理事件 - 但它可能有效。)
答案 1 :(得分: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
更可靠,因为它可以解决这个特定问题,而无需对输入队列排序,Dispatcher优先级等做任何假设。
答案 2 :(得分:1)
这对我有用(感谢上面提供的调查)
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (e.Action == NotifyCollectionChangedAction.Remove)
{
Focus();
}
}
答案 3 :(得分:0)
根据@Kirill提供的答案,我认为这个特定问题的正确答案是将以下代码添加到从TreeView派生的类中。
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;
}
}
}
}
答案 4 :(得分:0)
基于上面的答案,这里有适用于我的解决方案(它还修复了其他各种问题,例如通过模型选择项目后失去焦点等)。
请注意 OnSelected 覆盖(完全向下滚动),这实际上是诀窍。
这是在VS2015 for 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
}
}