Я пытался построить TreeView
с иерархической настройкой, заполненной флажками. И так, чтобы узнать, какие флажки отмечены позже в коде.
На моей пластине все еще есть две проблемы:
- Однако я пытаюсь дать
TreeView
ItemsSource
как Binding
, TreeView отображается пустым при запуске программы. Этого не произойдет, если я вручную установлю ItemsSource
в коде позади. - У меня также есть проблема с получением информации
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);
}
}
}