Comunicación entre dos ASPxGridView con pestañas (patrón selección → detalle)

 

Qué vamos a lograr

Una página GridsConnections.aspx con un control de pestañas (ASPxPageControl):

  • Pestaña 1 (Selección): un combo que muestra ARTICULO_ID + NOMBRE_CORTO, un botón Agregar, y una grilla "carrito" con lo elegido.
  • Pestaña 2 (Detalle/Modificar): una grilla con los datos completos, editable, que muestra solo los artículos agregados en la pestaña 1.

La idea central: dos grids no se hablan directo; comparten un estado en Session y la segunda grilla se filtra con FilterExpression.

Paso 1 — Registrar los namespaces necesarios

Aquí está la trampa que da error si falta: ASPxPageControl/TabPage viven en ASPxTabControl, pero ContentControl vive en ASPxClasses. Hay que registrar ambos.

<%@ Page Title="Conexión entre Grids" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Master" CodeBehind="GridsConnections.aspx.vb" Inherits="WebApplication2.GridsConnections" %>
<%@ Register Assembly="DevExpress.Web.v13.2, Version=13.2.12.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" Namespace="DevExpress.Web.ASPxGridView" TagPrefix="dx" %>
<%@ Register Assembly="DevExpress.Web.v13.2, Version=13.2.12.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" Namespace="DevExpress.Web.ASPxEditors" TagPrefix="dx" %>
<%@ Register Assembly="DevExpress.Web.v13.2, Version=13.2.12.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" Namespace="DevExpress.Web.ASPxTabControl" TagPrefix="dx" %>
<%@ Register Assembly="DevExpress.Web.v13.2, Version=13.2.12.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" Namespace="DevExpress.Web.ASPxClasses" TagPrefix="dx" %>

