TreeView控件的用法还是有蛮多坑点的,最好记录一下。

参考项目:


静态的树形结构

如果树形结构的所有子节点都已经确定且不会改动,可以直接在控制层用C#代码来生成这个TreeView。

            var rootItem = new OutlineTreeData
{
outlineTypeName = "David Weatherbeam",
Children=
{
new OutlineTreeData
{
outlineTypeName="Alberto Weatherbeam",
Children=
{
new OutlineTreeData
{
outlineTypeName="Zena Hairmonger",
Children=
{
new OutlineTreeData
{
outlineTypeName="Sarah Applifunk",
}
}
},new OutlineTreeData
{
outlineTypeName="Jenny van Machoqueen",
Children=
{
new OutlineTreeData
{
outlineTypeName="Nick van Machoqueen",
},
new OutlineTreeData
{
outlineTypeName="Matilda Porcupinicus",
},
new OutlineTreeData
{
outlineTypeName="Bronco van Machoqueen",
}
}
}
}
},
new OutlineTreeData
{
outlineTypeName="Komrade Winkleford",
Children=
{
new OutlineTreeData
{
outlineTypeName="Maurice Winkleford",
Children=
{
new OutlineTreeData
{
outlineTypeName="Divinity W. Llamafoot",
}
}
},
new OutlineTreeData
{
outlineTypeName="Komrade Winkleford, Jr.",
Children=
{
new OutlineTreeData
{
outlineTypeName="Saratoga Z. Crankentoe",
},
new OutlineTreeData
{
outlineTypeName="Excaliber Winkleford",
}
}
}
}
}
}
};

运行后能看到树形结构是下面的样子。


获取TreeViewItem控件

前台页面xaml:

 <!-- 树形结构 -->
<TreeView x:Name="treeView" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,-10,0,0"
ItemsSource="{Binding ItemTreeDataList}" BorderThickness="0" Width="215" Height="210">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle> <TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock x:Name="treeViewItemTB" Text="{Binding itemName}" Tag="{Binding itemId}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

尝试过在初始化时获取TreeViewItem,发现都是为Null。

  • TreeViewItem item= (TreeViewItem)(myWindow.treeView.ItemContainerGenerator.ContainerFromIndex(0)); // 无法获取,为Null!
  • VisualTreeHelper.GetChild(); // 无法获取,为Null!

谷歌一下,看到这篇解答,下面这位跟我遇到的情况一样,用以上方法都无法获取TreeViewItem。

不过他给出的答案是通过点击来获取到被选中的TreeViewItem。


给TreeView默认选中一个TreeViewItem

上面的办法通过点击TreeViewItem来从事件中获得这个控件,但是如果我们想生成TreeView后不靠手动点击,立马自动选中一个默认的TreeViewItem呢?注意此时控件还未渲染,通过ItemContainerGenerator无法获取到控件。

此时只能考虑使用MVVM的绑定机制来获取控件!因为如果修改数据后,UI的更新是延迟的,无法立刻获取到前台控件。

绑定:

xaml中TreeView的ItemsSource绑定到ViewModel中的ItemTreeDataList列表,该列表中的元素类型是自定义ItemTreeData实体类。在样式中绑定好IsExpanded和IsSelected属性。TreeViewItem模板是绑定到Children属性,该属性在ItemTreeData实体类中。

ViewModel:

// Item的树形结构
private ObservableCollection<ItemTreeData> itemTreeDataList;
public ObservableCollection<ItemTreeData> ItemTreeDataList
{
get { return itemTreeDataList; }
set { SetProperty(ref itemTreeDataList, value); }
}

ItemTreeData实体类:

    public class ItemTreeData // 自定义Item的树形结构
{
public int itemId { get; set; } // ID
public string itemName { get; set; } // 名称
public int itemStep { get; set; } // 所属的层级
public int itemParent { get; set; } // 父级的ID private ObservableCollection<ItemTreeData> _children = new ObservableCollection<ItemTreeData >();
public ObservableCollection<ItemTreeData> Children { // 树形结构的下一级列表
get
{
return _children;
}
} public bool IsExpanded { get; set; } // 节点是否展开
public bool IsSelected { get; set; } // 节点是否选中
}

