WPF TreeView с виртуализацией - выберите и представьте объект - PullRequest
0 голосов
/ 18 сентября 2018

Я работаю с древовидным представлением WPF совсем недавно, и у меня действительно ужасное время, когда я пытаюсь отобразить выбранный элемент на экране, когда пользователь использует функцию поиска, которая устанавливает свойство IsSelected наобъект поддержки.

В настоящее время мой подход использует метод в этом ответе: https://stackoverflow.com/a/34620549/800318

    private void FocusTreeViewNode(TreeViewEntry node)
    {
        if (node == null) return;
        var nodes = (IEnumerable<TreeViewEntry>)LeftSide_TreeView.ItemsSource;
        if (nodes == null) return;

        var stack = new Stack<TreeViewEntry>();
        stack.Push(node);
        var parent = node.Parent;
        while (parent != null)
        {
            stack.Push(parent);
            parent = parent.Parent;
        }

        var generator = LeftSide_TreeView.ItemContainerGenerator;
        while (stack.Count > 0)
        {
            var dequeue = stack.Pop();
            LeftSide_TreeView.UpdateLayout();

            var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
            if (stack.Count > 0)
            {
                treeViewItem.IsExpanded = true;
            }
            else
            {
                if (treeViewItem == null)
                {
                    //This is being triggered when it shouldn't be
                    Debugger.Break();
                }
                treeViewItem.IsSelected = true;
            }
            treeViewItem.BringIntoView();
            generator = treeViewItem.ItemContainerGenerator;
        }
    }

TreeViewEntry - это мой тип данных поддержки, который имеет ссылку на его родительский узел.Leftside_TreeView - это виртуализированный TreeView, который связан со списком моих объектов.Отключить виртуализацию нельзя, так как при ее отключении производительность действительно плохая.

Когда я ищу объект и обнаруживается объект данных поддержки, я вызываю этот метод FocusTreeViewNode () с объектом в качестве его параметра.Обычно он работает при первом вызове, выбирая объект и выводя его на экран.

При выполнении поиска второй раз, узел для выбора передается, однако вызывается ContainerFromItem (), когда стекОпорожненный (поэтому он пытается создать контейнер для самого объекта) возвращает ноль.Когда я отлаживаю это, я вижу объект, который я ищу, в списке элементов ContainerGenerator, но по какой-то причине он не возвращается.Я посмотрел все, что нужно сделать с UpdateLayout () и другие вещи, но я не могу понять это.

Некоторые объекты в контейнере могут быть вне страницы, даже после того, как родительский узел перенесенв поле зрения - например, экспандер имеет под ним 250 элементов, и только 60 визуализируются одновременно.Может ли это быть проблемой?

Обновление

Вот пример проекта, который создает виртуальное древовидное представление, показывающее эту проблему.https://github.com/Mgamerz/TreeViewVirtualizingErrorDemo

Создайте его в VS, затем в поле поиска введите что-то вроде 4. Нажмите несколько раз поиск, и он выдаст исключение, сообщающее, что контейнер пуст, даже если вы откроете объект generatorВы можете ясно видеть, что это в генераторе.

Ответы [ 2 ]

0 голосов
/ 25 сентября 2018

Как и многие другие аспекты разработки WPF, эту операцию можно выполнить с помощью шаблона проектирования MVVM.

Создайте класс ViewModel, включая свойство IsSelected, которое содержит данные для каждого элемента дерева.

Отображение выбранного элемента может затем обрабатываться с помощью присоединенного свойства

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

Это может затем использоваться как часть стиля для TreeViewItems

<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>

Более подробная информация и полный пример использования моего недавнего сообщения в блоге .

Обновление 13 октября

Сообщение в блоге было исправлено при работе в стандартном режиме (режим без ленивой загрузки). Соответствующий демонстрационный проект показывает вложенную структуру данных, содержащую более 400 000 элементов, отображаемых в TreeView, и все же ответ на выбор любого случайного узла является мгновенным.

0 голосов
/ 25 сентября 2018

Довольно сложно получить TreeViewItem для данного элемента данных, во всех случаях, особенно в виртуализированных.

К счастью, Microsoft предоставила нам вспомогательную функцию здесь Как: найти TreeViewItem в TreeView , который я адаптировал, так что ему не нужен пользовательский класс VirtualizingStackPanel (требуется.NET Framework 4.5 или выше, для более старых версий, обратитесь по ссылке выше).

Вот как вы можете заменить FocusTreeViewNode метод:

private void FocusTreeViewNode(MenuItem node)
{
    if (node == null)
        return;

    var treeViewItem = GetTreeViewItem(tView, node);
    treeViewItem?.BringIntoView();
}


public static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
    if (container == null)
        throw new ArgumentNullException(nameof(container));

    if (item == null)
        throw new ArgumentNullException(nameof(item));

    if (container.DataContext == item)
        return container as TreeViewItem;

    if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
    {
        container.SetValue(TreeViewItem.IsExpandedProperty, true);
    }

    container.ApplyTemplate();
    if (container.Template.FindName("ItemsHost", container) is ItemsPresenter itemsPresenter)
    {
        itemsPresenter.ApplyTemplate();
    }
    else
    {
        itemsPresenter = FindVisualChild<ItemsPresenter>(container);
        if (itemsPresenter == null)
        {
            container.UpdateLayout();
            itemsPresenter = FindVisualChild<ItemsPresenter>(container);
        }
    }

    var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
    var children = itemsHostPanel.Children;
    var virtualizingPanel = itemsHostPanel as VirtualizingPanel;
    for (int i = 0, count = container.Items.Count; i < count; i++)
    {
        TreeViewItem subContainer;
        if (virtualizingPanel != null)
        {
            // this is the part that requires .NET 4.5+
            virtualizingPanel.BringIndexIntoViewPublic(i);
            subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
        }
        else
        {
            subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
            subContainer.BringIntoView();
        }

        if (subContainer != null)
        {
            TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
            if (resultContainer != null)
                return resultContainer;

            subContainer.IsExpanded = false;
        }
    }
    return null;
}

private static T FindVisualChild<T>(Visual visual) where T : Visual
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
    {
        if (VisualTreeHelper.GetChild(visual, i) is Visual child)
        {
            if (child is T item)
                return item;

            item = FindVisualChild<T>(child);
            if (item != null)
                return item;
        }
    }
    return null;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...