Paso 2 — El cuerpo de la página GridsConnections.aspx

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<div class="container">
<h2>Comunicación entre dos Grids</h2>
<dx:ASPxPageControl ID="pageControl" runat="server" ActiveTabIndex="0" Width="100%" Theme="Aqua">
<TabPages>
<!-- ===== PESTAÑA 1: SELECCIÓN ===== -->
<dx:TabPage Text="1. Selección de artículos">
<ContentCollection>
<dx:ContentControl runat="server">
<div style="margin: 15px 0;">
<span style="margin-right: 8px;">Artículo:</span>
<dx:ASPxComboBox ID="cbArticulos" runat="server" DataSourceID="dsArticulosCombo"
TextField="NOMBRE_CORTO" ValueField="ARTICULO_ID" ValueType="System.Int32"
IncrementalFilteringMode="Contains" Width="320px" Theme="Aqua"
NullText="-- Seleccione un artículo --">
<Columns>
<dx:ListBoxColumn FieldName="ARTICULO_ID" Caption="ID" Width="60px" />
<dx:ListBoxColumn FieldName="NOMBRE_CORTO" Caption="Nombre corto" Width="240px" />
</Columns>
</dx:ASPxComboBox>
<dx:ASPxButton ID="btnAgregar" runat="server" Text="Agregar"
Theme="Aqua" AutoPostBack="true" OnClick="btnAgregar_Click"
style="margin-left: 8px; display: inline-block; vertical-align: top;" />
</div>
<h4>Artículos seleccionados</h4>
<dx:ASPxGridView ID="gvSeleccion" runat="server" AutoGenerateColumns="False"
KeyFieldName="ARTICULO_ID" Theme="Aqua" Width="100%"
OnCustomButtonCallback="gvSeleccion_CustomButtonCallback">
<Columns>
<dx:GridViewDataTextColumn FieldName="ARTICULO_ID" Caption="ID" VisibleIndex="0" Width="80px" />
<dx:GridViewDataTextColumn FieldName="NOMBRE_CORTO" Caption="Nombre corto" VisibleIndex="1" />
<dx:GridViewCommandColumn VisibleIndex="2" Width="90px">
<CustomButtons>
<dx:GridViewCommandColumnCustomButton ID="btnQuitar" Text="Quitar" />
</CustomButtons>
</dx:GridViewCommandColumn>
</Columns>
<SettingsText EmptyDataRow="Aún no hay artículos seleccionados." />
</dx:ASPxGridView>
</dx:ContentControl>
</ContentCollection>
</dx:TabPage>
<!-- ===== PESTAÑA 2: DETALLE / MODIFICAR ===== -->
<dx:TabPage Text="2. Detalle / Modificar">
<ContentCollection>
<dx:ContentControl runat="server">
<div style="margin: 15px 0;">
<dx:ASPxGridView ID="gvDetalle" runat="server" AutoGenerateColumns="False"
DataSourceID="dsArticulosFull" KeyFieldName="ARTICULO_ID" Theme="Aqua" Width="100%">
<Columns>
<dx:GridViewCommandColumn ShowEditButton="True" VisibleIndex="0" Width="70px" />
<dx:GridViewDataTextColumn FieldName="ARTICULO_ID" ReadOnly="True" VisibleIndex="1" />
<dx:GridViewDataTextColumn FieldName="NOMBRE" VisibleIndex="2" />
<dx:GridViewDataTextColumn FieldName="NOMBRE_CORTO" VisibleIndex="3" />
<dx:GridViewDataComboBoxColumn FieldName="TAX_ID" Caption="Impuesto" VisibleIndex="4">
<PropertiesComboBox DataSourceID="dsImpuestos"
TextField="TAX_NOMBRE" ValueField="TAX_ID" ValueType="System.Int32"
DropDownStyle="DropDownList" IncrementalFilteringMode="StartsWith" />
</dx:GridViewDataComboBoxColumn>
<dx:GridViewDataTextColumn FieldName="PRECIO" VisibleIndex="5" />
<dx:GridViewDataTextColumn FieldName="MEDIDA" VisibleIndex="6" />
<dx:GridViewDataCheckColumn FieldName="FAVORITO" Caption="Favorito" VisibleIndex="7">
<PropertiesCheckEdit ValueType="System.Int32" ValueChecked="1" ValueUnchecked="0" />
</dx:GridViewDataCheckColumn>
</Columns>
<SettingsText EmptyDataRow="No hay artículos que mostrar. Agregue artículos en la pestaña 1." />
</dx:ASPxGridView>
</div>
</dx:ContentControl>
</ContentCollection>
</dx:TabPage>
</TabPages>
</dx:ASPxPageControl>
<!-- ===== ORÍGENES DE DATOS ===== -->
<!-- Combo de la pestaña 1: sólo ID y nombre corto -->
<asp:SqlDataSource ID="dsArticulosCombo" runat="server"
ConnectionString="<%$ ConnectionStrings:SPOSDBConnectionString %>"
SelectCommand="SELECT [ARTICULO_ID], [NOMBRE_CORTO] FROM [Articulos] ORDER BY [NOMBRE_CORTO]">
</asp:SqlDataSource>
<!-- Grid de la pestaña 2: datos completos + edición -->
<asp:SqlDataSource ID="dsArticulosFull" runat="server"
ConnectionString="<%$ ConnectionStrings:SPOSDBConnectionString %>"
SelectCommand="SELECT [ARTICULO_ID], [NOMBRE], [NOMBRE_CORTO], [TAX_ID], [PRECIO], [MEDIDA], [FAVORITO] FROM [Articulos]"
UpdateCommand="UPDATE [Articulos] SET [NOMBRE] = @NOMBRE, [NOMBRE_CORTO] = @NOMBRE_CORTO, [TAX_ID] = @TAX_ID, [PRECIO] = @PRECIO, [MEDIDA] = @MEDIDA, [FAVORITO] = @FAVORITO WHERE [ARTICULO_ID] = @ARTICULO_ID">
<UpdateParameters>
<asp:Parameter Name="NOMBRE" Type="String" />
<asp:Parameter Name="NOMBRE_CORTO" Type="String" />
<asp:Parameter Name="TAX_ID" Type="Int32" />
<asp:Parameter Name="PRECIO" Type="Double" />
<asp:Parameter Name="MEDIDA" Type="String" />
<asp:Parameter Name="FAVORITO" Type="Int32" />
<asp:Parameter Name="ARTICULO_ID" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
<!-- Combo de impuestos dentro del grid de detalle -->
<asp:SqlDataSource ID="dsImpuestos" runat="server"
ConnectionString="<%$ ConnectionStrings:SPOSDBConnectionString %>"
SelectCommand="SELECT [TAX_ID], [TAX_NOMBRE], [PORCENTAJE] FROM [Impuestos] ORDER BY [TAX_NOMBRE]">
</asp:SqlDataSource>
</div>
</asp:Content>

Paso 3 — El code-behind GridsConnections.aspx.vb (el "cerebro" de la comunicación)

