65.9K
CodeProject 正在变化。 阅读更多。
Home

模板化分层重复器控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.12/5 (10投票s)

2009 年 3 月 5 日

CPOL

7分钟阅读

viewsIcon

46773

downloadIcon

1022

用于创建 TreeViews 和显示分层数据的模板化数据绑定控件。

引言

我曾经需要实现一个 treeview 类型的控件,并且有一些非常棘手的 CSS 要求。我查看了 ASP.NET TreeView 和相应的 CSS Friendly Adapter,但我仍然无法按照我需要的方式调整布局。内置 TreeView 的问题在于无法控制 HTML 的渲染方式。我想要的是一个模板化的 treeview,这样我就可以在 aspx 页面上快速轻松地更改 HTML 输出,而无需编写大量代码和重新编译。

这就是 HierarchicalRepeater 的作用。它有一个 ItemTemplate,与 Repeater 控件的 ItemTemplate 类似,您可以在其中指定每个数据项的 HTML 输出。

您可以通过本文下载的示例项目展示了这个控件作为 TreeView 的工作示例,并提供了一个 ASP.NET 实现的 Nested Sortable jQuery 插件,该插件允许拖放式地重新排序树中的数据项。 

使用控件

在您的网站中添加对 DaveControls.dll 的引用,该文件可在下载的项目中找到。在 aspx 页面的顶部添加以下行,以注册该控件以供您的页面使用:

<%@ Register Assembly="DaveControls"
    Namespace="DaveControls.HierarchicalControls" TagPrefix="hc" %>

然后,您可以将 HierarchicalRepeater 控件放置在 aspx 页面的任何位置,如下所示:

<hc:HierarchicalRepeater runat="server" ID="SimpleHierarchicalRepeater">

    <HeaderTemplate>
        <p>This is the repeater header</p>
        <ul class="indent">
    </HeaderTemplate>

    <ItemTemplate>
        <li>
            <%# DataBinder.Eval(Container.DataItem, "Text") %>
            <asp:PlaceHolder ID="ChildrenPlaceholder" runat="server" />
        </li>
    </ItemTemplate>

    <FooterTemplate>
        </ul>
        <p>This is the repeater footer</p>
    </FooterTemplate>

    <ChildrenHeaderTemplate>
        <ul>
    </ChildrenHeaderTemplate>

    <ChildrenFooterTemplate>
        </ul>
    </ChildrenFooterTemplate>

</hc:HierarchicalRepeater>

控件的主要部分需要注意以下几点:

HeaderTemplate

这是控件的头部。这部分将在所有内容之前渲染。在此示例中,我希望将内容渲染为带有 indent CSS 类的无序列表,因此我在头部包含以下内容:

<ul class="indent">

ItemTemplate

这是为每个数据项渲染的模板。您可以像在 RepeaterItemTemplate 中一样,绑定到底层数据项对象的属性。我使用以下代码绑定到了 Text 属性:

<%# DataBinder.Eval(Container.DataItem, "Text") %>

ItemTemplate 中还有一个 Placeholder 控件,它将包含每个数据项的子项。子数据项将使用与 ItemTemplate 相同的模板进行渲染。Placeholder ID 必须设置为 ChildrenPlaceholder 才能渲染子项。

<asp:PlaceHolder ID="ChildrenPlaceholder" runat="server" />

FooterTemplate

这将在控件的底部渲染。我需要关闭在 HeaderTemplate 中打开的无序列表,因此我包含以下内容:

</ul>

ChildrenHeaderTemplate

这将渲染在子数据项列表的顶部。在此示例中,我希望所有子列表都渲染为无 CSS 类的无序列表,因此我在 ChildrenHeaderTemplate 中使用以下内容:

<ul>

ChildrenFooterTemplate

这将渲染在子项列表的底部。在此示例中,我只想关闭我的无序列表,因此我使用以下内容:

</ul>

数据绑定

控件要显示的数据项必须继承自 DaveControls.HierarchicalControls.Data.HierarchyDataItemBase 类,该类可在本文下载的项目中找到。这个基类管理数据的分层结构并提供了一些定位数据项的方法。因此,您创建的派生类只需要包含您希望在控件中显示的属性。

我将使用以下非常简单的示例。派生类仅包含一个名为 Text 的属性,该属性在上方的 ItemTemplate 中显示:

private class SimpleTestData : HierarchyDataItemBase // inherits from base class
{
    private string _text;
    public string Text
    {
        get { return _text; }
        set { _text = value; }
    }

    public SimpleTestData(string text)
    {
        _text = text;
    }
}

