WPF TreeView-将展开的项目滚动到视图中

WPF TreeView-将展开的项目滚动到视图中,wpf,treeview,Wpf,Treeview,我正在尝试编写一个自定义WPF/MVVM TreeView控件,当父项展开时,该控件将自动滚动(尽可能多)子项到视图中 public static class perTreeViewItemHelper { public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem) { return (bool)treeViewItem.GetValue(BringSelectedItemI

我正在尝试编写一个自定义WPF/MVVM TreeView控件,当父项展开时,该控件将自动滚动(尽可能多)子项到视图中

public static class perTreeViewItemHelper
{
    public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(BringSelectedItemIntoViewProperty);
    }

    public static void SetBringSelectedItemIntoView(TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(BringSelectedItemIntoViewProperty, value);
    }

    public static readonly DependencyProperty BringSelectedItemIntoViewProperty =
        DependencyProperty.RegisterAttached(
            "BringSelectedItemIntoView",
            typeof(bool),
            typeof(perTreeViewItemHelper),
            new UIPropertyMetadata(false, BringSelectedItemIntoViewChanged));

    private static void BringSelectedItemIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (!(args.NewValue is bool))
            return;

        var item = obj as TreeViewItem;

        if (item == null)
            return;

        if ((bool)args.NewValue)
            item.Selected += OnTreeViewItemSelected;
        else
            item.Selected -= OnTreeViewItemSelected;
    }

    private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
    {
        var item = e.OriginalSource as TreeViewItem;
        item?.BringIntoView();

        // prevent this event bubbling up to any parent nodes
        e.Handled = true;
    }

    public static bool GetBringExpandedChildrenIntoView(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(BringExpandedChildrenIntoViewProperty);
    }

    public static void SetBringExpandedChildrenIntoView(TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(BringExpandedChildrenIntoViewProperty, value);
    }

    public static readonly DependencyProperty BringExpandedChildrenIntoViewProperty =
        DependencyProperty.RegisterAttached(
            "BringExpandedChildrenIntoView",
            typeof(bool),
            typeof(perTreeViewItemHelper),
            new UIPropertyMetadata(false, BringExpandedChildrenIntoViewChanged));

    private static void BringExpandedChildrenIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (!(args.NewValue is bool))
            return;

        var item = obj as TreeViewItem;

        if (item == null)
            return;

        if ((bool)args.NewValue)
            item.Expanded += OnTreeViewItemExpanded;
        else
            item.Expanded -= OnTreeViewItemExpanded;
    }

    private static void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
    {
        var item = e.OriginalSource as TreeViewItem;

        if (item == null)
            return;

        // use DispatcherPriority.ContextIdle, so that we wait for all of the UI elements for any newly visible children to be created

        // first bring the last child into view
        Action action = () =>
        {
            var lastChild = item.ItemContainerGenerator.ContainerFromIndex(item.Items.Count - 1) as TreeViewItem;
            lastChild?.BringIntoView();
        };

        item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);

        // then bring the expanded item (back) into view
        action = () => { item.BringIntoView(); };
        item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);

        // prevent this event bubbling up to any parent nodes
        e.Handled = true;
    }
}
我发现了这篇文章,但它似乎只在树项目实际从TreeViewItem下降时才起作用

我的Tree Item类只是一个普通的C#对象——就像Josh Smith的博客一样,它是在Tree Item类的IsExpanded属性设置器中惰性加载的

进入OnExpand事件处理程序,我可以看到原始源是一个TreeViewItem(由幕后框架创建),它的header字段中有我的tree item对象。此TreeViewItem只有一个可视子项,即网格,而网格又有三个可视子项:ToggleButton、Border和ItemsPresenter


是否有人提示我如何为我的子项获取相应的可视组件,然后在该组件上调用BringIntoView()?

发现了一个非常好的行为,可以将所选项滚动到视图中,位于此处:

不幸的是,这不能满足项目扩展时的需要,但可以根据您的确切要求进行修改。如果你需要帮助,请告诉我


就编程而言,五年是一段很长的时间

最近,在获得2500次查看的徽章后,我又回到了这个问题。碰巧我在我的博客上发表了一篇关于这些问题的解决方案的帖子-

我知道在stackoverflow上只链接的答案通常不被认可,所以我也会在这里发布关键元素

1) TreeViewItem的helper包含两个附加属性,一个用于将选定项目滚动到视图中,另一个用于在项目展开时将尽可能多的子项滚动到视图中

