How to iterate through all controls inside a DetailRow of an ASPxGridView from DevExpress

23 views Asked by At

I'm trying to apply application-wide defaults to DevExpress' ASPxGridView controls programmatically. I iterate through all controls on the page and then call my "GridSetupDefaults" method. This works for grids placed directly on the Page, but won't affect (as expected) grids inside Detail Templates. I know that the common way of setting up DevExpress controls is through the Init event but since my solution relies on iterating through all the controls on the page, I really want to use it also for the templated controls (and I also want to avoid having to add the Init handler on hundreds of detail grids manually, since the application is really big).

I managed to hack my way out of this conundrum by subscribing to the DetailRowExpandedChanged event of the "outer" grids, and then using some reflection voodoo to get hold of the GridViewRenderHelper.DetailRowTemplates property which holds the controls of the detail template:

protected override void OnLoad(EventArgs e) {
    foreach (ASPxGridView grid in Controls.All().OfType<ASPxGridView>()) {
        grid.SetupGridDefaults();
        grid.DetailRowExpandedChanged += Grid_DetailRowExpandedChanged;
    }
}

private void Grid_DetailRowExpandedChanged(object sender, ASPxGridViewDetailRowEventArgs e) {
    if (!e.Expanded) return;
    var grid = (ASPxGridView)sender;
    // We need to call FindDetailRowTemplateControl to populate DetailRowTemplates
    grid.FindDetailRowTemplateControl(e.VisibleIndex, "");
    // getting the RenderHelper... which is protected internal
    var prop = grid.GetType().GetProperty("RenderHelper", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, typeof(GridViewRenderHelper), [], []);
    var renderHelper = (GridViewRenderHelper)prop.GetValue(grid);
    if (renderHelper?.DetailRowTemplates.Count > 0)
        foreach (var template in renderHelper.DetailRowTemplates)
            foreach (ASPxGridView detailGrid in template.Controls.All().OfType<ASPxGridView>())
                detailGrid.SetupGridDefaults();
}

This code is very brittle, relies on calling FindDetailRowTemplateControl to ensure controls are created in the detail template (which may force them to be created earlier than expected, or recreated after it was cleared), and is very cryptic code to achieve what I would expect to be a simple matter of getting the Detail Template NamingContainer then iterate over it's controls.

That's why I'm asking here if there is a straight-forward way of doing this which I'm missing. It's probably a lot easier than this!

Note: For those wondering what the All() method is, it's a very nice extension method to iterate over the page's entire Control hierarchy with nice LINQ semantics by David Findley:

public static class ControlExtensions
{
    /// <summary>
    /// Recursively enumerates all controls on a <see cref="ControlCollection"/> (<see cref="Control.Controls"/>)
    /// </summary>
    /// <remarks>This lovely method was taken from https://asp-blogs.azurewebsites.net/dfindley/linq-the-uber-findcontrol
    /// by the amazing David Findley (https://asp-blogs.azurewebsites.net/dfindley)</remarks>
    /// <param name="controls">The collection of controls to enumerate</param>
    /// <returns>An enumerable which yields every control (and it's children) found in the control collection.</returns>
    public static IEnumerable<Control> All(this ControlCollection controls) {
        foreach (Control control in controls) {
            foreach (Control grandChild in control.Controls.All())
                yield return grandChild;

            yield return control;
        }
    }
}
0

There are 0 answers