Microsoft

大多数 ASP.NET 应用程序依赖于来自后端数据源的某种程度的数据呈现。 数据绑定控件一直是与动态 Web 应用程序中的数据交互的关键部分。 ASP.NET 2.0 对数据绑定控件引入了一些实质性改进,包括新的 BaseDataBoundControl 类和声明性语法。

大多数 ASP.NET 应用程序依赖于来自后端数据源的某种程度的数据呈现。 数据绑定控件一直是与动态 Web 应用程序中的数据交互的关键部分。 ASP.NET 2.0 对数据绑定控件引入了一些实质性改进,包括新的 BaseDataBoundControl 类和声明性语法。

BaseDataBoundControl 充当 DataBoundControl 类和 HierarchicalDataBoundControl 类的基类。 在本模块中,我们将讨论派生自 DataBoundControl 的以下类:

  • AdRotator
  • GridView
  • FormView
  • DetailsView
  • 我们还将讨论派生自 HierarchicalDataBoundControl 类的以下类:

  • TreeView
  • SiteMapPath
  • DataBoundControl 类

    DataBoundControl 类是一个抽象类, (VB) 中标记为 MustInherit,用于与表格或列表样式数据交互。 以下控件是派生自 DataBoundControl 的一些控件。

    AdRotator

    使用 AdRotator 控件,可以在链接到特定 URL 的网页上显示图形横幅。 显示的图形使用 控件的属性进行旋转。 可以使用 Impressions 属性配置特定广告在页面上显示的频率,并且可以使用关键字 (keyword) 筛选来筛选广告。

    AdRotator 控件使用数据库中的 XML 文件或表来获取数据。 XML 文件中使用以下属性来配置 AdRotator 控件。

    ImageUrl

    要为广告显示的图像的 URL。

    单击广告时用户应将其带到的 URL。 这应该经过 URL 编码。

    AlternateText

    在工具提示中显示并由屏幕阅读器读取的备用文本。 当 ImageUrl 指定的图像不可用时,也会显示。

    定义在使用关键字 (keyword) 筛选时可以使用的关键字 (keyword) 。 如果指定,则仅显示关键字 (keyword) 与关键字 (keyword) 筛选器匹配的广告。

    一个加权数字,用于确定特定广告可能显示的频率。 它相对于同一文件中其他广告的印象。 XML 文件中所有广告的集体印象的最大值为 2,048,000,000 1。

    广告的高度(以像素为单位)。

    广告的宽度(以像素为单位)。

    Height 和 Width 属性覆盖 AdRotator 控件本身的高度和宽度。

    典型的 XML 文件可能如下所示:

    <?xml version="1.0" encoding="utf-8" ?> <Advertisements xmlns="http://schemas.microsoft.com/AspNet/AdRotator-Schedule-File"> <Ad> <ImageUrl>~/images/Contoso_ad.gif</ImageUrl> <NavigateUrl>http://www.contoso-ltd.com</NavigateUrl> <AlternateText>Ad for Contoso, Ltd. Web site</AlternateText> <Impressions>100</Impressions> </Ad> <Ad> <ImageUrl>~/images/Aspnet_ad.gif</ImageUrl> <NavigateUrl>http://www.asp.net</NavigateUrl> <AlternateText>Ad for ASP.NET Web site</AlternateText> <Impressions>50</Impressions> </Ad> </Advertisements>
    

    在上面的示例中,Contoso 的广告显示的可能性是 ASP.NET 网站的广告的两倍,因为 Impressions 属性的值。

    若要显示上述 XML 文件中的广告,请将 AdRotator 控件添加到页面,并将 AdvertisingFile 属性设置为指向 XML 文件,如下所示:

    <asp:AdRotator ID="AdRotator1" runat="server" AdvertisementFile="App_Data/Ads.xml" />
    

    如果选择使用数据库表作为 AdRotator 控件的数据源,则首先需要使用以下架构设置数据库:

    Data type AlternateText nvarchar (length) 如果找不到图像,则显示的文本。 在某些浏览器中,文本显示为工具提示。 备用文本还用于辅助功能,以便无法看到图形的用户可以听到其说明大声朗读。 nvarchar (length) 页面可以筛选的广告类别。 int (4) 一个数字,指示广告显示频率的可能性。 数字越大,广告的显示频率就越高。 XML 文件中所有展示值的总和不能超过 2,048,000,000 - 1。 int (4) 图像的宽度(以像素为单位)。 int (4) 图像的高度(以像素为单位)。

    如果已有具有不同架构的数据库,可以使用 AlternateTextFieldImageUrlFieldNavigateUrlField 属性将 AdRotator 属性映射到现有数据库。 若要在 AdRotator 控件中显示数据库中的数据,请将数据源控件添加到页面,将数据源控件的连接字符串配置为指向数据库,并将 AdRotator 控件的 DataSourceID 属性设置为数据源控件的 ID。 如果需要以编程方式配置 AdRotator 广告,请使用 AdCreated 事件。 AdCreated 事件采用两个参数:一个对象,另一个是 AdCreatedEventArgs 的实例。 AdCreatedEventArgs 是对所创建广告的引用。

    以下代码片段以编程方式为广告设置 ImageUrl、NavigateUrl 和 AlternateText:

    protected void AdRotator1_AdCreated(object sender, System.Web.UI.WebControls.AdCreatedEventArgs e) { e.ImageUrl = "images/contoso_ad.gif"; e.NavigateUrl = "http://www.contoso-ltd.com/"; e.AlternateText = "Ad for Contoso, Ltd Web site"; }
    

    列表控件包括 ListBox、DropDownList、CheckBoxList、RadioButtonList 和 BulletedList。 其中每个控件都可以是绑定到数据源的数据。 它们使用数据源中的一个字段作为显示文本,并且可以选择使用第二个字段作为项的值。 还可以在设计时静态添加项,并且可以混合使用静态项和从数据源添加的动态项。

    若要数据绑定列表控件,请将数据源控件添加到页面。 为数据源控件指定 SELECT 命令,然后将列表控件的 DataSourceID 属性设置为数据源控件的 ID。 使用 DataTextFieldDataValueField 属性定义控件的显示文本和值。 此外,可以使用 DataTextFormatString 属性控制显示文本的外观,如下所示:

    价格: {0:C} 对于数值/小数数据。 显示文本“Price:”,后跟货币格式的数字。 货币格式取决于 Page 指令或 Web.config 文件中的 culture 属性中指定的区域性设置。 {0:D4} 对于整数数据。 不能与十进制数字一起使用。 整数显示在 4 个字符宽的零填充字段中。 {0:N2}% 对于数值数据。 显示具有 2 位小数位数的数字,后跟文本“%”。 {0:000.0} 对于数值/小数数据。 数字四舍五入到小数点后一位。 不到三位的数字用零填充。 {0:D} 对于日期/时间数据。 显示长日期格式 (“星期四,1996 年 8 月 6 日”) 。 日期格式取决于页面或 Web.config 文件的区域性设置。 {0:d} 对于日期/时间数据。 显示短日期格式 (“12/31/99”) 。 {0:yy-MM-dd} 对于日期/时间数据。 以数字年月天格式显示日期 (96-08-06)

    GridView

    GridView 控件允许使用声明性方法显示和编辑表格数据,并且是 DataGrid 控件的后续版本。 GridView 控件中提供了以下功能。

  • 绑定到数据源控件,例如 SqlDataSource。
  • 内置排序功能。
  • 内置更新和删除功能。
  • 内置分页功能。
  • 内置行选择功能。
  • 以编程方式访问 GridView 对象模型,以动态设置属性、处理事件等。
  • 多个键字段。
  • 超链接列的多个数据字段。
  • 可通过主题和样式自定义外观。
  • GridView 控件中的每个列都由 DataControlField 对象表示。 默认情况下,AutoGenerateColumns 属性设置为 true,这将为数据源中的每个字段创建一个 AutoGeneratedField 对象。 然后,每个字段在 GridView 控件中呈现为列,其顺序是每个字段在数据源中的显示顺序。 还可以手动控制 GridView 控件中显示的列字段,方法是将 AutoGenerateColumns 属性设置为 false ,然后定义自己的列字段集合。 不同的列字段类型确定控件中列的行为。

    下表列出了可以使用的不同列字段类型。

    列字段类型

    若要以声明方式定义列字段集合,请先在 GridView 控件的开始标记和结束<>标记之间添加开始和结束列标记。 接下来,列出要在开始和结束 <列标记之间包括的列> 字段。 指定的列将按列出的顺序添加到 Columns 集合中。 Columns 集合存储控件中的所有列字段,并允许以编程方式管理 GridView 控件中的列字段。

    显式声明的列字段可以与自动生成的列字段组合显示。 当同时使用这两个字段时,首先呈现显式声明的列字段,然后呈现自动生成的列字段。

    绑定到数据源控件

    GridView 控件可以绑定到数据源控件 ((例如 SqlDataSourceObjectDataSource 等) )以及实现 System.Collections.IEnumerable 接口 ((如 System.Data.DataView、System.Collections.ArrayList 或 System.Collections.Hashtable) )的任何数据源。 使用以下方法之一将 GridView 控件绑定到相应的数据源类型:

  • 若要绑定到数据源控件,请将 GridView 控件的 DataSourceID 属性设置为数据源控件的 ID 值。 GridView 控件自动绑定到指定的数据源控件,并可以利用数据源控件的功能来执行排序、更新、删除和分页功能。 这是绑定到数据的首选方法。
  • 若要绑定到实现 System.Collections.IEnumerable 接口的数据源,请以编程方式将 GridView 控件的 DataSource 属性设置为数据源,然后调用 DataBind 方法。 使用此方法时,GridView 控件不提供内置的排序、更新、删除和分页功能。 需要自己提供此功能。
  • GridView 控件操作

    GridView 控件提供了许多内置功能,允许用户对控件中的项进行排序、更新、删除、选择和分页。 当 GridView 控件绑定到数据源控件时,GridView 控件可以利用数据源控件的功能,并提供自动排序、更新和删除功能。

    GridView 控件可以支持对其他类型的数据源进行排序、更新和删除;但是,需要为这些操作提供相应的事件处理程序实现。

    排序允许用户通过单击列的标题,对 GridView 控件中的项进行特定列的排序。 若要启用排序,请将 AllowSorting 属性设置为 true

    单击 ButtonFieldTemplateField 列字段中的按钮(命令名称分别为“编辑”、“删除”和“选择”)时,将启用自动更新、删除和选择功能。 如果 AutoGenerateEditButton、AutoGenerateDeleteButton 或 AutoGenerateSelectButton 属性分别设置为 true,GridView 控件可以自动添加带有“编辑”、“删除”或“选择”按钮的 CommandField 列字段。

    GridView 控件不直接支持将记录插入数据源。 但是,可以通过将 GridView 控件与 DetailsView 或 FormView 控件结合使用来插入记录。

    GridView 控件可以自动将记录分解为页,而不是同时显示数据源中的所有记录。 若要启用分页,请将 AllowPaging 属性设置为 true

    自定义 GridView 的外观

    可以通过为控件的不同部分设置样式属性来自定义 GridView 控件的外观。 下表列出了不同的样式属性。

    Style 属性 RowStyle GridView 控件中数据行的样式设置。 如果还设置了 AlternatingRowStyle 属性,则数据行在 RowStyle 设置和 AlternatingRowStyle 设置之间交替显示。 SelectedRowStyle GridView 控件中所选行的样式设置。

    还可以显示或隐藏控件的不同部分。 下表列出了控制显示或隐藏哪些部分的属性。

    FormView

    FormView 控件用于显示数据源中的单个记录。 它类似于 DetailsView 控件,只不过它显示用户定义的模板而不是行字段。 创建自己的模板可以更灵活地控制数据的显示方式。 FormView 控件支持以下功能:

  • 绑定到数据源控件,例如 SqlDataSource 和 ObjectDataSource。
  • 内置插入功能。
  • 内置的更新和删除功能。
  • 内置分页功能。
  • 以编程方式访问 FormView 对象模型,以动态设置属性、处理事件等。
  • 通过用户定义的模板、主题和样式自定义外观。
  • 要使 FormView 控件显示内容,需要为控件的不同部分创建模板。 大多数模板都是可选的;但是,必须为配置控件的模式创建模板。 例如,支持插入记录的 FormView 控件必须定义插入项模板。 下表列出了可以创建的不同模板。

    编辑项模板和插入项模板中的输入控件可以使用双向绑定表达式绑定到数据源的字段。 这允许 FormView 控件自动提取输入控件的值进行更新或插入操作。 双向绑定表达式还允许编辑项模板中的输入控件自动显示原始字段值。

    绑定到数据

    FormView 控件可以绑定到数据源控件 ((例如 SqlDataSource、AccessDataSource、 ObjectDataSource 等) ),也可以绑定到实现 System.Collections.IEnumerable 接口的任何数据源 (,例如 System.Data.DataView、System.Collections.ArrayList 和 System.Collections.Hashtable) 。 使用以下方法之一将 FormView 控件绑定到相应的数据源类型:

  • 若要绑定到数据源控件,请将 FormView 控件的 DataSourceID 属性设置为数据源控件的 ID 值。 FormView 控件自动绑定到指定的数据源控件,并可以利用数据源控件的功能来执行插入、更新、删除和分页功能。 这是绑定到数据的首选方法。
  • 若要绑定到实现 System.Collections.IEnumerable 接口的数据源,请以编程方式将 FormView 控件的 DataSource 属性设置为数据源,然后调用 DataBind 方法。 使用此方法时,FormView 控件不提供内置的插入、更新、删除和分页功能。 需要使用适当的事件来提供此功能。
  • FormView 控件操作

    FormView 控件提供了许多内置功能,允许用户更新、删除、插入和翻页控件中的项。 将 FormView 控件绑定到数据源控件时,FormView 控件可以利用数据源控件的功能,并提供自动更新、删除、插入和分页功能。 FormView 控件可为其他类型的数据源的更新、删除、插入和分页操作提供支持;但是,必须提供相应的事件处理程序以及这些操作的实现。

    由于 FormView 控件使用模板,因此它不提供自动生成命令按钮以执行更新、删除或插入操作的方法。 必须在相应的模板中手动包括这些命令按钮。 FormView 控件可识别某些按钮,这些按钮的 CommandName 属性设置为特定值。 下表列出了 FormView 控件可识别的命令按钮。

    Button Commandname 值 “Page” 在分页操作中用于表示执行分页的页导航行中的按钮。 若要指定分页操作,请将按钮的 CommandArgument 属性设置为“Next”、“Prev”、“First”、“Last”或要导航到的页面的索引。 引发 PageIndexChanging 和 PageIndexChanged 事件。 用于更新操作,以尝试使用用户提供的值更新数据源中显示的记录。 引发 ItemUpdating 和 ItemUpdated 事件。

    与“删除”按钮 (不同,单击“编辑”或“新建”按钮时,FormView 控件将分别进入编辑或插入模式) 。 在编辑模式下,为当前数据项显示 EditItemTemplate 属性中包含的内容。 通常,编辑项模板的定义是,将“编辑”按钮替换为“更新”和“取消”按钮。 通常还显示适合字段数据类型 (的输入控件,例如 TextBox 或 CheckBox 控件) ,并显示字段的值供用户修改。 单击“更新”按钮可更新数据源中的记录,同时单击“取消”按钮将放弃任何更改。

    同样,当控件处于插入模式时,将针对数据项显示 InsertItemTemplate 属性中包含的内容。 插入项模板通常定义为“新建”按钮替换为“插入”和“取消”按钮,并显示空输入控件,以便用户输入新记录的值。 单击“插入”按钮会将记录插入数据源中,而单击“取消”按钮将放弃任何更改。

    FormView 控件提供分页功能,允许用户导航到数据源中的其他记录。 启用后,页导航行将显示在包含页面导航控件的 FormView 控件中。 若要启用分页,请将 AllowPaging 属性设置为 true。 可以通过设置 PagerStyle 和 PagerSettings 属性中包含的对象的属性来自定义寻呼行。 可以使用 PagerTemplate 属性创建自己的 UI,而不是使用内置的页导航行 UI。

    自定义 FormView 的外观

    可以通过为控件的不同部分设置样式属性来自定义 FormView 控件的外观。 下表列出了不同的样式属性。

    Style 属性 ItemDeleted 当“删除”按钮 (其 CommandName 属性设置为“删除”的按钮时发生,) 单击,但在 FormView 控件从数据源中删除记录之后。 此事件通常用于检查删除操作的结果。 ItemDeleting 单击“删除”按钮,但在 FormView 控件从数据源中删除记录之前发生。 此事件通常用于取消删除操作。 ItemInserted 当“插入”按钮 (其 CommandName 属性设置为“Insert”的按钮时发生,) 单击,但在 FormView 控件插入记录之后。 此事件通常用于检查插入操作的结果。 ItemInserting 单击“插入”按钮,但在 FormView 控件插入记录之前发生。 此事件通常用于取消插入操作。 ItemUpdated 当“更新”按钮 (其 CommandName 属性设置为“Update”的按钮时发生,) 单击,但在 FormView 控件更新行之后。 此事件通常用于检查更新操作的结果。 ItemUpdating 单击“更新”按钮时,但在 FormView 控件更新记录之前发生。 此事件通常用于取消更新操作。 ModeChanged 在 FormView 控件将模式 (更改为编辑、插入或只读模式) 之后发生。 此事件通常用于在 FormView 控件更改模式时执行任务。 在 FormView 控件将模式更改为编辑、插入或只读模式 () 之前发生。 此事件通常用于取消模式更改。 PageIndexChanged 单击其中一个寻呼按钮,但在 FormView 控件处理分页操作之后发生。 当用户导航到控件中的其他记录后需要执行任务时,通常使用此事件。 PageIndexChanging 单击其中一个寻呼按钮时,但在 FormView 控件处理分页操作之前发生。 此事件通常用于取消分页操作。

    DetailsView

    DetailsView 控件用于在表中显示数据源中的单个记录,其中记录的每个字段都显示在表中的一行中。 它可以与 GridView 控件结合使用,用于主-详细信息方案。 DetailsView 控件支持以下功能:

  • 绑定到数据源控件,例如 SqlDataSource。
  • 内置插入功能。
  • 内置更新和删除功能。
  • 内置分页功能。
  • 以编程方式访问 DetailsView 对象模型,以动态设置属性、处理事件等。
  • 可通过主题和样式自定义外观。
  • DetailsView 控件中的每个数据行都是通过声明字段控件创建的。 不同的行字段类型确定控件中行的行为。 字段控件派生自 DataControlField。 下表列出了可以使用的不同行字段类型。

    列字段类型

    默认情况下,AutoGenerateRows 属性设置为 true,这将为数据源中可绑定类型的每个字段自动生成绑定行字段对象。 有效的可绑定类型为 String、DateTime、Decimal、Guid 和基元类型集。 然后,每个字段以文本的形式显示在一行中,其顺序是每个字段在数据源中的显示顺序。

    自动生成行提供了一种快速轻松地显示记录中每个字段的方法。 但是,若要使用 DetailsView 控件的高级功能,必须显式声明要包含在 DetailsView 控件中的行字段。 若要声明行字段,请先将 AutoGenerateRows 属性设置为 false。 接下来,在 DetailsView 控件的开始标记和结束 <标记之间添加开始和结束字段> 标记。 最后,列出要在开始和结束 <字段标记之间包括的行字段> 。 指定的行字段将按列出的顺序添加到 Fields 集合中。 Fields 集合允许以编程方式管理 DetailsView 控件中的行字段。

    自动生成的行字段不会添加到 Fields 集合。

    使用 DetailsView 绑定到数据

    DetailsView 控件可以绑定到数据源控件(如 SqlDataSource 或 AccessDataSource),也可以绑定到实现 System.Collections.IEnumerable 接口的任何数据源,例如 System.Data.DataView、System.Collections.ArrayList 和 System.Collections.Hashtable。

    使用以下方法之一将 DetailsView 控件绑定到相应的数据源类型:

  • 若要绑定到数据源控件,请将 DetailsView 控件的 DataSourceID 属性设置为数据源控件的 ID 值。 DetailsView 控件自动绑定到指定的数据源控件。 这是绑定到数据的首选方法。
  • 若要绑定到实现 System.Collections.IEnumerable 接口的数据源,请以编程方式将 DetailsView 控件的 DataSource 属性设置为数据源,然后调用 DataBind 方法。
  • 此控件可用于显示用户输入,其中可能包括恶意客户端脚本。 在应用程序中显示之前,请检查从客户端发送的任何信息,以获取可执行脚本、SQL 语句或其他代码。 ASP.NET 提供输入请求验证功能,以阻止用户输入中的脚本和 HTML。

    DetailsView 中的数据操作

    DetailsView 控件提供内置功能,允许用户更新、删除、插入和翻页控件中的项。 当 DetailsView 控件绑定到数据源控件时,DetailsView 控件可以利用数据源控件的功能,并提供自动更新、删除、插入和分页功能。

    DetailsView 控件可以为其他类型的数据源提供更新、删除、插入和分页操作的支持;但是,必须在适当的事件处理程序中为这些操作提供实现。

    DetailsView 控件可以通过将 AutoGenerateEditButton、AutoGenerateDeleteButton 或 AutoGenerateInsertButton 属性分别设置为 true 来自动添加具有“编辑”、“删除”或“新建”按钮的 CommandField 行字段。 与“删除”按钮 (不同,单击“编辑”或“新建”按钮时,DetailsView 控件将分别进入编辑或插入模式) 。 在编辑模式下,“编辑”按钮将替换为“更新”和“取消”按钮。 适用于字段数据类型 (的输入控件(如 TextBox 或 CheckBox 控件) )显示,其中包含字段的值供用户修改。 单击“更新”按钮可更新数据源中的记录,同时单击“取消”按钮将放弃任何更改。 同样,在插入模式下,“新建”按钮将替换为“插入”和“取消”按钮,并显示空输入控件,以便用户输入新记录的值。

    DetailsView 控件提供分页功能,允许用户导航到数据源中的其他记录。 启用后,页面导航控件将显示在页导航行中。 若要启用分页,请将 AllowPaging 属性设置为 true。 可以使用 PagerStyle 和 PagerSettings 属性自定义寻呼行。

    自定义 DetailsView 的外观

    可以通过为控件的不同部分设置样式属性来自定义 DetailsView 控件的外观。 下表列出了不同的样式属性。

    Style 属性 RowStyle DetailsView 控件中数据行的样式设置。 同时设置 AlternatingRowStyle 属性时,数据行在 RowStyle 设置和 交替RowStyle 设置之间交替显示。 FieldHeaderStyle DetailsView 控件标题列的样式设置。

    DetailsView 中的事件

    DetailsView 控件提供了多个事件,你可以针对这些事件进行编程。 这样,每当事件发生时,就可以运行自定义例程。 下表列出了 DetailsView 控件支持的事件。 DetailsView 控件还从其基类继承这些事件:DataBinding、DataBound、Disposed、Init、Load、PreRender 和 Render。

    ASP.NET 2.0 中的 Menu 控件设计为功能齐全的导航系统。 它可以轻松地将数据绑定到分层数据源,例如 SiteMapDataSource。

    菜单控制结构可以声明性或动态方式定义,由单个根节点和任意数量的子节点组成。 以下代码以声明方式定义 Menu 控件的菜单。

    <asp:menu id="NavigationMenu" disappearafter="2000" staticdisplaylevels="2" staticsubmenuindent="10" orientation="Vertical" font-names="Arial" target="_blank" runat="server"> <staticmenuitemstyle backcolor="LightSteelBlue" forecolor="Black"/> <statichoverstyle backcolor="LightSkyBlue"/> <dynamicmenuitemstyle backcolor="Black" forecolor="Silver"/> <dynamichoverstyle backcolor="LightSkyBlue" forecolor="Black"/> <items> <asp:menuitem navigateurl="Home.aspx" text="Home" tooltip="Home"> <asp:menuitem navigateurl="Music.aspx" text="Music" tooltip="Music"> <asp:menuitem navigateurl="Classical.aspx" text="Classical" tooltip="Classical"/> <asp:menuitem navigateurl="Rock.aspx" text="Rock" tooltip="Rock"/> <asp:menuitem navigateurl="Jazz.aspx" text="Jazz" tooltip="Jazz"/> </asp:menuitem> <asp:menuitem navigateurl="Movies.aspx" text="Movies" tooltip="Movies"> <asp:menuitem navigateurl="Action.aspx" text="Action" tooltip="Action"/> <asp:menuitem navigateurl="Drama.aspx" text="Drama" tooltip="Drama"/> <asp:menuitem navigateurl="Musical.aspx" text="Musical" tooltip="Musical"/> </asp:menuitem> </asp:menuitem> </items> </asp:menu>
    

    在上面的示例中,Home.aspx 节点是根节点。 所有其他节点嵌套在根节点中的不同级别。

    菜单控件可以呈现两种类型的菜单:静态菜单和动态菜单。 静态菜单由始终可见的菜单项组成。 动态菜单由菜单项组成,这些菜单项仅在用户用鼠标悬停在菜单项上时才可见。 客户通常会将静态菜单与声明性定义的菜单和动态菜单与运行时数据绑定的菜单混淆。 事实上,动态菜单和静态菜单与填充方法无关。 术语静态动态仅指菜单默认是静态显示还是仅在用户执行某些操作时显示。

    StaticDisplayLevels 属性用于配置多少个菜单级别是静态的,因此默认显示。 在上面的示例中,将 StaticDisplayLevels 属性设置为值 2 将导致菜单静态显示“开始”节点、“音乐”节点和“电影”节点。 当用户将鼠标悬停在父节点上时,将动态显示所有其他节点。

    MaximumDynamicDisplayLevels 属性配置菜单能够显示的最大动态级别数。 任何级别高于 MaximumDynamicDisplayLevels 属性所指定值的动态菜单将被丢弃。

    几乎可以肯定,你可能会遇到由于 MaximumDynamicDisplayLevels 属性而未呈现菜单的情况。 在这些情况下,请确保属性设置得足够充分,以便显示客户菜单。

    数据绑定菜单控件

    Menu 控件可以绑定到任何分层数据源,例如 SiteMapDataSource 或 XMLDataSource。 SiteMapDataSource 是将数据绑定到 Menu 控件最常用的方法,因为它从 Web.sitemap 文件馈送,并且其架构为 Menu 控件提供了一个已知的 API。 下面的列表显示了一个简单的 Web.sitemap 文件。

    <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="~/default.aspx" description="Our Home Page" title="Home"> <siteMapNode url="~/products/default.aspx" title="Products" description="Our Products"> <siteMapNode url="~/products/winprods.aspx" title="Windows Products" description="Windows Products" /> <siteMapNode url="~/products/webprods.aspx" title="Web Products" description="Web Products" /> </siteMapNode> <siteMapNode url="~/services/default.aspx" title="Services" description="Our Services"> <siteMapNode url="~/services/consulting.aspx" title="Consulting Services" description="Consulting Services" /> <siteMapNode url="~/services/develop.aspx" title="Development Services" description="Development Services" /> </siteMapNode> </siteMapNode> </siteMap>
    

    请注意,只有一个根 siteMapNode 元素,在本例中为 Home 元素。 可以为每个 siteMapNode 配置多个属性。 最常用的属性包括:

  • Url 指定在用户单击菜单项时显示的 URL。 如果此属性不存在,则节点只会在单击时回发。
  • 标题 指定在菜单项上显示的文本。
  • 描述 用作节点的文档。 当鼠标悬停在节点上时,还会显示为工具提示。
  • siteMapFile 允许嵌套站点地图。 此属性必须指向格式正确的 ASP.NET 站点地图文件。
  • 角色 允许通过 ASP.NET 安全修整来控制节点的外观。
  • 请注意,虽然这些属性都是可选的,但如果未指定这些属性,菜单的行为可能就不是预期的。 例如,如果指定 了 url 属性,但 description 属性未指定,则节点将不可见,并且无法导航到指定的 URL。

    控制菜单操作

    有几个属性会影响 ASP.NET 菜单控件的操作; Orientation 属性、 DisappearAfter 属性、 StaticItemFormatString 属性和 StaticPopoutImageUrl 属性只是其中的一部分。

  • 方向可以设置为水平垂直,并控制静态菜单项是水平布局在一行中,还是垂直排列并堆叠在一起。 此属性不会影响动态菜单。
  • 消失后 属性配置动态菜单在鼠标移开后应保持可见的时长。 该值以毫秒为单位指定,默认值为 500。 将此属性设置为值 -1 将导致菜单永远不会自动消失。 在这种情况下,只有在用户单击菜单外部时,菜单才会消失。
  • StaticItemFormatString 属性可以轻松地在菜单系统中保持一致的谓词。 指定此属性时, {0} 应输入 以代替数据源中显示的说明。 例如,若要让练习 1 中的菜单项显示“访问我们的产品页面”等,需要为 StaticItemFormatString 指定“访问我们的 {0} 页面”。 在运行时,ASP.NET 会将任何出现的 {0} 替换为菜单项的正确说明。
  • StaticPopoutImageUrl 属性指定用于指示特定菜单节点具有子节点的图像,可以通过将鼠标悬停在某个菜单节点上方进行访问。 动态菜单将继续使用默认图像。
  • 模板化菜单控件

    Menu 控件是一个模板控件,允许使用两个不同的 ItemTemplate;StaticItemTemplate 和 DynamicItemTemplate。 使用这些模板,可以轻松地将服务器控件或用户控件添加到菜单中。

    若要在 Visual Studio .NET 中编辑模板,请单击菜单上的“智能标记”按钮,然后选择“编辑模板”。 然后,可以选择编辑 StaticItemTemplate 还是 DynamicItemTemplate。

    加载页面时,添加到 StaticItemTemplate 的任何控件都将显示在静态菜单中。 添加到 DynamicItemTemplate 的任何控件都将显示在所有弹出菜单上。

    Menu 控件具有两个唯一的事件; MenuItemClickedMenuItemDatabound 事件。

    单击菜单项时,将引发 MenuItemClicked 事件。 菜单项数据绑定时,将引发 MenuItemDatabound 事件。 传递给事件处理程序的 MenuEventArgs 通过 Item 属性提供对菜单项的访问权限。

    控制菜单外观

    还可以使用可用于设置菜单格式的许多样式中的一个或多个来影响菜单控件的外观。 其中包括 StaticMenuStyleDynamicMenuStyleDynamicMenuItemStyleDynamicSelectedStyleDynamicHoverStyle。 这些属性是使用标准 HTML 样式字符串配置的。 例如,以下内容将影响动态菜单的样式。

    <DynamicMenuStyle BorderStyle="Outset" BorderWidth="1px"
        BackColor="#D6D3CE" />
    

    如果使用任何悬停样式,则需要在 runat 元素设置为 server 的页面中添加<头>元素。

    菜单控件还支持使用 ASP.NET 2.0 主题。

    TreeView 控件

    TreeView 控件以树状结构显示数据。 与 Menu 控件一样,可以轻松地将数据绑定到任何分层数据源,例如 SiteMapDataSource。

    客户可能会询问有关 ASP.NET 2.0 中的 TreeView 控件的第一个问题是,它是否与可用于 ASP.NET 1.x 的 TreeView IE WebControl 相关。 事实并非如此。 ASP.NET 2.0 TreeView 控件从头开始编写,与以前可用的 IE TreeView WebControl 相比,该控件具有显著改进。

    我不会详细介绍如何将 TreeView 控件绑定到站点地图,因为它的执行方式与 Menu 控件完全相同。 但是,TreeView 控件在操作方式上存在一些明显的差异。

    默认情况下,TreeView 控件显示完全展开。 若要在初始加载时更改扩展级别,请修改控件的 ExpandDepth 属性。 当 TreeView 在扩展特定节点时绑定数据时,这一点尤其重要。

    DataBinding the TreeView 控件

    与 Menu 控件不同,TreeView 非常适合处理大量数据。 因此,除了将数据绑定到 SiteMapDataSource 或 XMLDataSource 之外,TreeView 通常是绑定到数据集或其他关系数据的数据。 如果 TreeView 控件绑定到大量数据,最好仅绑定到控件中实际可见的数据。 然后,当展开 TreeView 节点时,可以将数据绑定到其他数据。

    在这些情况下,TreeView 的 PopulateOnDemand 属性应设置为 true。 然后,需要为 TreeNodePopulate 方法提供实现。

    不带回发的数据绑定

    请注意,在上一示例中首次展开节点时,页面会回发并刷新。 这在本示例中不是问题,但你可以想象它可能位于具有大量数据的生产环境中。 更好的方案是,TreeView 仍会动态填充其节点,但没有回发到服务器。

    通过将 PopulateNodesFromClientPopulateOnDemand 属性设置为 true,ASP.NET TreeView 控件将动态填充节点,而无需回发。 扩展父节点时,将从客户端发出 XMLHttp 请求,并触发 OnTreeNodePopulate 事件。 服务器使用 XML 数据岛进行响应,该数据岛随后用于数据绑定子节点。

    ASP.NET 动态创建实现此功能的客户端代码。 <生成包含脚本>的脚本标记指向 AXD 文件。 例如,下面的列表显示了生成 XMLHttp 请求的脚本代码的脚本链接。

    <script src="/WebSite1/WebResource.axd?d=QQG9vSdBi4_k2y9AeP_iww2&
        amp;t=632514255396426531"
        type="text/javascript"></script>
    

    如果在浏览器中浏览上述 AXD 文件并将其打开,将看到实现 XMLHttp 请求的代码。 此方法可防止客户修改脚本文件。

    控制 TreeView 控件的操作

    TreeView 控件具有影响控件操作的多个属性。 最明显的属性是 ShowCheckBoxesShowExpandCollapseShowLines

    ShowCheckBoxes 属性会影响节点在呈现时是否显示检查框。 此属性的有效值为 NoneRootParentLeafAll。 它们会影响 TreeView 控件,如下所示:

    使用复选框时, CheckedNodes 属性将返回在回发时选中的 TreeView 节点的集合。

    ShowExpandCollapse 属性控制根节点和父节点旁边的展开/折叠图像的外观。 如果此属性设置为 false,则 TreeView 节点将呈现为超链接,并通过单击链接展开/折叠。

    ShowLines 属性控制是否显示将父节点连接到子节点的行。 如果 false (默认) ,则不显示任何行。 如果 为 true,TreeView 控件将使用 LineImagesFolder 属性指定的文件夹中的线条图像。

    为了自定义 TreeView 行的外观,Visual Studio .NET 2005 包括行Designer工具。 可以使用 TreeView 控件上的“智能标记”按钮访问此工具,如下所示。

    选择“自定义线条图像”菜单选项时,将启动“线条Designer”工具,以便配置 TreeView 线条的外观。

    TreeView 事件

    TreeView 控件具有以下唯一事件:

  • SelectedNodeChanged 在基于 SelectAction 属性选择节点时发生。
  • TreeNodeCheckChanged 在节点复选框状态更改时发生。
  • TreeNodeExpanded 在基于 SelectAction 属性展开节点时发生。
  • TreeNodeCollapsed 在节点折叠时发生。
  • TreeNodeDataBound 在节点绑定数据时发生。
  • TreeNodePopulate 在填充节点时发生。
  • SelectAction 属性允许配置在选择节点时触发的事件。 SelectAction 属性提供以下操作:

  • 选择节点时,TreeNodeSelectAction.Expand 引发 TreeNodeExpanded。
  • TreeNodeSelectAction.None 在选择节点时不引发任何事件。
  • TreeNodeSelectAction.Select 在选择节点时引发 SelectedNodeChanged 事件。
  • TreeNodeSelectAction.SelectExpand 在选择节点时引发 SelectedNodeChanged 事件和 TreeNodeExpanded 事件。
  • 使用 TreeView 控制外观

    TreeView 控件提供了许多属性,用于通过样式控制控件的外观。 以下属性可用。

    其中每个属性都是只读的。 但是,它们将分别返回一个 TreeNodeStyle 对象,并且可以使用 property-subproperty 格式修改该对象的属性。 例如,若要设置 SelectedNodeStyleForeColor 属性,请使用以下语法:

    <asp:TreeView id=sampleTreeView
        SelectedNodeStyle-ForeColor=Blue
        runat=server>
    

    请注意,上述标记未关闭。 这是因为使用此处所示的声明性语法时,也会在 HTML 代码中包含 TreeViews 节点。

    也可以使用 property.subproperty 格式在代码中指定样式属性。 例如,若要在代码中设置 RootNodeStyleForeColor 属性,请使用以下语法:

    treeView1.RootNodeStyle.ForeColor = System.Drawing.Color.Red;
    

    有关不同样式属性的完整列表,请参阅 TreeNodeStyle 对象上的 MSDN 文档。

    SiteMapPath 控件

    SiteMapPath 控件为 ASP.NET 开发人员提供痕迹导航控件。 与其他导航控件一样,可以轻松地将数据绑定到分层数据源,例如 SiteMapDataSource 或 XmlDataSource。

    SiteMapPath 控件由 SiteMapNodeItem 对象组成。 有三种类型的节点:根节点、父节点和当前节点。 根节点是分层结构顶部的节点。 当前节点表示当前页。 所有其他节点都是父节点。

    控制 SiteMapPath 控件的操作

    控制 SiteMapPath 控件操作的属性如下所示:

    模板化 SiteMapPath 控件

    SiteMapControl 是一个模板化控件,因此,可以定义用于显示控件的不同模板。 若要编辑 SiteMapPath 控件中的模板,请单击控件上的“智能标记”按钮,然后从菜单中选择“编辑模板”。 此时会显示 SiteMapTasks 菜单,如下所示,可在其中选择可用的不同模板。

    NodeTemplate 模板引用 SiteMapPath 中的任何节点。 如果节点是根节点或当前节点,并且配置了 RootNodeTemplateCurrentNodeTemplate ,则会重写 NodeTemplate。

    SiteMapPath 事件

    SiteMapPath 控件有两个不是派生自 Control 类的事件; ItemCreated 事件和 ItemDataBound 事件。 创建 SiteMapPath 项时,将引发 ItemCreated 事件。 在 SiteMapPath 节点的数据绑定期间调用 DataBind 方法时,将引发 ItemDataBound。 SiteMapNodeItemEventArgs 对象通过 Item 属性提供对特定 SiteMapNodeItem 的访问权限。

    使用 SiteMapPath 控制外观

    以下样式可用于设置 SiteMapPath 控件的格式。

    NodeStyle 属性由 CurrentNodeStyle 或 RootNodeStyle 重写。 其中每个属性都是只读的,并返回 一个 Style 对象。 若要使用这些属性之一影响节点的外观,需要设置返回的 Style 对象的属性。 例如,下面的代码更改当前节点的 forecolor 属性。

    <asp:SiteMapPath runat="server" ID="SiteMapPath1"
      CurrentNodeStyle-ForeColor="Orange"/>
    

    还可以以编程方式应用 属性,如下所示:

    this.SiteMapPath1.CurrentNodeStyle.ForeColor =
        System.Drawing.Color.Orange;
    

    如果应用了模板,则不会应用该样式。

    实验室 1:配置 ASP.NET 菜单控件

  • 创建新网站。

  • 通过选择“文件”、“新建”、“文件”并从文件模板列表中选择“站点地图”来添加站点地图文件。

  • 默认情况下,打开网站地图 (Web.sitemap) 并对其进行修改,使其如下所示。 在站点地图文件中链接到的页面实际上不存在,但这不会是本练习的问题。

    <?xml version="1.0" encoding="utf-8" ?>
    <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
        <siteMapNode url="~/default.aspx">
            <siteMapNode url="~/products/default.aspx"
              title="Products" description="Our Products">
                <siteMapNode url="~/products/winprods.aspx"
                  title="Windows Products" description="Windows Products" />
                <siteMapNode url="~/products/webprods.aspx"
                  title="Web Products" description="Web Products" />
            </siteMapNode>
            <siteMapNode url="~/services/default.aspx"
              title="Services" description="Our Services">
                <siteMapNode url="~/services/consulting.aspx"
                  title="Consulting Services"
                  description="Consulting Services" />
                <siteMapNode url="~/services/develop.aspx"
                  title="Development Services"
                  description="Development Services" />
            </siteMapNode>
        </siteMapNode>
    </siteMap>
    
  • 在“设计”视图中打开默认的 Web 窗体。

  • 在“工具箱”的“导航”部分中,向页面添加新的 Menu 控件。

  • 从工具箱的“数据”部分,添加新的 SiteMapDataSource。 SiteMapDataSource 将自动使用站点中的 Web.sitemap 文件。 (Web.sitemap 文件 必须 位于 site 的根文件夹中。)

  • 单击“菜单”控件,然后单击“智能标记”按钮以显示“菜单任务”对话框。

  • 在“选择数据源”下拉列表中,选择“SiteMapDataSource1”。

  • 单击“自动套用格式”链接,然后选择“菜单”的格式。

  • 在“属性”窗格中,将 StaticDisplayLevels 属性设置为 2。 菜单控件现在应在Designer中显示“主页”、“产品和服务”节点。

  • 在浏览器中浏览页面以使用菜单。 (由于已添加到站点地图的页面实际上不存在,因此尝试浏览时会看到错误。)

    尝试更改 StaticDisplayLevels 和 MaximumDynamicDisplayLevels 属性,并了解它们如何影响菜单的呈现方式。

    实验室 2:动态绑定 TreeView 控件

    本练习假定你已在本地运行SQL Server,并且 Northwind 数据库存在于 SQL Server 实例上。 如果不满足这些条件,请更改示例中的连接字符串。 请注意,可能还需要指定SQL Server身份验证,而不是受信任的连接。

  • 创建新网站。

  • 切换到 Default.aspx 的“代码”视图,将所有代码替换为下面列出的代码。

    <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Data" %>
    <%@ Import Namespace="System.Data.SqlClient" %>
    <script runat="server">
        void PopulateNode(Object sender, TreeNodeEventArgs e) {
            // Call the appropriate method to populate a node at a particular level.
            switch (e.Node.Depth) {
                case 0:
                    // Populate the first-level nodes.
                    PopulateCategories(e.Node);
                    break;
                case 1:
                    // Populate the second-level nodes.
                    PopulateProducts(e.Node);
                    break;
                default:
                    // Do nothing.
                    break;
        void PopulateCategories(TreeNode node) {
            // Query for the product categories. These are the values
            // for the second-level nodes.
            DataSet ResultSet = RunQuery("Select CategoryID, CategoryName From Categories");
            // Create the second-level nodes.
            if (ResultSet.Tables.Count > 0) {
                // Iterate through and create a new node for each row in the query results.
                // Notice that the query results are stored in the table of the DataSet.
                foreach (DataRow row in ResultSet.Tables[0].Rows) {
                    // Create the new node. Notice that the CategoryId is stored in the Value property
                    // of the node. This will make querying for items in a specific category easier when
                    // the third-level nodes are created.
                    TreeNode newNode = new TreeNode();
                    newNode.Text = row["CategoryName"].ToString();
                    newNode.Value = row["CategoryID"].ToString();
                    // Set the PopulateOnDemand property to true so that the child nodes can be
                    // dynamically populated.
                    newNode.PopulateOnDemand = true;
                    // Set additional properties for the node.
                    newNode.SelectAction = TreeNodeSelectAction.Expand;
                    // Add the new node to the ChildNodes collection of the parent node.
                    node.ChildNodes.Add(newNode);
        void PopulateProducts(TreeNode node) {
            // Query for the products of the current category. These are the values
            // for the third-level nodes.
            DataSet ResultSet = RunQuery("Select ProductName From Products Where CategoryID=" + node.Value);
            // Create the third-level nodes.
            if (ResultSet.Tables.Count > 0) {
                // Iterate through and create a new node for each row in the query results.
                // Notice that the query results are stored in the table of the DataSet.
                foreach (DataRow row in ResultSet.Tables[0].Rows) {
                    // Create the new node.
                    TreeNode NewNode = new TreeNode(row["ProductName"].ToString());
                    // Set the PopulateOnDemand property to false, because these are leaf nodes and
                    // do not need to be populated.
                    NewNode.PopulateOnDemand = false;
                    // Set additional properties for the node.
                    NewNode.SelectAction = TreeNodeSelectAction.None;
                    // Add the new node to the ChildNodes collection of the parent node.
                    node.ChildNodes.Add(NewNode);
        DataSet RunQuery(String QueryString) {
            // Declare the connection string. This example uses Microsoft SQL Server
            // and connects to the Northwind sample database.
            String ConnectionString = "server=localhost;database=NorthWind;Integrated Security=SSPI";
            SqlConnection DBConnection = new SqlConnection(ConnectionString);
            SqlDataAdapter DBAdapter;
            DataSet ResultsDataSet = new DataSet();
            try {
                // Run the query and create a DataSet.
                DBAdapter = new SqlDataAdapter(QueryString, DBConnection);
                DBAdapter.Fill(ResultsDataSet);
                // Close the database connection.
                DBConnection.Close();
            } catch (Exception ex) {
                // Close the database connection if it is still open.
                if (DBConnection.State == ConnectionState.Open) {
                    DBConnection.Close();
                Message.Text = "Unable to connect to the database.";
            return ResultsDataSet;
    </script>
        <form id="Form1" runat="server">
                TreeView PopulateNodesFromClient Example</h3>
            <asp:TreeView ID="LinksTreeView" 
              Font-Name="Arial" ForeColor="Blue" EnableClientScript="true"
              PopulateNodesFromClient="false" 
              OnTreeNodePopulate="PopulateNode" runat="server"
                ExpandDepth="0">
                <Nodes>
                    <asp:TreeNode Text="Inventory" SelectAction="Expand"
                                  PopulateOnDemand="True" Value="Inventory" />
                </Nodes>
            </asp:TreeView>
            <asp:Label ID="Message" runat="server" />
        </form>
    </body>
    </html>
    
  • 将页面另存为 treeview.aspx。

  • 浏览页面。

  • 首次显示页面时,请在浏览器中查看页面的源。 请注意,仅将可见节点发送到客户端。

  • 单击任何节点旁边的加号。

  • 再次在页面上查看源。 请注意,新显示的节点现在已存在。

    实验室 3:使用 GridView 和 DetailsView 查看和编辑数据

  • 创建新网站。

  • 向网站添加新web.config。

  • 将连接字符串添加到web.config文件,如下所示:

    <connectionStrings>
        <add name="Northwind"
             providerName="System.Data.SqlClient"
             connectionString="Data Source=localhost;Integrated Security=SSPI;
             Initial Catalog=Northwind;"/>
    </connectionStrings>
    

    可能需要根据环境更改连接字符串。

  • 保存并关闭 web.config 文件。

  • 打开 Default.aspx 并添加新的 SqlDataSource 控件。

  • 将 SqlDataSource 控件的 ID 更改为 Products

  • “SqlDataSource 任务 ”菜单中,单击“ 配置数据源”。

  • 在连接下拉列表中选择“ Northwind ”,然后单击“下一步”。

  • “名称”下拉列表中选择“产品”,检查“ProductID”、“ProductName”、“UnitPrice”和“UnitsInStock”复选框,如下所示。

  • 单击“下一步”。
  • 单击“完成”。
  • 切换到“源”视图并检查生成的代码。 请注意已添加到 SqlDataSource 控件的 SelectCommandDeleteCommandInsertCommandUpdateCommand 。 另请注意添加的参数。
  • 切换到“设计”视图,并将新的 GridView 控件添加到页面。
  • 从“选择数据源”下拉列表中选择“产品”。
  • 选中 “启用分页 ”和 “启用选择” ,如下所示。
  • 单击 “编辑列” 链接,并确保选中 “自动生成字段 ”。
  • 单击" 确定"。
  • 选中 GridView 控件后,单击“属性”窗格中 DataKeyNames 属性旁边的按钮。
  • “可用数据字段”列表中选择“ProductID”,>然后单击按钮添加它。
  • 单击“确定”。
  • 向页面添加新的 SqlDataSource 控件。
  • 将 SqlDataSource 控件的 ID 更改为 “详细信息”。
  • 在“SqlDataSource 任务”菜单中,选择“ 配置数据源”。
  • 从下拉列表中选择“ Northwind ”,然后单击“ 下一步”。
  • 从“名称”下拉列表中选择“产品”,并在“列”列表框中检查 </strong>* 复选框。
  • 单击 WHERE 按钮。
  • “列”下拉列表中选择“ProductID”。
  • 在“运算符” = 下拉列表中选择。
  • “源”下拉列表中选择“控制”。
  • 从“控件 ID”下拉列表中选择“GridView1”。
  • 单击“ 添加” 按钮添加 WHERE 子句。
  • 单击" 确定"。
  • 单击“高级”按钮,检查“生成 INSERT”、“UPDATE”和“DELETE 语句”复选框。
  • 单击" 确定"。
  • 单击“ 下一步 ”,然后单击“ 完成”。
  • 将 DetailsView 控件添加到页面。
  • “选择数据源 ”下拉列表中,选择“ 详细信息”。
  • 选中 “启用编辑 ”复选框,如下所示。
  • 图 5 39.保存页面并浏览 Default.aspx。 40. 单击不同记录旁边的 “选择” 链接,查看“详细信息”视图自动更新。 41. 单击 DetailsView 控件中的 “编辑” 链接。 42. 对记录进行更改,然后单击“ 更新”。

    Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see: https://aka.ms/ContentUserFeedback.

    提交和查看相关反馈

  •