从 GridView 控件的脚注中添加一个新记录

本文档是 Visual C# 教程 (切换到 Visual Basic 教程)

因为 GridView 控件没有提供内置的插入功能来插入新的数据记录,所以本文档将介绍如何为 GridView 控件添加一个插入界面。

« 前一篇教程  |  下一篇教程 »

简介

正如我们在数据插入、更新和删除概述 教程中所讨论的那样 ,GridView 、DetailsView 和 FormView Web 控件都包含内置的数据修改功能。当使用声明数据源控件时,可以方便迅速地对这三个 Web 控件进行配置使其能修改数据,并且在某些情况下无需编写一行代码。不幸的是,只有 DetailsView 控件和 FormView 控件提供了内置的插入、编辑和删除功能,而 GridView 控件只提供了编辑和删除支持。尽管如此,只要下少许功夫,就可以为 GridView 控件也添加插入界面。

在为GridView 控件添加插入功能时 , 我们需要确定添加多少条新记录 , 还需要创建插入界面以及编写插入新记录的代码。在本教程中,我们将着眼于如何为 GridView 控件的脚注行添加插入界面(请参见图 1 )。每一列的脚注单元都包含了适当的数据采集用户界面元素(例如,对产品的名称是 TextBox ,对供应商是 DropDownList 等等)。另外,我们还需要一列用于 Add 按钮,单击该按钮时将会引起一次回传,并使用脚注行提供的数值向 Products 表中插入一条新记录。

图1 :脚注行提供了添加新记录的界面

步骤1:在 GridView 中显示产品信息

考虑在GridView 控件的脚注中创建插入界面之前 , 首先让我们关注一下如何为页面添加一个GridView 控件 , 以列出数据库中各个产品。首先,打开 EnhancedGridView 文件夹中的 InsertThroughFooter.aspx 页,然后将 GridView 控件从 工具箱 拖放到设计器中,将 GridView 控件的 ID 属性设置为 Products 。接下来,使用 GridView 控件的智能标记将它绑定到一个名为 ProductsDataSource 的新 ObjectDataSource 上。

图2 : 创建一个名为 ProductsDataSource 的新 ObjectDataSource

将新建的ObjectDataSource 配置为使用 ProductsBLL 类的 GetProducts() 方法来检索产品信息。在本教程中 , 我们重点关注添加插入功能 , 而无需考虑编辑和删除功能。所以,请确保 INSERT 选项卡上的下拉列表选为 AddProduct() , UPDATE 和 DELETE 选项卡上的下拉列表选为 “(None)” 。

图3 : 将 AddProduct 方法映射到 ObjectDataSource 的 Insert() 方法

图4 :将 UPDATE 和 DELETE 选项卡中的下拉列表选为“(None)”

完成ObjectDataSource 的Configure Data Source 向导后 ,Visual Studio 将会自动把这些字段添加到 GridView 控件的相应的数据字段中。现在,保留所有 Visual Studio 添加的字段。本教程稍后,我们将回到此处,将那些添加新记录时不需要其值的字段删除。

因为数据库中产品数量接近80 个 , 所以为了添加一条新记录 , 用户不得不一直滚动滚动条 , 直到滚到Web 页面的底部。因此,我们需要启动分页功能,这就使得插入界面更加可视化和便于使用。要打开分页功能,只需简单地在 GridView 控件的智能标记中选中 “Enable Paging” 复选框即可。

这时 GridView 控件和 ObjectDataSource 的声明标记应该和以下文字类似 :

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="ProductName" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" 
            SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" 
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" 
            SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" 
            SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" 
            SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" 
            SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
            SortExpression="Discontinued" />
        <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" 
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" 
            ReadOnly="True" SortExpression="SupplierName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" 
    SelectMethod="GetProducts" TypeName="ProductsBLL">
    <InsertParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="unitsInStock" Type="Int16" />
        <asp:Parameter Name="unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="reorderLevel" Type="Int16" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
    </InsertParameters>
</asp:ObjectDataSource>

图5 :在 GridView 控件中分页显示所有的产品数据字段

步骤2 :添加脚注行

除了标题行和数据行 ,GridView 控件还包含一个脚注行。是否显示标题行和脚注行取决于 GridView 控件的 ShowHeaderShowFooter 属性的值。要显示脚注行,只需将 ShowFooter 属性设置为 True 。如图 6 所示,将 ShowFooter 属性值设置为 True ,在网格中添加了一个脚注行。

