【问题标题】:Expanding/selecting a treeview node when it is not currently visible在当前不可见时展开/选择树视图节点
【发布时间】:2014-02-08 05:56:33
【问题描述】:

我需要更改托管在单独选项卡中的树视图中的选定节点。 此外,如果父节点没有展开,我希望展开节点。

在通过 SO、Google 等进行了大约一个小时的无果搜索后,我决定发布一个问题。

当它全部可见时,我可以找到并展开所需的节点,但是当 treeveiw 被另一个选项卡项遮挡时,它不会更新。我也不完全确定该项目是否被“选中” - 在调试器中它说 ISelected 为真,并且父项的 IsExpanded 属性也为真。

我在以下代码行中简化了我的实际问题:

XAML(选项卡控件有两个项目,一个是重现问题的按钮,一个是应该更新的树视图):

<Window x:Class="TreeviewTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <TabControl>
        <TabItem Header="Select">
            <Button Content="Select Delta!" Click="ButtonBase_OnClick" />
        </TabItem>
        <TabItem Header="Tree">
            <TreeView ItemsSource="{Binding NodesDisplay}" Name ="treTest">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenDisplay}">
                        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
                        <HierarchicalDataTemplate.ItemTemplate>
                            <HierarchicalDataTemplate>
                                <TextBlock Text="{Binding Path=Name}" />
                            </HierarchicalDataTemplate>
                        </HierarchicalDataTemplate.ItemTemplate>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>

        </TabItem>
    </TabControl>

</Grid>

主窗口代码:

namespace TreeviewTest
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow 
    {
        public ObservableCollection<TreeNode> Nodes { get; set; }

        public ICollectionView NodesDisplay { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            Nodes = new ObservableCollection<TreeNode>
            {
                new TreeNode(new List<TreeLeaf>
                    {
                        new TreeLeaf{Name = "Alpha"},
                        new TreeLeaf{Name = "Beta"}
                    }){ Name = "One" },
                new TreeNode(new List<TreeLeaf>
                    {
                        new TreeLeaf{Name = "Delta"},
                        new TreeLeaf{Name = "Gamma"}
                    }){ Name = "Two" }

            };
            NodesDisplay = CollectionViewSource.GetDefaultView(Nodes);
            DataContext = this;
        }

        public class TreeNode
        {
            public string Name { get; set; }
            public ObservableCollection<TreeLeaf> Children { get; private set; }

            public ICollectionView ChildrenDisplay { get; private set; }

            public TreeNode(IEnumerable<TreeLeaf> leaves)
            {
                Children = new ObservableCollection<TreeLeaf>(leaves);
                ChildrenDisplay = CollectionViewSource.GetDefaultView(Children);
            }
        }

        public class TreeLeaf
        {
            public string Name { get; set; }
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            EnsureCanIterateThroughCollection(treTest);
            var rootLevelToSelect = Nodes.First(x => x.Name == "Two");
            TreeViewItem root = treTest.ItemContainerGenerator.ContainerFromItem(rootLevelToSelect) as TreeViewItem;

            EnsureCanIterateThroughCollection(root);
            var leafLevelToSelect = rootLevelToSelect.Children.First(x => x.Name == "Delta");
            TreeViewItem leaf = root.ItemContainerGenerator.ContainerFromItem(leafLevelToSelect) as TreeViewItem;

            if (!root.IsExpanded)
                root.IsExpanded = true;

            leaf.IsSelected = true;
            ReflectivelySelectTreeviewItem(leaf);
        }

        //Got this from another SO post - not sure is setting IsSelected on the node is actually doing what I think it is...
        private static void ReflectivelySelectTreeviewItem(TreeViewItem node)
        {
            MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
            selectMethod.Invoke(node, new object[] { true });
        }

        private static void EnsureCanIterateThroughCollection(ItemsControl itemsControl)
        {
            if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.NotStarted)
            ForceGenerateChildContent(itemsControl);
        }

        private static void ForceGenerateChildContent(ItemsControl itemsControl)
        {
            itemsControl.ApplyTemplate();

            IItemContainerGenerator generator = itemsControl.ItemContainerGenerator;

            GeneratorPosition position = generator.GeneratorPositionFromIndex(0);
            using (generator.StartAt(position, GeneratorDirection.Forward, true))
            {
                for (int i = 0; i < itemsControl.Items.Count; i++)
                {
                    DependencyObject dp = generator.GenerateNext();
                    generator.PrepareItemContainer(dp);
                }
            }
        }
    }
}

另外 - 另一个 XAML sn-p 做同样的事情,但树视图是可见的 - 你应该能够看到树视图展开并选择项目

<Window x:Class="TreeviewTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="30" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Button Content="Select Delta!" Click="ButtonBase_OnClick" />
    <TreeView ItemsSource="{Binding NodesDisplay}" Name ="treTest" Grid.Row="1">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenDisplay}">
                <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
                <HierarchicalDataTemplate.ItemTemplate>
                    <HierarchicalDataTemplate>
                        <TextBlock Text="{Binding Path=Name}" />
                    </HierarchicalDataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</Grid>

