首页 > 解决方案 > c# wpf TreeView 选择根项目的问题

问题描述

我对 C# 中的 WPF TreeView 有一个奇怪的问题。运行我的应用程序时,我想从选定的 TreeView 节点展开和折叠所有节点。当我选择一个根节点时, IsSelected 属性不会更新。当我手动展开根节点并单击子节点时,将更新 IsSelected 属性。问题是当我使用上下文菜单展开和折叠根节点上的子节点时,根节点没有展开。当我在使用上下文菜单后展开根节点时,我可以看到所有子节点都已正确展开。

我在互联网上搜索了很多,但找不到类似的问题。此外,我阅读了很多关于 C# WPF TreeViews 的教程和操作指南。到目前为止,我的实现在我看来是正确的,这就是为什么我不明白为什么单击节点时未设置根属性 IsSelected。

谁能帮我?我已经实现了 TreeView、ViewModel 和 Model,如下所示。提前致谢

BR

迈克尔


<TreeView
Grid.Row="1"
Grid.Column="0"
Margin="5"
ContextMenuOpening="FrameworkElement_OnContextMenuOpening"
ItemsSource="{Binding HierarchialTestObjects}"
SelectedItemChanged="TreeView_OnSelectedItemChanged">
<TreeView.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type vm:HierarchialTestObjectViewModel}" ItemsSource="{Binding Childs}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding Path=Name}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
    <ContextMenu>
        <MenuItem Command="{Binding ExpandAllTreeItemsCommand}" Header="Expand All" />
        <MenuItem Command="{Binding CollapseAllTreeItemsCommand}" Header="Collapse All" />
    </ContextMenu>
</TreeView.ContextMenu>

namespace mynamespace.subspace.report.data
{
// ... usings removed

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

    public List<HierarchialTestObject> Childs { get; set; } = new List<HierarchialTestObject>();

    public List<string> Path { get; set; } = new List<string>();

    public List<string> ParentPath => Path?.AsEnumerable()?.Reverse()?.Skip(1)?.Reverse()?.ToList();

    public IHierarchialItem Item { get; set; }

    public HierarchialTestObject(IHierarchialItem item)
    {
        Item = item;
        Name = item.ShortDescription;
    }

    public void SetChilds(IEnumerable<IHierarchialItem> childs)
    {
        childs.ToList().ForEach(c => Childs?.Add(new HierarchialTestObject(c)));
    }
}
}

视图模型

namespace mynamespace.subspace.report.viewmodel
{
// ... usings removed

public class HierarchialTestObjectViewModel : ViewModelBase
{
    public delegate void NotifyTreeItemSelected(HierarchialTestObjectViewModel item);

    public event NotifyTreeItemSelected OnTreeItemSelected;

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_isSelected != value)
            {
                SetProperty(ref _isSelected, value, nameof(IsSelected));
                if (value)
                {
                    // this is a hack-ish workaround because i cannot use PRISM or other similar libs
                    OnTreeItemSelected?.Invoke(this);
                }
            }
        }
    }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (_isExpanded != value)
            {
                SetProperty(ref _isExpanded, value, nameof(IsExpanded));
                RaisePropertyChanged(nameof(IconType));
            }
        }
    }



    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                SetProperty(ref _name, value, nameof(Name));
            }
        }
    }



    private IHierarchialItem _item;
    public IHierarchialItem Item
    {
        get { return _item; }
        set
        {
            if (_item != value)
            {
                SetProperty(ref _item, value, nameof(Item));
            }
        }
    }


    private List<string> _path;
    public List<string> Path
    {
        get { return _path; }
        set
        {
            if (_path != value)
            {
                SetProperty(ref _path, value, nameof(Path));
            }
        }
    }

    private ObservableCollection<HierarchialTestObjectViewModel> _childs;
    public ObservableCollection<HierarchialTestObjectViewModel> Childs
    {
        get { return _childs; }
        set
        {
            if (_childs != value)
            {
                SetProperty(ref _childs, value, nameof(Childs));
            }
        }
    }

    public HierarchialTestObjectViewModel(HierarchialTestObject obj)
    {
        var childs = obj.Childs.Select(c => new HierarchialTestObjectViewModel(c)).ToList();
        Path = obj.Path ?? new List<string>();
        Childs = new ObservableCollection<HierarchialTestObjectViewModel>(childs);
        Item = obj.Item;
        Name = obj.Name;
    }

    public void ExpandRecursively()
    {
        IsExpanded = true;
        Childs?.ToList().ForEach(c => c.ExpandRecursively());
    }

    public void CollapseRecursively()
    {
        IsExpanded = false;
        Childs?.ToList().ForEach(c => c.CollapseRecursively());
    }

}
}

标签: c#wpftreeview

解决方案


问题是您从未设置ItemContainerStyle根项目的 。仅适用于子项。因此,根数据模型的IsExpandedIsSelected属性没有连接到它们的TreeViewItem数据容器。

只需设置TreeView.ItemContainerStyle即可解决问题:

<TreeView.ItemContainerStyle>
  <Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
  </Style>  
</TreeView.ItemContainerStyle>

评论

TreeViewItem.IsSelected默认配置为BindingMode.TwoWay
TreeViewItem.IsSelected并将TreeViewItem.IsExpanded它们Binding.UpdateSourceTrigger设置为UpdateSourceTrigger.PropertyChanged默认值,这是所有 Dependency Property的默认值,除非它们被显式设置为不同的触发器,只有少数属性是这种情况(例如默认TextBox.Text设置为UpdateSourceTrigger.LostFocus)。
这样可以减少代码并提高可读性。


推荐阅读