Example T124512
Visible to All Users

Grid View for ASP.NET Web Forms - Cascading Combo Boxes in Batch Edit Mode

This example demonstrates how to implement cascading combo box editors in ASPxGridView.

CascadingCBBatchEditMode

Setup the Grid and its Column Editors

Create an ASPxGridView control, set the grid's edit mode to Batch, and add two columns of the GridViewDataComboBoxColumn type. Add a ASPxHiddenField control to the markup.

XML
<dx:ASPxHiddenField runat="server" ID="hf" ClientInstanceName="hf"></dx:ASPxHiddenField> <dx:ASPxGridView ID="grid" ClientInstanceName="grid" runat="server" KeyFieldName="CustomerId"> <Columns> <%--...--%> <dx:GridViewDataComboBoxColumn Caption="Country" FieldName="CountryId"> <PropertiesComboBox EnableCallbackMode="true" ValueField="CountryId" TextField="CountryName" ValueType="System.Int32"> <%--...--%> </PropertiesComboBox> </dx:GridViewDataComboBoxColumn> <dx:GridViewDataComboBoxColumn Caption="City" FieldName="CityId"> <PropertiesComboBox EnableCallbackMode="true" CallbackPageSize="20" ValueType="System.Int32" TextField="CityName" ValueField="CityId" ... /> </dx:GridViewDataComboBoxColumn> </Columns> <%--...--%> <SettingsEditing Mode="Batch" /> </dx:ASPxGridView>

Manually Update the Secondary Combo Box Items

Handle the grid's client-side BatchEditStartEditing event. In the event handler, get the cell value (the batchEditApi.GetCellValue method) and call the ASPxHiddenField control's method to save this value. Call the secondary editor's PerformCallback method to update its data.

XML
<dx:ASPxGridView ID="grid" runat="server"...> <%--...--%> <ClientSideEvents BatchEditStartEditing="OnBatchEditStartEditing" /> </dx:ASPxGridView>
JavaScript
var curentEditingIndex; var lastCountry = null; function OnBatchEditStartEditing(s, e) { curentEditingIndex = e.visibleIndex; var currentCountry = grid.batchEditApi.GetCellValue(curentEditingIndex, "CountryId"); hf.Set("CurrentCountry", currentCountry); if (currentCountry != lastCountry && e.focusedColumn.fieldName == "CityId" && currentCountry != null) { lastCountry = currentCountry; RefreshData(currentCountry); } } function RefreshData(countryValue) { hf.Set("CurrentCountry", countryValue); CityEditor.PerformCallback(); }

Respond to a Selection Change

Handle the primary editor's client-side SelectedIndexChanged event. In this event handler, get the editor value (the GetValue method) and save it to the ASPxHiddenField control's collection. Call the secondary editor's PerformCallback method to send a callback to the server.

XML
<dx:ASPxGridView ID="grid" runat="server"...> <Columns> <%--...--%> <dx:GridViewDataComboBoxColumn Caption="Country" FieldName="CountryId"> <PropertiesComboBox ValueField="CountryId" TextField="CountryName" ValueType="System.Int32"> <ClientSideEvents SelectedIndexChanged="CountriesCombo_SelectedIndexChanged" /> </PropertiesComboBox> </dx:GridViewDataComboBoxColumn> <dx:GridViewDataComboBoxColumn Caption="City" FieldName="CityId" /> </Columns> <%--...--%> </dx:ASPxGridView>
JavaScript
var isCustomCascadingCallback = false; function CountriesCombo_SelectedIndexChanged(s, e) { lastCountry = s.GetValue(); isCustomCascadingCallback = true; RefreshData(lastCountry); }

Filter the Secondary Combo Box Values on the Server Side

In the CellEditorInitialize event handler, access the editors and add a handler to the secondary editor's EndCallback event. In the ItemsRequestedByFilterCondition event handler, filter the secondary editor's data source based on the value from the ASPxHiddenField control's collection and bind the filtered values to the editor.

