在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>類型
顯示效果:
其實還可以完成共多功能,等有時間在去寫。