WPF TreeView - 如何滚动如此扩展的分支是可见的

时间:2010-02-11 00:16:54

标签: wpf treeview scroll

当我在树视图中展开项目以便滚动时,会出现滚动条。但是,它不会向下滚动新扩展的项目分支 - 它们会被控件的底部裁剪掉。因此,当我继续在树的底部扩展项目时,我必须手动向下滚动以查看新的子项。有人建议如何让它自动滚动显示新扩展的项目吗?

6 个答案:

答案 0 :(得分:17)

在TreeView上,处理TreeViewItem.Expanded事件(由于事件冒泡,您可以在TreeView级别执行此操作)。在Expanded处理程序中,在引发事件的TreeViewItem上调用BringIntoView。

您可能需要一些试验和错误才能在事件处理程序代码中获取TreeViewItem。我认为(尚未检查)您的Expanded事件处理程序的sender参数将是TreeView(因为那是附加事件处理程序的地方)而不是TreeViewItem。 e.Source或e.OriginalSource可能是TreeViewItem数据模板中的一个元素。因此,您可能需要使用VisualTreeHelper来查看可视树以查找TreeViewItem。但是如果你使用调试器检查发送者和RoutedEventArgs,这应该是微不足道的。

(如果你能够使这个工作并且想要捆绑它以便你不必将相同的事件处理程序附加到每个TreeView,那么应该很容易将它封装为attached behaviour将允许您以声明方式应用它,包括通过Style。)

答案 1 :(得分:17)

您可以使用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;  
    }
}

答案 2 :(得分:15)

在IsSelected触发器上使用依赖项属性:

<Style TargetType="{x:Type TreeViewItem}">
 <Style.Triggers>
  <Trigger Property="IsSelected" Value="True">
    <Setter Property="commands:TreeViewItemBehavior.BringIntoViewWhenSelected" Value="True" />
  </Trigger>
</Style.Triggers>

这是依赖属性的代码:

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();
}

答案 3 :(得分:2)

感谢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();
}

答案 4 :(得分:0)

我结合以下策略修改了Jared的答案:https://stackoverflow.com/a/42238409/2477582

主要优点在于,没有n个孩子的n个BringIntoView()调用。对于覆盖儿童所有身高的区域,只有BringIntoView调用一次。

此外,还可以实现所提到主题的目的。但是,如果不需要的话,这部分可以删除。

/// <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;
}

上面的解决方案与此一起工作:

/// <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;
}

此部分负责在每次点击时切换元素:

/// <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;
}

最后是XAML部分:

<TreeView>
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView" />
            <EventSetter Event="Selected" Handler="TreeViewItem_Selected" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

答案 5 :(得分:0)

树上的一个简单事件侦听器为我工作:

await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("login");