XML
<dx:ASPxGridView ID="grid" runat="server" OnCellEditorInitialize="grid_CellEditorInitialize" ...> <Columns> <%--...--%> <dx:GridViewDataComboBoxColumn Caption="City" FieldName="CityId"> <PropertiesComboBox OnItemsRequestedByFilterCondition="OnItemsRequestedByFilterCondition" ValueType="System.Int32" TextField="CityName" ValueField="CityId"> </Columns> <%--...--%> </dx:ASPxGridView>
JavaScript
function CitiesCombo_EndCallback(s, e) { if (isCustomCascadingCallback) { if (s.GetItemCount() > 0) grid.batchEditApi.SetCellValue(curentEditingIndex, "CityId", s.GetItem(0).value); isCustomCascadingCallback = false; } }
C#
protected void grid_CellEditorInitialize(object sender, ASPxGridViewEditorEventArgs e) { if(e.Column.FieldName == "CountryId") e.Editor.ClientInstanceName = "CountryEditor"; if (e.Column.FieldName != "CityId") return; var editor = (ASPxComboBox)e.Editor; editor.ClientInstanceName = "CityEditor"; editor.ClientSideEvents.EndCallback = "CitiesCombo_EndCallback"; } protected void OnItemsRequestedByFilterCondition(object source, ListEditItemsRequestedByFilterConditionEventArgs e) { ASPxComboBox editor = source as ASPxComboBox; IQueryable<City> query; var take = e.EndIndex - e.BeginIndex + 1; var skip = e.BeginIndex; int countryValue = GetCurrentCountry(); if (countryValue > -1) query = entity.Cities.Where(city => city.CityName.Contains(e.Filter) && city.Country.CountryId == countryValue).OrderBy(city => city.CityId); else query = Enumerable.Empty<City>().AsQueryable(); query = query.Skip(skip).Take(take); editor.DataSource = query.ToList(); editor.DataBind(); }

Documentation

Files to Look At

More Examples

Example Code

