WPF C# TreeView 所选项目在父项目上触发

WPF C# TreeView selected item getting triggered on parent items

所以我正在尝试为使用树视图作为主要导航控件的应用程序制作导航 (back/forwards) 按钮,但是有时当我向后和向前导航 select即使我只设置 selected 一次,离子更改事件也会同时触发多个项目。

下面是一个说明问题的示例应用程序,当您 select 树视图中的项目时,它会显示在列表视图中,并且历史记录中的当前位置由哪个项目标识 select 在列表视图中编辑。如果您 select 第 1 项、第 5 项、第 9 项和第 16 项,然后按后退按钮直到您到达第一项,这可以正常工作。但是,当向前按时,从项目 5 到项目 9 时,它还会触发项目 4 和项目 1 中的事件,从而导致导航逻辑失败。

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:this="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="500" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2" HorizontalAlignment="Center">
        <Button x:Name="buttonBack" Content="Back" Width="150" Margin="0,0,20,0" Click="buttonBack_Click"/>
        <Button x:Name="buttonForwards" Content="Forwards" Width="150" Margin="20,0,0,0" Click="buttonForwards_Click"/>
    </StackPanel>

    <TreeView x:Name="tvTest" Margin="2" Grid.Column="0" Grid.Row="1" SelectedItemChanged="tvTest_SelectedItemChanged">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type this:TreeViewObject}" ItemsSource="{Binding Children}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Content}" Margin="3,0,0,0"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="true"/>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>

    <ListView x:Name="lvTest" Grid.Column="1" Grid.Row="1" Margin="2"/>
</Grid>
</Window>

以及应用程序的主要代码:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

namespace WpfApplication1
{
    public class TreeViewObject : INotifyPropertyChanged
    {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    private int _id = -1;
    public int ID
    {
        get { return _id; }
        set
        {
            _id = value;
            OnPropertyChanged("ID");
        }
    }

    private string _content = "";
    public string Content
    {
        get { return _content; }
        set
        {
            _content = value;
            OnPropertyChanged("Content");
        }
    }

    private ObservableCollection<TreeViewObject> _children = new ObservableCollection<TreeViewObject>();
    public ObservableCollection<TreeViewObject> Children
    {
        get { return _children; }
        set
        {
            _children = value;
            OnPropertyChanged("Children");
        }
    }
}

public class TreeViewObjectManager
{
    private static TreeViewObjectManager _instance = null;
    public static TreeViewObjectManager Instance
    {
        get { return _instance ?? (_instance = new TreeViewObjectManager()); }
        private set { _instance = value; }
    }

    private ObservableCollection<TreeViewObject> _items = new ObservableCollection<TreeViewObject>();
    public ObservableCollection<TreeViewObject> Items
    {
        get { return _items; }
        private set { _items = value; }
    }

    private List<TreeViewObject> _raw = new List<TreeViewObject>();

