Insertar un nuevo registro desde el pie de página de GridView (VB)

por Scott Mitchell

Descargar PDF

Aunque el control GridView no proporciona compatibilidad integrada para insertar un nuevo registro de datos, en este tutorial se muestra cómo mejorar GridView para incluir una interfaz de inserción.

Introducción

Como se describe en el tutorial Información general sobre la inserción, actualización y eliminación de datos, los controles web GridView, DetailsView y FormView incluyen funcionalidades de modificación de datos. Cuando se usa con controles de origen de datos declarativos, estos tres controles web se pueden configurar de forma rápida y sencilla para modificar datos y en escenarios sin necesidad de escribir una sola línea de código. Por desgracia, solo los controles DetailsView y FormView proporcionan funcionalidades integradas de inserción, edición y eliminación. GridView solo ofrece compatibilidad con la edición y la eliminación. Pero con un poco de esfuerzo, se puede mejorar el control GridView para incluir una interfaz de inserción.

Al agregar capacidades de inserción a GridView, debe decidir cómo se van a agregar los nuevos registros, crear la interfaz de inserción y escribir el código para insertar el nuevo registro. En este tutorial verá cómo agregar la interfaz de inserción a una fila de pie de página de GridView (vea la figura 1). La celda de pie de página de cada columna incluye el elemento de interfaz de usuario de colección de datos adecuado (un control TextBox si es un producto, un control DropDownList si es un proveedor, etc.). También se necesita una columna para un botón Agregar que, cuando se presione, provoque un postback e inserte un nuevo registro en la tabla Products con los valores proporcionados en la fila de pie de página.

The Footer Row Provides an Interface for Adding New Products

Figura 1: La fila de pie de página proporciona una interfaz para agregar nuevos productos (Haga clic para ver la imagen a tamaño completo)

Paso 1: Representación de información del producto en un control GridView

Antes de preocuparse por crear la interfaz de inserción en el pie de página del control GridView, se centrará en agregar un control GridView a la página donde se muestran los productos de la base de datos. Para empezar, abra la página InsertThroughFooter.aspx en la carpeta EnhancedGridView, arrastre un control GridView desde el cuadro de herramientas al Diseñador y establezca la propiedad ID de GridView enProducts. Luego, en la etiqueta inteligente del control GridView, elija enlazarlo a un nuevo ObjectDataSource denominado ProductsDataSource.

Create a New ObjectDataSource Named ProductsDataSource

Figura 2: Creación de una instancia de ObjectDataSource con el nombre ProductsDataSource (Haga clic para ver la imagen a tamaño completo)

Configure ObjectDataSource para que use el método GetProducts() de la clase ProductsBLL para recuperar información de producto. En este tutorial, se centrará exclusivamente en agregar funcionalidades de inserción; no se preocupará por la edición ni la eliminación. Por tanto, asegúrese de que la lista desplegable de la pestaña INSERT está establecida en AddProduct() y de que las listas desplegables de las pestañas UPDATE y DELETE están establecidas en (None).

Map the AddProduct Method to the ObjectDataSource s Insert() Method

Figura 3: Asignación del método AddProduct al método Insert() de ObjectDataSource (Haga clic para ver la imagen a tamaño completo)

Set the UPDATE and DELETE Tabs Drop-Down Lists to (None)

Figura 4: Establecimiento de las pestañas UPDATE y DELETE de las listas desplegables en (None) (Haga clic para ver la imagen a tamaño completo)

Después de completar el asistente para la configuración de orígenes de datos del ObjectDataSource, Visual Studio agregará automáticamente campos al control GridView por cada uno de los campos de datos correspondientes. Por ahora, deje todos los campos que Visual Studio ha agregado. Más adelante en este tutorial, quitará algunos de los campos cuyos valores no es necesario especificar al agregar un nuevo registro.

Como hay cerca de 80 productos en la base de datos, un usuario tendrá que desplazarse hasta la parte inferior de la página web para agregar un nuevo registro. Por tanto, se habilitará la paginación para que la interfaz de inserción sea más visible y accesible. Para activar la paginación, simplemente active la casilla Habilitar paginación en la etiqueta inteligente del control GridView.

Llegado este punto, el marcado declarativo del control GridView y de ObjectDataSource debe ser similar al siguiente:

<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>

All Product Data Fields are Displayed in a Paged GridView

