Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 有没有从IEnumerable获得的包装对象类?(项目来源于TreeView) 主要目的_C#_Wpf_Mvvm_User Controls - Fatal编程技术网

C# 有没有从IEnumerable获得的包装对象类?(项目来源于TreeView) 主要目的

C# 有没有从IEnumerable获得的包装对象类?(项目来源于TreeView) 主要目的,c#,wpf,mvvm,user-controls,C#,Wpf,Mvvm,User Controls,我想基于TreeView在WPF中创建自己的UserControl。我的目标是这样做,以便有机会从代码中更改SelectedItem 解决问题的一种方法 在MVVM模式中,您可以在TreeItemViewModel中创建“IsSelected”属性,并在ItemContainerStyle中绑定“IsSelected”,如下所示: XAML: 通过这样做,您可以查看绑定到ItemsSource的所有ObservableCollection,找到所需的元素并为其父元素更改“IsExpanded”和

我想基于TreeView在WPF中创建自己的UserControl。我的目标是这样做,以便有机会从代码中更改SelectedItem

解决问题的一种方法 在MVVM模式中,您可以在TreeItemViewModel中创建“IsSelected”属性,并在ItemContainerStyle中绑定“IsSelected”,如下所示:

XAML:

通过这样做,您可以查看绑定到ItemsSource的所有ObservableCollection,找到所需的元素并为其父元素更改“IsExpanded”和“IsSelected”

我想要什么 我希望我的UserControl中包含所有这些绑定。我的UserControl将从TreeView继承,我将创建我自己的MyItemsSource,它将采用IEnumerable(就像TreeView中的原始ItemsSource一样)。在我看来,我计划的下一个阶段是将IEnumerable中的对象包装到新类中,该类将具有另外两个属性:IsSelected和IsExpanded。然后在我的UserControl中绑定这些属性

为什么我要这个 由于这一点,在我未来的项目中,我希望能够在代码中添加这两个属性并更改SelectedItem

主要问题 我如何将从IEnumerable获得的对象(不知道我得到的类,因为它是UserControl)包装到具有两个附加属性的新类中

更多解释 用户控制类:

public partial class UserControl : TreeView
    {
        public UserControl()
        {
            InitializeComponent();
        }

        public System.Collections.IEnumerable MyItemsSource
        {
            set
            {
                ObservableCollection<UserControlTreeItemViewModel> ItemsSourceWrapped = new ObservableCollection<UserControlTreeItemViewModel>();

                // wrap objects in cycle
                foreach(var item in value)
                {
                    ItemsSourceWrapped.Add(new UserControlTreeItemViewModel(item));
                }

                this.ItemsSource = ItemsSourceWrapped;
            }
        }

    }

主要问题:是否有任何方法可以包装从IEnumerable获得的对象?

这是用于在TreeView控件中显示数据项的基本ViewModel类。它处理诸如子项集合、扩展、选择、检查(勾选框)之类的事情,包括更新父项和子项、禁用状态、延迟加载子项

public class perTreeViewItemViewModelBase : perViewModelBase
{
    // a dummy item used in lazy loading mode, ensuring that each node has at least one child so that the expand button is shown
    private static perTreeViewItemViewModelBase LazyLoadingChildIndicator { get; } 
        = new perTreeViewItemViewModelBase { Caption = "Loading Data ..." };

    private bool InLazyLoadingMode { get; set; }
    private bool LazyLoadTriggered { get; set; }
    private bool LazyLoadCompleted { get; set; }
    private bool RequiresLazyLoad => InLazyLoadingMode && !LazyLoadTriggered;

    // Has Children been overridden (e.g. to point at some private internal collection) 
    private bool LazyLoadChildrenOverridden => InLazyLoadingMode && !Equals(LazyLoadChildren, _childrenList);

    private readonly perObservableCollection<perTreeViewItemViewModelBase> _childrenList 
        = new perObservableCollection<perTreeViewItemViewModelBase>();

    /// <summary>
    /// LazyLoadingChildIndicator ensures a visible expansion toggle button in lazy loading mode 
    /// </summary>
    protected void SetLazyLoadingMode()
    {
        ClearChildren();
        _childrenList.Add(LazyLoadingChildIndicator);

        IsExpanded = false;
        InLazyLoadingMode = true;
        LazyLoadTriggered = false;
        LazyLoadCompleted = false;
    }