    private TreeViewObjectManager()
    {
        TreeViewObject obj1 = new TreeViewObject();
        obj1.ID = 1;
        obj1.Content = "Item 1";
        TreeViewObject obj2 = new TreeViewObject();
        obj2.ID = 2;
        obj2.Content = "Item 2";
        TreeViewObject obj3 = new TreeViewObject();
        obj3.ID = 3;
        obj3.Content = "Item 3";
        TreeViewObject obj4 = new TreeViewObject();
        obj4.ID = 4;
        obj4.Content = "Item 4";
        TreeViewObject obj5 = new TreeViewObject();
        obj5.ID = 5;
        obj5.Content = "Item 5";
        TreeViewObject obj6 = new TreeViewObject();
        obj6.ID = 6;
        obj6.Content = "Item 6";
        TreeViewObject obj7 = new TreeViewObject();
        obj7.ID = 7;
        obj7.Content = "Item 7";
        TreeViewObject obj8 = new TreeViewObject();
        obj8.ID = 8;
        obj8.Content = "Item 8";
        TreeViewObject obj9 = new TreeViewObject();
        obj9.ID = 9;
        obj9.Content = "Item 9";
        TreeViewObject obj10 = new TreeViewObject();
        obj10.ID = 10;
        obj10.Content = "Item 10";
        TreeViewObject obj11 = new TreeViewObject();
        obj11.ID = 11;
        obj11.Content = "Item 11";
        TreeViewObject obj12 = new TreeViewObject();
        obj12.ID = 12;
        obj12.Content = "Item 12";
        TreeViewObject obj13 = new TreeViewObject();
        obj13.ID = 13;
        obj13.Content = "Item 13";
        TreeViewObject obj14 = new TreeViewObject();
        obj14.ID = 14;
        obj14.Content = "Item 14";
        TreeViewObject obj15 = new TreeViewObject();
        obj15.ID = 15;
        obj15.Content = "Item 15";
        TreeViewObject obj16 = new TreeViewObject();
        obj16.ID = 16;
        obj16.Content = "Item 16";

        obj1.Children.Add(obj2);
        obj1.Children.Add(obj3);
        obj1.Children.Add(obj4);
        obj1.Children.Add(obj8);

        obj4.Children.Add(obj5);
        obj4.Children.Add(obj6);
        obj4.Children.Add(obj7);

        obj9.Children.Add(obj10);
        obj9.Children.Add(obj11);
        obj9.Children.Add(obj16);

        obj11.Children.Add(obj12);

        obj12.Children.Add(obj13);
        obj12.Children.Add(obj14);
        obj12.Children.Add(obj15);

        Items.Add(obj1);
        Items.Add(obj9);

        _raw.Add(obj1);
        _raw.Add(obj2);
        _raw.Add(obj3);
        _raw.Add(obj4);
        _raw.Add(obj5);
        _raw.Add(obj6);
        _raw.Add(obj7);
        _raw.Add(obj8);
        _raw.Add(obj9);
        _raw.Add(obj10);
        _raw.Add(obj11);
        _raw.Add(obj12);
        _raw.Add(obj13);
        _raw.Add(obj14);
        _raw.Add(obj15);
        _raw.Add(obj16);
    }

    public TreeViewObject GetObj(int id)
    {
        foreach(TreeViewObject obj in _raw)
        {
            if (obj.ID == id)
                return obj;
        }
        return null;
    }
}

public partial class MainWindow : Window
{
    private ObservableCollection<int> _history = new ObservableCollection<int>();
    public ObservableCollection<int> History
    {
        get { return _history; }
        set { _history = value; }
    }
    private int _currentHistory = 0;

    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = this;
        tvTest.ItemsSource = TreeViewObjectManager.Instance.Items;
        lvTest.ItemsSource = History;
    }