关于上面的层级/父级/下一级的概念,假设现在树形结构为以下结构,从上往下依此定义为根节点、零级节点、一级节点、二级节点。

/*
* rootItem
* |----zeroTreeItem
* |----firstTreeItem
* |----secondTreeItem
*/

控制层在生成TreeView时通过绑定的属性来设置默认选中的Item,预先将三个层级的Item分别装在不同的列表中使用。

现在尝试把二级节点中的第一个Item作为默认选中的Item。关键代码如下:

        // 构造轮廓选择的树形结构
private void InitTreeView()
{
// 添加树形结构
ItemTreeData item = GetTreeData();
myViewModel.ItemTreeDataList.Clear();
myViewModel.ItemTreeDataList.Add(item);
} // 构造树形结构
private ItemTreeData GetTreeData()
{
/*
* rootItem
* |----zeroTreeItem
* |----firstTreeItem
* |----secondTreeItem
*/ // 根节点
ItemTreeData rootItem = new ItemTreeData();
rootItem.itemId = -;
rootItem.itemName = " -- 请选择轮廓 -- ";
rootItem.itemStep = -;
rootItem.itemParent = -;
rootItem.IsExpanded = true; // 根节点默认展开
rootItem.IsSelected = false; for (int i = ; i < itemViewModel.ZeroStepList.Count; i++) // 零级分类
{
Items zeroStepItem = itemViewModel.ZeroStepList[i];
ItemTreeData zeroTreeItem = new ItemTreeData();
zeroTreeItem.itemId = zeroStepItem.itemId;
zeroTreeItem.itemName = zeroStepItem.itemName;
zeroTreeItem.itemStep = zeroStepItem.itemSteps;
zeroTreeItem.itemParent = zeroStepItem.itemParent;
if (i == )
{
zeroTreeItem.IsExpanded = true; // 只有需要默认选中的第一个零级分类是展开的
}
zeroTreeItem.IsSelected = false;
rootItem.Children.Add(zeroTreeItem); // 零级节点无条件加入根节点 for (int j = ; j < itemViewModel.FirstStepList.Count; j++) // 一级分类
{
Items firstStepItem = itemViewModel.FirstStepList[j];
if (firstStepItem.itemParent == zeroStepItem.itemId) //零级节点添加一级节点
{
ItemTreeData firstTreeItem = new ItemTreeData();
firstTreeItem.itemId = firstStepItem.itemId;
firstTreeItem.itemName = firstStepItem.itemName;
firstTreeItem.itemStep = firstStepItem.itemSteps;
firstTreeItem.itemParent = firstStepItem.itemParent;
if (j == )
{
firstTreeItem.IsExpanded = true; // 只有需要默认选中的第一个一级分类是展开的
}
firstTreeItem.IsSelected = false;
zeroTreeItem.Children.Add(firstTreeItem); for (int k = ; k < itemViewModel.SecondStepList.Count; k++) // 二级分类
{
Items secondStepItem = itemViewModel.SecondStepList[k];
if (secondStepItem.itemParent == firstStepItem.itemId) // 一级节点添加二级节点
{
ItemTreeData secondTreeItem = new ItemTreeData();
secondTreeItem.itemId = secondStepItem.itemId;
secondTreeItem.itemName = secondStepItem.itemName;
secondTreeItem.itemStep = secondStepItem.itemSteps;
secondTreeItem.itemParent = secondStepItem.itemParent;
if (k == )
{
// 默认选中第一个二级分类
secondTreeItem.IsExpanded = true; // 已经没有下一级了,这个属性无所谓
secondTreeItem.IsSelected = true;
}
firstTreeItem.Children.Add(secondTreeItem);
}
}
}
}
} return rootItem;
}

通过初始化时给被绑定的属性赋值,使得TreeView默认选中了二级节点中的第一个TreeViewItem,效果如下图:

注意点:

  • 只是修改目标节点的IsSelected = true还不够,还要把它的所有父节点(祖父节点)都设置IsExpanded = true才行!!!

显示选中TreeViewItem的完整分类路径

