Description:
This KB Article describes how to resolve frequently encountered issues when customizing controls inside DevExpress ASP.NET Template Containers.
1. The use of the Bind method / Two-Way data binding expressions within editors placed onto INamingContainer containers.
2. The use of the Container object (available inside ASPxGridView's Templates) placed into INamingContainer containers:
3. Why does the "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control" error message appear and how to solve this issue?
4. How to bind a control to a data item inside a Template Container of non-data-aware controls.
Answer:
- The use of the Bind method / Two-Way data binding expressions within editors placed onto INamingContainer containers.
There is an issue when binding our editors inside INamingContainer containers via the Bind method / Two-Way data binding expression:
The “Two-Way data binding” option does not work for controls inside containers that implement INamingContainer interface. Since all DevExpress ASP.NET navigation/layout controls implement this interface, this issue also affects them.
The code below illustrates this issue with the DevExpress ASPxGridView control (ASPxPageControl is the INamingContainer container):
ASPx<dx:ASPxGridView ID="gv" ... OnRowUpdating="gv_RowUpdating">
...
<Templates>
<EditForm>
...
<dx:ASPxPageControl ID="pc" runat="server" ActiveTabIndex="0">
<TabPages>
<dx:TabPage Text="EditForm Template INamingContainer">
<ContentCollection>
<dx:ContentControl ID="cntCtrl" runat="server">
CategoryName:
<dx:ASPxTextBox ID="txtCategoryName" runat="server" Width="170px" Value='<%#Bind("CategoryName")%>'>
</dx:ASPxTextBox>
</dx:ContentControl>
</ContentCollection>
</dx:TabPage>
</TabPages>
</dx:ASPxPageControl>
...
</EditForm>
</Templates>
</dx:ASPxGridView>
However, this behavior is a specific of the ASP.NET platform and can be reproduced with standard ASP.NET controls (for example, asp:GridView, asp:Wizard, asp:TextBox). The code below illustrates this issue with the MS FormView control (Wizard is the INamingContainer container):
ASPx<asp:FormView ...>
<EditItemTemplate>
...
CategoryName:
<asp:Wizard ID="wzd" runat="server">
<WizardSteps>
<asp:WizardStep ID="wzaStepOne" runat="server" Title="Step One">
<asp:TextBox ID="txtCategoryName" runat="server" Text='<%# Bind("CategoryName") %>' />
</asp:WizardStep>
</WizardSteps>
</asp:Wizard>
...
</EditItemTemplate>
...
</asp:FormView>
There are corresponding bug reports in the Microsoft Connect feedback center regarding this issue: Two-Way data binding option does not work for controls inside INamingContainer containers
formview control does not perform two-way databinding for controls which are allocated within asp:table
To avoid this issue, perform the following steps:
- Handle the data-aware (ASPxGridView, FormView, etc.) control *Inserting / *Updating events (ItemInserting, ItemUpdating, RowInserting, RowUpdating, etc.) events;
- Get a reference to the data-aware control via the “sender” object;
- Get a reference to a corresponding editor (bound via the Two-Way data binding expression) via the FindXControl method;
- Pass the corresponding editor’s value property (Value, Text, etc.) back to a datasource (which is usually represented by the “e.NewValues” dictionary) manually:
Solution:
C#using System;
using DevExpress.Web.ASPxGridView;
using DevExpress.Web.ASPxTabControl;
using DevExpress.Web.ASPxEditors;
...
protected void gv_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e) {
ASPxGridView gridView = (ASPxGridView)sender;
ASPxPageControl pageControl = (ASPxPageControl)gridView.FindEditFormTemplateControl("pc");
ASPxTextBox txt = (ASPxTextBox)pageControl.FindControl("txtCategoryName");
e.NewValues["CategoryName"] = txt.Text.Trim();
}
Visual BasicImports System
Imports DevExpress.Web.ASPxGridView
Imports DevExpress.Web.ASPxTabControl
Imports DevExpress.Web.ASPxEditors
...
Protected Sub gv_RowUpdating(ByVal sender As Object, ByVal e As DevExpress.Web.Data.ASPxDataUpdatingEventArgs)
Dim gridView As ASPxGridView = CType(sender, ASPxGridView)
Dim pageControl As ASPxPageControl = CType(gridView.FindEditFormTemplateControl("pc"), ASPxPageControl)
Dim txt As ASPxTextBox = CType(pageControl.FindControl("txtCategoryName"), ASPxTextBox)
e.NewValues("CategoryName") = txt.Text.Trim()
End Sub
The linked E3915: How to resolve issues with binding expressions when using INamingContainer containers Code Central example (the DXControlsTwoWayBinding.aspx page) illustrates this solution in action.
- The use of the Container object (available inside ASPxGridView's Templates) placed into INamingContainer containers:
When adjusting a custom layout of any ASPxGridView's Template, it is convenient to use a Container object (inherited from the base GridViewBaseRowTemplateContainer type) to get values associated with the current template context (KeyValue, VisibleIndex, ValidationGroup, etc.).
The Container object is no longer available when this custom layout is already wrapped with a INamingContainer container.
The code below illustrates this issue with the DevExpress ASPxGridView control (the Container object is inside the INamingContainer container):
ASPx<dx:ASPxGridView ...>
<Templates>
<EditForm>
...
<dx:ASPxPageControl ID="pc" runat="server" ActiveTabIndex="0">
<TabPages>
<dx:TabPage Text="EditForm Template INamingContainer">
<ContentCollection>
<dx:ContentControl ID="cntCtrl" runat="server">
CategoryName:
<dx:ASPxTextBox ID="txtCategoryName" runat="server" Width="170px"
Value='<%#Container.KeyValue%>'
ValidationSettings-ValidationGroup='<%#Container.ValidationGroup%>'
>
</dx:ASPxTextBox>
</dx:ContentControl>
</ContentCollection>
</dx:TabPage>
</TabPages>
</dx:ASPxPageControl>
...
</EditForm>
</Templates>
</dx:ASPxGridView>
To resolve this issue, use the approach described in the K18282: The general technique of using the Init/Load event handler KB Article: customize particular control properties based on container/parent values by handling the control's Init event:
ASPx<dx:ASPxTextBox ID="txtCategoryName" runat="server" Width="170px" OnInit="txtCategoryName_Init">
</dx:ASPxTextBox>
C#using System;
using DevExpress.Web.ASPxGridView;
using DevExpress.Web.ASPxTabControl;
using DevExpress.Web.ASPxEditors;
...
protected void txtCategoryName_Init(object sender, EventArgs e) {
ASPxTextBox txt = (ASPxTextBox)sender;
ASPxPageControl container = (ASPxPageControl)txt.NamingContainer;
GridViewEditFormTemplateContainer templateContainer = (GridViewEditFormTemplateContainer)container.NamingContainer;
txt.ValidationSettings.ValidationGroup = templateContainer.ValidationGroup;
txt.Value = templateContainer.KeyValue;
//etc.
}
Visual BasicImports System
Imports DevExpress.Web.ASPxGridView
Imports DevExpress.Web.ASPxTabControl
Imports DevExpress.Web.ASPxEditors
...
Protected Sub txtCategoryName_Init(ByVal sender As Object, ByVal e As EventArgs)
Dim txt As ASPxTextBox = CType(sender, ASPxTextBox)
Dim container As ASPxPageControl = CType(txt.NamingContainer, ASPxPageControl)
Dim templateContainer As GridViewEditFormTemplateContainer = CType(container.NamingContainer, GridViewEditFormTemplateContainer)
txt.ValidationSettings.ValidationGroup = templateContainer.ValidationGroup
txt.Value = templateContainer.KeyValue
'etc.
End Sub
The linked E3915: How to resolve issues with binding expressions when using INamingContainer containers Code Central example (the DXTemplatesContainerObject.aspx page) illustrates this solution in action.
-
Why does the "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control" error message appear and how to solve this issue:
We posted this information in a separate Knowledge Base article:
KA18619 -
How to bind a control to a data item inside a Template Container of non-data-aware controls.
- Data-Aware Control (ASPxDataView)
- Template Container (DataViewItemTemplateContainer)
- Non Data-Aware Control (ASPxRoundPanel)
- Template Container (RoundPanelHeaderTemplateContainer)
- Control With Data-Binding Expression:
ASPx<dx:ASPxDataView ...>
<ItemTemplate>
<dx:ASPxRoundPanel ...>
<HeaderTemplate>
<asp:Label ... Text='<%# Eval("CategoryID") %>' />
</HeaderTemplate>
</dx:ASPxRoundPanel>
</ItemTemplate>
</dx:ASPxDataView>
When using the above markup, the
"DataBinding: 'DevExpress.Web.ASPxRoundPanel.ASPxRoundPanel' does not contain a property with the name 'CategoryID'."
exception is thrown.
When binding to the field of a specific DataItem, and the control is not located inside a data-bound container, it is necessary to obtain this container through the parent control hierarchy:
ASPx<dx:ASPxDataView ...>
<ItemTemplate>
<dx:ASPxRoundPanel ...>
<HeaderTemplate>
<asp:Label ... Text='<%# DataBinder.Eval((Container.NamingContainer.NamingContainer.NamingContainer as DataViewItemTemplateContainer).DataItem, "CategoryName") %>' />
</HeaderTemplate>
</dx:ASPxRoundPanel>
</ItemTemplate>
</dx:ASPxDataView>
Note: You can the following approach to determine the class name of the required template container through the parent control's hierarchy:
C#Container.ToString(); Container.NamingContainer.ToString(); Container.NamingContainer.NamingContainer.ToString(); ...