    private void tvTest_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var selected = (sender as TreeView).SelectedItem as TreeViewObject;
        if(selected != null)
        {
            if(_history.Count == 0)
            {
                _history.Add(selected.ID);
                _currentHistory = _history.Count - 1;
            }
            else
            {
                if(_history.Count - 1 == _currentHistory)
                {
                    if (_history[_currentHistory] != selected.ID)
                    {
                        _history.Add(selected.ID);
                        _currentHistory = _history.Count - 1;
                    }
                }
                else
                {
                    if (_history[_currentHistory] != selected.ID)
                    {
                        for (int i = _history.Count - 1; i > _currentHistory; --i)
                            _history.RemoveAt(i);
                        _history.Add(selected.ID);
                        _currentHistory = _history.Count - 1;
                    }
                }
            }
        }
        lvTest.SelectedIndex = _currentHistory;
        e.Handled = true;
    }

    private void buttonBack_Click(object sender, RoutedEventArgs e)
    {
        if(_currentHistory > 0)
        {
            int tempID = _history[_currentHistory - 1];

            var obj = TreeViewObjectManager.Instance.GetObj(tempID);
            if(obj != null)
            {
                _currentHistory--;

                Action uiAction = () =>
                    {
                        var container = GetTreeViewItem(tvTest, obj);
                        if (container != null)
                            container.IsSelected = true;
                    };
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, uiAction);
            }
        }
    }

    private void buttonForwards_Click(object sender, RoutedEventArgs e)
    {
        if (_history.Count - 1 > _currentHistory)
        {
            int tempID = _history[_currentHistory + 1];

            var obj = TreeViewObjectManager.Instance.GetObj(tempID);
            if (obj != null)
            {
                _currentHistory++;

                Action uiAction = () =>
                    {
                        var container = GetTreeViewItem(tvTest, obj);
                        if (container != null)
                            container.IsSelected = true;
                    };
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, uiAction);
            }
        }
    }

    public TreeViewItem GetTreeViewItem(ItemsControl cont, object item)
    {
        if (cont != null)
        {
            if (cont.DataContext == item)
                return cont as TreeViewItem;

            if (cont is TreeViewItem && !((TreeViewItem)cont).IsExpanded)
                cont.SetValue(TreeViewItem.IsExpandedProperty, true);

            cont.ApplyTemplate();
            ItemsPresenter itemsPres = (ItemsPresenter)cont.Template.FindName("ItemsHost", cont);
            if (itemsPres != null)
                itemsPres.ApplyTemplate();
            else
            {
                itemsPres = FindVisualChild<ItemsPresenter>(cont);
                if (itemsPres == null)
                {
                    cont.UpdateLayout();
                    itemsPres = FindVisualChild<ItemsPresenter>(cont);
                }
            }

            System.Windows.Controls.Panel itemsHostPanel = (System.Windows.Controls.Panel)VisualTreeHelper.GetChild(itemsPres, 0);
            UIElementCollection children = itemsHostPanel.Children;

            for (int i = 0, count = cont.Items.Count; i < count; ++i)
            {
                TreeViewItem subCont;
                subCont = (TreeViewItem)cont.ItemContainerGenerator.ContainerFromIndex(i);
                subCont.BringIntoView();

                if (subCont != null)
                {
                    TreeViewItem result = GetTreeViewItem(subCont, item);
                    if (result != null)
                        return result;
                    else
                        subCont.IsExpanded = false;
                }
            }
        }
        return null;
    }

    private T FindVisualChild<T>(Visual visual) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                {
                    return descendent;
                }
            }
        }
        return null;
    }
}
}

如有任何关于如何解决此问题的建议,我们将不胜感激。

我找到了答案,通过删除折叠和展开树视图项目的代码,仍然可以获得该项目(请注意,这可能只有在您没有在树视图中使用虚拟化时才有效)。因此树视图的结构没有改变,我仍然可以访问该项目。然后在所选项目更改事件中,我刚刚在所选树视图项目上添加了对 BringIntoView 的调用,因此展开树视图以在需要时显示所选对象。

下面是调整后的GetTreeViewItem方法:

    public TreeViewItem GetTreeViewItem(ItemsControl cont, object item)
    {
        if(cont != null)
        {
            if (cont.DataContext == item)
                return cont as TreeViewItem;

            cont.ApplyTemplate();
            ItemsPresenter itemsPres = (ItemsPresenter)cont.Template.FindName("ItemsHost", cont);
            if (itemsPres != null)
                itemsPres.ApplyTemplate();
            else
            {
                itemsPres = FindVisualChild<ItemsPresenter>(cont);
                if(itemsPres == null)
                {
                    cont.UpdateLayout();
                    itemsPres = FindVisualChild<ItemsPresenter>(cont);
                }
            }

            System.Windows.Controls.Panel itemsHostPanel = (System.Windows.Controls.Panel)VisualTreeHelper.GetChild(itemsPres, 0);
            UIElementCollection children = itemsHostPanel.Children;

            for(int i = 0, count = cont.Items.Count; i < count; ++i)
            {
                TreeViewItem subCont;
                subCont = (TreeViewItem)cont.ItemContainerGenerator.ContainerFromIndex(i);

                if(subCont != null)
                {
                    TreeViewItem result = GetTreeViewItem(subCont, item);
                    if (result != null)
                        return result;
                }
            }
        }
        return null;
    }