    private string _caption;

    public string Caption
    {
        get => _caption;
        set => Set(nameof(Caption), ref _caption, value);
    }

    public void ClearChildren()
    {
        _childrenList.Clear();
    }

    /// <summary>
    /// Add a new child item to this TreeView item
    /// </summary>
    /// <param name="child"></param>
    public void AddChild(perTreeViewItemViewModelBase child)
    {
        if (LazyLoadChildrenOverridden)
        {
            throw new InvalidOperationException("Don't call AddChild for an item with LazyLoad mode set & LazyLoadChildren has been overridden");
        }

        if (_childrenList.Any() && _childrenList.First() == LazyLoadingChildIndicator)
        {
            _childrenList.Clear();
        }

        _childrenList.Add(child);
        SetChildPropertiesFromParent(child);
    }

    protected void SetChildPropertiesFromParent(perTreeViewItemViewModelBase child)
    { 
        child.Parent = this;

        // if this node is checked then all new children added are set checked 
        if (IsChecked.GetValueOrDefault())
        {
            child.SetIsCheckedIncludingChildren(true);
        }

        ReCalculateNodeCheckState();
    }

    protected void ReCalculateNodeCheckState()
    {
        var item = this;

        while (item != null)
        {
            if (item.Children.Any() && !Equals(item.Children.FirstOrDefault(), LazyLoadingChildIndicator))
            {
                var hasIndeterminateChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.HasValue);

                if (hasIndeterminateChild)
                {
                    item.SetIsCheckedThisItemOnly(null);
                }
                else
                {
                    var hasSelectedChild = item.Children.Any(c => c.IsEnabled && c.IsChecked.GetValueOrDefault());
                    var hasUnselectedChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.GetValueOrDefault());

                    if (hasUnselectedChild && hasSelectedChild)
                    {
                        item.SetIsCheckedThisItemOnly(null);
                    }
                    else
                    {
                        item.SetIsCheckedThisItemOnly(hasSelectedChild);
                    }
                }
            }