T124512/BatchEditScript.js
JavaScript
var curentEditingIndex; var lastCountry = null; var isCustomCascadingCallback = false; function CountriesCombo_SelectedIndexChanged(s, e) { lastCountry = s.GetValue(); isCustomCascadingCallback = true; RefreshData(lastCountry); } function CitiesCombo_EndCallback(s, e) { if (isCustomCascadingCallback) { if (s.GetItemCount() > 0) grid.batchEditApi.SetCellValue(curentEditingIndex, "CityId", s.GetItem(0).value); isCustomCascadingCallback = false; } } function OnBatchEditStartEditing(s, e) { curentEditingIndex = e.visibleIndex; var currentCountry = grid.batchEditApi.GetCellValue(curentEditingIndex, "CountryId"); hf.Set("CurrentCountry", currentCountry); if (currentCountry != lastCountry && e.focusedColumn.fieldName == "CityId" && currentCountry != null) { lastCountry = currentCountry; RefreshData(currentCountry); } } function RefreshData(countryValue) { hf.Set("CurrentCountry", countryValue); CityEditor.PerformCallback(); }
T124512/Default.aspx
ASPx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>ASPxGridView - How to implement cascading comboboxes in Batch Edit mode</title> <script src="BatchEditScript.js"></script> </head> <body> <form id="form1" runat="server"> ASPxGridView - How to implement cascading comboboxes in Batch Edit mode <dx:ASPxHiddenField runat="server" ID="hf" ClientInstanceName="hf"></dx:ASPxHiddenField> <dx:ASPxGridView ID="grid" ClientInstanceName="grid" runat="server" OnBatchUpdate="grid_BatchUpdate" KeyFieldName="CustomerId" OnCellEditorInitialize="grid_CellEditorInitialize"> <Columns> <dx:GridViewCommandColumn ShowNewButtonInHeader="true" ShowDeleteButton="true"></dx:GridViewCommandColumn> <dx:GridViewDataColumn FieldName="CustomerId" Visible="false"> <EditFormSettings Visible="False" /> </dx:GridViewDataColumn> <dx:GridViewDataTextColumn Width="140" FieldName="CustomerName"> <PropertiesTextEdit> <ValidationSettings RequiredField-IsRequired="true" Display="None"></ValidationSettings> </PropertiesTextEdit> </dx:GridViewDataTextColumn> <dx:GridViewDataComboBoxColumn Width="120" Caption="Country" FieldName="CountryId"> <PropertiesComboBox EnableCallbackMode="true" CallbackPageSize="30" ValueField="CountryId" TextField="CountryName" ValueType="System.Int32"> <ValidationSettings RequiredField-IsRequired="true" Display="None"></ValidationSettings> <ClientSideEvents SelectedIndexChanged="CountriesCombo_SelectedIndexChanged" /> </PropertiesComboBox> </dx:GridViewDataComboBoxColumn> <dx:GridViewDataComboBoxColumn Caption="City" FieldName="CityId" Width="120px"> <PropertiesComboBox EnableCallbackMode="true" CallbackPageSize="20" IncrementalFilteringMode="Contains" OnItemRequestedByValue="OnItemRequestedByValue" OnItemsRequestedByFilterCondition="OnItemsRequestedByFilterCondition" ValueType="System.Int32" TextField="CityName" ValueField="CityId"> </PropertiesComboBox> </dx:GridViewDataComboBoxColumn> </Columns> <ClientSideEvents BatchEditStartEditing="OnBatchEditStartEditing" /> <SettingsEditing Mode="Batch"> <BatchEditSettings ShowConfirmOnLosingChanges="true" EditMode="Cell" /> </SettingsEditing> </dx:ASPxGridView> </form> </body> </html>
T124512/Default.aspx.cs(vb)
C#
using System; using DevExpress.Web; using System.Linq; using System.Collections.Generic; using System.Collections.Specialized; public partial class _Default : System.Web.UI.Page { WorldCitiesEntities entity = new WorldCitiesEntities(); protected void Page_Init(object sender, EventArgs e) { ((GridViewDataComboBoxColumn)grid.Columns["CountryId"]).PropertiesComboBox.DataSource = entity.Countries.ToList(); grid.DataSource = entity.Customers.ToList(); if (!IsPostBack) { grid.DataBind(); } } protected void grid_CellEditorInitialize(object sender, ASPxGridViewEditorEventArgs e) { if (e.Column.FieldName == "CountryId") e.Editor.ClientInstanceName = "CountryEditor"; if (e.Column.FieldName != "CityId") return; var editor = (ASPxComboBox)e.Editor; editor.ClientInstanceName = "CityEditor"; editor.ClientSideEvents.EndCallback = "CitiesCombo_EndCallback"; } protected void OnItemRequestedByValue(object source, ListEditItemRequestedByValueEventArgs e) { int id; if (e.Value == null || !int.TryParse(e.Value.ToString(), out id)) return; ASPxComboBox editor = source as ASPxComboBox; var query = entity.Cities.Where(city => city.CityId == id); editor.DataSource = query.ToList(); editor.DataBind(); } protected void OnItemsRequestedByFilterCondition(object source, ListEditItemsRequestedByFilterConditionEventArgs e) { ASPxComboBox editor = source as ASPxComboBox; IQueryable<City> query; var take = e.EndIndex - e.BeginIndex + 1; var skip = e.BeginIndex; int countryValue = GetCurrentCountry(); if (countryValue > -1) query = entity.Cities.Where(city => city.CityName.Contains(e.Filter) && city.Country.CountryId == countryValue).OrderBy(city => city.CityId); else query = Enumerable.Empty<City>().AsQueryable(); query = query.Skip(skip).Take(take); editor.DataSource = query.ToList(); editor.DataBind(); } private int GetCurrentCountry() { object id = null; if (hf.TryGet("CurrentCountry", out id)) return Convert.ToInt32(id); return -1; } protected void grid_BatchUpdate(object sender, DevExpress.Web.Data.ASPxDataBatchUpdateEventArgs e) { throw new DemoException("Data modifications are not allowed in the online example. If you need to test the data editing functionality, please download the example on your machine and run it locally."); foreach (var args in e.InsertValues) InsertNewItem(args.NewValues); foreach (var args in e.UpdateValues) UpdateItem(args.Keys, args.NewValues); foreach (var args in e.DeleteValues) DeleteItem(args.Keys, args.Values); entity.SaveChanges(); e.Handled = true; } public void InsertNewItem(OrderedDictionary newValues) { Customer customer = new Customer(); LoadNewValues(customer, newValues); entity.Customers.Add(customer); } public void UpdateItem(OrderedDictionary keys, OrderedDictionary newValues) { int id = Convert.ToInt32(keys["CustomerId"]); Customer customer = entity.Customers.Where(x => x.CustomerId == id).FirstOrDefault<Customer>(); LoadNewValues(customer, newValues); } public void DeleteItem(OrderedDictionary keys, OrderedDictionary values) { int id = Convert.ToInt32(keys["CustomerId"]); Customer customer = entity.Customers.Where(x => x.CustomerId == id).FirstOrDefault<Customer>(); entity.Customers.Remove(customer); } protected void LoadNewValues(Customer item, OrderedDictionary values) { item.CustomerName = values["CustomerName"].ToString(); item.CityId = Convert.ToInt32(values["CityId"]); item.CountryId = Convert.ToInt32(values["CountryId"]); } }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.