前言
本文将探讨如何利用wpf框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项。我们将介绍如何使用wpf提供的控件、模板、布局、数据绑定等技术来构建这样一个树形表格。
一、运行效果
1.1默认样式

1.2 自定义样式

二、代码实现
2.1 创建自定义控件(treelistview)
新建一个继承自treeview的控件,并定义一个类型为viewbase的view依赖属性,用于在代码中指定列。
public class treelistview : treeview
{
public viewbase view
{
get { return (viewbase)getvalue(viewproperty); }
set { setvalue(viewproperty, value); }
}
public static readonly dependencyproperty viewproperty =
dependencyproperty.register("view", typeof(viewbase), typeof(treelistview));
}2.2 在treelistview控件模板中处理列头
为了在treelistview中显示列头,需要在合适的位置添加gridviewheaderrowpresenter控件,并在columns属性上绑定我们之前定义的view.columns属性。下面我们首先来分析treeview控件模板的代码。
<controltemplate targettype="{x:type treeview}">
<border x:name="bd" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" snapstodevicepixels="true">
<scrollviewer x:name="_tv_scrollviewer_" background="{templatebinding background}" cancontentscroll="false" focusable="false" horizontalscrollbarvisibility="{templatebinding scrollviewer.horizontalscrollbarvisibility}" padding="{templatebinding padding}" snapstodevicepixels="{templatebinding snapstodevicepixels}" verticalscrollbarvisibility="{templatebinding scrollviewer.verticalscrollbarvisibility}">
<itemspresenter/>
</scrollviewer>
</border>
<controltemplate.triggers>
<trigger property="isenabled" value="false">
<setter property="background" targetname="bd" value="{dynamicresource {x:static systemcolors.controlbrushkey}}"/>
</trigger>
<trigger property="virtualizingpanel.isvirtualizing" value="true">
<setter property="cancontentscroll" targetname="_tv_scrollviewer_" value="true"/>
</trigger>
</controltemplate.triggers>
</controltemplate>通过以上代码我们可以看出,只要将gridviewheaderrowpresenter控件添加到scrollviewer控件上面即可实现列头功能,但这样会有一个问题,那就是内容宽度超出控件宽度后,鼠标拖动横向滚动条时列头不会跟随下方的数据列表一起滚动。为解决这个问题我们需要将gridviewheaderrowpresenter放置到scrollviewer控件模板中,以下为完整代码。
<style x:key="{x:static gridview.gridviewscrollviewerstylekey}" targettype="{x:type scrollviewer}">
<setter property="focusable" value="false" />
<setter property="template">
<setter.value>
<controltemplate targettype="{x:type scrollviewer}">
<grid background="{templatebinding background}" snapstodevicepixels="true">
<grid.columndefinitions>
<columndefinition width="*" />
<columndefinition width="auto" />
</grid.columndefinitions>
<grid.rowdefinitions>
<rowdefinition height="*" />
<rowdefinition height="auto" />
</grid.rowdefinitions>
<dockpanel margin="{templatebinding padding}">
<scrollviewer
dockpanel.dock="top"
focusable="false"
horizontalscrollbarvisibility="hidden"
verticalscrollbarvisibility="hidden">
<gridviewheaderrowpresenter
margin="2,0,2,0"
allowscolumnreorder="{binding templatedparent.view.allowscolumnreorder, relativesource={relativesource templatedparent}}"
columnheadercontainerstyle="{binding templatedparent.view.columnheadercontainerstyle, relativesource={relativesource templatedparent}}"
columnheadercontextmenu="{binding templatedparent.view.columnheadercontextmenu, relativesource={relativesource templatedparent}}"
columnheaderstringformat="{binding templatedparent.view.columnheaderstringformat, relativesource={relativesource templatedparent}}"
columnheadertemplate="{binding templatedparent.view.columnheadertemplate, relativesource={relativesource templatedparent}}"
columnheadertemplateselector="{binding templatedparent.view.columnheadertemplateselector, relativesource={relativesource templatedparent}}"
columnheadertooltip="{binding templatedparent.view.columnheadertooltip, relativesource={relativesource templatedparent}}"
columns="{binding templatedparent.view.columns, relativesource={relativesource templatedparent}}"
snapstodevicepixels="{templatebinding snapstodevicepixels}" />
</scrollviewer>
<scrollcontentpresenter
x:name="part_scrollcontentpresenter"
cancontentscroll="{templatebinding cancontentscroll}"
content="{templatebinding content}"
contenttemplate="{templatebinding contenttemplate}"
keyboardnavigation.directionalnavigation="local"
snapstodevicepixels="{templatebinding snapstodevicepixels}" />
</dockpanel>
<scrollbar
x:name="part_horizontalscrollbar"
grid.row="1"
cursor="arrow"
maximum="{templatebinding scrollablewidth}"
minimum="0.0"
orientation="horizontal"
viewportsize="{templatebinding viewportwidth}"
visibility="{templatebinding computedhorizontalscrollbarvisibility}"
value="{binding horizontaloffset, mode=oneway, relativesource={relativesource templatedparent}}" />
<scrollbar
x:name="part_verticalscrollbar"
grid.column="1"
cursor="arrow"
maximum="{templatebinding scrollableheight}"
minimum="0.0"
orientation="vertical"
viewportsize="{templatebinding viewportheight}"
visibility="{templatebinding computedverticalscrollbarvisibility}"
value="{binding verticaloffset, mode=oneway, relativesource={relativesource templatedparent}}" />
<dockpanel
grid.row="1"
grid.column="1"
background="{binding background, elementname=part_verticalscrollbar}"
lastchildfill="false">
<rectangle
width="1"
dockpanel.dock="left"
fill="white"
visibility="{templatebinding computedverticalscrollbarvisibility}" />
<rectangle
height="1"
dockpanel.dock="top"
fill="white"
visibility="{templatebinding computedhorizontalscrollbarvisibility}" />
</dockpanel>
</grid>
</controltemplate>
</setter.value>
</setter>
</style>
<style targettype="{x:type local:treelistview}">
<setter property="scrollviewer.horizontalscrollbarvisibility" value="auto" />
<setter property="scrollviewer.verticalscrollbarvisibility" value="auto" />
<setter property="scrollviewer.cancontentscroll" value="true" />
<setter property="template">
<setter.value>
<controltemplate targettype="{x:type local:treelistview}">
<border borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}">
<scrollviewer padding="{templatebinding padding}" style="{staticresource {x:static gridview.gridviewscrollviewerstylekey}}">
<itemspresenter />
</scrollviewer>
</border>
</controltemplate>
</setter.value>
</setter>
</style>2.3 在treelistviewitem模板中处理子项的展开和收缩
新建一个继承自treeviewitem的类,命名为treelistviewitem(如有个性化需求,可以在该类中处理),编辑控件模板,在模板中添加以下代码。
<style targettype="{x:type local:treelistviewitem}">
<setter property="borderthickness" value="1" />
<setter property="template">
<setter.value>
<controltemplate targettype="{x:type local:treelistviewitem}">
<stackpanel>
<border
name="bd"
padding="{templatebinding padding}"
background="{templatebinding background}"
borderbrush="{templatebinding borderbrush}"
borderthickness="{templatebinding borderthickness}">
<gridviewrowpresenter
x:name="part_header"
columns="{binding relativesource={relativesource ancestortype=local:treelistview}, path=view.columns}"
content="{templatebinding header}" />
</border>
<itemspresenter x:name="itemshost" />
</stackpanel>
<controltemplate.triggers>
<trigger property="isexpanded" value="false">
<setter targetname="itemshost" property="visibility" value="collapsed" />
</trigger>
<multitrigger>
<multitrigger.conditions>
<condition property="hasheader" value="false" />
<condition property="width" value="auto" />
</multitrigger.conditions>
<setter targetname="part_header" property="minwidth" value="75" />
</multitrigger>
<multitrigger>
<multitrigger.conditions>
<condition property="hasheader" value="false" />
<condition property="height" value="auto" />
</multitrigger.conditions>
<setter targetname="part_header" property="minheight" value="19" />
</multitrigger>
<multitrigger>
<multitrigger.conditions>
<condition property="extensions:treeviewitemextensions.ismousedirectlyoveritem" value="true" />
</multitrigger.conditions>
<setter targetname="bd" property="background" value="{staticresource item.mouseover.background}" />
<setter targetname="bd" property="borderbrush" value="{staticresource item.mouseover.border}" />
</multitrigger>
<multitrigger>
<multitrigger.conditions>
<condition property="selector.isselectionactive" value="false" />
<condition property="isselected" value="true" />
</multitrigger.conditions>
<setter targetname="bd" property="background" value="{staticresource item.selectedinactive.background}" />
<setter targetname="bd" property="borderbrush" value="{staticresource item.selectedinactive.border}" />
</multitrigger>
<multitrigger>
<multitrigger.conditions>
<condition property="selector.isselectionactive" value="true" />
<condition property="isselected" value="true" />
</multitrigger.conditions>
<setter targetname="bd" property="background" value="{staticresource item.selectedactive.background}" />
<setter targetname="bd" property="borderbrush" value="{staticresource item.selectedactive.border}" />
</multitrigger>
<trigger property="isenabled" value="false">
<setter property="foreground" value="{dynamicresource {x:static systemcolors.graytextbrushkey}}" />
</trigger>
</controltemplate.triggers>
</controltemplate>
</setter.value>
</setter>
</style>此处的核心在于模板中添加了gridviewrowpresenter控件,并在columns属性上绑定了我们之前定义的view.columns属性,这样就可以在每一行上面显示列数据。还有一个关键点是itemspresenter,它用于显示子项数据,此处命名为itemshost,它由属性触发器中的代码来控件展开和收起。以下是属性触发器代码。
<trigger property="isexpanded" value="false">
<setter targetname="itemshost" property="visibility" value="collapsed" />
</trigger>2.4 在单元格模板中控件子项的展开与收起
为了达到展开和收起的效果,需要在首列的单元格中控制treelistviewitem的isexpanded属性。以下为完整代码。
<datatemplate x:key="expandcelltemplate">
<dockpanel>
<togglebutton
x:name="expander"
margin="{binding path=level, converter={staticresource levelindentconverter}, relativesource={relativesource ancestortype={x:type treelistviewitem}}}"
clickmode="press"
ischecked="{binding path=isexpanded, relativesource={relativesource ancestortype={x:type treelistviewitem}}}"
style="{staticresource expandcollapsetogglestyle}" />
<textblock text="{binding property1}" />
</dockpanel>
<datatemplate.triggers>
<datatrigger binding="{binding path=hasitems, relativesource={relativesource ancestortype={x:type treelistviewitem}}}" value="false">
<setter targetname="expander" property="visibility" value="hidden" />
</datatrigger>
</datatemplate.triggers>
</datatemplate>其关键代码为
ischecked="{binding path=isexpanded, relativesource={relativesource ancestortype={x:type treelistviewitem}}}"2.5 控件使用
<treelistview itemssource="{binding collection}">
<treelistview.itemtemplate>
<hierarchicaldatatemplate itemssource="{binding collection, isasync=true}" />
</treelistview.itemtemplate>
<treelistview.view>
<gridview>
<gridviewcolumn celltemplate="{staticresource expandcelltemplate}" header="property1" />
<gridviewcolumn displaymemberbinding="{binding property2}" header="property2" />
<gridviewcolumn displaymemberbinding="{binding property3}" header="property3" />
<gridviewcolumn displaymemberbinding="{binding property4}" header="property4" />
<gridviewcolumn displaymemberbinding="{binding property5}" header="property5" />
<gridviewcolumn displaymemberbinding="{binding property6}" header="property6" />
<gridviewcolumn displaymemberbinding="{binding property7}" header="property7" />
<gridviewcolumn displaymemberbinding="{binding property8}" header="property8" />
<gridviewcolumn displaymemberbinding="{binding property9}" header="property9" />
<gridviewcolumn displaymemberbinding="{binding property10}" header="property10" />
<gridviewcolumn displaymemberbinding="{binding property11}" header="property11" />
<gridviewcolumn displaymemberbinding="{binding property12}" header="property12" />
</gridview>
</treelistview.view>
</treelistview>以上就是wpf实现树形表格控件的示例代码的详细内容,更多关于wpf树形表格控件的资料请关注代码网其它相关文章!
发表评论