Figura 5: Todos los campos de datos de producto se muestran en un control GridView con paginación (Haga clic para ver la imagen a tamaño completo)

Además de filas de encabezado y de datos, GridView incluye una fila de pie de página. Las filas de encabezado y de pie de página se muestran en función de los valores de las propiedades ShowHeader y ShowFooter del control GridView. Para mostrar la fila de pie de página, simplemente establezca la propiedad ShowFooter en True. Como se muestra en la figura 6, al establecer la propiedad ShowFooter en True, se agrega una fila de pie de página a la tabla.

To Display the Footer Row, Set ShowFooter to True

Figura 6: Para mostrar la fila de pie de página, establezca ShowFooter en True (Haga clic para ver la imagen a tamaño completo)

Observe que la fila de pie de página tiene un color de fondo rojo oscuro. Esto se debe al tema DataWebControls que se ha creado y aplicado a todas las páginas en el tutorial Representación de datos con ObjectDataSource. En concreto, el archivo GridView.skin configura la propiedad FooterStyle de forma que usa la clase FooterStyle de CSS. La clase FooterStyle se define en Styles.css del modo siguiente:

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

Nota:

En tutoriales anteriores, se ha analizado el uso de la fila de pie de página de GridView. Si es necesario, consulte el tutorial Representación de información de resumen en el pie de página de GridView a modo de repaso.

Después de establecer la propiedad ShowFooter en True, dedique un momento a ver la salida en un explorador. Actualmente, la fila de pie de página no contiene ningún texto ni controles web. En el paso 3, modificará el pie de página de cada campo del control GridView para que incluya la interfaz de inserción adecuada.

The Empty Footer Row is Displayed Above the Paging Interface Controls

Figura 7: La fila de pie de página vacía se muestra encima de los controles de la interfaz de paginación (Haga clic para ver la imagen a tamaño completo)

En el tutorial Uso de controles TemplateField en el control GridView ha visto cómo personalizar profundamente la visualización de una determinada columna de GridView mediante controles TemplateField (en lugar de BoundField o CheckBoxField); en Personalización de la interfaz de modificación de datos, se ha examinado el uso de controles TemplateField para personalizar la interfaz de edición en un control GridView. Recordará que un control TemplateField se compone de una serie de plantillas que definen la combinación de marcado, controles web y sintaxis de enlace de datos usada para determinados tipos de filas. Por ejemplo, ItemTemplate especifica la plantilla usada para las filas de solo lectura, mientras que EditItemTemplate define la plantilla usada para la fila editable.

Junto con ItemTemplate y EditItemTemplate, el control TemplateField incluye también un objeto FooterTemplate que especifica el contenido de la fila de pie de página. Por tanto, se pueden agregar a FooterTemplate los controles web necesarios para la interfaz de inserción de cada campo. Para empezar, convierta todos los campos del control GridView en controles TemplateField. Para ello, haga clic en el vínculo Editar columnas de la etiqueta inteligente del control GridView, seleccione cada campo de la esquina inferior izquierda y haga clic en el vínculo Convertir este campo en un TemplateField.

Convert Each Field Into a TemplateField

Figura 8: Conversión de cada campo en un control TemplateField

Al hacer clic en Convertir este campo en un TemplateField, el tipo de campo actual se convierte en un control TemplateField equivalente. Por ejemplo, cada control BoundField se reemplazará por un control TemplateField con un elemento ItemTemplate que contiene una etiqueta donde se muestra el campo de datos correspondiente y un elemento EditItemTemplate que muestra el campo de datos en un control TextBox. El control BoundField ProductName se ha convertido en el siguiente marcado de 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>

Del mismo modo, el control CheckBoxField Discontinued se ha convertido en un control TemplateField cuyos elementos ItemTemplate y EditItemTemplate contienen un control web CheckBox (con el valor ItemTemplate de CheckBox deshabilitado). El control BoundField ProductID de solo lectura se ha convertido en un control TemplateField con un control Label en ItemTemplate y EditItemTemplate. En resumen, convertir un campo de GridView existente en un control TemplateField es una manera rápida y fácil de cambiar al uso de TemplateField, que son más personalizables, sin perder ninguna de las funciones de campo existentes.

Como el control GridView con el que trabaja no admite la edición, no dude en quitar EditItemTemplate de cada control TemplateField y dejar únicamente ItemTemplate. Tras ello, el marcado declarativo de GridView debe ser similar al siguiente:

<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>

Ahora que cada campo de GridView se ha convertido en un control TemplateField, se puede escribir la interfaz de inserción adecuada en el valor FooterTemplate de cada campo. Algunos campos no tendrán una interfaz de inserción (ProductID, por ejemplo); otros variarán en los controles web usados para recopilar la nueva información de producto.

Para crear la interfaz de edición, seleccione el vínculo Editar plantillas de la etiqueta inteligente del control GridView. Después, en la lista desplegable, seleccione el valor FooterTemplate del campo adecuado y arrastre el control adecuado desde el cuadro de herramientas al Diseñador.

Add the Appropriate Inserting Interface to Each Field s FooterTemplate

Figura 9: Adición de la interfaz de inserción adecuada al valor FooterTemplate de cada campo (Haga clic para ver la imagen a tamaño completo)

En la siguiente lista con viñetas se enumeran los campos de GridView y se especifica la interfaz de inserción que se va a agregar:

  • ProductID: ninguna.
  • ProductName: agregue un control TextBox y establezca su valor ID en NewProductName. Agregue también un control RequiredFieldValidator para asegurarse de que el usuario escribe un valor de nombre del nuevo producto.
  • SupplierID: ninguna.
  • CategoryID: ninguna.
  • QuantityPerUnit: agregue un control TextBox y establezca su valor ID en NewQuantityPerUnit.
  • UnitPrice: agregue un control TextBox denominado NewUnitPrice y un control CompareValidator que garantice que el valor especificado sea un valor de moneda mayor o igual que cero.
  • UnitsInStock: use un control TextBox cuyo valor ID esté establecido en NewUnitsInStock. Incluya un elemento CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • UnitsOnOrder: use un control TextBox cuyo valor ID esté establecido en NewUnitsOnOrder. Incluya un elemento CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • ReorderLevel: use un control TextBox cuyo valor ID esté establecido en NewReorderLevel. Incluya un elemento CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • Discontinued: agregue un control CheckBox y establezca su valor ID en NewDiscontinued.
  • CategoryName: agregue un control DropDownList y establezca su valor ID en NewCategoryID. Enlácelo a una nueva instancia de ObjectDataSource con el nombre CategoriesDataSource y configúrelo para usar el método CategoriesBLL de la clase GetCategories(). Haga que los elementos ListItem del control DropDownList muestren el campo de datos CategoryName, utilizando para ello el campo de datos CategoryID como sus valores.
  • SupplierName: agregue un control DropDownList y establezca su valor ID en NewSupplierID. Enlácelo a una nueva instancia de ObjectDataSource con el nombre SuppliersDataSource y configúrelo para usar el método SuppliersBLL de la clase GetSuppliers(). Haga que los elementos ListItem del control DropDownList muestren el campo de datos CompanyName, utilizando para ello el campo de datos SupplierID como sus valores.

En cada uno de los controles de validación, borre la propiedad ForeColor para que se use el color de primer plano blanco de la clase FooterStyle de CSS en lugar del rojo predeterminado. Use también la propiedad ErrorMessage para obtener una descripción detallada, pero establezca la propiedad Text en un asterisco. Para evitar que el texto del control de validación haga que la interfaz de inserción se ajuste en dos líneas, establezca la propiedad Wrap de FooterStyle en false en cada uno de los objetos FooterTemplate que usen un control de validación. Por último, agregue un control ValidationSummary debajo de GridView y establezca su propiedad ShowMessageBox en True y su propiedad ShowSummary en False.

Al agregar un nuevo producto, se deben proporcionar los valores CategoryID y SupplierID. Esta información se captura mediante los controles DropDownList en las celdas de pie de página de los campos CategoryName y SupplierName. Aquí se ha optado por usar estos campos en lugar de los controles TemplateField CategoryID y SupplierID porque en las filas de datos de la tabla probablemente el usuario esté más interesado en ver los nombres de categoría y proveedor que sus identificadores. Como ahora los valores de CategoryID y SupplierID se capturan en las interfaces de inserción de los campos CategoryName y SupplierName, se pueden quitar los controles TemplateField CategoryID y SupplierID del control GridView.