需求是如上图,要得到字符串"I户型_1.5-1_a2",即通过下划线连接得到“零级节点_一级节点_二级节点”,然后用一个TextBlock控件显示出来。注意这里不包含root根节点。

该功能可以写在树形结构的选项改变事件中。因为初始化TreeView时就选中了其中一个Item,所以初始化时也会调用到选项改变事件。

        // 树形结构的选项改变事件
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ItemTreeData selectedItem = (ItemTreeData)(myWindow.treeView.SelectedItem);
if (selectedItem != null)
{
// UI层找不到,只能在数据层找
List<string> nameList = new List<string>();
int step = selectedItem.itemStep; // 可能值为:-1,0,1,2
int parentId = selectedItem.itemParent; // 临时保存遍历中每次使用的Id
for (int i = ; i < step; i++) // 零级分类虽然有父节点root,但是数据层中没有相应的itemParent值(为-1)
{
ItemTreeData parent = this.GetTreeDataById(parentId);
if (parent != null)
{
nameList.Add(parent.itemName);
parentId = parent.itemParent;
}
} // 组拼字符串 = 零级名称 + 一级名称 + 二级名称
string text = "";
for (int i = nameList.Count; i > ; i--) // 倒序遍历
{
if (i == nameList.Count)
{
text = nameList[i - ];
}
else
{
text = text + "_" + nameList[i - ];
}
} if (string.IsNullOrEmpty(text))
{
myWindow.itemSelectedTB.Text = selectedItem.itemName;
}
else
{
myWindow.itemSelectedTB.Text = text + "_" + selectedItem.itemName;
}
}
} // 根据Id,获得树形结构中指定的Item
private ItemTreeData GetTreeDataById(int targetId)
{
ItemTreeData data = null; // 是否为根节点
ItemTreeData root = myViewModel.ItemTreeDataList[];
if (root.itemId == targetId)
{
data = root;
return data;
}
// 搜索零级大类
foreach (ItemTreeData zeroStepData in root.Children)
{
if (zeroStepData.itemId == targetId)
{
data = zeroStepData;
return data;
}
// 搜索一级分类
foreach (ItemTreeData firstStepData in zeroStepData.Children)
{
if (firstStepData.itemId == targetId)
{
data = firstStepData;
return data;
}
// 搜索二级分类
foreach (ItemTreeData secondStepData in firstStepData.Children)
{
if (secondStepData.itemId == targetId)
{
data = secondStepData;
return data;
}
}
}
} return data;
}

注意点:

  • 同样是找不到TreeViewItem控件!UI层找不到,所以只能在数据层找它关联的ItemTreeData对象,从数据层中去获取itemName属性值。
  • 因为每一级ItemTreeData对象中记录了它的父级ID,所以往根节点方向遍历父节点、祖父节点时,先加入到List中的是上一级的节点名。而需求是“零级节点_一级节点_二级节点”的顺序,所以在使用List时需要倒序遍历!

最后显示的完整分类如下:

