在创建Web方面,有很多地方都要涉及到层次数据的显示问题,最常见莫过与一个购物网站了,一件商品通常又包含很多相关信息:价格,介绍,图片等等,这就是具有层次关系的数据.本文的主题就是讲述如何使用ASP.NET(vb)来显示层次数据
    在本文中,将分两个部分来说明:

需要了解的相关知识 
嵌套数据控件显示层次数据  
   需要了解的相关知识
   1,创建具有多个表的DataSet(数据集),本文以SQL Server2000的Pubs数据库为例:
 import System.Data
 import System.Data.SqlClient
 dim objConn as new SqlConnection("Data Source=Localhost;Database=Pubs;user id=sa;password=sa")
 objConn.Open()
 dim strSql as string="Select * from jobs;Select * from employee"
 dim objAdapter as new SqlDataAdapter(strSql,objConn)
 dim objDs as new DataSet()
 objAdapter.Fill(objDs)
 objConn.Close()
'设定两个表的名称
 objDs.Tables(1).TalbeName="jobs"
 objDs.Tables(0).TableName="employee"
' 到此已经创建了具有两个表的dataset对象--objDs,但是这两个表之间还没有任何关系,现在我们需要为它创建关系
'为这两个表添加关系
 dim Parent as DataColumn=objDs.Tables("jobs").Columns("job_id")
 dim Child as DataColumn=objDs.Tables("employee").Columns("job_id")
 objDs.Relations.Add("RelationName",Parent,Child)
   2,导航表间关系,以简单的格式输出层次数据
  把上面的的两个表输出成如下格式:
  job1:
     employee1
     employee2
     ...
  job2:
     employee1
     employee12
     ...
  ....
[vb]
  dim ParentColumn
  dim ChildColumn
  for each ParentColumn in objDs.Tables("jobs").Rows
    Response.Write(ParentColumn("job_desc") & ":<br>")
    for each ChildColumn in ParentColumn.GetChildRows(RelationName)
       Response.Write(ChildColumn("lname") & "&nbsp;" & ChildColumn("fname") & "<br>")
    next
  next
  这个的例子输出的数据已经是有层次关系的了,但这个并不是本文的目的,在做开发web的时候我们并不能把逻辑代码嵌在html里
把代码和html分开正是ASP.NET和ASP的重要区别.在下一个部分,我们将结合上面学到的知识,使用ASP.NET提供的功能强大的数据控件Repeater,DataList,DataGrid在显示层次数据

 嵌套数据控件实现层次数据的显示
现在你可以使用Repeater嵌套DataList,或者DataList嵌套Repeater,简言之,你可以使用Repeater,DataList,DataGrid这三个控件的任意组合进行嵌套.在本文,将使用比较简单的Repeater嵌套Repeater做为例子,方便理解
请在上面的代码添加如下两句数据绑定的代码:
 ParentRepeater.DataSource=objDs.Tables("jobs").DefaultView
 ParentRepeater.DataBind()
然后在html里加入下面的代码:
 <asp:repeater id="ParentRepeater" runat="server">
  <itemtemplate>
     职位:<%# DataBinder.Eval(Container.DataItem,"job_desc") %><br>
       <asp:repeater id="ParentRepeater" runat="server" DataSource='<%# Ctype(Container.DataItem,DataRowView).CreateChildView("RelationName") %>'>
           <itemtemplate>
              职员:<%# DataBinder.Eval(Container.DataItem,"lname") %>
                   <%# DataBinder.Eval(Container.DataItem,"fname") %>
           </itemtemplate>    
       </asp:repeater>
   </itemtemplate>
 </asp:repeater>
本文旨在抛砖引玉,至于像DataList嵌套DataGrid的应用,原理大同小异,需要的是多发点时间多尝试不同的组合,你甚至可以尝试创建具有更多数据表以上的dataset,嵌套更多(层)以上的数据控件.

 

Another approach is to repeat an entire block of data. For example, if we define a relationship where the Customer has a list of contact information, for each customer we may want to show both the customer information and the nested, repeating contact information. Nesting a DataGrid inside of a DataList is a relatively low-impact way to achieve this result.

Figure 2 shows the results of nesting a DataGrid within our DataList. Before we talk about how to create this effect, let's take a moment to clear up some details. The first detail is that the DataGrid border was left in place to make it clear where the DataGrid was located. The second detail is that all of the contact information was intentionally initialized to the same value (e.g. Number=411 and Description=Information); displaying other values is simply a matter of modifying the relevant fields with code or user input. Finally, I used a strongly typed collection of customer objects and each Customer object contained a list of contact information. One could use any data structure that can be used as a DataSource for the DataList and DataGrid, such as a DataSet.


按此在新窗口打开图片

