潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据
目前自己对treeview的感慨很多
今天先讲 面对这种 表结构的数据 的其中一种绑定方法,后面多几列其他属性都没关系,例如多个字段,
1 A 0
2 B 0
3 C 0
4 D 1
5 E 2
6 F 4
7 G 1
...
就是递归型的表结构
然后通过treeview 展示( treeview 的name 叫 tv , collection 是 ObservableCollection<T> 的 一个实例)
1.首先你必须需要 要建立一个 跟treeview 结构很相似的一个集合,这里建议用 ObservableCollection<T> 这个集合很特殊,你要记得,例如 tv.ItemsSource = this.collection; 当你这样绑定时,修改collection的属性时就是修改treeview绑定的某些属性
2.在做绑定时一定要 搞清楚treeview的item的结构,你想呈现什么样的,每个 treeveiwItem就是一个对象 ,这个对象可以用一个类去替代,或者什么去替代
3. 不啰嗦了,说正题,新建一个实体类
例如
/// <summary> ////// </summary> public class ReportCategoryEntity { /// <summary> /// ID /// </summary> public int Id { get; set; } /// <summary> /// 名称 /// </summary> public string Title { get; set; } /// <summary> ///父节点 /// </summary> public int ParentID { get; set; } }
这个应该看得懂吧,加入那个递归表 叫 FoodCatagory(菜品目录) 表,现在只要递归显示出来就行了
把他所有行取出来 select * from FoodCatagory,如果其中不止基本的三列,有其他列,例如通过这些列的值 作为控制 treeview显示的样式的条件用 这也是很不错的点子,真的,比如说再加一个图片的地址,如果有个图片地址字典表,这里就存那个表中的字段了,如果其他字段你需要,那你可以继续写在 ReportCategoryEntity 这个类中
读出来的所有行封装到 List<ReportCategoryEntity> lstReportCategoryEntity ,这个应该会把,如果不会,说明你的oop基础没学好了
好了
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Collections.ObjectModel; using System.Windows.Media; namespace 命名空间 { public class TreeCategory : INotifyPropertyChanged { private ObservableCollection<TreeCategory> children = new ObservableCollection<TreeCategory>(); public TreeCategory() { } public TreeCategory(List<ReportCategoryEntity> totalCategory, int parentID) //0 根目录 { //Parent = this; foreach (ReportCategoryEntity item in totalCategory) { if (item.ParentID == parentID) { TreeCategory tc = new TreeCategory(totalCategory, item.Id); tc.Title = item.Title; children.Add(tc); } } } public ObservableCollection<TreeCategory> Children { get { return this.children; } } /// <summary> /// 显示的名称 /// </summary> private string title; public string Title { get { return title; } set { title = value; this.NotifyPropertyChanged("Title"); } } /// <summary> /// 是否选中 /// </summary> private bool isSelected = false; public bool IsSelected { set { this.isSelected = value; this.NotifyPropertyChanged("IsSelected"); } get { return this.isSelected; } } /// <summary> /// 标题字体颜色 /// </summary> private Brush foregroundBrush = new SolidColorBrush(Colors.Black); public Brush ForegroundBrush { set { this.foregroundBrush = value; this.NotifyPropertyChanged("ForegroundBrush"); } get { return this.foregroundBrush; } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } } }
接下来再建立一个最重要的 类,用来控制treeview显示的,看似像实体类,但又不像,下面是我建立的,本类下有个本类的集合的属性,还是ObservableCollection集合的,加了 INotifyPropertyChanged 只是为了达到只要修改这个集合的某些属性的值,就可以修改treeview的状态了。
例如我这里添加了 标题字体颜色 ForegroundBrush 这个属性,就是为了达到treeview的每个节点的显示颜色,你在递归的时候就可以根据 ReportCategoryEntity 的某些属性作为条件,然后动态给ForegroundBrush 赋值,要记住,每一个 TreeCategory 类就已经对应了一个 treeviewItem,该类的每一个字段都可以作为 treeview 显示出来的条件,至于前面treeview的样式模板该怎么写,很快你就清楚了
现在说一下 xaml 中前台的写法
<TreeView Name="tv" Width="170" Height="800" MaxHeight="520" BorderThickness="0"> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <Setter Property="TreeViewItem.IsExpanded" Value="True"/> <!--<Setter Property="TreeViewItem.IsSelected" Value="{Binding IsSelected}"/>--> <!--<EventSetter Event="MouseMove" Handler="tree_MouseMove" />--> <!--<EventSetter Event="KeyDown" Handler="treeViewItem_KeyDown" />--> <Setter Property="TreeViewItem.Margin" Value="0,1,0,0"/> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type c:TreeCategory}" ItemsSource="{Binding Children}"> <StackPanel Margin="-2,0,0,0" Orientation="Horizontal" x:Name="staTree"> <CheckBox Content="{Binding Title}" FontSize="14" FontFamily="微软雅黑" Tag="{Binding Children}" IsChecked="{Binding IsSelected}" Foreground="{Binding ForegroundBrush}" Unchecked="ck_Unchecked" Checked="ck_Checked"></CheckBox> </StackPanel> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsSelected}" Value="true"> <Setter TargetName="staTree" Property="Background" Value="White"/> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
重点看 <TreeView.ItemTemplate> 这个节点下面的 ,<TreeView.ItemContainerStyle>用来控制总体显示样式的
现在你应该有点清楚了
1. 首先你要知道 treeviewItem 对应一个 TreeCategory ,treeview控件每个节点下都有可能有子节点,像一个集合,所以我在TreeCategory 类中又包括了他自己的一个集合,已达到构造出和treeview很像的一个结构,感觉到了吗
2. ReportCategoryEntity 这个实体是 把从数据库读出来的每条数据的一个net样子,一条表中的数据对应一个 ReportCategoryEntity 实体对象,达到封装效果,不一定只有这三个字段,拓展他以达到treeview显示更丰富的效果,而且treeview的模板很好写,可以改造出很多种
3.就是 TreeCategory 类 ,如果以前没有接触xaml语言的可能会有疑问
public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } }
为什么会有这堆代码,而且每个属性后面,又调了这个 NotifyPropertyChanged 方法,我不知道原理,但我知道怎么用了,wpf的控件很奇特,不像winform控件或者 asp.net控件那么死,重写个控件很难,这就是为什么wpf 做的桌面软件五花八门,而且很炫,只要你的到控件,不脱离实际,控件都可以变成你想要的控件。又说了好多废话。
最重要的还是我的那个 递归
public TreeCategory(List<ReportCategoryEntity> totalCategory, int parentID) //0 根目录 { //Parent = this; foreach (ReportCategoryEntity item in totalCategory) { if (item.ParentID == parentID) { TreeCategory tc = new TreeCategory(totalCategory, item.Id);
//加一些你想要的字段,然后在这里赋值吧 tc.Title = item.Title; children.Add(tc); } } }
4.为了达到treeview显示的不同效果,加了数据触发器,这样后台一行代码也不要写,有必要的话,你可以再加一个值转换器吧 ,加一些你想要的数据触发器吧,丰富显示效果,例如根据某个字段作为判断条件,如果是什么,把image 控件的source地址换了,就可以达到不同的节点,显示不同的图片了 ,具体应用有很多
<HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsSelected}" Value="true"> <Setter TargetName="staTree" Property="Background" Value="White"/> </DataTrigger>
//加一些你想要的数据触发器吧,丰富显示效果,例如根据某个字段作为判断条件,如果是什么,把image 控件的source地址换了,就可以达到不同的节点,显示不同的图片了
</HierarchicalDataTemplate.Triggers>
后台绑定
private ObservableCollection<TreeCategory> collection = new ObservableCollection<TreeCategory>(); this.collection = new TreeCategory(ctagr, 0).Children; treeCategories.ItemsSource = this.collection;
ctagr 是个List<ReportCategoryEntity> 集合
先冒个泡
这是我做的一个菜品目录checkbox多选版本的 ,作为导航栏的,例如选中这个目录,列出这个目录下的所有菜,或者计算出这个菜品目录的菜的销售情况等等,扩展一下,部门与员工是挺经典的一个例子
至于选中根节点,子节点全部选中,那你可以写在checkbox的事件里,应该会吧,这我就不写了,建议递归吧,应为你不知道他下面有多少个子节点
这个我加了checkbox选择的模式,全选,全不选,反选,随选
这个我就再说一点吧,然后来体现ObservableCollection 集合的好处
你不可能修改treeviewItem的属性吧,那个有点傻了
直接修改ObservableCollection 中的 TreeCategory 的IsSelected属性就行了,treeview中自动体现
这里我就只说个全选,全不选 的吧
下面是我写的一个算法
为了防止你粘贴复制,不思考我就上个图吧
反选应该就不用我说了吧,留着思考吧
而且每个 节点的 checkbox都是可以改的,而且你还可以组合控件,例如把很多控件放在stackpanel 中,作为一个treeviewItem
例如每个节点鼠标移上去的效果你都可以很轻松的改的,就看你的技术了,鼠标移上去,我就不说了
再看个图吧
如果你熟悉treeview的话,迅雷看看的这个目录效果很轻松的可以做出来,那个TreeviewItem加载动画也可以做
,这是个 目录下有具体内容的treeview结构,这个如果也用这种MVVM思想的话也可以很轻松的做出来,我做过,多态做的,呵呵,下次有时间的话就再写篇 treeview下面有内容时,该怎么写。
我自己感觉迅雷看看有点像 Expander 加 treeview 控件呢?下次做一个迅雷看看左边的目录 制作教程吧,好了,就暂时啰嗦到这里吧