这个例子折腾了我好几天,今天终于搞定了,由于太晚了,就不说过程,只看结果和代码。
1、遍历文件夹:在这个例子中,CheckBox显示三种状态,另外父节点选中,子节点全选,父节点不选,子节点全不选;子节点部分选,父节点null状态;子节点全选,父节点选中状态;子节点全不选,父节点不选状态。这个例子适合遍历。
接下来看代码(这个代码的例子忘记了是哪个大神写的,下次找到后附链接加感谢):
class DirectoryTree(这里处理了原来的代码的一个bug,从而自动过滤掉不能读取的文件夹)
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CheckBoxTreeView { class DirectoryTree : INotifyPropertyChanged { public DirectoryTree() { this.isChecked = false; } public DirectoryTree(DirectoryTree parent, DirectoryInfo di) : this() { this.Parent = parent; this.Info = di; } public DirectoryTree(DirectoryTree parent, DirectoryInfo di, Boolean bChecked) : this(parent, di) { this.isChecked = bChecked; } public DirectoryInfo Info { get; set; } public DirectoryTree Parent { get; set; } public Boolean? IsChecked { get { return this.isChecked; } set { if (this.isChecked != value) { this.isChecked = value; NotifyPropertyChanged("IsChecked"); if (this.isChecked == true) // 如果节点被选中 { if (this.dirs != null) foreach (DirectoryTree dt in this.dirs) dt.IsChecked = true; if (this.Parent != null) { Boolean bExistUncheckedChildren = false; foreach (DirectoryTree dt in this.Parent.Directories) if (dt.IsChecked != true) { bExistUncheckedChildren = true; break; } if (bExistUncheckedChildren) this.Parent.IsChecked = null; else this.Parent.IsChecked = true; } } else if (this.isChecked == false) // 如果节点未选中 { if (this.dirs != null) foreach (DirectoryTree dt in this.dirs) dt.IsChecked = false; if (this.Parent != null) { Boolean bExistCheckedChildren = false; foreach (DirectoryTree dt in this.Parent.Directories) if (dt.IsChecked != false) { bExistCheckedChildren = true; break; } if (bExistCheckedChildren) this.Parent.IsChecked = null; else this.Parent.IsChecked = false; } } else { if (this.Parent != null) this.Parent.IsChecked = null; } } } } public ObservableCollection<DirectoryTree> Directories { get { if (this.dirs == null) { this.dirs = new ObservableCollection<DirectoryTree>(); try { foreach (DirectoryInfo di in Info.GetDirectories()) this.dirs.Add(new DirectoryTree(this, di, this.isChecked == true)); } catch { } } return dirs; } } public static ObservableCollection<DirectoryTree> InitRoot() { ObservableCollection<DirectoryTree> dts = new ObservableCollection<DirectoryTree>(); DriveInfo[] drvs = DriveInfo.GetDrives(); foreach (DriveInfo drv in drvs) { if (drv.DriveType == DriveType.Fixed) { DirectoryTree dt = new DirectoryTree(null, drv.RootDirectory); dts.Add(dt); } } return dts; } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } private Boolean? isChecked; private ObservableCollection<DirectoryTree> dirs; } }
主窗口后台代码:
using System; using System.Collections.Generic; 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 CheckBoxTreeView { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.tvDirectories.ItemsSource = DirectoryTree.InitRoot(); } } }
主窗口前台代码:
<Window x:Class="CheckBoxTreeView.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:CheckBoxTreeView" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <TreeView Name="tvDirectories" Grid.Column="0" Margin="0,0,5,0"> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:DirectoryTree}" ItemsSource="{Binding Directories}"> <StackPanel Orientation="Horizontal"> <CheckBox IsChecked="{Binding Path=IsChecked}" IsTabStop="False" Focusable="False" HorizontalAlignment="Center"/> <TextBlock Text="{Binding Path=Info}" HorizontalAlignment="Center"/> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
以上是遍历的代码。但是好多时候,我们需要定量的TreeView结构,这里我稍微修改一下上面的代码,改成双层结构的代码,同样的功能。先来看效果:
然后在上面的基础上修改代码:
Node class:
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TreeViewAndCheckboxTest { public class Node : INotifyPropertyChanged { public Node() { this.isChecked = false; } public Node(Node parent,string nodeName):this() { Parent = parent; NodeName = nodeName; } public Node(Node parent, string nodeName, Boolean bChecked) : this(parent, nodeName) { this.isChecked = bChecked; } public Node Parent; public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } private Boolean? isChecked; public Boolean? IsChecked { get { return this.isChecked; } set { if (this.isChecked != value) { this.isChecked = value; NotifyPropertyChanged("IsChecked"); if (this.isChecked == true) // 如果节点被选中 { if (this.childNode != null) foreach (Node dt in this.childNode) dt.IsChecked = true; if (this.Parent != null) { Boolean bExistUncheckedChildren = false; foreach (Node dt in this.Parent.ChildNode) if (dt.IsChecked != true) { bExistUncheckedChildren = true; break; } if (bExistUncheckedChildren) this.Parent.IsChecked = null; else this.Parent.IsChecked = true; } } else if (this.isChecked == false) // 如果节点未选中 { if (this.childNode != null) foreach (Node dt in this.childNode) dt.IsChecked = false; if (this.Parent != null) { Boolean bExistCheckedChildren = false; foreach (Node dt in this.Parent.ChildNode) if (dt.IsChecked != false) { bExistCheckedChildren = true; break; } if (bExistCheckedChildren) this.Parent.IsChecked = null; else this.Parent.IsChecked = false; } } else { if (this.Parent != null) this.Parent.IsChecked = null; } } } } private ObservableCollection<Node> childNode; public string NodeName { get; set; } public ObservableCollection<Node> InitRoot() { ObservableCollection<Node> dts = new ObservableCollection<Node>(); for(int i=0;i<2;i++) { Node dt = new Node(null, i.ToString()+"a"); dts.Add(dt); } return dts; } public ObservableCollection<Node> ChildNode { get { if (this.childNode == null) { this.childNode = new ObservableCollection<Node>(); if(NodeName.Equals("0a")) { for (int i = 0; i < 3; i++) { this.childNode.Add(new Node(this, "b" + i.ToString(), this.isChecked == true)); } } else if(NodeName.Equals("1a")) { for (int i = 0; i < 4; i++) { this.childNode.Add(new Node(this, "c" + i.ToString(), this.isChecked == true)); } } } return childNode; } } } }
主窗口后台代码:
using System; using System.Collections.Generic; 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 TreeViewAndCheckboxTest { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.tvDirectories.ItemsSource = new Node().InitRoot(); } } }
主窗口前台代码:
<Window x:Class="TreeViewAndCheckboxTest.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:TreeViewAndCheckboxTest" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <TreeView Name="tvDirectories" Grid.Column="0" Margin="0,0,5,0"> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding ChildNode}"> <StackPanel Orientation="Horizontal"> <CheckBox IsChecked="{Binding Path=IsChecked}" IsTabStop="False" Focusable="False" HorizontalAlignment="Center"/> <TextBlock Text="{Binding Path=NodeName}" HorizontalAlignment="Center"/> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Window>这样就完美实现了需要遍历的树结构,和自己组成树结构的两种形式的代码。至于形成更炫酷的功能,就只需要各种style和Template组合起来用即可。