            item = item.Parent;
        }
    }

    private void SetIsCheckedIncludingChildren(bool? value)
    {
        if (IsEnabled)
        {
            _isChecked = value;
            RaisePropertyChanged(nameof(IsChecked));

            foreach (var child in Children)
            {
                if (child.IsEnabled)
                {
                    child.SetIsCheckedIncludingChildren(value);
                }
            }
        }
    }

    private void SetIsCheckedThisItemOnly(bool? value)
    {
        _isChecked = value;
        RaisePropertyChanged(nameof(IsChecked));
    }

    /// <summary>
    /// Add multiple children to this TreeView item
    /// </summary>
    /// <param name="children"></param>
    public void AddChildren(IEnumerable<perTreeViewItemViewModelBase> children)
    {
        foreach (var child in children)
        {
            AddChild(child);
        }
    }

    /// <summary>
    /// Remove a child item from this TreeView item
    /// </summary>
    public void RemoveChild(perTreeViewItemViewModelBase child)
    {
        _childrenList.Remove(child);
        child.Parent = null;

        ReCalculateNodeCheckState();
    }

    public perTreeViewItemViewModelBase Parent { get; private set; }

    private bool? _isChecked = false;

    public bool? IsChecked
    {
        get => _isChecked;
        set
        {
            if (Set(nameof(IsChecked), ref _isChecked, value))
            {
                foreach (var child in Children)
                {
                    if (child.IsEnabled)
                    {
                        child.SetIsCheckedIncludingChildren(value);
                    }
                }

                Parent?.ReCalculateNodeCheckState();
            }
        }
    }

    private bool _isExpanded;

    public bool IsExpanded
    {
        get => _isExpanded;
        set
        {
            if (Set(nameof(IsExpanded), ref _isExpanded, value) && value && RequiresLazyLoad)
            {
                TriggerLazyLoading();
            }
        }
    }

    private bool _isEnabled = true;

    public bool IsEnabled
    {
        get => _isEnabled;
        set => Set(nameof(IsEnabled), ref _isEnabled, value);
    }

    public void TriggerLazyLoading()
    {
        var unused = DoLazyLoadAsync();
    }

    private async Task DoLazyLoadAsync()
    {
        if (LazyLoadTriggered)
        {
            return;
        }

        LazyLoadTriggered = true;

        var lazyChildrenResult = await LazyLoadFetchChildren()
            .EvaluateFunctionAsync()
            .ConfigureAwait(false);

        LazyLoadCompleted = true;

        if (lazyChildrenResult.IsCompletedOk)
        {
            var lazyChildren = lazyChildrenResult.Data;

            foreach (var child in lazyChildren)
            {
                SetChildPropertiesFromParent(child);
            }

            // If LazyLoadChildren has been overridden then just refresh the check state (using the new children) 
            // and update the check state (in case any of the new children is already set as checked)
            if (LazyLoadChildrenOverridden)
            {
                ReCalculateNodeCheckState();
            }
            else
            {
                AddChildren(lazyChildren); // otherwise add the new children to the base collection.
            }
        }

        RefreshChildren();
    }

    /// <summary>
    /// Get the children for this node, in Lazy-Loading Mode
    /// </summary>
    /// <returns></returns>
    protected virtual Task<perTreeViewItemViewModelBase[]> LazyLoadFetchChildren()
    {
        return Task.FromResult(new perTreeViewItemViewModelBase[0]);
    }

    /// <summary>
    /// Update the Children property
    /// </summary>
    public void RefreshChildren()
    {
        RaisePropertyChanged(nameof(Children));
    }

    /// <summary>
    /// In LazyLoading Mode, the Children property can be set to something other than
    /// the base _childrenList collection - e.g as the union ot two internal collections
    /// </summary>
    public IEnumerable<perTreeViewItemViewModelBase> Children => LazyLoadCompleted
                                                                ? LazyLoadChildren
                                                                : _childrenList;

    /// <summary>
    /// How are the children held when in lazy loading mode.
    /// </summary>
    /// <remarks>
    /// Override this as required in descendent classes - e.g. if Children is formed from a union
    /// of multiple internal child item collections (of different types) which are populated in LazyLoadFetchChildren()
    /// </remarks>
    protected virtual IEnumerable<perTreeViewItemViewModelBase> LazyLoadChildren => _childrenList;

    private bool _isSelected;

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            // if unselecting we don't care about anything else other than simply updating the property
            if (!value)
            {
                Set(nameof(IsSelected), ref _isSelected, false);
                return;
            }

            // Build a priority queue of operations
            //
            // All operations relating to tree item expansion are added with priority = DispatcherPriority.ContextIdle, so that they are
            // sorted before any operations relating to selection (which have priority = DispatcherPriority.ApplicationIdle).
            // This ensures that the visual container for all items are created before any selection operation is carried out.
            //
            // First expand all ancestors of the selected item - those closest to the root first
            //
            // Expanding a node will scroll as many of its children as possible into view - see perTreeViewItemHelper, but these scrolling
            // operations will be added to the queue after all of the parent expansions.
            var ancestorsToExpand = new Stack<perTreeViewItemViewModelBase>();

            var parent = Parent;
            while (parent != null)
            {
                if (!parent.IsExpanded)
                {
                    ancestorsToExpand.Push(parent);
                }

                parent = parent.Parent;
            }

            while (ancestorsToExpand.Any())
            {
                var parentToExpand = ancestorsToExpand.Pop();
                perDispatcherHelper.AddToQueue(() => parentToExpand.IsExpanded = true, DispatcherPriority.ContextIdle);
            }

            // Set the item's selected state - use DispatcherPriority.ApplicationIdle so this operation is executed after all
            // expansion operations, no matter when they were added to the queue.
            //
            // Selecting a node will also scroll it into view - see perTreeViewItemHelper
            perDispatcherHelper.AddToQueue(() => Set(nameof(IsSelected), ref _isSelected, true), DispatcherPriority.ApplicationIdle);

            // note that by rule, a TreeView can only have one selected item, but this is handled automatically by 
            // the control - we aren't required to manually unselect the previously selected item.

            // execute all of the queued operations in descending DispatcherPriority order (expansion before selection)
            var unused = perDispatcherHelper.ProcessQueueAsync();
        }
    }

    public override string ToString()
    {
        return Caption;
    }

    /// <summary>
    /// What's the total number of child nodes beneath this one
    /// </summary>
    public int ChildCount => Children.Count() + Children.Sum(c => c.ChildCount);
}
我不会尝试将此包装隐藏在控件中,而是在ViewModel中发布一个PersonContreeViewWrapper项的嵌套集合,以绑定为TreeView的ItemsSource