Del mismo modo, ProductID no se usa al agregar un nuevo producto, por lo que el control TemplateField ProductID también se puede quitar. Pero se dejará el campo ProductID en la tabla. Además de los controles TextBox, DropDownList, CheckBox y de validación que componen la interfaz de inserción, también se necesitará un botón Agregar que, cuando se presione, ponga en marcha la lógica para agregar el nuevo producto a la base de datos. En el paso 4 se incluirá un botón Agregar en la interfaz de inserción, en el elemento FooterTemplate del control TemplateField ProductID.

No dude en mejorar la apariencia de los distintos campos del control GridView. Por ejemplo, es posible que quiera dar un formato de moneda a los valores UnitPrice, alinear a la derecha los campos UnitsInStock, UnitsOnOrder y ReorderLevel, y actualizar los valores HeaderText de los controles TemplateField.

Después de crear las interfaces de inserción en FooterTemplate, de quitar los controles TemplateField SupplierID y CategoryID, y de mejorar la estética de la tabla aplicando formato y alineando los controles TemplateField, el marcado declarativo del control GridView debe ser similar al siguiente:

<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>

Cuando se visualiza en un explorador, la fila de pie de página del control GridView incluye ahora la interfaz de inserción completada (vea la figura 10). En este momento, la interfaz de inserción no incluye un medio para que el usuario indique que ha introducido los datos del nuevo producto y que quiere insertar un nuevo registro en la base de datos. Además, todavía no se ha abordado cómo se van a convertir los datos introducidos en el pie de página en un nuevo registro de la base de datos Products. En el paso 4 verá cómo incluir un botón Agregar a la interfaz de inserción y cómo ejecutar código en un postback cuando se haga clic en él. En el paso 5 se describe cómo insertar un nuevo registro con los datos del pie de página.

The GridView Footer Provides an Interface for Adding a New Record

Figura 10: El pie de página del control GridView proporciona una interfaz para agregar un nuevo registro (Haga clic para ver la imagen a tamaño completo)

Paso 4: Inclusión de un botón Agregar en la interfaz de inserción

Es necesario incluir un botón Agregar en algún lugar de la interfaz de inserción de la fila de pie de página, ya que carece actualmente de alguna forma de que el usuario indique que ha completado la entrada de la nueva información del producto. Este botón se podría colocar en uno de los elementos FooterTemplate existentes, o bien se podría agregar una nueva columna a la tabla para este propósito. En este tutorial, el botón Agregar se colocará en el elemento FooterTemplate del control TemplateField ProductID.

En el Diseñador, haga clic en el vínculo Editar plantillas de la etiqueta inteligente del control GridView y, después, seleccione el elemento FooterTemplate del campo ProductID en la lista desplegable. Agregue a la plantilla un control web Button (o LinkButton o ImageButton, si lo prefiere), establezca su identificador en AddProduct, CommandName en Insertar y su propiedad Text en Agregar, como se muestra en la figura 11.

Place the Add Button in the ProductID TemplateField s FooterTemplate

Figura 11: Colocación del botón Agregar en el elemento FooterTemplate del control TemplateField ProductID (Haga clic para ver la imagen a tamaño completo)

Una vez que haya incluido el botón Agregar, pruebe la página en un explorador. Fíjese en que, al hacer clic en el botón Agregar con datos no válidos en la interfaz de inserción, el postback se cortocircuita y el control ValidationSummary indica los datos no válidos (vea la figura 12). Cuando se especifican los datos adecuados, al hacer clic en el botón Agregar, se produce un postback. Pero no se agrega ningún registro a la base de datos. Tendrá que escribir de código para que la inserción se realice de verdad.

The Add Button s Postback is Short Circuited if There is Invalid Data in the Inserting Interface

Figura 12: El postback del botón Agregar se cortocircuita si hay datos no válidos en la interfaz de inserción (Haga clic para ver la imagen a tamaño completo)

Nota:

Los controles de validación de la interfaz de inserción no estaban asignados a un grupo de validación. Esto funciona bien siempre que la interfaz de inserción sea el único conjunto de controles de validación de la página. Pero si hay otros controles de validación en la página (como los controles de validación en la interfaz de edición de la cuadrícula), los controles de validación de la interfaz de inserción y las propiedades ValidationGroup del botón Agregar deben tener asignado el mismo valor para asociar estos controles a un grupo de validación determinado. Vea Disección de los controles de validación de ASP.NET 2.0 para más información sobre cómo dividir en grupos de validación los controles de validación y los botones de una página.

Paso 5: Inserción de un nuevo registro en la tabla Products

