У меня есть древовидное представление в WPF, в которое я хотел бы добавить флажки

Вот код

У меня есть класс Person, который содержит всю структуру

Person.cs

public class Person
    {
        readonly List<Person> _children = new List<Person>();
        public IList<Person> Children
        {
            get { return _children; }
        }

        public string Name { get; set; }
    }

Как я читал в других сообщениях, я использую ViewModel

PersonViewModel.cs

public class PersonViewModel : INotifyPropertyChanged
    {
        #region Data
        readonly ReadOnlyCollection<PersonViewModel> _children;
        readonly PersonViewModel _parent;
        readonly Person _person;

        bool _isExpanded=true;
        bool _isSelected;
        #endregion Data

        #region Constructors
        public PersonViewModel(Person person): this(person, null)
        {
        }

        private PersonViewModel(Person person, PersonViewModel parent)
        {
            _person = person;
            _parent = parent;

            _children = new ReadOnlyCollection<PersonViewModel>(
                    (from child in _person.Children
                     select new PersonViewModel(child, this))
                     .ToList<PersonViewModel>());
        }
        #endregion Constructors

        #region Person Properties
        public ReadOnlyCollection<PersonViewModel> Children
        {
            get { return _children; }
        }

        public string Name
        {
            get { return _person.Name; }
        }

        #endregion Person Properties

        #region Presentation Members
        #region IsExpanded

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is expanded.
        /// </summary>
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                {
                    _isExpanded = value;
                    OnPropertyChanged("IsExpanded");
                }

                // Expand all the way up to the root.
                if (_isExpanded && _parent != null)
                    _parent.IsExpanded = true;
            }
        }

        #endregion IsExpanded

        #region IsSelected

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is selected.
        /// </summary>
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value != _isSelected)
                {
                    _isSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }

        #endregion IsSelected

        #region NameContainsText

        public bool NameContainsText(string text)
        {
            if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(this.Name))
                return false;

            return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
        }

        #endregion NameContainsText

        #region Parent

        public PersonViewModel Parent
        {
            get { return _parent; }
        }

        #endregion Parent

        #endregion Presentation Members        

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion INotifyPropertyChanged Members
    }

Семейное древо ViewModel

FamilyTreeViewModel.cs

Открытый класс FamilyTreeViewModel {

#region Data
readonly PersonViewModel _rootPerson;
#endregion Data

#region Constructor
public FamilyTreeViewModel(Person rootPerson)
{
    _rootPerson = new PersonViewModel(rootPerson);

    FirstGeneration = new ReadOnlyCollection<PersonViewModel>(
        new PersonViewModel[]
        {
            _rootPerson
        });
}
#endregion Constructor

#region Properties
#region FirstGeneration
/// <summary>
/// Returns a read-only collection containing the first person 
/// in the family tree, to which the TreeView can bind.
/// </summary>
public ReadOnlyCollection<PersonViewModel> FirstGeneration { get; }
#endregion FirstGeneration
#endregion Properties

}

Код xaml MainWindow.xaml

<TreeView ItemsSource="{Binding FirstGeneration}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>

    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}" />

        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

MainWindow.xaml.cs

  public partial class MainWindow : Window
    {
        readonly FamilyTreeViewModel _familyTree;

        public MainWindow()
        {
            InitializeComponent();
            Person rootPerson = new Person
            {
                Name="Application Architect Right",
                Children =
                {
                    new Person
                    {
                        Name="Generate"
                    },
                    new Person
                    {
                        Name="Instances rights",
                        Children =
                        {
                            new Person
                            {
                                Name = "Create"
                            },
                            new Person
                            {
                                Name = "Modify"
                            },
                            new Person
                            {
                                Name = "Delete"
                            },
                            new Person
                            {
                                Name = "Exceptions Management"
                            }
                        }

                    },
                    new Person
                    {
                        Name="Templates rights",
                        Children =
                        {
                            new Person
                            {
                                Name = "Create"
                            },
                            new Person
                            {
                                Name = "Modify"
                            },
                            new Person
                            {
                                Name = "Delete"
                            }
                        }
                    },
                    new Person
                    {
                        Name="Parameters rights",
                        Children =
                        {
                            new Person
                            {
                                Name = "Create"
                            },
                            new Person
                            {
                                Name = "Modify"
                            },
                            new Person
                            {
                                Name = "Delete"
                            }
                        }
                    },
                }
            };


            // Create UI-friendly wrappers around the 
            // raw data objects (i.e. the view-model).
            _familyTree = new FamilyTreeViewModel(rootPerson);


            // Let the UI bind to the view-model.
            DataContext = _familyTree;

        }
    }

Может кто-нибудь может мне помочь?