其中一个原因是,如果希望在同一TreeView控件中显示多个项目类型,则必须能够为每个项目类型指定要使用的HierarchycalDataTemplate

<TreeView
    Grid.Column="0"
    ItemsSource="{Binding RootItemVms}">

    <TreeView.Resources>
        <HierarchicalDataTemplate
            DataType="{x:Type vm.PersonTreeViewWrapper}"
            ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <CheckBox
                    VerticalAlignment="Center"
                    Focusable="False"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                <TextBlock
                    Margin="4,0,8,0"
                    VerticalAlignment="Center"
                    Text="{Binding Model.DisplayName}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

还可以使用行为类向TreeView控件添加可绑定的SelectedItem属性

public class perTreeViewHelper : Behavior<TreeView>
{
    public object BoundSelectedItem
    {
        get => GetValue(BoundSelectedItemProperty);
        set => SetValue(BoundSelectedItemProperty, value);
    }

    public static readonly DependencyProperty BoundSelectedItemProperty =
        DependencyProperty.Register("BoundSelectedItem",
            typeof(object),
            typeof(perTreeViewHelper),
            new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnBoundSelectedItemChanged));

    private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue is perTreeViewItemViewModelBase item)
        {
            item.IsSelected = true;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        base.OnDetaching();
    }

    private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
    {
        BoundSelectedItem = args.NewValue;
    }
}
公共类perTreeViewHelper:行为
{
公共对象边界SelectedItem
{
get=>GetValue(BoundSelectedItemProperty);
set=>SetValue(BoundSelectedItemProperty,value);
}
公共静态只读从属属性BoundSelectedItemProperty=
DependencyProperty.Register(“BoundSelectedItem”,
类型(对象),
类型(perTreeViewHelper),
新的FrameworkPropertyMetadata(空,
默认情况下,FrameworkPropertyMetadataOptions.BindsTwoWay,
OnBoundSelectedItemChanged);
BoundSelectedItemChanged上的私有静态无效(DependencyObject对象、DependencyPropertyChangedEventArgs参数)
{
如果(args.NewValue为perTreeViewItemViewModelBase项)
{
item.IsSelected=true;
}
}
受保护的覆盖无效附加()
{
base.onatached();
AssociatedObject.SelectedItemChanged+=OnTreeViewSelectedItemChanged;
}
附加时受保护的覆盖无效()
{
AssociatedObject.SelectedItemChanged-=OnTreeViewSelectedItemChanged;
base.OnDetaching();
}
私有无效OnTreeViewSelectedItemChanged(对象对象对象、RoutedPropertyChangedEventArgs参数)
{
BoundSelectedItem=args.NewValue;
}
}

您可以在my上获得这些类及其用法的更多详细信息。

这是我用于在TreeView控件中显示数据项的基本ViewModel类。它处理诸如子项集合、扩展、选择、检查(勾选框)之类的事情,包括更新父项和子项、禁用状态、延迟加载子项

public class perTreeViewItemViewModelBase : perViewModelBase
{
    // a dummy item used in lazy loading mode, ensuring that each node has at least one child so that the expand button is shown
    private static perTreeViewItemViewModelBase LazyLoadingChildIndicator { get; } 
        = new perTreeViewItemViewModelBase { Caption = "Loading Data ..." };

    private bool InLazyLoadingMode { get; set; }
    private bool LazyLoadTriggered { get; set; }
    private bool LazyLoadCompleted { get; set; }
    private bool RequiresLazyLoad => InLazyLoadingMode && !LazyLoadTriggered;

    // Has Children been overridden (e.g. to point at some private internal collection) 
    private bool LazyLoadChildrenOverridden => InLazyLoadingMode && !Equals(LazyLoadChildren, _childrenList);

    private readonly perObservableCollection<perTreeViewItemViewModelBase> _childrenList 
        = new perObservableCollection<perTreeViewItemViewModelBase>();

    /// <summary>
    /// LazyLoadingChildIndicator ensures a visible expansion toggle button in lazy loading mode 
    /// </summary>
    protected void SetLazyLoadingMode()
    {
        ClearChildren();
        _childrenList.Add(LazyLoadingChildIndicator);

        IsExpanded = false;
        InLazyLoadingMode = true;
        LazyLoadTriggered = false;
        LazyLoadCompleted = false;
    }

    private string _caption;

    public string Caption
    {
        get => _caption;
        set => Set(nameof(Caption), ref _caption, value);
    }

    public void ClearChildren()
    {
        _childrenList.Clear();
    }

    /// <summary>
    /// Add a new child item to this TreeView item
    /// </summary>
    /// <param name="child"></param>
    public void AddChild(perTreeViewItemViewModelBase child)
    {
        if (LazyLoadChildrenOverridden)
        {
            throw new InvalidOperationException("Don't call AddChild for an item with LazyLoad mode set & LazyLoadChildren has been overridden");
        }

        if (_childrenList.Any() && _childrenList.First() == LazyLoadingChildIndicator)
        {
            _childrenList.Clear();
        }

        _childrenList.Add(child);
        SetChildPropertiesFromParent(child);
    }

    protected void SetChildPropertiesFromParent(perTreeViewItemViewModelBase child)
    { 
        child.Parent = this;

        // if this node is checked then all new children added are set checked 
        if (IsChecked.GetValueOrDefault())
        {
            child.SetIsCheckedIncludingChildren(true);
        }

        ReCalculateNodeCheckState();
    }

    protected void ReCalculateNodeCheckState()
    {
        var item = this;

        while (item != null)
        {
            if (item.Children.Any() && !Equals(item.Children.FirstOrDefault(), LazyLoadingChildIndicator))
            {
                var hasIndeterminateChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.HasValue);

                if (hasIndeterminateChild)
                {
                    item.SetIsCheckedThisItemOnly(null);
                }
                else
                {
                    var hasSelectedChild = item.Children.Any(c => c.IsEnabled && c.IsChecked.GetValueOrDefault());
                    var hasUnselectedChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.GetValueOrDefault());

                    if (hasUnselectedChild && hasSelectedChild)
                    {
                        item.SetIsCheckedThisItemOnly(null);
                    }
                    else
                    {
                        item.SetIsCheckedThisItemOnly(hasSelectedChild);
                    }
                }
            }

            item = item.Parent;
        }
    }

    private void SetIsCheckedIncludingChildren(bool? value)
    {
        if (IsEnabled)
        {
            _isChecked = value;
            RaisePropertyChanged(nameof(IsChecked));

            foreach (var child in Children)
            {
                if (child.IsEnabled)
                {
                    child.SetIsCheckedIncludingChildren(value);
                }
            }
        }
    }

    private void SetIsCheckedThisItemOnly(bool? value)
    {
        _isChecked = value;
        RaisePropertyChanged(nameof(IsChecked));
    }

    /// <summary>
    /// Add multiple children to this TreeView item
    /// </summary>
    /// <param name="children"></param>
    public void AddChildren(IEnumerable<perTreeViewItemViewModelBase> children)
    {
        foreach (var child in children)
        {
            AddChild(child);
        }
    }

    /// <summary>
    /// Remove a child item from this TreeView item
    /// </summary>
    public void RemoveChild(perTreeViewItemViewModelBase child)
    {
        _childrenList.Remove(child);
        child.Parent = null;

        ReCalculateNodeCheckState();
    }

    public perTreeViewItemViewModelBase Parent { get; private set; }

    private bool? _isChecked = false;

    public bool? IsChecked
    {
        get => _isChecked;
        set
        {
            if (Set(nameof(IsChecked), ref _isChecked, value))
            {
                foreach (var child in Children)
                {
                    if (child.IsEnabled)
                    {
                        child.SetIsCheckedIncludingChildren(value);
                    }
                }

                Parent?.ReCalculateNodeCheckState();
            }
        }
    }

    private bool _isExpanded;

    public bool IsExpanded
    {
        get => _isExpanded;
        set
        {
            if (Set(nameof(IsExpanded), ref _isExpanded, value) && value && RequiresLazyLoad)
            {
                TriggerLazyLoading();
            }
        }
    }

    private bool _isEnabled = true;

    public bool IsEnabled
    {
        get => _isEnabled;
        set => Set(nameof(IsEnabled), ref _isEnabled, value);
    }

    public void TriggerLazyLoading()
    {
        var unused = DoLazyLoadAsync();
    }

    private async Task DoLazyLoadAsync()
    {
        if (LazyLoadTriggered)
        {
            return;
        }

        LazyLoadTriggered = true;

        var lazyChildrenResult = await LazyLoadFetchChildren()
            .EvaluateFunctionAsync()
            .ConfigureAwait(false);

        LazyLoadCompleted = true;

        if (lazyChildrenResult.IsCompletedOk)
        {
            var lazyChildren = lazyChildrenResult.Data;

            foreach (var child in lazyChildren)
            {
                SetChildPropertiesFromParent(child);
            }

            // If LazyLoadChildren has been overridden then just refresh the check state (using the new children) 
            // and update the check state (in case any of the new children is already set as checked)
            if (LazyLoadChildrenOverridden)
            {
                ReCalculateNodeCheckState();
            }
            else
            {
                AddChildren(lazyChildren); // otherwise add the new children to the base collection.
            }
        }

        RefreshChildren();
    }

    /// <summary>
    /// Get the children for this node, in Lazy-Loading Mode
    /// </summary>
    /// <returns></returns>
    protected virtual Task<perTreeViewItemViewModelBase[]> LazyLoadFetchChildren()
    {
        return Task.FromResult(new perTreeViewItemViewModelBase[0]);
    }

    /// <summary>
    /// Update the Children property
    /// </summary>
    public void RefreshChildren()
    {
        RaisePropertyChanged(nameof(Children));
    }

    /// <summary>
    /// In LazyLoading Mode, the Children property can be set to something other than
    /// the base _childrenList collection - e.g as the union ot two internal collections
    /// </summary>
    public IEnumerable<perTreeViewItemViewModelBase> Children => LazyLoadCompleted
                                                                ? LazyLoadChildren
                                                                : _childrenList;

    /// <summary>
    /// How are the children held when in lazy loading mode.
    /// </summary>
    /// <remarks>
    /// Override this as required in descendent classes - e.g. if Children is formed from a union
    /// of multiple internal child item collections (of different types) which are populated in LazyLoadFetchChildren()
    /// </remarks>
    protected virtual IEnumerable<perTreeViewItemViewModelBase> LazyLoadChildren => _childrenList;

    private bool _isSelected;

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            // if unselecting we don't care about anything else other than simply updating the property
            if (!value)
            {
                Set(nameof(IsSelected), ref _isSelected, false);
                return;
            }

            // Build a priority queue of operations
            //
            // All operations relating to tree item expansion are added with priority = DispatcherPriority.ContextIdle, so that they are
            // sorted before any operations relating to selection (which have priority = DispatcherPriority.ApplicationIdle).
            // This ensures that the visual container for all items are created before any selection operation is carried out.
            //
            // First expand all ancestors of the selected item - those closest to the root first
            //
            // Expanding a node will scroll as many of its children as possible into view - see perTreeViewItemHelper, but these scrolling
            // operations will be added to the queue after all of the parent expansions.
            var ancestorsToExpand = new Stack<perTreeViewItemViewModelBase>();

            var parent = Parent;
            while (parent != null)
            {
                if (!parent.IsExpanded)
                {
                    ancestorsToExpand.Push(parent);
                }

                parent = parent.Parent;
            }

            while (ancestorsToExpand.Any())
            {
                var parentToExpand = ancestorsToExpand.Pop();
                perDispatcherHelper.AddToQueue(() => parentToExpand.IsExpanded = true, DispatcherPriority.ContextIdle);
            }

            // Set the item's selected state - use DispatcherPriority.ApplicationIdle so this operation is executed after all
            // expansion operations, no matter when they were added to the queue.
            //
            // Selecting a node will also scroll it into view - see perTreeViewItemHelper
            perDispatcherHelper.AddToQueue(() => Set(nameof(IsSelected), ref _isSelected, true), DispatcherPriority.ApplicationIdle);

            // note that by rule, a TreeView can only have one selected item, but this is handled automatically by 
            // the control - we aren't required to manually unselect the previously selected item.

            // execute all of the queued operations in descending DispatcherPriority order (expansion before selection)
            var unused = perDispatcherHelper.ProcessQueueAsync();
        }
    }

    public override string ToString()
    {
        return Caption;
    }

    /// <summary>
    /// What's the total number of child nodes beneath this one
    /// </summary>
    public int ChildCount => Children.Count() + Children.Sum(c => c.ChildCount);
}
我不会尝试将此包装隐藏在控件中,而是在ViewModel中发布一个PersonContreeViewWrapper项的嵌套集合,以绑定为TreeView的ItemsSource