Al usar las características de edición integradas del control GridView, este controla automáticamente todo el trabajo necesario para realizar la actualización. En concreto, cuando se hace clic en el botón Actualizar, copia los valores especificados desde la interfaz de edición a los parámetros de la colección UpdateParameters de ObjectDataSource e inicia la actualización mediante la invocación del método Update() de ObjectDataSource. Como el control GridView no proporciona una funcionalidad integrada de este tipo para realizar inserciones, se debe implementar código que llame al método Insert() de ObjectDataSource y copie los valores de la interfaz de inserción en la colección InsertParameters de ObjectDataSource.

Esta lógica de inserción debe ejecutarse después de hacer clic en el botón Agregar. Como se describe en el tutorial Adición y respuesta a los botones en un control GridView, cuando se hace clic en un control Button, LinkButton o ImageButton de un control GridView, se activa el evento RowCommand del control GridView en el postback. Este evento se activa tanto si el control Button, LinkButton o ImageButton se ha agregado explícitamente (como el botón Agregar en la fila de pie de página), como si se ha agregado automáticamente mediante el control GridView (como los controles LinkButton de la parte superior de cada columna cuando se selecciona Habilitar ordenación, o los controles LinkButton de la interfaz de paginación cuando se selecciona Habilitar paginación).

Por tanto, para responder al usuario que hace clic en el botón Agregar, es necesario crear un controlador de eventos para el evento RowCommand del control GridView. Como este evento se activa cada vez que se hace clic en cualquier control Button, LinkButton o ImageButton del control GridView, es fundamental continuar con la lógica de inserción solo si la propiedad CommandName pasada al controlador de eventos se asigna al valor CommandName del botón Agregar (Insertar). Además, solo se debería continuar si los controles de validación notifican datos válidos. Para dar cabida a esto, cree un controlador de eventos para el evento RowCommand con el código siguiente:

Protected Sub Products_RowCommand(sender As Object, e As GridViewCommandEventArgs) _
    Handles Products.RowCommand
    
    ' Insert data if the CommandName == "Insert" 
    ' and the validation controls indicate valid data...
    If e.CommandName = "Insert" AndAlso Page.IsValid Then
        ' TODO: Insert new record...
    End If
End Sub

Nota:

Es posible que se pregunte por qué el controlador de eventos se molesta en comprobar la propiedad Page.IsValid. Al fin y al cabo, ¿no se suprimirá el postback si se proporcionan datos no válidos en la interfaz de inserción? Esta suposición es correcta siempre que el usuario no haya deshabilitado JavaScript o haya tomado medidas para eludir la lógica de validación del lado cliente. En resumen, nunca se debe confiar estrictamente en la validación del lado cliente; siempre se debe realizar una comprobación de validez del lado servidor antes de trabajar con los datos.

En el paso 1 se ha creado el elemento ObjectDataSource ProductsDataSource de forma que su método Insert() se asigna al método AddProduct de la clase ProductsBLL. Para insertar el nuevo registro en la tabla Products, simplemente se puede invocar el método Insert() de ObjectDataSource:

Protected Sub Products_RowCommand(sender As Object, e As GridViewCommandEventArgs) _
    Handles Products.RowCommand
    
    ' Insert data if the CommandName == "Insert" 
    ' and the validation controls indicate valid data...
    If e.CommandName = "Insert" AndAlso Page.IsValid Then
        ' Insert new record
        ProductsDataSource.Insert()
    End If
End Sub

Ahora que se ha invocado el método Insert(), lo único que queda es copiar los valores de la interfaz de inserción en los parámetros pasados al método AddProduct de la clase ProductsBLL. Como ha visto en el tutorial Examen de los eventos relacionados con la inserción, actualización y eliminación, esto se puede lograr mediante el evento Inserting de ObjectDataSource. En el evento Inserting, es necesario hacer referencia mediante programación a los controles de la fila de pie de tabla del control GridView Products y asignar sus valores a la colección e.InputParameters. Si el usuario omite un valor, como dejar el control TextBox ReorderLevel en blanco, es necesario especificar que el valor insertado en la base de datos debe ser NULL. Como el método AddProducts admite tipos que aceptan valores NULL en los campos de base de datos que aceptan valores NULL, simplemente use un tipo que acepte valores NULL y establezca su valor en Nothing en caso de que la entrada del usuario se omita.