HierarchyDataItemBase 实现 ICollection 并充当其子项的容器。以下代码通过创建一个根对象 testDataSource 并使用 Add 方法向其添加子项来构建 SimpleTestData 对象的 {0} 数据层次结构。

SimpleTestData testDataSource = new SimpleTestData("rootItem");

testDataSource.Add(new SimpleTestData("Pie"));
testDataSource.Add(new SimpleTestData("Cake"));
SimpleTestData item3 = new SimpleTestData("Fruit");

SimpleTestData item3a = new SimpleTestData("Apples");
SimpleTestData item3b = new SimpleTestData("Pears");
SimpleTestData item3c = new SimpleTestData("Oranges");

item3.Add(item3a);
item3.Add(item3b);
item3.Add(item3c);

SimpleTestData item3a1 = new SimpleTestData("Granny Smith");
SimpleTestData item3a2 = new SimpleTestData("Golden Delicious");

item3a.Add(item3a1);
item3a.Add(item3a2);

testDataSource.Add(item3);

testDataSource.Add(new SimpleTestData("Crisps"));

可以使用以下代码将 HierarchicalRepeater 控件绑定到上述对象:

SimpleHierarchicalRepeater.DataSource = testDataSource;
SimpleHierarchicalRepeater.DataBind();

渲染的 HTML 输出

控件的 HTML 输出如下:

<p>This is the repeater header</p>
<ul class="indent">
    <li>Pie</li>
    <li>Cake</li>
    <li>Fruit
        <ul>
            <li>Apples
                <ul>
                    <li>Granny Smith</li>
                    <li>Golden Delicious</li>
                </ul>
            </li>
            <li>Pears</li>
            <li>Oranges</li>
        </ul>
    </li>
    <li>Crisps</li>
</ul>
<p>This is the repeater footer</p>

访问数据项 - 有用属性

上面的示例展示了如何仅使用该控件来显示数据。但是,如何访问绑定到控件的各个项呢?首先,让我们看看控件中每个节点可用的、描述节点在数据层次结构中位置的一些属性。

GlobalIndex

每个项从上到下的整数索引,无论其在层次结构中的嵌套程度如何。

SiblingIndex

每个项相对于其同级项的整数索引,同级项是指同一嵌套级别、具有相同父项的其他项。

Path

一个从根目录到每个元素的相对路径,由其同级索引及其父项的同级索引组成。

一头雾水?这里有一个例子可以帮助说明每个属性。下面的代码显示了 ItemTemplate 以及输出上述属性的语法。

<ItemTemplate>
    <li><strong><%# DataBinder.Eval(Container.DataItem, "Text") %></strong>
        GlobalIndex: <%# Container.GlobalIndex %>,
        SiblingIndex: <%# Container.SiblingIndex %>,
        Path: <%# Container.Path %>
    <asp:PlaceHolder ID="ChildrenPlaceholder" runat="server" />
    </li>
</ItemTemplate>

下面显示了控件绑定到与上一个示例相同的数据源:

  • Pie GlobalIndex: 0, SiblingIndex: 0, Path: /0
  • Cake GlobalIndex: 1, SiblingIndex: 1, Path: /1
  • Fruit GlobalIndex: 2, SiblingIndex: 2, Path: /2
    • Apples GlobalIndex: 3, SiblingIndex: 0, Path: /2/0
      • Granny Smith GlobalIndex: 4, SiblingIndex: 0, Path: /2/0/0
      • Golden Delicious GlobalIndex: 5, SiblingIndex: 1, Path: /2/0/1
    • Pears GlobalIndex: 6, SiblingIndex: 1, Path: /2/1
    • Oranges GlobalIndex: 7, SiblingIndex: 2, Path: /2/2
  • Crisps GlobalIndex: 8, SiblingIndex: 3, Path: /3

事件

下面显示了已实现的事件。当事件触发时,事件处理程序会收到一个事件参数对象,该对象描述了控件中哪个特定节点触发了事件。

OnItemCommand

Repeater 控件的 OnItemCommand 相同。例如,当单击 LinkButton 或类似控件时触发。事件处理程序接收一个类型为 HierarchicalRepeaterCommandEventArgs 的事件参数对象。

<hc:HierarchicalRepeater ID="HierarchicalRepeater1" runat="server"
    OnItemCommand="PropertiesHierarchicalRepeater_ItemCommand">
...
// event handler
protected void PropertiesHierarchicalRepeater_ItemCommand(object sender,
    DaveControls.HierarchicalControls.HierarchicalRepeaterCommandEventArgs e)
{
    // can access properties for each item as follows

    int globalIndex = e.Item.GlobalIndex;
    int siblingIndex = e.Item.SiblingIndex;
    string path = e.Item.Path;
}

