我要求我需要动态地向TreeView
添加节点,并使用CheckBox
es添加节点。如果选择了一个CheckBox
,则也会选择子项。
主要是我想动态地将数据添加到TreeView
。
答案 0 :(得分:14)
一旦你知道如何做,这非常简单。
为树视图项数据创建一个视图模型类(我在这里称之为CheckableItem
)。它需要这三件事:
Children
类型的ObservableCollection<CheckableItem>
属性。IsChecked
属性Visibility
,在其设定器,提高PropertyChanged
,并且还通过在项目迭代Children
,并设置其{{1} } property。在此类中实现其他属性以将项目的数据公开给绑定(我的示例只假设名为IsChecked
的内容)。或者,您只需实施Value
类Item
类,并在模板中使用object
,但我会将这一点告诉您。
现在为您的类创建一个ContentPresenter
,如下所示:
HierarchicalDataTemplate
...以及使用它的<HierarchicalDataTemplate
DataType="{x:Type local:CheckableItem}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</HierarchicalDataTemplate>
(我假设你已经填充了这些对象的集合,当然):
TreeView
工作原理:<TreeView ItemsSource="{Binding MyCollectionOfCheckableItems}"/>
使用TreeView
呈现其HierarchicalDataTemplate
中的每个项目。 ItemsSource
是一个模板,用于创建HierarchicalDataTemplate
(在本例中为HeaderedItemsControl
),使用其模板呈现标题,然后使用其TreeViewItem
作为源控件的项目 - 由于它们都是ItemsSource
,因此CheckableItem
会变为TreeViewItem
。在那之后,它一直是乌龟。
This是如何HierarchicalDataTemplate
实际上在实践工作,但与大多数的例子,我发现,它有那么多花里胡哨,它的排序很难看到一个很好的概述简单的基本原则是。如果你了解MVVM,那么前一段是你需要知道的90%。
答案 1 :(得分:10)
检查出来:
DataModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication102
{
public class Family : DependencyObject
{
public string Name { get; set; }
public List<Person> Members { get; set; }
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
}
ItemHelper.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication102
{
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Family && ((bool?)e.NewValue).HasValue)
foreach (Person p in (d as Family).Members)
ItemHelper.SetIsChecked(p, (bool?)e.NewValue);
if (d is Person)
{
int checked = ((d as Person).GetValue(ItemHelper.ParentProperty) as Family).Members.Where(x => ItemHelper.GetIsChecked(x) == true).Count();
int unchecked = ((d as Person).GetValue(ItemHelper.ParentProperty) as Family).Members.Where(x => ItemHelper.GetIsChecked(x) == false).Count();
if (unchecked > 0 && checked > 0)
{
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, null);
return;
}
if (checked > 0)
{
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, true);
return;
}
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(ItemHelper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(ItemHelper.IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(ItemHelper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(ItemHelper.ParentProperty);
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication102.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication102"
Title="MainWindow" Height="220" Width="250">
<StackPanel>
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Families}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Person}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Content="?" Click="Button_PrintCrew_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication102
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Family> Families { get; set; }
public MainWindow()
{
InitializeComponent();
this.Families = new ObservableCollection<Family>();
this.Families.Add(new Family() { Name = "Simpsons", Members = new List<Person>() { new Person() { Name = "Homer" }, new Person() { Name = "Bart" } } });
this.Families.Add(new Family() { Name = "Griffin", Members = new List<Person>() { new Person() { Name = "Peter" }, new Person() { Name = "Stewie" } } });
this.Families.Add(new Family() { Name = "Fry", Members = new List<Person>() { new Person() { Name = "Philip J." } } });
foreach (Family family in this.Families)
foreach (Person person in family.Members)
person.SetValue(ItemHelper.ParentProperty, family);
}
private void Button_PrintCrew_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Family family in this.Families)
foreach (Person person in family.Members)
if (ItemHelper.GetIsChecked(person) == true)
crew += person.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Your crew: " + crew;
}
}
}
答案 2 :(得分:3)
我添加了@ pr0gg3r的答案,使其成为通用的。我不确定这是否是最好的方式,但它更灵活一点。
MainWindow是相同的,但其他类略有不同。
IParent.cs
interface IParent<T>
{
IEnumerable<T> GetChildren();
}
DataModel.cs
using System;
using System.Collections.Generic;
using System.Windows;
public class Family : DependencyObject, IParent<object>
{
public string Name { get; set; }
public List<Person> Members { get; set; }
IEnumerable<object> IParent<object>.GetChildren()
{
return Members;
}
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
ItemHelper.cs
using System.Linq;
using System.Windows;
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IParent<object> sect = d as IParent<object>;
DependencyObject depObj = d as DependencyObject;
if (sect != null)
{
if (((bool?)e.NewValue).HasValue)
{
foreach (DependencyObject p in sect.GetChildren())
{
SetIsChecked(p, (bool?)e.NewValue);
}
}
}
if (depObj != null)
{
var parentObject = depObj.GetValue(ParentProperty) as IParent<object>;
var parentDO = depObj.GetValue(ParentProperty) as DependencyObject;
int ch = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == true).Count() ?? 0;
int un = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == false).Count() ?? 0;
if (un > 0 && ch > 0)
{
SetIsChecked(parentDO, null);
return;
}
if (ch > 0)
{
SetIsChecked(parentDO, true);
return;
}
SetIsChecked(parentDO, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element?.SetValue(IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element?.GetValue(IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty =
DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element?.SetValue(ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return element?.GetValue(ParentProperty);
}
}
答案 3 :(得分:2)
答案 4 :(得分:0)
在this guide之后,我找到了成功。
下面是该指南的完整源代码。
MainWindow.xaml:
<Window x:Class="TreeView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
</Style>
<HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" />
<ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView Height="287" HorizontalAlignment="Left" Margin="12,12,0,0" x:Name="treeView1" VerticalAlignment="Top" Width="229"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TreeView
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
treeView1.ItemsSource = TreeViewModel.SetTree("Top Level");
}
}
}
TreeViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TreeView
{
public class TreeViewModel : INotifyPropertyChanged
{
TreeViewModel(string name)
{
Name = name;
Children = new List<TreeViewModel>();
}
#region Properties
public string Name { get; private set; }
public List<TreeViewModel> Children { get; private set; }
public bool IsInitiallySelected { get; private set; }
bool? _isChecked = false;
TreeViewModel _parent;
#region IsChecked
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, true, true); }
}
void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == _isChecked) return;
_isChecked = value;
if (updateChildren && _isChecked.HasValue) Children.ForEach(c => c.SetIsChecked(_isChecked, true, false));
if (updateParent && _parent != null) _parent.VerifyCheckedState();
NotifyPropertyChanged("IsChecked");
}
void VerifyCheckedState()
{
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, true);
}
#endregion
#endregion
void Initialize()
{
foreach (TreeViewModel child in Children)
{
child._parent = this;
child.Initialize();
}
}
public static List<TreeViewModel> SetTree(string topLevelName)
{
List<TreeViewModel> treeView = new List<TreeViewModel>();
TreeViewModel tv = new TreeViewModel(topLevelName);
treeView.Add(tv);
//Perform recursive method to build treeview
#region Test Data
//Doing this below for this example, you should do it dynamically
//***************************************************
TreeViewModel tvChild4 = new TreeViewModel("Child4");
tv.Children.Add(new TreeViewModel("Child1"));
tv.Children.Add(new TreeViewModel("Child2"));
tv.Children.Add(new TreeViewModel("Child3"));
tv.Children.Add(tvChild4);
tv.Children.Add(new TreeViewModel("Child5"));
TreeViewModel grtGrdChild2 = (new TreeViewModel("GrandChild4-2"));
tvChild4.Children.Add(new TreeViewModel("GrandChild4-1"));
tvChild4.Children.Add(grtGrdChild2);
tvChild4.Children.Add(new TreeViewModel("GrandChild4-3"));
grtGrdChild2.Children.Add(new TreeViewModel("GreatGrandChild4-2-1"));
//***************************************************
#endregion
tv.Initialize();
return treeView;
}
public static List<string> GetTree()
{
List<string> selected = new List<string>();
//select = recursive method to check each tree view item for selection (if required)
return selected;
//***********************************************************
//From your window capture selected your treeview control like: TreeViewModel root = (TreeViewModel)TreeViewControl.Items[0];
// List<string> selected = new List<string>(TreeViewModel.GetTree());
//***********************************************************
}
#region INotifyPropertyChanged Members
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}