Protected Sub ProductsDataSource_Inserting _
    (sender As Object, e As .ObjectDataSourceMethodEventArgs) _
    Handles ProductsDataSource.Inserting
    
    ' Programmatically reference Web controls in the inserting interface...
    Dim NewProductName As TextBox = _
        Products.FooterRow.FindControl("NewProductName")
    Dim NewCategoryID As DropDownList = _
        Products.FooterRow.FindControl("NewCategoryID")
    Dim NewSupplierID As DropDownList = _
        Products.FooterRow.FindControl("NewSupplierID")
    Dim NewQuantityPerUnit As TextBox = _
        Products.FooterRow.FindControl("NewQuantityPerUnit")
    Dim NewUnitPrice As TextBox = _
        Products.FooterRow.FindControl("NewUnitPrice")
    Dim NewUnitsInStock As TextBox = _
        Products.FooterRow.FindControl("NewUnitsInStock")
    Dim NewUnitsOnOrder As TextBox = _
        Products.FooterRow.FindControl("NewUnitsOnOrder")
    Dim NewReorderLevel As TextBox = _
        Products.FooterRow.FindControl("NewReorderLevel")
    Dim NewDiscontinued As 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)
    Dim quantityPerUnit As String = Nothing
    If Not String.IsNullOrEmpty(NewQuantityPerUnit.Text) Then
        quantityPerUnit = NewQuantityPerUnit.Text
    End If
    e.InputParameters("quantityPerUnit") = quantityPerUnit
    Dim unitPrice As Nullable(Of Decimal) = Nothing
    If Not String.IsNullOrEmpty(NewUnitPrice.Text) Then
        unitPrice = Convert.ToDecimal(NewUnitPrice.Text)
    End If
    e.InputParameters("unitPrice") = unitPrice
    Dim unitsInStock As Nullable(Of Short) = Nothing
    If Not String.IsNullOrEmpty(NewUnitsInStock.Text) Then
        unitsInStock = Convert.ToInt16(NewUnitsInStock.Text)
    End If
    e.InputParameters("unitsInStock") = unitsInStock
    Dim unitsOnOrder As Nullable(Of Short) = Nothing
    If Not String.IsNullOrEmpty(NewUnitsOnOrder.Text) Then
        unitsOnOrder = Convert.ToInt16(NewUnitsOnOrder.Text)
    End If
    e.InputParameters("unitsOnOrder") = unitsOnOrder
    Dim reorderLevel As Nullable(Of Short) = Nothing
    If Not String.IsNullOrEmpty(NewReorderLevel.Text) Then
        reorderLevel = Convert.ToInt16(NewReorderLevel.Text)
    End If
    e.InputParameters("reorderLevel") = reorderLevel
    e.InputParameters("discontinued") = NewDiscontinued.Checked
End Sub

Una vez que se complete el controlador de eventos Inserting, se pueden agregar nuevos registros a la tabla de base de datos Products desde la fila de pie de página del control GridView. Pruebe a agregar varios productos nuevos.

Mejora y personalización de la operación de adición

Actualmente, al hacer clic en el botón Agregar, se agrega un nuevo registro a la tabla de base de datos, pero no se proporciona ningún tipo de pista visual que indique que el registro se ha agregado correctamente. Lo ideal es que un control web Label o un cuadro de alerta del lado cliente informe al usuario de que la inserción se ha completado correctamente. Lo dejo como ejercicio para el lector.

El control GridView usado en este tutorial no aplica ningún criterio de ordenación a los productos enumerados, ni permite al usuario final ordenar los datos. Por tanto, los registros se ordenan como están en la base de datos, por el campo de clave principal. Como cada nuevo registro tiene un valor ProductID mayor que el último, cada vez que se agrega un nuevo producto, se coloca al final de la tabla. Por tanto, seguramente sea interesante dirigir al usuario de forma automática a la última página del control GridView después de agregar un nuevo registro. Esto se puede lograr si se agrega la siguiente línea de código después de la llamada a ProductsDataSource.Insert() en el controlador de eventos RowCommand para indicar que hay que dirigir al usuario a la última página después de enlazar los datos al control GridView:

' Indicate that the user needs to be sent to the last page
SendUserToLastPage = True

SendUserToLastPage es una variable booleana de nivel de página que tiene asignado un valor inicial de False. En el controlador de eventos DataBound del control GridView, si SendUserToLastPage es false, la propiedad PageIndex se actualiza para dirigir al usuario a la última página.