其中一个原因是,如果希望在同一TreeView控件中显示多个项目类型,则必须能够为每个项目类型指定要使用的HierarchycalDataTemplate

<TreeView
    Grid.Column="0"
    ItemsSource="{Binding RootItemVms}">

    <TreeView.Resources>
        <HierarchicalDataTemplate
            DataType="{x:Type vm.PersonTreeViewWrapper}"
            ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <CheckBox
                    VerticalAlignment="Center"
                    Focusable="False"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                <TextBlock
                    Margin="4,0,8,0"
                    VerticalAlignment="Center"
                    Text="{Binding Model.DisplayName}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

还可以使用行为类向TreeView控件添加可绑定的SelectedItem属性

public class perTreeViewHelper : Behavior<TreeView>
{
    public object BoundSelectedItem
    {
        get => GetValue(BoundSelectedItemProperty);
        set => SetValue(BoundSelectedItemProperty, value);
    }

    public static readonly DependencyProperty BoundSelectedItemProperty =
        DependencyProperty.Register("BoundSelectedItem",
            typeof(object),
            typeof(perTreeViewHelper),
            new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnBoundSelectedItemChanged));

    private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue is perTreeViewItemViewModelBase item)
        {
            item.IsSelected = true;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        base.OnDetaching();
    }

    private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
    {
        BoundSelectedItem = args.NewValue;
    }
}
公共类perTreeViewHelper:行为
{
公共对象边界SelectedItem
{
get=>GetValue(BoundSelectedItemProperty);
set=>SetValue(BoundSelectedItemProperty,value);
}
公共静态只读从属属性BoundSelectedItemProperty=
DependencyProperty.Register(“BoundSelectedItem”,
类型(对象),
类型(perTreeViewHelper),
新的FrameworkPropertyMetadata(空,
默认情况下,FrameworkPropertyMetadataOptions.BindsTwoWay,
OnBoundSelectedItemChanged);
BoundSelectedItemChanged上的私有静态无效(DependencyObject对象、DependencyPropertyChangedEventArgs参数)
{
如果(args.NewValue为perTreeViewItemViewModelBase项)
{
item.IsSelected=true;
}
}
受保护的覆盖无效附加()
{
base.onatached();
AssociatedObject.SelectedItemChanged+=OnTreeViewSelectedItemChanged;
}
附加时受保护的覆盖无效()
{
AssociatedObject.SelectedItemChanged-=OnTreeViewSelectedItemChanged;
base.OnDetaching();
}
私有无效OnTreeViewSelectedItemChanged(对象对象对象、RoutedPropertyChangedEventArgs参数)
{
BoundSelectedItem=args.NewValue;
}
}
你可以得到更多的细节
<TreeView
    Grid.Column="0"
    ItemsSource="{Binding RootItemVms}">

    <TreeView.Resources>
        <HierarchicalDataTemplate
            DataType="{x:Type vm.PersonTreeViewWrapper}"
            ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <CheckBox
                    VerticalAlignment="Center"
                    Focusable="False"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                <TextBlock
                    Margin="4,0,8,0"
                    VerticalAlignment="Center"
                    Text="{Binding Model.DisplayName}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>
public class perTreeViewHelper : Behavior<TreeView>
{
    public object BoundSelectedItem
    {
        get => GetValue(BoundSelectedItemProperty);
        set => SetValue(BoundSelectedItemProperty, value);
    }

    public static readonly DependencyProperty BoundSelectedItemProperty =
        DependencyProperty.Register("BoundSelectedItem",
            typeof(object),
            typeof(perTreeViewHelper),
            new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnBoundSelectedItemChanged));

    private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue is perTreeViewItemViewModelBase item)
        {
            item.IsSelected = true;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        base.OnDetaching();
    }

    private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
    {
        BoundSelectedItem = args.NewValue;
    }
}