Figure 2: The nested DataGrid in the DataList. The DataGrid border was left in for clarity.

We already know how to create the ID, First Name, Last Name, and Picture columns; I re-used the code provided in Listing 1. The new part is the nested contact information. Let's talk about a convenient means of creating this effect.

To create the nested effect, do this:

Drop a DataGrid onto the Web Page containing the HTML table/DataList, but not intermixed with those controls. 
Use the Properties window to modify the properties of the DataGrid. For example, set AutoGenerateColumns to False and define the (DataBindings) property as shown in Figure 3. (Basically, the DataSource property was set to DataBinder.Eval(Container.DataItem, "ContactInformationList") where the Container.DataItem will be a Customer object.) 
Then, switch to the HTML view and add a new column to the ItemTemplate and SelectedItemTemplate using the data cell tag-pair <td></td>. 
In the HTML, find the definition of the DataGrid—it will begin with the tag <DataGrid> and end with a matching </DataGrid>. Paste the DataGrid definition in between the new <td></td> pair defined in the ItemTemplate and SelectedItemTemplate. 
Add the code that supplies the DataSource and calls DataBind in the code-behind, and you're ready to run the example. 

按此在新窗口打开图片
Figure 3: Use the DataBindings dialog to define the DataSource for the DataGrid.

For your perusal, the ItemTemplate containing the DataGrid—now playing the role of template—is provided in Listing 2 and the complete implementation of the Customer and ContactInformation is provided without comment in Listing 3.

Listing 2: The revised ItemTemplate, including the nested DataGrid definition (highlighted), section from Listing 1.

1:    <ItemTemplate>
2:  <tr height="30px">
3:    <td>
4:      <asp:LinkButton id="Linkbutton2" CommandName="Select"
                        runat="server">
5:        <%# DataBinder.Eval(Container.DataItem, "ID") %>
          </asp:LinkButton></td>
6:    <td>
7:      <asp:Label id="Label6" runat="server">
8:      <%# DataBinder.Eval(Container.DataItem, "FirstName") %>
        </asp:Label></td>
9:    <td>
10:     <asp:Label id="Label7" runat="server">
11:       <%# DataBinder.Eval(Container.DataItem, "LastName") %>
          </asp:Label></td>
12:   <td>
13:     <asp:Label id="Label8" runat="server">
14:       <%# DataBinder.Eval(Container.DataItem, "Email") %>
          </asp:Label></td>
15:   <td>
16:     <asp:DataGrid id=DataGrid1
17:       DataSource=
18:       '<%# DataBinder.Eval(Container.DataItem,
                               "ContactInformationList") %>'
19:       runat="server" AutoGenerateColumns="False">
20:       <Columns>
21:         <asp:BoundColumn DataField="ID" ReadOnly="True" 
22:           HeaderText="ID"></asp:BoundColumn>
23:         <asp:BoundColumn DataField="Number"
                             HeaderText="Number">
            </asp:BoundColumn>
24:         <asp:BoundColumn DataField="Description"
25:           HeaderText="Description"></asp:BoundColumn>
26:       </Columns>
27:     </asp:DataGrid> 
28:   </td>
29:   <td align="center" valign="center" rowspan="2">
30:     <asp:Image id="Image1" Height="60px" Width="40px"
31:       ImageUrl='<%# DataBinder.Eval(Container.DataItem,
                                        "Picture") %>'
32:       runat="server"></asp:Image>
33:   </td>
34: </tr>
35: <tr height="30px">
36:   <td></td>
37:   <td></td>
38:   <td></td>
39:   <td></td>
40: </tr>
41:   </ItemTemplate>

Listing 3: The strongly typed Customer and ContactInformation collections used for the samples.

Public Class CustomerList
  Inherits CollectionBase

  Public Shared Function GetCustomerList() As CustomerList
    Dim obj As CustomerList = New CustomerList
    obj.Add(New Customer(1, "Paul", "Kimmel", _
                            "pkimmel@softconcept.com", _
                            "~/Images/PaulKimmel.jpg"))
    obj.Add(New Customer(2, "Brad", "Jones", ""))
    obj.Add(New Customer(3, "Robert", "Golieb", ""))
    obj.Add(New Customer(4, "Geoff", "Caylor", ""))
    obj.Add(New Customer(5, "Joe 'Bilbo'", "Shook", ""))
    Return obj
  End Function

  Default Public Property Item(ByVal Index As Integer) As Customer
  Get
    Return CType(List(Index), Customer)
  End Get
  Set(ByVal Value As Customer)
    List(Index) = Value
  End Set
  End Property

  Public Function Add(ByVal Value As Customer) As Integer
    Return List.Add(Value)
  End Function

End Class