Protected Sub Products_DataBound(sender As Object, e As EventArgs) _
    Handles Products.DataBound
    
    ' Send user to last page of data, if needed
    If SendUserToLastPage Then
        Products.PageIndex = Products.PageCount - 1
    End If
End Sub

La razón por la que la propiedad PageIndex se establece en el controlador de eventos DataBound (en lugar del controlador de eventos RowCommand) es que cuando el controlador de eventos RowCommand se activa, aún falta agregar el nuevo registro a la tabla de base de datos Products. Por tanto, en el controlador de eventos RowCommand, el índice de la última página (PageCount - 1) representa el índice de la última página antes de agregar el nuevo producto. En la mayoría de los productos que se agregan, el índice de la última página es el mismo después de agregar el nuevo producto. Pero cuando el producto agregado da como resultado un nuevo índice de última página, si se actualiza incorrectamente el elemento PageIndex en el controlador de eventos RowCommand, se dirigirá a la antepenúltima página (el índice de última página anterior a la adición del nuevo producto) en lugar del nuevo índice de última página. Como el controlador de eventos DataBound se activa después de agregar el nuevo producto y los datos se vuelven a enlazar a la tabla, al establecer la propiedad PageIndex allí, sabrá que está en el índice de última página correcto.

Por último, el control GridView usado en este tutorial es bastante amplio debido al número de campos que se deben recopilar para agregar un nuevo producto. Debido a este ancho, es posible que se prefiera un diseño vertical del control DetailsView. El ancho general del control GridView podría reducirse recopilando menos entradas. Quizás no sea necesario recopilar los campos UnitsOnOrder, UnitsInStock y ReorderLevel al agregar un nuevo producto, en cuyo caso estos campos se podrían quitar del control GridView.

Para ajustar los datos recopilados, puede usar uno de estos dos enfoques:

  • Seguir usando el método AddProduct, que espera valores en los campos UnitsOnOrder, UnitsInStock y ReorderLevel. En el controlador de eventos Inserting, proporcione valores predeterminados codificados de forma rígida que se usarán para estas entradas que se han quitado de la interfaz de inserción.
  • Crear una sobrecarga del método AddProduct en la claseProductsBLL que no acepte entradas en los campos UnitsOnOrder, UnitsInStock y ReorderLevel. Después, en la página ASP.NET, configure ObjectDataSource para usar esta nueva sobrecarga.

Ambas opciones funcionarán igual de bien. En los tutoriales anteriores se ha usado la última opción y se crearon varias sobrecargas para el método UpdateProduct de la clase ProductsBLL.

Resumen

El control GridView carece de las funcionalidades de inserción integradas que sí tienen los controles DetailsView y FormView, pero con un poco de esfuerzo se puede agregar una interfaz de inserción a la fila de pie de página. Para mostrar la fila de pie de página en el control GridView, simplemente establezca su propiedad ShowFooter en True. El contenido de la fila de pie de página se puede personalizar para cada campo; para ello, el campo se convierte en un control TemplateField y se agrega la interfaz de inserción a FooterTemplate. Como ha visto en este tutorial, FooterTemplate puede contener controles Button, TextBox, DropDownList y CheckBox, así como controles de origen de datos para rellenar controles web basados en datos (como DropDownList) y controles de validación. Junto con los controles para recopilar la entrada del usuario, es necesario agregar un control Button, LinkButton o ImageButton.

Cuando se hace clic en el botón Agregar, se invoca el método Insert() de ObjectDataSource para iniciar el flujo de trabajo de inserción. Después, ObjectDataSource llamará al método de inserción configurado (en este tutorial, el método AddProduct de la clase ProductsBLL). Será necesario copiar los valores de la interfaz de inserción del control GridView en la colección InsertParameters de ObjectDataSource antes de invocar el método de inserción. Esto se puede lograr si se hace referencia mediante programación a los controles web de la interfaz de inserción en el controlador de eventos Inserting de ObjectDataSource.

Con este tutorial se completa el análisis de las técnicas existentes para mejorar la apariencia del control GridView. En el siguiente grupo de tutoriales se examinará cómo trabajar con datos binarios, como imágenes, PDF, documentos de Word, etc. y los controles web de datos.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él a través de mitchell@4GuysFromRolla.com. o de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores. La revisora principal de este tutorial ha sido Bernadette Leigh. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.