Imports System.Data
Imports DevExpress.Web.ASPxGridView
Public Class GridsConnections
Inherits System.Web.UI.Page
' El "puente" entre los dos grids: un DataTable en la sesión del usuario.
Private Const SessionKey As String = "ArticulosSeleccionados"
' Carrito de selección. Vive en Session para sobrevivir a los postbacks.
Private ReadOnly Property Seleccion As DataTable
Get
Dim dt = TryCast(Session(SessionKey), DataTable)
If dt Is Nothing Then
dt = New DataTable()
dt.Columns.Add("ARTICULO_ID", GetType(Integer))
dt.Columns.Add("NOMBRE_CORTO", GetType(String))
Session(SessionKey) = dt
End If
Return dt
End Get
End Property
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
BindSeleccion()
End If
' El filtro del detalle se recalcula en cada render para reflejar el carrito.
AplicarFiltroDetalle()
End Sub
' Agrega el artículo elegido en el combo al carrito (sin duplicados).
Protected Sub btnAgregar_Click(ByVal sender As Object, ByVal e As EventArgs)
If cbArticulos.Value Is Nothing Then Return
Dim id As Integer = Convert.ToInt32(cbArticulos.Value)
Dim yaExiste As Boolean = Seleccion.Select("ARTICULO_ID = " & id).Length > 0
If Not yaExiste Then
Seleccion.Rows.Add(id, cbArticulos.Text)
End If
cbArticulos.Value = Nothing
BindSeleccion()
AplicarFiltroDetalle()
End Sub
' Quita un artículo del carrito con el botón "Quitar".
Protected Sub gvSeleccion_CustomButtonCallback(ByVal sender As Object, ByVal e As ASPxGridViewCustomButtonCallbackEventArgs)
If e.ButtonID = "btnQuitar" Then
Dim id As Integer = Convert.ToInt32(gvSeleccion.GetRowValues(e.VisibleIndex, "ARTICULO_ID"))
For Each row As DataRow In Seleccion.Select("ARTICULO_ID = " & id)
row.Delete()
Next
Seleccion.AcceptChanges()
BindSeleccion()
AplicarFiltroDetalle()
End If
End Sub
Private Sub BindSeleccion()
gvSeleccion.DataSource = Seleccion
gvSeleccion.DataBind()
End Sub
' Convierte el carrito en un FilterExpression: el grid de detalle sólo
' muestra (y permite editar) los artículos seleccionados.
Private Sub AplicarFiltroDetalle()
Dim ids = Seleccion.AsEnumerable().
Select(Function(r) r.Field(Of Integer)("ARTICULO_ID")).ToArray()
If ids.Length = 0 Then
gvDetalle.FilterExpression = "ARTICULO_ID In (-1)" ' no muestra nada
Else
gvDetalle.FilterExpression = "ARTICULO_ID In (" & String.Join(",", ids) & ")"
End If
End Sub
End Class

Paso 4 — El archivo designer GridsConnections.aspx.designer.vb

En Visual Studio se genera solo, pero si lo creas a mano necesitas declarar los controles para que el code-behind los "vea":

Option Strict On
Option Explicit On
Partial Public Class GridsConnections
Protected WithEvents pageControl As Global.DevExpress.Web.ASPxTabControl.ASPxPageControl
Protected WithEvents cbArticulos As Global.DevExpress.Web.ASPxEditors.ASPxComboBox
Protected WithEvents btnAgregar As Global.DevExpress.Web.ASPxEditors.ASPxButton
Protected WithEvents gvSeleccion As Global.DevExpress.Web.ASPxGridView.ASPxGridView
Protected WithEvents gvDetalle As Global.DevExpress.Web.ASPxGridView.ASPxGridView
Protected WithEvents dsArticulosCombo As Global.System.Web.UI.WebControls.SqlDataSource
Protected WithEvents dsArticulosFull As Global.System.Web.UI.WebControls.SqlDataSource
Protected WithEvents dsImpuestos As Global.System.Web.UI.WebControls.SqlDataSource
End Class

Cómo funciona la comunicación (resumen mental)

  1. En la pestaña 1 eliges en el combo y pulsas AgregarbtnAgregar_Click guarda el artículo en el DataTable de Session (el carrito) y refresca la grilla de selección.
  2. AplicarFiltroDetalle() corre en cada carga de página: toma los IDs del carrito y arma un FilterExpression (ARTICULO_ID In (1,2,3)).
  3. La pestaña 2 tiene su grilla atada a un SqlDataSource que trae todo, pero el FilterExpression hace que solo se vean los seleccionados. Como el SqlDataSource conserva el UpdateCommand, la edición sigue funcionando.
  4. El botón Quitar elimina del carrito y todo se recalcula.

Errores comunes

  • "Etiqueta de servidor 'dx:ContentControl' desconocida" → falta registrar el namespace DevExpress.Web.ASPxClasses (Paso 1).
  • La pestaña 2 no se actualiza → asegúrate de llamar AplicarFiltroDetalle() en Page_Load siempre (no solo en Not IsPostBack).
  • El botón no dispara nada → revisa AutoPostBack="true" y OnClick="btnAgregar_Click".

Para agregarlo al menú (Site.Master)

<li><a runat="server" href="~/GridsConnections">Grids conectados</a></li>

Comentarios

Entradas más populares de este blog

Sacando aceleracion de un vehiculo en c++

Ejemplo de suma Pascal

Gráfica de Seno y Coseno en C++ | Código C++