Поле TreeView заполнено флажком и отображается пустым при получении его ItemsSource через Binding - PullRequest
0 голосов
/ 05 апреля 2020

Я пытался построить TreeView с иерархической настройкой, заполненной флажками. И так, чтобы узнать, какие флажки отмечены позже в коде.

На моей пластине все еще есть две проблемы:

  1. Однако я пытаюсь дать TreeView ItemsSource как Binding, TreeView отображается пустым при запуске программы. Этого не произойдет, если я вручную установлю ItemsSource в коде позади.
  2. У меня также есть проблема с получением информации IsChecked о флажках, поскольку я не могу полагаться на привязку.

Вот интересная часть кода, который я придумал:

РЕДАКТИРОВАТЬ:
Превратил мой код в нечто более минимальное Воспроизводимый пример по рекомендациям Питера Дунихо. Исходный источник дерева для показа в TreeView - это файл XML. Я помещаю эмуляцию XmlDocument, которую я получаю от него, в конструктор MainWindow (данные хранятся в xmlDoc).
Сделано так, что MainWindow реализует INotifyPropertyChanged, хотя я действительно не знаю, как мне его вызывать в TestWindow действительно.
Попытка установки узла root моего дерева TagCheckboxNode как datacontext, не работала много.
Попытка установки ItemsSource также через Binding.ElementName.

- - XAML ---

<Window x:Name="TestWindow" x:Class="WpfTests.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTests"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="350">
    <Grid>
        <TreeView x:Name="TreeView_Add_TagSelector" Margin="10,10,10,40" ItemsSource="{Binding ElementName=TestWindow, Path=TagCheckboxesTreeSource}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:TagCheckboxNode}" ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <CheckBox Content="{Binding Path=Name}" IsChecked="{Binding Path=IsChecked}" />
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        <TextBlock x:Name="TextBlock_Add_CurrentlySelectedTags" Margin="10,0,0,10" TextWrapping="WrapWithOverflow" Text="Currently selected tags: ---" Height="18" VerticalAlignment="Bottom"/>
    </Grid>
</Window>

--- C# ---

namespace WpfTests
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        XmlDocument xmlDoc;
        TagCheckboxNode tagCheckboxesTreeSource;
        List<string> selectedTags;

        public event PropertyChangedEventHandler PropertyChanged;

        public MainWindow()
        {
            // Sample XmlDocument
            xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.DocumentElement;
            XmlElement categories = xmlDoc.CreateElement(String.Empty, "categories", String.Empty);

            XmlElement catElement1 = xmlDoc.CreateElement(String.Empty, "category", String.Empty);
            catElement1.SetAttribute("name", "cat1");

            XmlElement tagElement11 = xmlDoc.CreateElement(String.Empty, "tag", String.Empty);
            tagElement11.SetAttribute("name", "tag11");
            catElement1.AppendChild(tagElement11);

            XmlElement tagElement12 = xmlDoc.CreateElement(String.Empty, "tag", String.Empty);
            tagElement12.SetAttribute("name", "tag12");
            catElement1.AppendChild(tagElement12);

            categories.AppendChild(catElement1);

            XmlElement catElement2 = xmlDoc.CreateElement(String.Empty, "category", String.Empty);
            catElement2.SetAttribute("name", "cat2");

            XmlElement tagElement21 = xmlDoc.CreateElement(String.Empty, "tag", String.Empty);
            tagElement21.SetAttribute("name", "tag21");
            catElement2.AppendChild(tagElement21);

            XmlElement tagElement22 = xmlDoc.CreateElement(String.Empty, "tag", String.Empty);
            tagElement22.SetAttribute("name", "tag22");
            catElement2.AppendChild(tagElement22);

            categories.AppendChild(catElement2);

            xmlDoc.AppendChild(categories);

            LoadTagTree();
            InitializeComponent();
        }

        private TagCheckboxNode TagCheckboxesTreeSource
        {
            get => tagCheckboxesTreeSource;
            set
            {
                tagCheckboxesTreeSource = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TagCheckBoxTreeChanged"));
            }
        }

        private void LoadTagTree()
        {
            //ObservableCollection<TagCheckboxNode> categories = new ObservableCollection<TagCheckboxNode>();
            TagCheckboxNode categories = new TagCheckboxNode { Name = "categories" };
            foreach (XmlElement category in xmlDoc.DocumentElement)
            {
                TagCheckboxNode categoryItem = new TagCheckboxNode { Name = category.GetAttribute("name") };
                foreach (XmlElement tag in category)
                {
                    TagCheckboxNode tagItem = new TagCheckboxNode(categoryItem) { Name = tag.GetAttribute("name") };
                    categoryItem.Children.Add(tagItem);
                }
                //categories.Add(categoryItem);
                categories.Children.Add(categoryItem);
            }

            //TagCheckboxesTreeSource = categories;
            //TreeView_Add_TagSelector.ItemsSource = categories;
            this.DataContext = categories;
        }

        //private void TreeView_Add_TagSelector_Selection(object sender, DataTransferEventArgs e)
        //{
        //    selectedTags = new List<string>();
        //    // Need to get the names of the checked checkboxes into selectedTags here
        //    TextBlock_Add_CurrentlySelectedTags.Text = "Currently selected tags: " + SelectedTagsToString;
        //}

        private string SelectedTagsToString
        {
            get
            {
                string output = "";
                foreach (string tag in selectedTags) output += tag + " ";
                return output;
            }
        }
    }

    class TagCheckboxNode : INotifyPropertyChanged
    {
        TagCheckboxNode parent;
        ObservableCollection<TagCheckboxNode> children;
        bool? isChecked;
        bool shouldParentAlsoCheck;

        public TagCheckboxNode(TagCheckboxNode parent = null, bool shouldParentAlsoCheck = true)
        {
            this.isChecked = false;
            this.children = new ObservableCollection<TagCheckboxNode>();
            this.parent = parent;
            this.shouldParentAlsoCheck = shouldParentAlsoCheck;
        }

        public ObservableCollection<TagCheckboxNode> Children => children;
        public bool ShouldParentAlsoCheck => shouldParentAlsoCheck;
        public string Name { get; set; }

        public bool? IsChecked
        {
            get { return isChecked; }
            set { SetIsChecked(value, true, shouldParentAlsoCheck); }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
        {
            if (isChecked != value)
            {
                isChecked = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
                if (updateChildren && isChecked.HasValue)
                {
                    for (int i = 0; i < children.Count; i++)
                    {
                        children[i].SetIsChecked(isChecked, true, false);
                    }
                }
                if (updateParent && parent != null) parent.VerifyCheckState();
            }
        }

        void VerifyCheckState()
        {
            bool? state = null;
            for (int i = 0; i < children.Count; i++)
            {
                bool? current = children[i].IsChecked;
                if (i == 0)
                {
                    state = current;
                }
                else if (state != current)
                {
                    state = null;
                    break;
                }
            }
            SetIsChecked(state, false, shouldParentAlsoCheck);
        }
    }
}

