WPF TreeView - 如何滚动使展开的分支可见
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2241367/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF TreeView - How to scroll so expanded branch is visible
提问by Jared
When I expand items in my treeview so that scrolling is necessary, a scrollbar appears. However, it doesn't scroll down for the newly expanded branch of items - they get cropped by the bottom of the control. So as I continue expanding items at the bottom of the tree, I have to keep manually scrolling down to see the new children. Anyone have a suggestion for how make it automatically scroll to show the newly expanded items?
当我在树视图中展开项目以便需要滚动时,会出现一个滚动条。但是,它不会为新扩展的项目分支向下滚动 - 它们被控件底部裁剪。因此,当我继续扩展树底部的项目时,我必须继续手动向下滚动以查看新的子项。有人对如何让它自动滚动以显示新扩展的项目有建议吗?
采纳答案by itowlson
On the TreeView, handle the TreeViewItem.Expanded event (you can do this at the TreeView level because of event bubbling). In the Expanded handler, call BringIntoView on the TreeViewItem that raised the event.
在 TreeView 上,处理 TreeViewItem.Expanded 事件(由于事件冒泡,您可以在 TreeView 级别执行此操作)。在 Expanded 处理程序中,在引发事件的 TreeViewItem 上调用 BringIntoView。
You may need a bit of trial and error to get hold of the TreeViewItem in your event handler code. I think (haven't checked) that the sender argument to your Expanded event handler will be the TreeView (since that's where the event handler is attached) rather than the TreeViewItem. And the e.Source or e.OriginalSource may be an element in the TreeViewItem's data template. So you may need to use VisualTreeHelper to walk up the visual tree to find the TreeViewItem. But if you use the debugger to inspect the sender and the RoutedEventArgs this should be trivial to figure out.
您可能需要反复试验才能在事件处理程序代码中获取 TreeViewItem。我认为(尚未检查)您的 Expanded 事件处理程序的 sender 参数将是 TreeView(因为这是附加事件处理程序的位置)而不是 TreeViewItem。而 e.Source 或 e.OriginalSource 可能是 TreeViewItem 的数据模板中的一个元素。因此,您可能需要使用 VisualTreeHelper 沿着可视化树向上查找 TreeViewItem。但是,如果您使用调试器来检查发送者和 RoutedEventArgs,这应该很容易弄清楚。
(If you're able to get this working and want to bundle it up so you don't have to attach the same event handler to every TreeView, it should be easy to encapsulate it as an attached behaviourwhich will allow you to apply it declaratively, including via a Style.)
(如果您能够使其正常工作并希望将其捆绑起来,这样您就不必将相同的事件处理程序附加到每个 TreeView,那么将其封装为附加行为应该很容易,这将允许您应用它声明性地,包括通过样式。)
回答by Vinod.M.S
You can use a simple EventSetter in TreeViewItem style to invoke an event handler when the item is selected. Then call BringIntoView for the item.
您可以在 TreeViewItem 样式中使用简单的 EventSetter 来在选择项目时调用事件处理程序。然后调用该项目的BringIntoView。
<TreeView >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="Selected" Handler="TreeViewSelectedItemChanged" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
private void TreeViewSelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
item.BringIntoView();
e.Handled = true;
}
}
回答by Mike
Use a dependency property on an IsSelected trigger:
在 IsSelected 触发器上使用依赖属性:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="commands:TreeViewItemBehavior.BringIntoViewWhenSelected" Value="True" />
</Trigger>
</Style.Triggers>
Here's the code for the dependency property:
这是依赖属性的代码:
public static bool GetBringIntoViewWhenSelected(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringIntoViewWhenSelectedProperty);
}
public static void SetBringIntoViewWhenSelected(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringIntoViewWhenSelectedProperty, value);
}
public static readonly DependencyProperty BringIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached("BringIntoViewWhenSelected", typeof(bool),
typeof(TreeViewItemBehavior), new UIPropertyMetadata(false, OnBringIntoViewWhenSelectedChanged));
static void OnBringIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TreeViewItem item = depObj as TreeViewItem;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.BringIntoView();
}
回答by Jared
Thanks to itowlson's answer, here's the expanded event handler code that works for both of my trees
感谢 itowlson 的回答,这是适用于我的两棵树的扩展事件处理程序代码
private static void Tree_Expanded(object sender, RoutedEventArgs e)
{
// ignore checking, assume original source is treeviewitem
var treeViewItem = (TreeViewItem)e.OriginalSource;
var count = VisualTreeHelper.GetChildrenCount(treeViewItem);
for (int i = count - 1; i >= 0; --i)
{
var childItem = VisualTreeHelper.GetChild(treeViewItem, i);
((FrameworkElement)childItem).BringIntoView();
}
// do NOT call BringIntoView on the actual treeviewitem - this negates everything
//treeViewItem.BringIntoView();
}
回答by Nicolas
I modified Jared's answer in combination with the strategy from here: https://stackoverflow.com/a/42238409/2477582
我结合这里的策略修改了 Jared 的答案:https: //stackoverflow.com/a/42238409/2477582
The main advantage is that there aren't n
calls of BringIntoView() for n
childs. There is only one call of BringIntoView for an area that covers all of the child's heights.
主要优点是没有n
为n
孩子们调用BringIntoView() 。对于覆盖孩子所有身高的区域,只有一次调用BringIntoView。
Additionally, the purpose of the referred topic is realized as well. But this part may be removed, if unwanted.
此外,也实现了所引用主题的目的。但如果不需要,这部分可能会被删除。
/// <summary>Prevents automatic horizontal scrolling, while preserving automatic vertical scrolling and other side effects</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
// Ignore re-entrant calls
if (m_SuppressRequestBringIntoView)
return;
// Cancel the current scroll attempt
e.Handled = true;
// Call BringIntoView using a rectangle that extends into "negative space" to the left of our
// actual control. This allows the vertical scrolling behaviour to operate without adversely
// affecting the current horizontal scroll position.
m_SuppressRequestBringIntoView = true;
try
{
TreeViewItem tvi = sender as TreeViewItem;
if (tvi != null)
{
// take care of children
int ll_ChildCount = VisualTreeHelper.GetChildrenCount(tvi);
double ll_Height = tvi.ActualHeight;
if (ll_ChildCount > 0)
{
FrameworkElement ll_LastChild = VisualTreeHelper.GetChild(tvi, ll_ChildCount - 1) as FrameworkElement;
ll_Height += ll_ChildCount * ll_LastChild.ActualHeight;
}
Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, ll_Height);
tvi.BringIntoView(newTargetRect);
}
}
catch (Exception ex)
{
m_Log.Debug("Error in TreeViewItem_RequestBringIntoView: " + ex.ToString());
}
m_SuppressRequestBringIntoView = false;
}
The above solution works together with this:
上述解决方案与此一起使用:
/// <summary>Correctly handle programmatically selected items (needed due to the custom implementation of TreeViewItem_RequestBringIntoView)</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
((TreeViewItem)sender).BringIntoView();
e.Handled = true;
}
This part takes care of toggling the elements at each click:
这部分负责在每次点击时切换元素:
/// <summary>Support for single click toggle</summary>
private void TreeViewItem_MouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem tvi = null;
// Source may be TreeViewItem directly, or be a ContentPresenter
if (e.Source is TreeViewItem)
{
tvi = e.Source as TreeViewItem;
}
else if (e.Source is ContentPresenter)
{
tvi = (e.Source as ContentPresenter).TemplatedParent as TreeViewItem;
}
if (tvi == null || e.Handled) return;
tvi.IsExpanded = !tvi.IsExpanded;
e.Handled = true;
}
Finally the XAML part:
最后是 XAML 部分:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView" />
<EventSetter Event="Selected" Handler="TreeViewItem_Selected" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
回答by JJ_Coder4Hire
A simple event listener on the tree worked for me:
树上的一个简单的事件侦听器对我有用:
<TreeView Margin="10,40,10,10" Grid.Column="0" x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectedItemChanged="TreeView_SelectedItemChanged" />
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
if (e.NewValue == null)
return;
((TreeViewItem)e.NewValue).BringIntoView();
}