我将不胜感激 - 我对 treeview 和 WPF 以及数据绑定的理解是,它不如为您提供 IsSynchronisedWithCurrentItem 的东西那样好用,这就是为什么我试图手动处理对 treeview 的更新并且我也在尝试以编程方式选择树中的项目。如果我在这方面错了,我希望有一个指针向我展示如何以更“WPF”的方式来做!

【问题讨论】:

  • 不确定这是否有帮助,因为它是 Winforms:stackoverflow.com/questions/14075841/…
  • @JohnBartels,感谢您的信息,但使用 winforms 是我真的不想做的事情(我实际上正在将 winforms 组件迁移到 WPF!)我知道我可以使用 winforms 主机控件托管树视图,但如果可能的话,我真的想避免这种情况。

标签: c# wpf xaml treeview


【解决方案1】:

当标签页不可见时,不会创建控件。只有当您切换到它时,才会创建控件。将布尔值添加到 TreeNode 视图模型并绑定 IsSelected 属性。

TreeNodeVm.cs:

using Microsoft.Practices.Prism.ViewModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace TreeViewSelectTest
{
    public class TreeNodeVm : NotificationObject
    {
        private TreeNodeVm Parent { get; set; }

        private bool _isSelected = false;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                _isSelected = value;
                RaisePropertyChanged(() => IsSelected);
            }
        }

        private bool _isExpanded = false;
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                _isExpanded = value;
                RaisePropertyChanged(() => IsExpanded);
            }
        }

        public ObservableCollection<TreeNodeVm> Children { get; private set; }

        public string Header { get; set; }

        public TreeNodeVm()
        {
            this.Children = new ObservableCollection<TreeNodeVm>();
            this.Children.CollectionChanged += Children_CollectionChanged;
        }

        void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach (var newChild in e.NewItems.Cast<TreeNodeVm>())
                {
                    newChild.Parent = this;
                }
            }
        }

        public TreeNodeVm(string header, IEnumerable<TreeNodeVm> children)
            : this()
        {
            this.Header = header;
            foreach (var child in children)
                Children.Add(child);
        }

        public void MakeVisible()
        {
            if (Parent != null)
            {
                Parent.MakeVisible();
            }
            this.IsExpanded = true;
        }

        public void Select()
        {
            MakeVisible();

            this.IsSelected = true;
        }
    }
}

MainWindow.xaml:

<Window x:Class="TreeViewSelectTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
            <Button Content="Select B1" Click="btSelectB1_Click" />
        </StackPanel>
        <TabControl>
            <TabItem Header="treeview">
                <TreeView ItemsSource="{Binding Path=RootNode.Children}">
                    <TreeView.Resources>
                        <Style TargetType="{x:Type TreeViewItem}">
                            <Setter Property="IsSelected" Value="{Binding Path=IsSelected,Mode=TwoWay}" />
                            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}" />
                        </Style>
                    </TreeView.Resources>
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                            <TextBlock Text="{Binding Header}" />
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
            </TabItem>
            <TabItem Header="the other item">
                <Button />
            </TabItem>
        </TabControl>
    </DockPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TreeViewSelectTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            RootNode = new TreeNodeVm("Root", new[]
            {
                new TreeNodeVm("A", new [] {
                    new TreeNodeVm("A1", new TreeNodeVm[0]),
                    new TreeNodeVm("A2", new TreeNodeVm[0]),
                    new TreeNodeVm("A3", new TreeNodeVm[0])
                }),
                new TreeNodeVm("B", new [] {
                    new TreeNodeVm("B1", new TreeNodeVm[0])
                })
            });

            InitializeComponent();
            this.DataContext = this;
        }

        public TreeNodeVm RootNode { get; private set; }

        private void btSelectB1_Click(object sender, RoutedEventArgs e)
        {
            RootNode.Children[1].Children[0].Select();
        }
    }
}

当您调用TreeNodeVm.Select() 时,它将更新视觉效果的未来状态。切换回标签页后,将应用模板并将视觉效果创建为展开并选中。

【讨论】:

  • 嗨 Mitch,你说得对,章节必须可见一次,并且所有根节点都需要展开,以便树节点在不可见时进行选择。我试图用上面代码中的 ForceGenerateChildContent 方法来解决这个问题——但这并没有达到我希望的效果。但是,您对使用 IsSelected 的建议似乎并没有让我更进一步,因为我已经可以选择一个节点。也许我需要完全按照我刚才所说的去做——即在加载时显示所有树节点。我试试看。
  • @Jay,也许你误会了。我的观点是你可以在你的视图模型上保持选定的状态。 UI 的状态将随之而来。
  • 啊,是的,我明白你在说什么 - 这肯定是一种更 WPF 的方式来保持我的观点同步。它实际上并没有完全回答我的问题,但我会采用 vm 更改。 (+1)
  • @Jay,我很高兴听到这个消息,它没有处理什么部分?
  • 我将此标记为已接受的答案,因为采用此模式帮助我清理了我的代码,并且您对控件直到可见才创建的评论使我得到了答案。我正在做的有点像 hack,但我一直在等到树被填充,等到它通过挂钩到更新的布局然后展开所有节点,将它们全部折叠然后隐藏树来真正可见。这一切发生得如此之快,最终用户一点也不聪明,但它允许我以编程方式访问树,即使它被隐藏,因为它都“在那里”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 2014-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多