【WPF】树形结构TreeView的用法(MVVM)的更多相关文章

  1. 部门树形结构,使用Treeview控件显示部门

    部门树形结构.设计张部门表用于存储部门编码.名称.上级部门id,使用Treeview控件显示部门树,并实现部门增删改.移动.折叠等功能.特别提示,部门有层级关系,可用donetbar的adtree控件 ...

  2. Delphi TreeView – 自动展开树形结构

    Delphi TreeView – 自动展开树形结构 当处理完TreeView控件树形结构的数据后,需要默认自动全部展开,可以用到TreeView的Expanded属性. 1 2 3 4 5 6 7 ...

  3. ThinkPHP第二十天(getField用法、常用管理员表结构、树形结构前小图标CSS)

    1.getField($fields,$sepa=null) A:当$fields为1个字段,$sepa=null的时候,返回一个符合条件的记录的字段. B:如果要取得所有符合条件记录字段,需要$se ...

  4. WPF如何用TreeView制作好友列表、播放列表

    WPF如何用TreeView制作好友列表.播放列表 前言 TreeView这个控件对于我来说是用得比较多的,以前做的小聊天软件(好友列表).音乐播放器(播放列表).类库展示器(树形类结构)等都用的是T ...

  5. Delphi中根据分类数据生成树形结构的最优方法

    一. 引言:    TreeView控件适合于表示具有多层次关系的数据.它以简洁的界面,表现形式清晰.形象,操作简单而深受用户喜爱.而且用它可以实现ListView.ListBox所无法实现的很多功能 ...

  6. WPF如何用TreeView制作好友列表、播放列表(转)

    WPF如何用TreeView制作好友列表.播放列表 前言 TreeView这个控件对于我来说是用得比较多的,以前做的小聊天软件(好友列表).音乐播放器(播放列表).类库展示器(树形类结构)等都用的是T ...

  7. 使用ztree.js,受益一生,十分钟学会使用tree树形结构插件

    看到ztree.js,这几个字眼,毋庸置疑,那肯定就是tree树形结构了,曾经的swing年代有jtree,后来jquery年代有jstree和treeview,虽然我没写过,但是我见过,一些小功能做 ...

  8. VBA读取文件夹下所有文件夹及文件内容,并以树形结构展示

    Const TR_LEVEL_MARK = "+"Const TR_COL_INDEX = "A"Const TR_COL_LEVEL = "E&qu ...

  9. [SQL Server]树形结构的创建

    对于SQL Server来说,构建显示一个树形结构不是一件容易的事情,逻辑构造能力不是它的强项.不过也不是说它没有能力干这个事情,只要换一种思维方式就可以理解它的工作原理. 例如,现在有一张表的内容如 ...

随机推荐

  1. android蓝牙开发---与蓝牙模块进行通信

    近半个月来一直在搞android蓝牙这方面,主要是项目需要与蓝牙模块进行通信.开头的进展很顺利,但因为蓝牙模块不在我这里,所以只能用手机测试.一开头就发现手机的蓝牙不能用,为了证明这点,我刷了四次不同 ...

  2. docker 和 vagrant 作为程序发布 和 开发的独立而统一的运行环境

    docker 和 vagrant 作为程序发布 和 开发的运行环境,可以提供打包程序,并使得程序运行在一个独立的虚拟环境中,避免程序发布到客户机之后,环境不一致导致的诸多问题.     refer: ...

  3. SharePoint 2013 Designer工作流——Parallel Block的应用

    参考目录 安装和配置SharePoint 2013 Workflow SharePoint 2013 实现多级审批工作流 在自定义Workflow时,往往会遇到这样场景,某个审批需要被多人查阅,每个查 ...

  4. 探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现

    对于SharePoint Developers来说,往往会过多的去关注SharePoint平台和工具,而把设计模式和代码的可测试性放在了一个较低的优先级.这并不是说SharePoint Develop ...

  5. 怎样为你的CSDN博客增加百度统计

    曾经CSDN使用的 量子统计 能够非常好的统计我们的博客的訪问数量.地域等等信息,可是不知道后来为什么不在使用了.那么怎样找到 一种替换的方式那? 下边,就给大家介绍一下怎样使用百度统计. 百度统计账 ...

  6. 常用DC-DC;AC-DC电源芯片

    求推荐几个常用的开关电源芯片http://bbs.21ic.com/icview-1245974-1-1.html(出处: 21ic电子技术论坛) 1.1 DC-DC电源转换器 1.低噪声电荷泵DC- ...

  7. JS中parseint和number的区别

    两者定义的区别 parseInt将字符串(String)类型转为整数类型.Number() 函数把对象(Object)的值转换为数字. parseInt得到的结果是整数或者NaN,而Number得到的 ...

  8. Oracle多表关联如何更新多个字段

    注意点:1.被update主表一定要加上过滤条件.2.查询出来更新结果集,同时也要作为被更新主表的条件,作为同步大家都是更新这部分数据.update student stu set (stu.name ...

  9. IOS App 后台运行

    使用block的另一个用处是可以让程序在后台较长久的运行.在以前,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作.但是应用可以调用UIApplication的beg ...

  10. Linux学习笔记--SSH免password登录

    须要实现的效果: 有两台server:"192.168.201.236" 和 "192.168.201.237" 须要实现:在server"192.1 ...