This example demonstrates how to implement master-detail functionality and update all grid controls on a custom button click in batch edit mode.
Overview
Follow the steps below to update master and detail grids simultaneously:
- Create a master grid control, specify its Templates.DetailRow property, and add a detail grid to the template.
- Hide default command buttons in the grid's server-side CommandButtonInitialize event handler and use the master grid's StatusBar property to create custom Save Changes and Cancel Changes buttons.ASPx
<Templates> <!-- ... --> <StatusBar> <div style="text-align: right"> <dx:ASPxButton ID="btn" Text="Save Changes" RenderMode="Link" AutoPostBack="false" runat="server"> <ClientSideEvents Click="OnClick" /> </dx:ASPxButton> <dx:ASPxButton ID="btn2" Text="Cancel Changes" RenderMode="Link" AutoPostBack="false" runat="server"> <ClientSideEvents Click="OnCancel" /> </dx:ASPxButton> </div> </StatusBar> </Templates>
C#protected void Grid_CommandButtonInitialize(object sender, ASPxGridViewCommandButtonEventArgs e) { if (e.ButtonType == ColumnCommandButtonType.Update || e.ButtonType == ColumnCommandButtonType.Cancel) { e.Visible = false; } }
- Handle the master grid's client-side DetailRowCollapsing and DetailRowExpanding events to get the visible indexes of expanded rows.JavaScript
var visibleIndicies = []; function AddCurrentDetailGrid(visibleIndex) { if (visibleIndicies.indexOf(visibleIndex) == -1) visibleIndicies.push(visibleIndex); } function RemoveCurrentDetailGrid(visibleIndex) { var arrayIndex = visibleIndicies.indexOf(visibleIndex); if (arrayIndex > -1) visibleIndicies.splice(arrayIndex, 1); } function OnExpanding(s, e) { AddCurrentDetailGrid(e.visibleIndex); } function OnCollapsing(s, e) { RemoveCurrentDetailGrid(e.visibleIndex); }
- In the custom button's
Click
event handler, call the master grid'sPerformCallback
method to update all grid controls on the server.JavaScriptvar buttonFlag = false; function OnClick(s, e) { if (visibleIndicies.length == 0) grid.UpdateEdit(); else { buttonFlag = true; grid.PerformCallback(visibleIndicies); } }
- Handle the master grid's server-side CustomCallback event. In the handler, call the master grid's FindDetailRowTemplateContol method to access all detail grids and use their UpdateEdit methods to update data.C#
protected void Grid_CustomCallback(object sender, ASPxGridViewCustomCallbackEventArgs e) { ASPxGridView parentGrid = sender as ASPxGridView; parentGrid.UpdateEdit(); parentGrid.DataBind(); if (String.IsNullOrEmpty(e.Parameters)) return; string[] paramArray = e.Parameters.Split(','); for (int i = 0; i < paramArray.Length; i++) { if (String.IsNullOrWhiteSpace(paramArray[i])) continue; ASPxGridView child = parentGrid.FindDetailRowTemplateControl(Convert.ToInt32(paramArray[i]), "grid2") as ASPxGridView; if (child != null) { child.UpdateEdit(); child.DataBind(); } } }
Files to Review
- Default.aspx (VB: Default.aspx)
- Default.aspx.cs (VB: Default.aspx.vb)
- UpdateLogic.js (VB: UpdateLogic.js)
Documentation
More Examples
- Grid View for ASP.NET Web Forms - Simple master-detail implementation
- Grid View for ASP.NET Web Forms - How to refresh a master grid on a detail grid callback
Example Code
ASPx<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="DevExpress.Web.v14.1, Version=14.1.15.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web" TagPrefix="dx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>How to update a master grid and all its detail grids operating in Batch Edit mode simultaneously</title>
<script src="UpdateLogic.js"></script>
</head>
<body>
<form id="frmMain" runat="server">
<b>How to update a master grid and all its detail grids operating in Batch Edit mode simultaneously</b>
<dx:ASPxGridView ID="Grid" runat="server" KeyFieldName="CategoryID" OnCommandButtonInitialize="Grid_CommandButtonInitialize" OnCustomCallback="Grid_CustomCallback" ClientInstanceName="grid" OnBatchUpdate="Grid_BatchUpdate" AutoGenerateColumns="False" DataSourceID="nwd1">
<Columns>
<dx:GridViewCommandColumn ShowNewButtonInHeader="True" VisibleIndex="0" />
<dx:GridViewDataTextColumn FieldName="CategoryID" ReadOnly="True" VisibleIndex="1">
<EditFormSettings Visible="False" />
</dx:GridViewDataTextColumn>
<dx:GridViewDataTextColumn FieldName="CategoryName" VisibleIndex="2">
</dx:GridViewDataTextColumn>
<dx:GridViewDataTextColumn FieldName="Description" VisibleIndex="3">
</dx:GridViewDataTextColumn>
</Columns>
<ClientSideEvents DetailRowExpanding="OnExpanding" DetailRowCollapsing="OnCollapsing" EndCallback="OnEndCallback" CallbackError="OnCallbackError" BatchEditConfirmShowing="OnConfirm" />
<SettingsDetail ShowDetailButtons="true" ShowDetailRow="true" />
<Templates>
<DetailRow>
<dx:ASPxGridView runat="server" OnCommandButtonInitialize="Grid_CommandButtonInitialize" KeyFieldName="ProductID" OnBatchUpdate="grid_BatchUpdate" OnBeforePerformDataSelect="grid2_BeforePerformDataSelect" ID="grid2" AutoGenerateColumns="False" DataSourceID="nwd2">
<Columns>
<dx:GridViewCommandColumn ShowNewButtonInHeader="true" ShowDeleteButton="True" VisibleIndex="0" />
<dx:GridViewDataTextColumn FieldName="ProductID" ReadOnly="True" VisibleIndex="1">
<EditFormSettings Visible="False" />
</dx:GridViewDataTextColumn>
<dx:GridViewDataTextColumn FieldName="ProductName" VisibleIndex="2">
</dx:GridViewDataTextColumn>
<dx:GridViewDataCheckColumn FieldName="Discontinued" VisibleIndex="4" />
</Columns>
<ClientSideEvents BatchEditConfirmShowing="OnConfirm" />
<SettingsEditing Mode="Batch" />
</dx:ASPxGridView>
</DetailRow>
<StatusBar>
<div style="text-align: right">
<dx:ASPxButton ID="btn" Text="Save Changes" RenderMode="Link" AutoPostBack="false" runat="server">
<ClientSideEvents Click="OnClick" />
</dx:ASPxButton>
<dx:ASPxButton ID="btn2" Text="Cancel Changes" RenderMode="Link" AutoPostBack="false" runat="server">
<ClientSideEvents Click="OnCancel" />
</dx:ASPxButton>
</div>
</StatusBar>
</Templates>
<SettingsEditing Mode="Batch" />
</dx:ASPxGridView>
<asp:AccessDataSource ID="nwd1" runat="server" DataFile="~/App_Data/nwind.mdb" DeleteCommand="DELETE FROM [Categories] WHERE [CategoryID] = ?" InsertCommand="INSERT INTO [Categories] ([CategoryName], [Description]) VALUES (?, ?)" SelectCommand="SELECT [CategoryID], [CategoryName], [Description] FROM [Categories]" UpdateCommand="UPDATE [Categories] SET [CategoryName] = ?, [Description] = ? WHERE [CategoryID] = ?">
<DeleteParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
</DeleteParameters>
<InsertParameters>
<asp:Parameter Name="CategoryName" Type="String" />
<asp:Parameter Name="Description" Type="String" />
</InsertParameters>
<UpdateParameters>
<asp:Parameter Name="CategoryName" Type="String" />
<asp:Parameter Name="Description" Type="String" />
<asp:Parameter Name="CategoryID" Type="Int32" />
</UpdateParameters>
</asp:AccessDataSource>
<asp:AccessDataSource ID="nwd2" runat="server" DataFile="~/App_Data/nwind.mdb" DeleteCommand="DELETE FROM [Products] WHERE [ProductID] = ?" InsertCommand="INSERT INTO [Products] ([ProductName], [CategoryID], [Discontinued]) VALUES (?, ?, ?)" SelectCommand="SELECT [ProductID], [ProductName], [CategoryID] , [Discontinued] FROM [Products] WHERE ([CategoryID] = ?)" UpdateCommand="UPDATE [Products] SET [ProductName] = ?, [Discontinued] = ? WHERE [ProductID] = ?">
<DeleteParameters>
<asp:Parameter Name="ProductID" Type="Int32" />
</DeleteParameters>
<InsertParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
</InsertParameters>
<SelectParameters>
<asp:SessionParameter DefaultValue="?" Name="CategoryID" SessionField="Category" Type="Int32" />
</SelectParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="ProductID" Type="Int32" />
</UpdateParameters>
</asp:AccessDataSource>
<dx:ASPxLabel runat="server" ForeColor="Red" Font-Size="Large" Font-Bold="true" Text="" ID="lbl" ClientInstanceName="lbl"></dx:ASPxLabel>
</form>
</body>
</html>
C#using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using DevExpress.Web.Data;
using DevExpress.Web;
using System.Data;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
protected void Grid_BatchUpdate(object sender, ASPxDataBatchUpdateEventArgs e)
{
foreach (var args in e.InsertValues)
InsertNewItem(args.NewValues, true,(ASPxGridView)sender);
foreach (var args in e.UpdateValues)
UpdateItem(args.Keys, args.NewValues, true);
foreach (var args in e.DeleteValues)
DeleteItem(args.Keys, args.Values, true);
e.Handled = true;
}
protected void InsertNewItem(OrderedDictionary newValues, bool isParent, ASPxGridView currentGrid)
{
//comment the bellow line to check data modifications
throw new NotImplementedException("Data modifications aren't allowed in online example");
if (isParent)
Insert(newValues,nwd1);
else{
nwd2.InsertParameters["CategoryID"].DefaultValue = currentGrid.GetMasterRowKeyValue().ToString();
Insert(newValues, nwd2);
}
}
private void Insert(OrderedDictionary newValues, AccessDataSource datasource)
{
foreach (var item in newValues.Keys)
{
datasource.InsertParameters[(string)item].DefaultValue = Convert.ToString(newValues[item]);
}
datasource.Insert();
}
protected void UpdateItem(OrderedDictionary keys, OrderedDictionary newValues, bool isParent)
{
//comment the bellow line to check data modifications
throw new NotImplementedException("Data modifications aren't allowed in online example");
if (isParent)
Update(keys, newValues, nwd1);
else
Update(keys, newValues, nwd2);
}
private void Update(OrderedDictionary keys, OrderedDictionary newValues, AccessDataSource datasource)
{
foreach (var item in newValues.Keys)
{
datasource.UpdateParameters[(string)item].DefaultValue = Convert.ToString(newValues[item]);
}
datasource.UpdateParameters[nwd2.UpdateParameters.Count - 1].DefaultValue = Convert.ToString(keys[0]);
datasource.Update();
}
protected void DeleteItem(OrderedDictionary keys, OrderedDictionary values, bool isParent)
{
//comment the bellow line to check data modifications
throw new NotImplementedException("Data modifications aren't allowed in online example");
if (isParent)
{
nwd1.DeleteParameters[0].DefaultValue = Convert.ToString(keys["CategoryID"]);
nwd1.Delete();
}
else
{
nwd2.DeleteParameters[0].DefaultValue = Convert.ToString(keys["ProductID"]);
nwd2.Delete();
}
}
protected void grid2_BeforePerformDataSelect(object sender, EventArgs e)
{
ASPxGridView child = sender as ASPxGridView;
GridViewDetailRowTemplateContainer container = child.NamingContainer as GridViewDetailRowTemplateContainer;
child.ClientInstanceName = "detailGrid" + container.KeyValue;
Session["Category"] = child.GetMasterRowKeyValue();
}
protected void Grid_CustomCallback(object sender, ASPxGridViewCustomCallbackEventArgs e)
{
ASPxGridView parentGrid = sender as ASPxGridView;
parentGrid.UpdateEdit();
parentGrid.DataBind();
if (String.IsNullOrEmpty(e.Parameters))
return;
string[] paramArray = e.Parameters.Split(',');
for (int i = 0; i < paramArray.Length; i++)
{
if (String.IsNullOrWhiteSpace(paramArray[i]))
continue;
ASPxGridView child = parentGrid.FindDetailRowTemplateControl(Convert.ToInt32(paramArray[i]), "grid2") as ASPxGridView;
if (child != null)
{
child.UpdateEdit();
child.DataBind();
}
}
}
protected void grid_BatchUpdate(object sender, ASPxDataBatchUpdateEventArgs e)
{
foreach (var args in e.InsertValues)
InsertNewItem(args.NewValues, false, (ASPxGridView)sender);
foreach (var args in e.UpdateValues)
UpdateItem(args.Keys, args.NewValues, false);
foreach (var args in e.DeleteValues)
DeleteItem(args.Keys, args.Values, false);
e.Handled = true;
}
protected void Grid_CommandButtonInitialize(object sender, ASPxGridViewCommandButtonEventArgs e)
{
if (e.ButtonType == ColumnCommandButtonType.Update || e.ButtonType == ColumnCommandButtonType.Cancel)
{
e.Visible = false;
}
}
}
JavaScriptvar visibleIndicies = [];
var errorMessage = "";
var buttonFlag = false;
function OnClick(s, e) {
if (visibleIndicies.length == 0)
grid.UpdateEdit();
else {
buttonFlag = true;
grid.PerformCallback(visibleIndicies);
}
}
function OnCancel(s, e) {
for (var i = 0; i < visibleIndicies.length; i++) {
var currentKey = grid.GetRowKey(visibleIndicies[i]);
var childgrid = ASPxClientControl.GetControlCollection().GetByName("detailGrid" + currentKey);
if (childgrid != undefined && childgrid != null) {
childgrid.CancelEdit();
}
}
grid.CancelEdit();
}
function OnConfirm(s, e) {
if (e.requestTriggerID == 'Grid' && buttonFlag) {
e.cancel = true;
}
}
function AddCurrentDetailGrid(visibleIndex) {
if (visibleIndicies.indexOf(visibleIndex) == -1)
visibleIndicies.push(visibleIndex);
}
function RemoveCurrentDetailGrid(visibleIndex) {
var arrayIndex = visibleIndicies.indexOf(visibleIndex);
if (arrayIndex > -1)
visibleIndicies.splice(arrayIndex, 1);
}
function OnExpanding(s, e) {
AddCurrentDetailGrid(e.visibleIndex);
}
function OnCollapsing(s, e) {
RemoveCurrentDetailGrid(e.visibleIndex);
}
function OnEndCallback(s, e) {
if (buttonFlag)
buttonFlag = false;
if (errorMessage != "") {
lbl.SetText(errorMessage);
errorMessage = "";
}
else
lbl.SetText("");
}
function OnCallbackError(s, e) {
errorMessage = e.message;
e.handled = true;
}