图6 :为了显示脚注行 , 把ShowFooter 属性设置为True

请注意 , 脚注行的背景色是暗红色。这是由于在使用 ObjectDataSource 显示数据 教程中我们将创建的 Theme 应用于所有页面了。特别是, GridView.skin 文件将 FooterStyle 属性如此配置,使其使用 FooterStyle CSS 类, FooterStyle 类在 Styles.css 中定义如下:

.FooterStyle { background-color: #a33; color: White; text-align: right; }

注意 : 在前面的教程中 , 我们已经探讨了 GridView 的脚注行的使用。如果需要,请重新返回在 GridView 的脚注中显示小结信息 教程进行复习。

将ShowFooter 属性值设置为 True 后 , 花点时间在浏览器中浏览一下输出。目前,脚注行不包含任何文本或 Web 控件。在步骤 3 中,我们将修改每个 GridView 控件字段的脚注,从而使其包含适当的插入界面。

图7 :空白脚注行显示于分页界面控件的上面

步骤3:定制脚注行

回顾在 GridView 控件中使用 TemplateField 教程, 我们已经知道了怎样使用TemplateField ( 而非 BoundField 或CheckBoxField ) 来精确地定制GridView 控件的某个特定列的显示 ; 而在自定义数据修改界面 教 程中 , 我们已经了解了在 GridView 控件中使用 TemplateField 来定制编辑页面的方法。回想一下, TemplateField 是由许多的模板构成的,这些模板定义了一些混合标记、 Web 控件和用于某些类型行的数据绑定语法。例如 ItemTemplate 定义了用于只读行的模板,而 EditItemTemplate 则定义了用于可编辑行的模板。

除了ItemTemplate 和 EditItemTemplate 模板外 ,TemplateField 还包含了一个FooterTemplate 模板 , 它指定了脚注行的内容。因此,我们可以将每个字段的插入界面所需的 Web 控件添加到 FooterTemplate 中。首先,将 GridView 控件中的所有字段都转换成 TemplateField 字段。单击 GridView 控件智能标记上的 “Edit Columns” 链接,然后选中左下角的每个字段,接着单击 “Convert this field into a TemplateField” 链接,就可以完成这个转换。

图8 :将每个字段都转换成 TemplateField

单击“Convert this field into a TemplateField” 链接将当前的字段类型转换为等价的 TemplateField 。例如,每个 BoundField 都被替换为一个带有 ItemTemplate 和 EditItemTemplate 的 TemplateField 字段,其中 ItemTemplate 包含了一个用来显示相应数据字段的 Label 控件,而 EditItemTemplate 则用来显示 TextBox 控件中的数据字段。 ProductName BoundField 已经被转换成以下 TemplateField 标示:

<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName">
    <EditItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" 
            Text='<%# Bind("ProductName") %>'></asp:TextBox>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="Label2" runat="server" 
            Text='<%# Bind("ProductName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

同样 ,Discontinued CheckBoxField 也被转换成了一个 TemplateField , 其ItemTemplate 和 EditItemTemplate 都包含一个 CheckBox Web 控件 (ItemTemplate 的 CheckBox 被禁用) 。只读的 ProductID BoundField 也被转换成了一个 TemplateField ,其 ItemTemplate 和 EditItemTemplate 都有一个标签控件。简而言之,将现有的 GridView 字段转换为 TemplateField ,是转换为更加可定制的 TemplateField 的一种方便迅速的方法,这种转换不会丢失现有字段的任何功能。

因为我们正在使用的 GridView 控件并不支持编辑功能 , 所以能够随意将EditItemTemplate 从每个 TemplateField 中删除 , 只留下ItemTemplate 。完成这些工作之后,您的 GridView 控件的声明标记看起来应该类似如下 :

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False" ShowFooter="True">
    <Columns>
        <asp:TemplateField HeaderText="ProductID" InsertVisible="False" 
            SortExpression="ProductID">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("ProductID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="ProductName" SortExpression="ProductName">
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="SupplierID" SortExpression="SupplierID">
            <ItemTemplate>
                <asp:Label ID="Label3" runat="server" 
                    Text='<%# Bind("SupplierID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="CategoryID" SortExpression="CategoryID">
            <ItemTemplate>
                <asp:Label ID="Label4" runat="server" 
                    Text='<%# Bind("CategoryID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="QuantityPerUnit" 
            SortExpression="QuantityPerUnit">
            <ItemTemplate>
                <asp:Label ID="Label5" runat="server" 
                    Text='<%# Bind("QuantityPerUnit") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice">
            <ItemTemplate>
                <asp:Label ID="Label6" runat="server" 
                    Text='<%# Bind("UnitPrice") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitsInStock" 
            SortExpression="UnitsInStock">
            <ItemTemplate>
                <asp:Label ID="Label7" runat="server" 
                    Text='<%# Bind("UnitsInStock") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitsOnOrder" 
            SortExpression="UnitsOnOrder">
            <ItemTemplate>
                <asp:Label ID="Label8" runat="server" 
                    Text='<%# Bind("UnitsOnOrder") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="ReorderLevel" 
            SortExpression="ReorderLevel">
            <ItemTemplate>
                <asp:Label ID="Label9" runat="server" 
                    Text='<%# Bind("ReorderLevel") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Discontinued" 
            SortExpression="Discontinued">
            <ItemTemplate>
                <asp:CheckBox ID="CheckBox1" runat="server" 
                    Checked='<%# Bind("Discontinued") %>' Enabled="false" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="CategoryName" 
            SortExpression="CategoryName">
            <ItemTemplate>
                <asp:Label ID="Label10" runat="server" 
                    Text='<%# Bind("CategoryName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="SupplierName" 
            SortExpression="SupplierName">
            <ItemTemplate>
                <asp:Label ID="Label11" runat="server" 
                    Text='<%# Bind("SupplierName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

既然每个GridView 控件字段都已转换为一个 TemplateField , 那么我们就可以向每个字段的 FooterTemplate 输入恰当的插入界面了。有些字段没有插入界面(例如, ProductID ),而其它字段也会根据其采集新产品信息所用的 Web 控件的不同而拥有或没有插入界面。

为了创建编辑界面 , 从GridView 控件的智能标记中选中 “Edit Templates” 链接 , 接着 , 在下拉列表中选择相应字段的 FooterTemplate , 并将其对应的控件从工具箱中拖放到设计器中。

图9 :为每个字段的 FooterTemplate 添加恰当的插入界面

下面逐一列出了 GridView 控件的每个字段 , 并给出了向这些字段添加的插入界面 :

  • ProductID – 无
  • ProductName – 添加一个 TextBox 控件 , 并将其 ID 设置为 NewProductName 。同时 , 还要添加一个 RequiredFieldValidator 控件 , 以确保用户为新产品的名字输入一个值。
  • SupplierID – 无。
  • CategoryID – 无。
  • QuantityPerUnit – 添加一个 TextBox 控件 , 并将其 ID 设置为 NewQuantityPerUnit 。
  • UnitPrice – 添加一个名称为 NewUnitPrice 的TextBox 控件和一个 CompareValidator 控件 , 以确保输入的数值为大于或等于 0 的货币值。
  • UnitsInStock – 使用一个 TextBox 控件 , 并将其 ID 设置为 NewUnitsInStock 。同时还应添加一个 CompareValidator 控件 , 以确保输入的数值为大于或等于 0 的整数。
  • UnitsOnOrder – 使用一个 TextBox 控件 , 并将其 ID 设置为 NewUnitsOnOrder 。同时还应添加一个 CompareValidator 控件 , 以确保输入的数值为大于或等于 0 的整数。
  • ReorderLevel – 使用一个 TextBox 控件 , 将其 ID 设置为 NewReorderLevel 。同时还应添加一个 CompareValidator 控件 , 以确保输入的数值为大于或等于 0 的整数。
  • Discontinued – 添加一个 CheckBox 控件 , 并将其 ID 设置为NewDiscontinued 。
  • CategoryName – 添加一个 DropDownList 控件 , 并将其 ID 设置为 NewCategoryID 。同时将其绑定到名为 CategoriesDataSource 的新 ObjectDataSource 上 , 并将其配置为使用 CategoriesBLL 类的 GetCategories() 方法。使用 DropDownList 控件的 ListItems 显示 CategoryName 数据字段,显示时使用 CategoryID 数据字段作为这些 ListItems 的值。
  • SupplierName – 添加一个 DropDownList 控件 , 并将其 ID 设置为 NewSupplierID 。同时将其绑定到名为 SuppliersDataSource 的新 ObjectDataSource 上 , 并将其配置为使用 SuppliersBLL 类的 GetSuppliers() 方法。使用 DropDownList 控件的 ListItems 显示 CompanyName 数据字段,显示时使用 SupplierID 数据字段作为 ListItems 的值。

清除每一个验证控件的 ForeColor 属性 , 这样就会使用 FooterStyle CSS 类的白色替代默认的红色作为前景色。还要使用 ErrorMessage 属性做一个详细的描述,但是请将 Text 属性设置为一个星号。为了防止验证控件的文本把插入界面自动变为两行,请将每个使用验证控件的 FooterTemplates 的 FooterStyle 的 Wrap 属性设置为 False 。最后,在 GridView 控件的下方添加一个 ValidationSummary 控件,将其 ShowMessageBox 属性设置为 True , ShowSummary 属性设置为 False 。

添加一个新产品时 , 我们需要提供CategoryID 和 SupplierID 。这些信息可以通过 CategoryName 和 SupplierName 字段的脚注单元中的 DropDownLists 控件来获得。这里选择使用这两个字段,而不使用 CategoryID 和 SupplierID TemplateField ,是因为在网格的数据行中,用户最可能感兴趣的是查看产品的类别和供应商而不是它们的 ID 值。因为现在已经在 CategoryName 和 SupplierName 字段的插入界面中获取了 CategoryID 和 SupplierID 值,所以我们可以从 GridView 控件中删除 CategoryID 和 SupplierID 的 TemplateField 。

类似地 , 添加新产品时也没有使用 ProductID , 所以ProductID TemplateField 也可以删除。尽管可以将其删除 , 但是我们还是把它列在网格中吧。组成插入界面的控件除了 TextBox 、 DropDownList 、 CheckBox 和验证控件外,我们还需要一个 Add 按钮,单击该按钮就会执行将新产品添加到数据库中的逻辑操作。在步骤 4 中,我们将在 ProductID TemplateField 的 FooterTemplate 模板的插入界面中添加 Add 按钮。

现在可以自由地改进各种不同的 GridView 字段的外观。例如,您想将 UnitPrice 的数值格式化为货币形式,将 UnitsInStock 、 UnitsOnOrder 和 ReorderLevel 字段右对齐,更新 TemplateField 字段的 HeaderText 值。

在 FooterTemplate 中创建这些插入界面后 , 删除了 SupplierID 和CategoryID 的 TemplateField , 并通过格式化和对齐排列这些TemplateField 字段改进了网格的视觉效果 ,至此, 您的 GridView 控件的声明标记看起来应该类似如下 :

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False" ShowFooter="True">
    <Columns>
        <asp:TemplateField HeaderText="ProductID" InsertVisible="False" 
            SortExpression="ProductID">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("ProductID") %>'></asp:Label>
            </ItemTemplate>
            <ItemStyle HorizontalAlign="Center" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewProductName" runat="server"></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                    runat="server" ControlToValidate="NewProductName"
                    Display="Dynamic"  ForeColor="
                    ErrorMessage="You must enter a name for the new product.">
                    * </asp:RequiredFieldValidator>
            </FooterTemplate>
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
            <ItemTemplate>
                <asp:Label ID="Label10" runat="server" 
                    Text='<%# Bind("CategoryName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:DropDownList ID="NewCategoryID" runat="server" 
                    DataSourceID="CategoriesDataSource"
                    DataTextField="CategoryName" DataValueField="CategoryID">
                </asp:DropDownList>
                <asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
                    OldValuesParameterFormatString="original_{0}" 
                    SelectMethod="GetCategories" TypeName="CategoriesBLL">
                </asp:ObjectDataSource>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
            <ItemTemplate>
                <asp:Label ID="Label11" runat="server" 
                    Text='<%# Bind("SupplierName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:DropDownList ID="NewSupplierID" runat="server" 
                    DataSourceID="SuppliersDataSource"
                    DataTextField="CompanyName" DataValueField="SupplierID">
                </asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource" 
                    runat="server" OldValuesParameterFormatString="original_{0}" 
                    SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
                </asp:ObjectDataSource>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Qty/Unit" SortExpression="QuantityPerUnit">
            <ItemTemplate>
                <asp:Label ID="Label5" runat="server" 
                    Text='<%# Bind("QuantityPerUnit") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewQuantityPerUnit" runat="server"></asp:TextBox>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <ItemTemplate>
                <asp:Label ID="Label6" runat="server" 
                    Text='<%# Bind("UnitPrice", "{0:c}") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                $<asp:TextBox ID="NewUnitPrice" runat="server" Columns="8" />
                <asp:CompareValidator ID="CompareValidator1" runat="server" 
                    ControlToValidate="NewUnitPrice"
                    ErrorMessage="You must enter a valid currency value greater than 
                        or equal to 0.00. Do not include the currency symbol."
                    ForeColor="" Operator="GreaterThanEqual" Type="Currency" 
                    ValueToCompare="0" Display="Dynamic">
                    * </asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units In Stock" 
            SortExpression="Units In Stock">
            <ItemTemplate>
                <asp:Label ID="Label7" runat="server" 
                    Text='<%# Bind("UnitsInStock") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewUnitsInStock" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator2" runat="server" 
                    ControlToValidate="NewUnitsInStock" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for units 
                        in stock that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                        ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
            <ItemTemplate>
                <asp:Label ID="Label8" runat="server" 
                    Text='<%# Bind("UnitsOnOrder") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewUnitsOnOrder" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator3" runat="server" 
                    ControlToValidate="NewUnitsOnOrder" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for units on 
                        order that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                    ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
            <ItemTemplate>
                <asp:Label ID="Label9" runat="server" 
                    Text='<%# Bind("ReorderLevel") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewReorderLevel" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator4" runat="server" 
                    ControlToValidate="NewReorderLevel" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for reorder 
                        level that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                    ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
            <ItemTemplate>
                <asp:CheckBox ID="CheckBox1" runat="server" 
                    Checked='<%# Bind("Discontinued") %>' Enabled="false" />
            </ItemTemplate>
            <FooterTemplate>
                <asp:CheckBox ID="NewDiscontinued" runat="server" />
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Center" />
            <FooterStyle HorizontalAlign="Center" />
        </asp:TemplateField>
    </Columns>
</asp:GridView>

现在通过浏览器浏览时 , 可以看到GridView 控件的脚注行包含了完整的插入界面 ( 请参见图 10 )。 此时,插入界面并没有为用户提供一个方法来表示:用户已经为新产品输入了数据,并且用户想向数据库中插入一条新产品记录。我们还必须解决怎样将输入到脚注的数据转换成Products 数据库中的一条新纪录的问题。在步骤 4 中,我们将了解如何为插入界面添加一个 Add 按钮,以及如何在单击该按钮时执行回传代码。。步骤 5 会告诉您如何使用脚注中的数据插入一条新记录。

图10 :GridView 脚注提供了一个界面用于添加新记录

步骤4 : 在插入界面中添加一个 Add 按钮

我们需要在插入界面的某个地方插入一个Add 按钮 , 因为现在脚注行的插入界面缺少一种方法来指示用户已经完成了新产品信息的输入。这个按钮可以放在现有的 FooterTemplate 中,也可以向网格中添加一个新列来实现这个目的。在本教程中,我们把 Add 按钮放在 ProductID TemplateField 字段的 FooterTemplate 模板中。

在设计器中 , 单击GridView 控件智能标记上的 “Edit Templates” 链接 , 然后在从下拉列表中选择 ProductID 字段的 FooterTemplate 模板。向该模板添加一个 Web 按钮控件(如果您喜欢的话,可以添加 LinkButton 或 ImageButton ),将该控件的 ID 设置为 AddProduct ,其 CommandName 设置为 Insert ,其 Text 属性设置为 “Add” ,如图 11 所示。

图11 : 在 ProductID TemplateField 字段的 FooterTemplate 模板中添置一个 Add 按钮

一旦添加了Add 按钮 , 请在浏览器中测试该页面。请注意,如果插入界面中的数据是非法数的,单击 Add 按钮后,那么回传将会被中断,而 ValidationSummary 控件会指示这个非法数据(请参见图 12 )。如果输入数据正确,那么单击 Add 按钮后就会启动回传。但是,并没有记录添加到了数据库中。我们还需要写一些代码来实际执行插入操作。

图12 : 如果输入界面中存在非法数据 , 那么Add 按钮 的 回传 就会被中断

注意 : 插入界面中的验证控件没有被分配给一个验证组。这样,只要插入界面是页面上的唯一验证控件集 , 这些控件就工作的非常好。但是,如果页面上还有其它的验证控件(如网格的编辑界面中的验证控件),那么插入界面中的验证控件和 Add 按钮的 ValidationGroup 属性值应该指定为相同的数值,以便将这些控件关联成一个特定的验证组。有关将一个页面上的验证控件和按钮分割到验证组的信息,请参考在 ASP.NET 2.0 中分割验证控件

步骤5 : 向 Products 表中插入新记录

使用GridView 控件内置编辑功能时 ,GridView 控件自动处理更新操作必需的所有工作。特别是,单击 Update 按钮后,就会将编辑界面输入的值复制给 ObjectDataSource 的 UpdateParameters 集合中的参数,然后调用 ObjectDataSource 的 Update() 方法来启动更新操作。因为 GridView 控件没有提供这样内置的插入功能,所以我们必须实现一些代码来调用 ObjectDataSource 的 Insert() 方法,将插入界面中的数值复制到 ObjectDataSource 的 InsertParameters 集合中。

应该在单击Add 按钮后再执行这个插入逻辑操作。正如在添加和响应 GridView 控件的按钮 教程中探讨的那样,无论何时只要单击了 GridView 控件中的 Button 、 LinkButton 或 ImageButton , GridView 控件的 RowCommand 事件都会触发回传。不管是否显式地添加了 Button 、 LinkButton 或 ImageButton (例如,脚注行中的 Add 按钮),或者由 GridView 控件自动添加这些按钮(例如,选中 “Enable Sorting” 时, GridView 控件自动在每一列顶部自动添加 LinkButton ;或者选中 “Enable Paging” 时, GridView 控件自动在分页界面中添加 LinkButton ),只要单击这些按钮就会触发该事件。

所以 , 为了响应用户对 Add 按钮的单击 , 我们需要为 GridView 控件的RowCommand 事件创建一个Event Handler 。由于每当单击 GridView 控件中的任何 Button 、 LinkButton 或 ImageButton 按钮时,这个事件都会被触发,所以只有当传递到 Event Handler 中的 CommandName 属性与 Add 按钮 (“Insert”) 的 CommandName 值一致时才执行这个插入逻辑操作,这一点是很关键的。另外,只能在验证控件报告为有效数据时才可以继续进行操作。为了实现这个目标,需要创建一个含有下列代码的 Event Handler:

protected void Products_RowCommand(object sender, GridViewCommandEventArgs e)
{
    // Insert data if the CommandName == "Insert" 
    // and the validation controls indicate valid data...
    if (e.CommandName == "Insert" && Page.IsValid)
    {
        // TODO: Insert new record...
    }
}

注意 : 您可能会疑惑为什么需要 Event Handler 费事来检查 Page.IsValid 的属性呢 ? 要回答这个问题 , 不妨来假设一下 : 如果在插入界面中输入了非法数据 , 回传操作将会被终止吗 ? 只要用户没有禁用 JavaScript 或使用了一些方法避开了客户端的验证逻辑 , 这个假设就是完全成立的。简而言之,您永远不能只依赖客户端的验证逻辑,而应当在每次使用数据时必须使用服务器端的验证逻辑对其进行验证。

在步骤 1 中 , 我们创建了 ProductsDataSource ObjectDataSource , 这样其 Insert() 方法就被映射到了 ProductsBLL 类的 AddProduct 方法上。要向 Products 表中插入一条新记录,我们只需调用 ObjectDataSource 的 Insert() 方法即可。

protected void Products_RowCommand(object sender, GridViewCommandEventArgs e)
{
    // Insert data if the CommandName == "Insert" 
    // and the validation controls indicate valid data...
    if (e.CommandName == "Insert" && Page.IsValid)
    {
        // Insert new record
        ProductsDataSource.Insert();
    }
}

既然已经调用了Insert() 方法 , 那么剩下的工作就是将插入界面中的值复制到传递给ProductsBLL 类的AddProduct 方法的参数中。正如我们在探讨与插入、更新、删除相关的事件 教程中回顾的那样,这个复制过程可以通过 ObjectDataSource 的插入事件来完成。在这个插入事件中,我们需要通过编码引用 Products GridView 控件脚注行的各个控件,将它们的值分配给 e.InputParameters 集合。如果用户省略了某个值 – 例如用户将 ReorderLevel TextBox 留为空白 – ,我们需要将这个要插入到数据库中的值指定为 NULL 。由于 AddProducts 方法可接受这些可空类型的数据用于可空数据库字段,所以当用户的输入为空值时,只需使用可空类型的数据,将其值设置为空值即可。

protected void ProductsDataSource_Inserting
    (object sender, ObjectDataSourceMethodEventArgs e)
{
    // Programmatically reference Web controls in the inserting interface...
    TextBox NewProductName = 
        (TextBox)Products.FooterRow.FindControl("NewProductName");
    DropDownList NewCategoryID = 
        (DropDownList)Products.FooterRow.FindControl("NewCategoryID");
    DropDownList NewSupplierID = 
        (DropDownList)Products.FooterRow.FindControl("NewSupplierID");
    TextBox NewQuantityPerUnit = 
        (TextBox)Products.FooterRow.FindControl("NewQuantityPerUnit");
    TextBox NewUnitPrice = 
        (TextBox)Products.FooterRow.FindControl("NewUnitPrice");
    TextBox NewUnitsInStock = 
        (TextBox)Products.FooterRow.FindControl("NewUnitsInStock");
    TextBox NewUnitsOnOrder = 
        (TextBox)Products.FooterRow.FindControl("NewUnitsOnOrder");
    TextBox NewReorderLevel = 
        (TextBox)Products.FooterRow.FindControl("NewReorderLevel");
    CheckBox NewDiscontinued = 
        (CheckBox)Products.FooterRow.FindControl("NewDiscontinued");
    // Set the ObjectDataSource's InsertParameters values...
    e.InputParameters["productName"] = NewProductName.Text;
    
    e.InputParameters["supplierID"] = 
        Convert.ToInt32(NewSupplierID.SelectedValue);
    e.InputParameters["categoryID"] = 
        Convert.ToInt32(NewCategoryID.SelectedValue);
    
    string quantityPerUnit = null;
    if (!string.IsNullOrEmpty(NewQuantityPerUnit.Text))
        quantityPerUnit = NewQuantityPerUnit.Text;
    e.InputParameters["quantityPerUnit"] = quantityPerUnit;
    decimal? unitPrice = null;
    if (!string.IsNullOrEmpty(NewUnitPrice.Text))
        unitPrice = Convert.ToDecimal(NewUnitPrice.Text);
    e.InputParameters["unitPrice"] = unitPrice;
    short? unitsInStock = null;
    if (!string.IsNullOrEmpty(NewUnitsInStock.Text))
        unitsInStock = Convert.ToInt16(NewUnitsInStock.Text);
    e.InputParameters["unitsInStock"] = unitsInStock;
    short? unitsOnOrder = null;
    if (!string.IsNullOrEmpty(NewUnitsOnOrder.Text))
        unitsOnOrder = Convert.ToInt16(NewUnitsOnOrder.Text);
    e.InputParameters["unitsOnOrder"] = unitsOnOrder;
    short? reorderLevel = null;
    if (!string.IsNullOrEmpty(NewReorderLevel.Text))
        reorderLevel = Convert.ToInt16(NewReorderLevel.Text);
    e.InputParameters["reorderLevel"] = reorderLevel;
    
    e.InputParameters["discontinued"] = NewDiscontinued.Checked;
}

完成插入 Event Handler 后 , 就可以通过GridView 控件的脚注行向 Products 数据库添加新记录了。请尝试添加几条新记录。

增强和定制 Add 操作

截止目前 , 可以通过单击Add 按钮向数据库表中添加一条新记录了 , 但是并未提供任何种类的可视反馈信息 , 来告知用户已经成功添加了这个记录。更理想是使用一个 Web 标签控件或客户端警告对话框来通知用户他们的插入操作已经成功完成了。我们把这个功能实现作为一个练习留给读者。

本教程中使用的 GridView 控件既不为列出的产品提供排序也不允许用户对这些数据进行排序。因此,数据库中的记录是按它们的主键字段来进行排序的。因为每个新记录的 ProductID 值都比最后一条记录的值大,这样每次添加新产品时,这个产品都被添加到网格的末尾。因此,您可能想在添加完新记录后向用户自动呈现 Gridview 控件的最后一页。要实现这个目的,您可以在 RowCommand Event Handler中,在调用 ProductsDataSource.Insert() 方法的代码后面添加下面一行代码,用来指示数据绑定到 GridView 控件后用户需要转向最后一个页面:

// Indicate that the user needs to be sent to the last page
SendUserToLastPage = true;

SendUserToLastPage 是一个页面级布尔型变量 , 其初始值为False 。在 GridView 控件的DataBound Event Handler 中 , 如果 SendUserToLastPage 值为false , 那么就会更新PageIndex 属性值将用户转向最后一个页面。

protected void Products_DataBound(object sender, EventArgs e)
{
    // Send user to last page of data, if needed
    if (SendUserToLastPage)
        Products.PageIndex = Products.PageCount - 1;
}

在DataBound Event Handler (而非 RowCommand Event Handler ) 中设置 PageIndex 属性 , 是因为当RowCommand Event Handler 触发时我们还没有将新记录添加到了产品数据库表中。所以在 RowCommand Event Handler 中,最后页面的页面索引 (PageCount - 1) 表示的是新产品添加之前最后页面的索引。对于大多数被添加的产品来说,添加完新产品后的最后页面索引是相同的。但是,如果我们在添加新产品时产生了一个新的最后页面索引,而我们没有在 RowCommand Event Handler 中正确地更新这个 PageIndex ,那么我们将会被转到倒数第二个页面(添加新产品之前的最后页面索引),而不是转向了新的最后页面索引上。因为在添加完新产品和将数据重新绑定到网格上后才会激发 DataBound Event Handler,所以我们可以通过设置我们知道的 PageIndex 属性,就可以获取正确的最后页面索引值。

最终,由于添加新产品时必须采集众多字段的数据,所以本教程中的 GridView 控件特别宽。正是由于这个宽度,所以 DetailsView 控件的输出首选垂直输出。通过减少采集输入的个数就可以降低 GridView 控件的总宽度。也许在添加新产品时我们不需要采集 UnitsOnOrder UnitsInStock 和 ReorderLevel 字段的数据,这时我们就可以将这些字段从 GridView 控件中删除。

为了调整数据采集的个数 , 我们可以使用以下两种方法 :

  • 继续使用 AddProduct 方法 , 这个方法仍然需要 UnitsOnOrder 、UnitsInStock 和 ReorderLevel 字段的值。但是在插入 Event Handler 中 , 对那些已经从插入界面中删除的输入提供固定值。
  • 在 ProductsBLL 类中创建一个新的 AddProduct 方法的重载 , 该重载不接受 UnitsOnOrder 、UnitsInStock 和 ReorderLevel 字段的输入 。接着,在 ASP.NET 页面中配置 ObjectDataSource 来使用这个新的重载。

这两种方法的作用都是一样的 , 您可以任选其一。在前面的教程中,我们选择使用了后一种方法,为 ProductsBLL 类的 UpdateProduct 方法创建了多个重载。

小结

GridView 控件缺少在 DetailsView 控件和FormView 控件中都有的内置插入功能 , 但是只需稍微努力一下就可以在脚注行添加一个插入界面。要在 GridView 控件中显示这个脚注行,只需简单地将其 ShowFooter 属性值设置为 True 即可。可以通过脚注行的每个字段来定制脚注行的内容,要完成这个定制,我们需要将这些字段转换成 TemplateField 字段,并将插入界面添加到 FooterTemplate 中。正如在本教程所看到的那样, FooterTemplate 可以包含 Button 、 TextBox 、 DropDownList 、 CheckBox 、构造数据驱动型 Web 控件的数据源控件
(如 DropDownList )和验证控件。除了用于采集用户输入的控件外,还需要 Add Button 、 LinkButton 或 ImageButton 。

单击Add 按钮时 ,调用 ObjectDataSource 的 Insert() 方法 , 从而启动插入工作流。接着 ObjectDataSource 会调用已配置的插入方法(本教程中是 ProductsBLL 类的 AddProduct 方法)。在调用插入方法之前,我们必须将 GridView 控件插入界面中的值复制到 ObjectDataSource 的 InsertParameters 集合中。这可以通过编码引用 ObjectDataSource 的插入 Event Handler 中的插入界面 Web 控件来完成。

本教程教会了我们在增强 GridView 控件外观方面的方法 , 在接下来的教程中 , 我们将会详细探讨如何使用二进制数据 ( 如图像、PDF 文档、Word 文档等等 ) 以及 Web 数据控件。

快乐编程!

 

 

下一篇教程