Public Class Customer
  Private FID As Integer
  Private FFirstName As String
  Private FLastName As String
  Private FEmail As String
  Private FPicture As String
  Private FContactInformationList As ContactInformationList

  Public Sub New()
  End Sub

  Public Sub New(ByVal ID As Integer, ByVal FirstName As String, _
    ByVal LastName As String, ByVal EMail As String, _
    Optional ByVal Picture As String = "~/Images/Unavailable.jpg")

    FID = ID
    FFirstName = FirstName
    FLastName = LastName
    FEmail = EMail
    FPicture = Picture

    FContactInformationList = _
     ContactInformationList.GetContactInformationList(ID)
  End Sub


  Public Property ID() As Integer
  Get
    Return FID
  End Get
  Set(ByVal Value As Integer)
    FID = Value
  End Set
  End Property

  Public Property FirstName() As String
  Get
    Return FFirstName
  End Get
  Set(ByVal Value As String)
    FFirstName = Value
  End Set
  End Property

  Public Property LastName() As String
  Get
    Return FLastName
  End Get
  Set(ByVal Value As String)
    FLastName = Value
  End Set
  End Property

  Public Property Email() As String
  Get
    Return FEmail
  End Get
  Set(ByVal Value As String)
    FEmail = Value
  End Set
  End Property

  Public Property Picture() As String
  Get
    Return FPicture
  End Get
  Set(ByVal Value As String)
    FPicture = Value
  End Set
  End Property

  Public ReadOnly Property ContactInformationList() _
         As ContactInformationList
  Get
    Return FContactInformationList
  End Get
  End Property

End Class

Public Class ContactInformationList
    Inherits CollectionBase

  Public Shared Function GetContactInformationList(ByVal ID _
         As Integer) As ContactInformationList
    Dim obj As ContactInformationList = New ContactInformationList
    obj.Add(New ContactInformation(ID, "411", "Information"))
    obj.Add(New ContactInformation(ID, "www.yahoo.com", _
                                   "People Search"))
    Return obj
  End Function

  Default Public Property Item(ByVal Index As Integer) _
          As ContactInformation
  Get
    Return CType(List(Index), ContactInformation)
  End Get
  Set(ByVal Value As ContactInformation)
    List(Index) = Value
  End Set
  End Property

  Public Function Add(ByVal Value As ContactInformation) As Integer
    Return List.Add(Value)
  End Function

End Class

Public Class ContactInformation
  Private FID As Integer
  Private FNumber As String
  Private FDescription As String

  Public Sub New(ByVal ID As Integer, ByVal Number As String, _
    ByVal Description As String)
      FID = ID
      FNumber = Number
      FDescription = Description
  End Sub

  Public Property ID() As Integer
  Get
    Return FID
  End Get
  Set(ByVal Value As Integer)
    FID = Value
  End Set
  End Property

  Public Property Number() As String
  Get
    Return FNumber
  End Get
  Set(ByVal Value As String)
    FNumber = Value
  End Set
  End Property

  Public Property Description() As String
  Get
    Return FDescription
  End Get
  Set(ByVal Value As String)
    FDescription = Value
  End Set
  End Property
End Class

It is worth noting that if we don't use the HTML table interspersed with the DataList, much, if not all, of the editing of the DataList and DataGrid can occur visually. As with a lot of programming, unfortunately, the most advanced things still have to be done by hand.

Templating with a UserControl
An excellent way to separate tasks and make managing the code-behind easier is to heavily employ UserControls. For example, what if we wanted to programmatically interact with the nested DataGrid? Unfortunately, DataGrid1 doesn't actually exist in the code-behind. When we cut the DataGrid from the page and pasted it in the template, we have a template definition for the grid, but each row creates a grid dynamically. Getting to those templated, dynamic grids is messy and time consuming. We have to implement events for the DataList and invoke FindControl or rely on the literal column position. This results in complicated code, dynamic binding of events, and slower performance; calling FindControl incurs some overhead.

There is a better way. If we place the DataGrid on its own UserControl, we can modify the definition of the DataGrid and add extra code at any time. By placing the UserControl containing the DataGrid in the DataList, we get the same visual effect as if we placed the grid in the DataList directly, yet any modifications to the separate UserControl-with-DataGrid are easier to effect and show up immediately in the DataList. The net benefit is that our DataList's HTML is much simpler, the same visual result can be affected, the code-behind for the DataGrid can be implemented at design time much more easily, and the UserControl can be used in other contexts. The DataList, then, simply devolves to a mechanism for repeating the template section.

There is a drawback using the DataList with a nested DataGrid approach: editing nested DataGrid seems to be broken. If you invoke an edit behavior on a DataGrid, your code will receive the Edit behavior but not subsequent events, such as Update. This is a problem. Fortunately, there is a solution and the clue resides in the role that our DataList has been relegated too in the last paragraph.