OnItemCreated

在控件中的项创建时触发,在数据绑定之前。事件处理程序接收一个类型为 HierarchicalRepeaterEventArgs 的事件参数对象。

<hc:HierarchicalRepeater ID="HierarchicalRepeater1" runat="server"
    OnItemCreated="PropertiesHierarchicalRepeater_ItemCreated">
...
// event handler
protected void PropertiesHierarchicalRepeater_ItemCreated(object sender,
    DaveControls.HierarchicalControls.HierarchicalRepeaterEventArgs e)
{
    // can access properties for each item in the same way as before

    int globalIndex = e.Item.GlobalIndex;
    int siblingIndex = e.Item.SiblingIndex;
    string path = e.Item.Path;
}

OnItemDataBound

在项数据绑定后触发。此事件的事件处理程序接收与 OnItemDataBound (HierarchicalRepeaterEventArgs) 相同的事件参数对象类型。

<hc:HierarchicalRepeater ID="HierarchicalRepeater1" runat="server"
    OnItemDataBound="PropertiesHierarchicalRepeater_ItemDataBound">
...
// event handler
protected void PropertiesHierarchicalRepeater_ItemDataBound(object sender,
    DaveControls.HierarchicalControls.HierarchicalRepeaterEventArgs e)
{
    // can access properties for each item in the same way as before

    int globalIndex = e.Item.GlobalIndex;
    int siblingIndex = e.Item.SiblingIndex;
    string path = e.Item.Path;
}

使用属性从数据源访问数据项

上述部分描述了用于控件节点、提供其在数据层次结构中位置的有用属性。这些属性可用于在数据源中查找相应的数据项。以下方法是 HierarchyDataItemBase 类的一部分,可以通过它们检索数据源中的数据项。

HierarchyDataItemBase GetItem(string path)

通过 Path 返回 HierarchyDataItemBase 对象。

// Executing this code on the data source in our example, retrievedItem
// will be the HierarchyDataItemBase object for the item "Granny Smith"

HierarchyDataItemBase retrievedItem = testDataSource.GetItem("/2/0/0");

HierarchyDataItemBase GetItem(int globalIndex)

通过 GlobalIndex 返回 HierarchyDataItemBase 对象。

// This code will also retrieve the item "Granny Smith", but this time
// uses the globalIndex rather than its path

HierarchyDataItemBase retrievedItem = testDataSource.GetItem(4);

创建可展开和折叠的树视图

最好直接查看下载样本中的 TreeView.aspx 页面,以了解工作中的 treeview 的示例。

样本显示了 div 元素如何通过 JavaScript onclick 事件制作展开和折叠按钮。子元素的列表被赋予了具有 GlobalIndex 属性的 id 值,以使其唯一,从而可以根据点击的展开/折叠 div 进行显示/隐藏。

树视图中节点的展开和折叠完全通过客户端 JavaScript 完成。为了持久化用户的更改,节点的展开/折叠状态被序列化为一个 string 并放入一个 HiddenField 控件中。然后,服务器端读取此序列化值,以使用新的展开/折叠状态更新数据源,从而可以重新绑定 HierarchicalRepeater 控件。

Nested Sortable ASP.NET 实现

Nested Sortable jQuery 插件 (非我所作) 是一个 JavaScript 插件,它允许通过拖放来重新组织树视图的项。样本项目中的 NestedSortable.aspx 页面展示了如何将 NestedSortable JavaScript 与 HierarchicalRepeater 控件集成以实现拖放排序。

为了在回发时持久化更改,使用了与 TreeView 样本类似的方法:

  1. 当用户移动项时,将序列化后的更改放入隐藏字段(JavaScript 插件已提供此功能)。
  2. 通过从重新排序之前的数据源中提取选定的数据项来执行 ItemCommand 事件。
  3. 使用用户所做的更改重新排序数据源,这些更改已在隐藏字段中序列化。
  4. 将重新排序的数据源存储在页面的 ViewState 中。
  5. 使用重新排序的数据源数据绑定 HierarchicalRepeater

同样,最好查看 NestedSortable.aspx.cs 文件以更清楚地了解其工作原理。

结论

HierarchicalRepeater 控件是一个模板化数据绑定控件,类似于 Repeater,它提供了一种快速简便的方法来显示分层数据。它恢复了在使用内置 ASP.NET TreeView 控件时丢失的 HTML 渲染控制权。

本文附带的可下载项目中提供的示例展示了该控件如何用于实现功能齐全的 TreeView 控件和嵌套可排序列表。 

历史

  • 2009 年 3 月 5 日:初始发布
© . All rights reserved.