当树形视图当前不可见时,展开/选择它

时间:2014-01-18 22:28:20

标签: c# wpf xaml treeview

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

经过大约一个小时无聊搜索SO,谷歌等人后,我决定发一个问题。

当所有节点全部可见时,我可以找到并展开它,但是当树形被另一个标签项遮挡时,它不会更新。我也不完全确定该项被“选中” - 在调试器中它表示ISelected为true,并且父项的IsExpanded属性也为真。

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

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

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

MainWindow代码:

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片段执行相同的操作,但树视图可见 - 您应该能够看到树视图展开并选择项目

<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一样好用,这就是为什么我试图手动处理树视图的更新而且我也在尝试以编程方式选择树中的项目。如果我在这方面错了,我会喜欢一个指针,告诉我如何以更“'WPF'的方式做到这一点!

1 个答案:

答案 0 :(得分:3)

当标签页不可见时,不会创建控件。只有切换到它后才会创建控件。向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()时,它会更新视觉效果的未来状态。切换回标签页后,将应用模板,并将视觉效果创建为展开和选中。