1 Ответ

0 голосов
/ 05 апреля 2020

Очевидно, что привязка не разрешается. Когда вы определяете привязку без установки Binding.Source, Binding.RelativeSource или Binding.ElementName, привязка будет неявно связываться с DataContext текущего Binding.Target.

В вашем случае DataContext не установлено, и поэтому Binding.Source должно быть установлено явно.
Чтобы установить MainWindow в качестве Binding.Source, вы можете использовать либо свойство Binding.ElementName (когда назван исходный элемент управления) или свойство Binding.RelativeSource (которое будет проходить по визуальному дереву, чтобы найти соответствующий элемент источника):

<TreeView ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=MainWindow},
                        Path=TagCheckboxesTreeSource}" />

Обновить

Теперь, когда вы исправили При привязке данных и размещении MainWindow.xaml.cs я обнаружил еще одну ошибку.
При вызове события PropertyChanged произошла ошибка. Вы передаете неправильное имя свойства. Чтобы предотвратить эту глупую ошибку, используйте выражение nameof (которое предоставляет поддержку компилятора и IDE-инструмента рефакторинга) или атрибут CallerMemberName. Оба решения будут поддерживать переименование свойств без прерывания уведомления (или любого другого кода, основанного на строковом представлении элемента или типа).

nameof ()
Работает с любым членом класса и с типами (классы, структуры, интерфейсы)

private TagCheckboxNode TagCheckboxesTreeSource
{
  get => tagCheckboxesTreeSource;
  set
  {
    tagCheckboxesTreeSource = value;
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.TagCheckboxesTreeSource)));
  }
}

CallerMemberNameAttribute
Еще одно большое преимущество этого решения ( кроме вызова без параметров), этот вызов события работает с любым свойством , независимо от имени или типа.
Атрибут автоматически получает имя вызывающего члена (метода или свойства), который имеет вызвал метод и автоматически назначил его строковому параметру, который украшен атрибутом.

private TagCheckboxNode TagCheckboxesTreeSource
{
  get => tagCheckboxesTreeSource;
  set
  {
    tagCheckboxesTreeSource = value;
    OnPropertyChanged(); // Invoke like this from any property setter
  }
}

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
  this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...