在WPF实际项目开发的时候,经常会用到带CheckBox的TreeView,虽然微软在WPF的TreeView中没有提供该功能,但是微软在WPF中提供强大的ItemTemplate模板功能和自定义样式,那我们可以自己写一个这样的控件供自己使用。
我自己写的这个比较简单。
首先写一个供TreeView使用的数据模型,并且实现INotifyPropertyChanged接口,用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知,当属性改变时,相应的UI表现也改变。主要字段Id,Name,Icon,ToolTip,IsChecked,IsExpanded,Parent,Children
//*************************************************** // // 文件名(FileName) : TreeModel.cs // // 作者(Author) : zsm // // 创建时间(CreateAt): 2013-03-18 14:23:58 // // 描述(Description) : 供TreeView实用的数据模型 // //*************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace Com.FMS.Model { public class TreeModel : INotifyPropertyChanged { #region 私有变量 /// <summary> /// Id值 /// </summary> private string _id; /// <summary> /// 显示的名称 /// </summary> private string _name; /// <summary> /// 图标路径 /// </summary> private string _icon; /// <summary> /// 选中状态 /// </summary> private bool _isChecked; /// <summary> /// 折叠状态 /// </summary> private bool _isExpanded; /// <summary> /// 子项 /// </summary> private IList<TreeModel> _children; /// <summary> /// 父项 /// </summary> private TreeModel _parent; #endregion /// <summary> /// 构造 /// </summary> public TreeModel() { Children = new List<TreeModel>(); _isChecked = false; IsExpanded = false; _icon = "/Images/16_16/folder_go.png"; } /// <summary> /// 键值 /// </summary> public string Id { get { return _id; } set { _id = value; } } /// <summary> /// 显示的字符 /// </summary> public string Name { get { return _name; } set { _name = value; } } /// <summary> /// 图标 /// </summary> public string Icon { get { return _icon; } set { _icon = value; } } /// <summary> /// 指针悬停时的显示说明 /// </summary> public string ToolTip { get { return String.Format("{0}-{1}", Id, Name); } } /// <summary> /// 是否选中 /// </summary> public bool IsChecked { get { return _isChecked; } set { if (value != _isChecked) { _isChecked = value; NotifyPropertyChanged("IsChecked"); if (_isChecked) { //如果选中则父项也应该选中 if (Parent != null) { Parent.IsChecked = true; } } else { //如果取消选中子项也应该取消选中 foreach (TreeModel child in Children) { child.IsChecked = false; } } } } } /// <summary> /// 是否展开 /// </summary> public bool IsExpanded { get { return _isExpanded; } set { if (value != _isExpanded) { //折叠状态改变 _isExpanded = value; NotifyPropertyChanged("IsExpanded"); } } } /// <summary> /// 父项 /// </summary> public TreeModel Parent { get { return _parent; } set { _parent = value; } } /// <summary> /// 子项 /// </summary> public IList<TreeModel> Children { get { return _children; } set { _children = value; } } /// <summary> /// 设置所有子项的选中状态 /// </summary> /// <param name="isChecked"></param> public void SetChildrenChecked(bool isChecked) { foreach (TreeModel child in Children) { child.IsChecked = IsChecked; child.SetChildrenChecked(IsChecked); } } /// <summary> /// 设置所有子项展开状态 /// </summary> /// <param name="isExpanded"></param> public void SetChildrenExpanded(bool isExpanded) { foreach (TreeModel child in Children) { child.IsExpanded = isExpanded; child.SetChildrenExpanded(isExpanded); } } /// <summary> /// 属性改变事件 /// </summary> public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } } }
创建一个用户控件,主要含有一个TreeView控件,ContextMenu右键菜单项,UI代码如下(其中的路径请根据实际修改):
<UserControl x:Class="Com.FMS.View.UserControls.ZsmTreeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Com.FMS.Model" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <DockPanel> <Border DockPanel.Dock="Bottom"> <StackPanel Orientation="Horizontal" ToolTip="右键有更多功能哦!"> <Image Height="16" Width="16" Source="Images/16_16/emoticon_smile.png"></Image> <Label Content="右键有更多功能哦!" Foreground="Gray"></Label> </StackPanel> </Border> <Border> <TreeView Name="tvZsmTree"> <TreeView.ContextMenu> <ContextMenu> <MenuItem Name="menuExpandAll" Header="全部展开" Click="menuExpandAll_Click"> <MenuItem.Icon> <Image Source="/Com.FMS;component/Images/16_16/folder_open_arrow.png" /> </MenuItem.Icon> </MenuItem> <MenuItem Name="menuUnExpandAll" Header="全部折叠" Click="menuUnExpandAll_Click"> <MenuItem.Icon> <Image Source="/Com.FMS;component/Images/16_16/folder_close_arrow.png" /> </MenuItem.Icon> </MenuItem> <MenuItem Name="menuSelectAll" Header="全部选中" Click="menuSelectAll_Click"> <MenuItem.Icon> <Image Source="/Com.FMS;component/Images/16_16/tick.png" /> </MenuItem.Icon> </MenuItem> <MenuItem Name="menuUnSelectAll" Header="全部取消" Click="menuUnSelectAll_Click"> <MenuItem.Icon> <Image Source="/Com.FMS;component/Images/16_16/delete.png" /> </MenuItem.Icon> </MenuItem> </ContextMenu> </TreeView.ContextMenu> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter> <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:TreeModel}" ItemsSource="{Binding Children}"> <StackPanel Margin="-2,0,0,0" Orientation="Horizontal" x:Name="staTree"> <CheckBox ToolTip="{Binding ToolTip}" FontSize="14" FontFamily="微软雅黑" Tag="{Binding Children}" IsChecked="{Binding IsChecked, Mode=TwoWay}"> <StackPanel Orientation="Horizontal"> <Image VerticalAlignment="Center" Source="{Binding Icon}" ></Image> <TextBlock Text="{Binding Name}"></TextBlock> </StackPanel> <CheckBox.ContextMenu> <ContextMenu> <MenuItem Name="menuSelectAllChild" Header="全部选中子项" Click="menuSelectAllChild_Click"> <MenuItem.Icon> <Image Source="/Com.FMS;component/Images/16_16/tick.png" /> </MenuItem.Icon> </MenuItem> </ContextMenu> </CheckBox.ContextMenu> </CheckBox> </StackPanel> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsChecked}" Value="true"> <Setter TargetName="staTree" Property="Background" Value="White"/> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Border> </DockPanel> </Grid> </UserControl>
交互逻辑的代码中,现在主要有控件数据ItemsSourceData属性,设置对应Id的项为选中状态SetCheckedById、忽略层次关系的情况下获取选中项CheckedItemsIgnoreRelation等方法,以及右键的选中所有子项菜单、全部选中、全部取消选中、全部折叠、全部展开等事件,交互逻辑代码为:
//*************************************************** // // 文件名(FileName) : ZsmTreeView.xaml.cs // // 作者(Author) : zsm // // 创建时间(CreateAt): 2013-03-15 16:52:40 // // 描述(Description) : 带CheckBox的TreeView控件的交互逻辑代码 // //*************************************************** 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 Com.FMS.View.UserControls { /// <summary> /// ZsmTreeView.xaml 的交互逻辑 /// </summary> public partial class ZsmTreeView : UserControl { #region 私有变量属性 /// <summary> /// 控件数据 /// </summary> private IList<Model.TreeModel> _itemsSourceData; #endregion /// <summary> /// 构造 /// </summary> public ZsmTreeView() { InitializeComponent(); } /// <summary> /// 控件数据 /// </summary> public IList<Model.TreeModel> ItemsSourceData { get { return _itemsSourceData; } set { _itemsSourceData = value; tvZsmTree.ItemsSource = _itemsSourceData; } } /// <summary> /// 设置对应Id的项为选中状态 /// </summary> /// <param name="id"></param> /// <returns></returns> public int SetCheckedById(string id, IList<Model.TreeModel> treeList) { foreach (var tree in treeList) { if (tree.Id.Equals(id)) { tree.IsChecked = true; return 1; } if (SetCheckedById(id, tree.Children) == 1) { return 1; } } return 0; } /// <summary> /// 设置对应Id的项为选中状态 /// </summary> /// <param name="id"></param> /// <returns></returns> public int SetCheckedById(string id) { foreach (var tree in ItemsSourceData) { if (tree.Id.Equals(id)) { tree.IsChecked = true; return 1; } if (SetCheckedById(id, tree.Children) == 1) { return 1; } } return 0; } /// <summary> /// 获取选中项 /// </summary> /// <returns></returns> public IList<Model.TreeModel> CheckedItemsIgnoreRelation() { return GetCheckedItemsIgnoreRelation(_itemsSourceData); } /// <summary> /// 私有方法,忽略层次关系的情况下,获取选中项 /// </summary> /// <param name="list"></param> /// <returns></returns> private IList<Model.TreeModel> GetCheckedItemsIgnoreRelation(IList<Model.TreeModel> list) { IList<Model.TreeModel> treeList = new List<Model.TreeModel>(); foreach (var tree in list) { if (tree.IsChecked) { treeList.Add(tree); } foreach (var child in GetCheckedItemsIgnoreRelation(tree.Children)) { treeList.Add(child); } } return treeList; } /// <summary> /// 选中所有子项菜单事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void menuSelectAllChild_Click(object sender, RoutedEventArgs e) { if (tvZsmTree.SelectedItem != null) { Model.TreeModel tree = (Model.TreeModel)tvZsmTree.SelectedItem; tree.IsChecked = true; tree.SetChildrenChecked(true); } } /// <summary> /// 全部展开菜单事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void menuExpandAll_Click(object sender, RoutedEventArgs e) { foreach (Model.TreeModel tree in tvZsmTree.ItemsSource) { tree.IsExpanded = true; tree.SetChildrenExpanded(true); } } /// <summary> /// 全部折叠菜单事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void menuUnExpandAll_Click(object sender, RoutedEventArgs e) { foreach (Model.TreeModel tree in tvZsmTree.ItemsSource) { tree.IsExpanded = false; tree.SetChildrenExpanded(false); } } /// <summary> /// 全部选中事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void menuSelectAll_Click(object sender, RoutedEventArgs e) { foreach (Model.TreeModel tree in tvZsmTree.ItemsSource) { tree.IsChecked = true; tree.SetChildrenChecked(true); } } /// <summary> /// 全部取消选中 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void menuUnSelectAll_Click(object sender, RoutedEventArgs e) { foreach (Model.TreeModel tree in tvZsmTree.ItemsSource) { tree.IsChecked = false; tree.SetChildrenChecked(false); } } /// <summary> /// 鼠标右键事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { TreeViewItem item = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem; if (item != null) { item.Focus(); e.Handled = true; } } static DependencyObject VisualUpwardSearch<T>(DependencyObject source) { while (source != null && source.GetType() != typeof(T)) source = VisualTreeHelper.GetParent(source); return source; } } }
在使用控件的时候,要在xaml中引入命名控件(根据实际引入)
xmlns:my="clr-namespace:Com.FMS.View.UserControls"
<!--使用控件-->
<my:ZsmTreeView x:Name="ztvModule" />
ztvModule.ItemsSourceData = treeList;//treeList为IList<TreeModel>类型
显示效果:
其实还可以完成共多功能,等有时间在去写。