Заранее спасибо

-1
Stéphan F 23 Сен 2018 в 12:26

2 ответа

Лучший ответ

Я не уверен, что полностью понимаю ваш вопрос, но пробовали ли вы редактировать свой XAML таким образом?

    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
      <StackPanel Orientation=Horizontal>
        <TextBlock Text="{Binding Name}" />
        <Checkbox IsChecked="{Binding IsSelected} />
      </StackPanel>
    </HierarchicalDataTemplate>

Поскольку по умолчанию в древовидной структуре в wpf нет флажка, лучше всего отредактировать шаблон элементов, чтобы добавить флажок.

Поскольку флажок привязан к свойству IsSelected вашего PersonViewModel, вы можете сделать что-то вроде этого, если хотите обновить выбор дочерних элементов.

public bool IsSelected
{
   ...
    set
    {
        if (value != _isSelected)
        {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
            this.UpdateChildSelection();
        }
    }
}


private void UpdateChildSelection()
{
    foreach(var child in Children)
    {
        child.IsSelected = this.IsSelected;
    }
}
1
Alexandre Asselin 23 Сен 2018 в 12:03

Это мой базовый класс ViewModel для элементов TreeView, который включает каскадную проверку.

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 LoadingDataItem { get; }

    static perTreeViewItemViewModelBase()
    {
        LoadingDataItem = new perTreeViewItemViewModelBase { Caption = "Loading Data ..." };
    }

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

    public perTreeViewItemViewModelBase(bool addLoadingDataItem = false)
    {
        if (addLoadingDataItem)
            _childrenList.Add(LoadingDataItem);
    }

    private string _caption;

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

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

    public void AddChild(perTreeViewItemViewModelBase child)
    {
        if (_childrenList.Any() && _childrenList.First() == LoadingDataItem)
            ClearChildren();

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

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

        if (IsChecked.GetValueOrDefault())
            child.IsChecked = true;
    }

    public void AddChildren(IEnumerable<perTreeViewItemViewModelBase> children)
    {
        foreach (var child in children)
            AddChild(child);
    }

    protected perTreeViewItemViewModelBase Parent { get; private set; }

    private bool? _isChecked = false;

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

                SetParentIsChecked();
            }
        }
    }

    private bool _isExpanded;

    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (!Set(nameof(IsExpanded), ref _isExpanded, value) || IsInitialised || IsInitialising)
                return;

            var unused = InitialiseAsync();
        }
    }

    private bool _isEnabled = true;

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

    public bool IsInitialising { get; private set; }
    public bool IsInitialised { get; private set; }

    public async Task InitialiseAsync()
    {
        if (IsInitialised || IsInitialising)
            return;

        IsInitialising = true;
        await InitialiseChildrenAsync().ConfigureAwait(false);
        foreach (var child in InitialisedChildren)
            SetChildPropertiesFromParent(child);
        IsInitialised = true;
        RaisePropertyChanged(nameof(Children));
    }

    protected virtual Task InitialiseChildrenAsync()
    {
        return Task.CompletedTask;
    }

    public IEnumerable<perTreeViewItemViewModelBase> Children => IsInitialised
        ? InitialisedChildren
        : _childrenList;

    // override this as required in descendent classes
    // e.g. if Children is a union of multiple child item collections which are populated in InitialiseChildrenAsync()
    protected virtual IEnumerable<perTreeViewItemViewModelBase> InitialisedChildren => _childrenList;

    private bool _isSelected;

    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            // ensure that all ancestor items are expanded, so this item will be visible
            if (value)
            {
                var parent = Parent;
                while (parent != null)
                {
                    parent.IsExpanded = true;
                    parent = parent.Parent;
                }
            }

            if (_isSelected == value)
                return;

            // use DispatcherPriority.ContextIdle so that we wait for any children of newly expanded items to be fully created in the
            // parent TreeView, before setting IsSelected for this item (which will scroll it into view - see perTreeViewItemHelper)
            perDispatcherHelper.CheckBeginInvokeOnUI(() => Set(nameof(IsSelected), ref _isSelected, value), DispatcherPriority.ContextIdle);

            // 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.
        }
    }

    private void SetIsCheckedIncludingChildren(bool? value)
    {
        _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));
    }

    private void SetParentIsChecked()
    {
        var parent = Parent;

        while (parent != null)
        {
            var hasIndeterminateChild = parent.Children.Any(c => c.IsEnabled && !c.IsChecked.HasValue);

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

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

            parent = parent.Parent;
        }
    }

    public override string ToString()
    {
        return Caption;
    }
}

Дополнительные сведения и пример его использования см. В моем недавнем сообщении в блоге,

1
Peregrine 24 Сен 2018 в 07:36