WPF的TreeView不像WinForms的TreeView那样,通过TreeNode实现树型节点。WPF的TreeView实现起来,我感觉有点像VM(View-Model)模式,就像WinForms中的DataGrid,控件负责显示,数据在DataTable中。那么我们在实现TreeView时,也需要用这样的思路,数据在Model中,控件是View负责显示。
我需要实现的最终目标是:
1.节点是异步加载的(解决效率问题)
2.节点是无限级别的(递归的,这样可复用与目录结构、组织架构等数据类型)
3.节点是可以多选的(用CheckBox选择)
4.XAML方式绑定(减少UI上Code量)
5.方便得获取选中项信息
先看效果图:
1)数据库设计与准备
为了演示方便,采用了ACCESS数据库,添加一个Department表
表字段:
数据内容:
2)创建WPF项目
3)数据访问类
为了图方便,我采用了Linq to DataSet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using DepartmentTreeView.DB;
- using System.Collections;
- using DepartmentTreeView.DB.SampleDataSetTableAdapters;
- namespace DepartmentTreeView {
- public class DepartmentHelper {
- static SampleDataSet ds = new SampleDataSet();
- static DepartmentTableAdapter da = new DepartmentTableAdapter();
- static DepartmentHelper() {
- da.Fill(ds.Department);
- }
- public static object GetDepartment(int did) {
- var depart = ds.Department.Where(c => c.DID == did).SingleOrDefault();
- return depart;
- }
- public static IEnumerable GetSubDepartments(int pid) {
- var list = ds.Department.Where(c => c.PID == pid).ToList();
- return list;
- }
- }
- }
4)创建TreeView 的Model类DepartmentViewModel
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ComponentModel;
- using System.Collections.ObjectModel;
- namespace DepartmentTreeView {
- public class DepartmentViewModel:INotifyPropertyChanged {
- //临时子节点用,当Expanded时移除此节点,添加子节点
- static readonly DepartmentViewModel _temp = new DepartmentViewModel(null);
- //选中的子节点
- private static ObservableCollection<DepartmentViewModel> _checkedItems = new ObservableCollection<DepartmentViewModel>();
- //根节点
- static DepartmentViewModel _rootItem;
- #region fields&properties
- private bool? _isChecked;
- public bool? IsChecked {
- get { return _isChecked; }
- set {
- SetCheckState(value, true, true);
- }
- }
- private void SetCheckState(bool? value, bool updateChildren, bool updateParent) {
- if (_isChecked != value) {
- _isChecked = value;
- //通知选中项的集合
- if (_isChecked == true) {
- _checkedItems.Add(this);
- PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
- } else if (_isChecked == false) {
- _checkedItems.Remove(this);
- PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
- }
- PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
- if (updateChildren) {
- if (HasChildren()) {
- Children.ForEach(c => c.SetCheckState(value, true, false));
- }
- }
- if (updateParent && _parent != null) {
- _parent.VerifyState();
- }
- }
- }
- private void VerifyState() {
- bool? state = null;
- for (int i = 0; i < this.Children.Count; ++i) {
- bool? currentState = this.Children[i].IsChecked;
- if (i == 0) {
- state = currentState;
- } else if (state != currentState) {
- state = null;
- break;
- }
- }
- this.SetCheckState(state, false, true);
- }
- private bool _isExpanded;
- public bool IsExpanded {
- get { return _isExpanded; }
- set {
- if (value != _isExpanded) {
- _isExpanded = value;
- PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
- }
- if (!HasChildren()) {
- Children.Remove(_temp);
- LoadChildren();
- }
- }
- }
- private object _current;
- public object Current {
- get { return _current; }
- set { _current = value; }
- }
- public string DisplayText {
- get { return ((DepartmentTreeView.DB.SampleDataSet.DepartmentRow)Current)["DName"].ToString(); }
- }
- private DepartmentViewModel _parent;
- public DepartmentViewModel Parent {
- get { return _parent; }
- set { _parent = value; }
- }
- private List<DepartmentViewModel> _children;
- public List<DepartmentViewModel> Children {
- get { return _children; }
- private set { _children = value; }
- }
- #endregion
- public static List<DepartmentViewModel> Create() {
- var list = DepartmentHelper.GetSubDepartments(0);
- DepartmentViewModel root = new DepartmentViewModel(null);
- _rootItem = root;
- root.Children.Clear();
- foreach (var item in list) {
- root.Children.Add(new DepartmentViewModel(item));
- }
- return root.Children;
- }
- private DepartmentViewModel(object currentObject) {
- Current = currentObject;
- _isChecked = false;
- Children = new List<DepartmentViewModel>();
- Children.Add(_temp);
- }
- /// <summary>
- /// 初始化,用于设置父节点
- /// </summary>
- private void Init() {
- if (!HasChildren()) return;
- foreach (DepartmentViewModel child in Children) {
- child.Parent = this;
- child.Init();
- }
- PropertyChanged(this, new PropertyChangedEventArgs("Children"));
- }
- /// <summary>
- /// 加载子节点
- /// </summary>
- private void LoadChildren() {
- if (Current != null) {
- int pid = Convert.ToInt32(((DepartmentTreeView.DB.SampleDataSet.DepartmentRow)Current)["DID"]);
- var list = DepartmentHelper.GetSubDepartments(pid);
- foreach (var item in list) {
- DepartmentViewModel model = new DepartmentViewModel(item) { _isChecked = this.IsChecked };
- if (model.IsChecked == true) {
- _checkedItems.Add(model);
- PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
- }
- Children.Add(model);
- }
- Init();
- }
- }
- /// <summary>
- /// 判断是否有子节点(逻辑是:如果只有一个临时子节点,说明没有真正的子节点)
- /// </summary>
- /// <returns></returns>
- private bool HasChildren() {
- return !(Children.Count == 1 && Children[0] == _temp);
- }
- public ObservableCollection<DepartmentViewModel> CheckedItems {
- get {
- return _checkedItems;
- }
- }
- public event PropertyChangedEventHandler PropertyChanged;
- }
- }
5)创建WPF窗体
- <Window x:Class="DepartmentTreeView.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:DepartmentTreeView"
- Title="MainWindow" Height="329" Width="212" FontFamily="Arial">
- <Window.Resources>
- <ObjectDataProvider x:Key="depProvider" ObjectType="{x:Type local:DepartmentViewModel}" MethodName="Create" />
- <Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
- <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}" />
- </Style>
- <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children}">
- <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
- <CheckBox Focusable="False" IsChecked="{Binding IsChecked,Mode=TwoWay}" VerticalAlignment="Center" />
- <ContentPresenter Content="{Binding DisplayText,Mode=OneWay}" Margin="2,0" />
- </StackPanel>
- </HierarchicalDataTemplate>
- </Window.Resources>
- <Grid Margin="5">
- <Grid.RowDefinitions>
- <RowDefinition />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <TreeView Name="tvDepartment" Grid.Row="0" ItemContainerStyle="{StaticResource TreeViewItemStyle}" ItemsSource="{Binding Source={StaticResource depProvider}}" ItemTemplate="{StaticResource CheckBoxItemTemplate}" />
- <TextBlock Name="tbText" Margin="5" Grid.Row="1">当前选中项:<TextBlock Text="{Binding Source={StaticResource depProvider},Path=CheckedItems.Count,Mode=OneWay,IsAsync=True}" /></TextBlock>
- <Button Margin="5" Grid.Row="2" Content="Get CheckedItems" Click="Button_Click" />
- </Grid>
- </Window>
6)窗口的后置代码
- using System.Collections.Generic;
- using System.Text;
- using System.Windows;
- using System.Windows.Data;
- using System.Windows.Documents;
- using System.ComponentModel;
- namespace DepartmentTreeView {
- /// <summary>
- /// MainWindow.xaml 的交互逻辑
- /// </summary>
- public partial class MainWindow : Window {
- public MainWindow() {
- InitializeComponent();
- }
- private void Button_Click(object sender, RoutedEventArgs e) {
- ObjectDataProvider provider = FindResource("depProvider") as ObjectDataProvider;
- List<DepartmentViewModel> firstLevelItems = provider.Data as List<DepartmentViewModel>;
- ICollectionView view = CollectionViewSource.GetDefaultView(firstLevelItems);
- DepartmentViewModel rootItem = view.CurrentItem as DepartmentViewModel;
- List<DepartmentViewModel> checkedItems = CollectionViewSource.GetDefaultView(provider) as List<DepartmentViewModel>;
- StringBuilder builder = new StringBuilder();
- foreach (DepartmentViewModel checkItem in rootItem.CheckedItems) {
- builder.AppendLine(checkItem.DisplayText);
- }
- MessageBox.Show("Checked items:/n" + builder.ToString());
- }
- }
- }
好了,到此大功告成!!!
源代码:http://download.csdn.net/source/3253097