public static class perTreeViewItemHelper
{
    public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(BringSelectedItemIntoViewProperty);
    }

    public static void SetBringSelectedItemIntoView(TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(BringSelectedItemIntoViewProperty, value);
    }

    public static readonly DependencyProperty BringSelectedItemIntoViewProperty =
        DependencyProperty.RegisterAttached(
            "BringSelectedItemIntoView",
            typeof(bool),
            typeof(perTreeViewItemHelper),
            new UIPropertyMetadata(false, BringSelectedItemIntoViewChanged));

    private static void BringSelectedItemIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (!(args.NewValue is bool))
            return;

        var item = obj as TreeViewItem;

        if (item == null)
            return;

        if ((bool)args.NewValue)
            item.Selected += OnTreeViewItemSelected;
        else
            item.Selected -= OnTreeViewItemSelected;
    }

    private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
    {
        var item = e.OriginalSource as TreeViewItem;
        item?.BringIntoView();

        // prevent this event bubbling up to any parent nodes
        e.Handled = true;
    }

    public static bool GetBringExpandedChildrenIntoView(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(BringExpandedChildrenIntoViewProperty);
    }

    public static void SetBringExpandedChildrenIntoView(TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(BringExpandedChildrenIntoViewProperty, value);
    }

    public static readonly DependencyProperty BringExpandedChildrenIntoViewProperty =
        DependencyProperty.RegisterAttached(
            "BringExpandedChildrenIntoView",
            typeof(bool),
            typeof(perTreeViewItemHelper),
            new UIPropertyMetadata(false, BringExpandedChildrenIntoViewChanged));

    private static void BringExpandedChildrenIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (!(args.NewValue is bool))
            return;

        var item = obj as TreeViewItem;

        if (item == null)
            return;

        if ((bool)args.NewValue)
            item.Expanded += OnTreeViewItemExpanded;
        else
            item.Expanded -= OnTreeViewItemExpanded;
    }

    private static void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
    {
        var item = e.OriginalSource as TreeViewItem;

        if (item == null)
            return;

        // use DispatcherPriority.ContextIdle, so that we wait for all of the UI elements for any newly visible children to be created

        // first bring the last child into view
        Action action = () =>
        {
            var lastChild = item.ItemContainerGenerator.ContainerFromIndex(item.Items.Count - 1) as TreeViewItem;
            lastChild?.BringIntoView();
        };

        item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);

        // then bring the expanded item (back) into view
        action = () => { item.BringIntoView(); };
        item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);

        // prevent this event bubbling up to any parent nodes
        e.Handled = true;
    }
}
2) TreeViewItem的样式包括以下属性

<Style x:Key="perExpandCollapseToggleStyle"
       TargetType="ToggleButton">
    <Setter Property="Focusable" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToggleButton">
                <Grid Width="10"
                      Height="10"
                      Background="Transparent">
                    <Path x:Name="ExpanderGlyph"
                          Margin="1"
                          HorizontalAlignment="Left"
                          VerticalAlignment="Center"
                          Data="M 0,3 L 0,5 L 3,5 L 3,8 L 5,8 L 5,5 L 8,5 L 8,3 L 5,3 L 5,0 L 3,0 L 3,3 z"
                          Fill="LightGreen"
                          Stretch="None" />
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter TargetName="ExpanderGlyph" Property="Data" Value="M 0,0 M 8,8 M 0,3 L 0,5 L 8,5 L 8,3 z" />
                        <Setter TargetName="ExpanderGlyph" Property="Fill" Value="Red" />
                    </Trigger>

                    <Trigger Property="IsEnabled" Value="False">
                        <Setter TargetName="ExpanderGlyph" Property="Fill" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="perTreeViewItemContainerStyle"
       TargetType="{x:Type TreeViewItem}">

    <!-- Link the properties of perTreeViewItemViewModelBase to the corresponding ones on the TreeViewItem -->
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    <Setter Property="IsEnabled" Value="{Binding IsEnabled}" />

    <!-- Include the two "Scroll into View" behaviors -->
    <Setter Property="vhelp:perTreeViewItemHelper.BringSelectedItemIntoView" Value="True" />
    <Setter Property="vhelp:perTreeViewItemHelper.BringExpandedChildrenIntoView" Value="True" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"
                                          MinWidth="14" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <ToggleButton x:Name="Expander"
                                  Grid.Row="0"
                                  Grid.Column="0"
                                  ClickMode="Press"
                                  IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                  Style="{StaticResource perExpandCollapseToggleStyle}" />

                    <Border x:Name="PART_Border"
                            Grid.Row="0"
                            Grid.Column="1"
                            Padding="{TemplateBinding Padding}"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">

                        <ContentPresenter x:Name="PART_Header"
                                          Margin="0,2"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                          ContentSource="Header" />

                    </Border>

                    <ItemsPresenter x:Name="ItemsHost"
                                    Grid.Row="1"
                                    Grid.Column="1" />
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsExpanded" Value="false">
                        <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
                    </Trigger>

                    <Trigger Property="HasItems" Value="false">
                        <Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
                    </Trigger>

                    <!--  Use the same colors for a selected item, whether the TreeView is focussed or not  -->
                    <Trigger Property="IsSelected" Value="true">
                        <Setter TargetName="PART_Border" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                    </Trigger>

                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="{x:Type TreeView}">
    <Setter Property="ItemContainerStyle" Value="{StaticResource perTreeViewItemContainerStyle}" />
</Style>