获取页面上特定类型的所有Web控件

39

我一直在考虑如何获取页面上的所有控件并对它们执行任务,下面是相关问题的链接:

如何以编程方式搜索C# DropDownList

我需要一段代码来扫描页面,获取所有DropDownList控件,并将它们返回到列表中。

我目前必须编辑每个单独的控件,我更愿意能够动态地遍历每个控件以执行我的任务。

8个回答

63

请查看我的之前在SO上的回答

基本上,思路是使用以下方式包装迭代控件集合的递归:

private void GetControlList<T>(ControlCollection controlCollection, List<T> resultCollection)
where T : Control
{
    foreach (Control control in controlCollection)
    {
        //if (control.GetType() == typeof(T))
        if (control is T) // This is cleaner
            resultCollection.Add((T)control);

        if (control.HasControls())
            GetControlList(control.Controls, resultCollection);
    }
}

并且要使用它:
List<DropDownList> allControls = new List<DropDownList>();
GetControlList<DropDownList>(Page.Controls, allControls )
foreach (var childControl in allControls )
{
//     call for all controls of the page
}

[2013年11月26日修订]:这里有一种更加优雅的方法来达成这个目标。我写了两个扩展方法,可以在控件树中双向遍历。这些方法使用了更多Linq的方式,因为它生成了一个可枚举对象:

/// <summary>
/// Provide utilities methods related to <see cref="Control"/> objects
/// </summary>
public static class ControlUtilities
{
    /// <summary>
    /// Find the first ancestor of the selected control in the control tree
    /// </summary>
    /// <typeparam name="TControl">Type of the ancestor to look for</typeparam>
    /// <param name="control">The control to look for its ancestors</param>
    /// <returns>The first ancestor of the specified type, or null if no ancestor is found.</returns>
    public static TControl FindAncestor<TControl>(this Control control) where TControl : Control
    {
        if (control == null) throw new ArgumentNullException("control");

        Control parent = control;
        do
        {
            parent = parent.Parent;
            var candidate = parent as TControl;
            if (candidate != null)
            {
                return candidate;
            }
        } while (parent != null);
        return null;
    }

    /// <summary>
    /// Finds all descendants of a certain type of the specified control.
    /// </summary>
    /// <typeparam name="TControl">The type of descendant controls to look for.</typeparam>
    /// <param name="parent">The parent control where to look into.</param>
    /// <returns>All corresponding descendants</returns>
    public static IEnumerable<TControl> FindDescendants<TControl>(this Control parent) where TControl : Control
    {
        if (parent == null) throw new ArgumentNullException("control");

        if (parent.HasControls())
        {
            foreach (Control childControl in parent.Controls)
            {
                var candidate = childControl as TControl;
                if (candidate != null) yield return candidate;

                foreach (var nextLevel in FindDescendants<TControl>(childControl))
                {
                    yield return nextLevel;
                }
            }
        }
    }
}

由于 this 关键字的存在,这些方法成为了扩展方法,可以简化代码。

例如,要查找页面中的所有 DropDownList,只需调用以下方法:

var allDropDowns = this.Page.FindControl<DropDownList>();

由于使用了yield关键字,以及Linq足够聪明地推迟了枚举的执行,你可以调用以下代码(例如):

var allDropDowns = this.Page.FindDescendants<DropDownList>();
var firstDropDownWithCustomClass = allDropDowns.First(
    ddl=>ddl.CssClass == "customclass"
    );

枚举将在满足First方法中的断言时停止。整个控件树不会被遍历。

1
最全面的答案,而且它像魔法一样运行良好。干杯,伙计。 - Anicho
这篇在 MSDN 上的文章很不错。 - Anicho
我很喜欢它。不再需要使用yield关键字真是太好了,这使得即使我声明Visual Studio跳过它,递归函数仍然会运行。 - JonathanWolfson
1
@Fernando68:你应该在SO上提出一个具体的问题...我展示的代码来自一个更大的项目...我不能公开它。 - Steve B
@SteveB 我们不能使用递归方法吗? - Ali.Rashidi
显示剩余3条评论

16
foreach (DropDownList dr in this.Page.Form.Controls.OfType<DropDownList>())
{

}

是的!你需要迭代每个控件的控件集合 - 递归。但我认为这些扩展方法真的很有用,因为它们可以使你的代码更紧凑 - 用更少的行完成更多的工作。 - marko
我无法理解这个答案如何能够正确地处理包含其他控件的控件的任何页面。正如@JamesJohnson所指出的那样,要正确地查找所有DropDownList,您需要递归地搜索所有控件(就像其他答案所做的那样),而不仅仅是顶级控件。在我的实现中,这个答案没有返回我页面上存在的所有控件。我有什么遗漏吗? - David Rogers

9
这里是一个递归版本,返回所请求类型的控件集合,而不是利用另一个参数:
using System.Collections.Generic;
using System.Web.UI;
// ...
public static List<T> GetControls<T>(ControlCollection Controls)
where T : Control {
  List<T> results = new List<T>();
  foreach (Control c in Controls) {
    if (c is T) results.Add((T)c);
    if (c.HasControls()) results.AddRange(GetControls<T>(c.Controls));
  }
  return results;
}

插入到你的类中(静态可选)。


6

我曾经有过这个问题,虽然我发现Steve B的答案很有用,但我想要一个扩展方法,所以我重新设计了它:

    public static IEnumerable<T> GetControlList<T>(this ControlCollection controlCollection) where T : Control
    {
        foreach (Control control in controlCollection)
        {
            if (control is T)
            {
                yield return (T)control;
            }

            if (control.HasControls())
            {
                foreach (T childControl in control.Controls.GetControlList<T>())
                {
                    yield return childControl;
                }
            }
        }
    }

我在app_code文件夹中的一个类中添加“this”,但是出现了“Type expected”的智能感知错误。 - Fandango68

1

在页面上循环控件并不难 - 你只需要在每个控件内查找更多的控件。

你可以像这样做:

foreach(var control in Page)
{
    if(control is DropDownList)
    {
        //Do whatever
    }
    else
    {
        //Call this function again to search for controls within this control
    }
}

2
这就是 else 的作用...//再次调用此函数以在此控件中搜索控件...这要求进行递归操作... - Mark Williams

1

您可以使用递归逻辑来获取所有控件,如下所示:

private void PopulateSelectList(Control parentCtrl, List<DropDownList> selectList)
{
    foreach (Control ctrl in parentCtrl.Controls)
    {
        if (ctrl is DropDownList)
        {
            selectList.Add(((DropDownList)ctrl);
            continue;
        }
        FindAllControls(ctrl, selectList);
    }
}

0

如果您使用system.web.ui中的表单组件,则此方法有效。但是,如果您从system.web.mvc中使用它们,则此方法无效。因此,我想出了以下解决方法。

for (Int32 idx = 0; idx < formCollection.Count; idx += 1)
                    {
                    String Name = formCollection.Keys[idx];
                    String value = formCollection[idx];

                    if (Name.Substring(0, 3).ToLower() == "chk")

                        {
                        Response.Write(Name + " is a checkbox <br/>");
                        }
                    else if (Name.Substring(0, 5).ToLower() == "txtar")
                        {
                        Response.Write(Name + " is a text area <br/>");
                        }
                    else if (Name.Substring(0, 2).ToLower() == "rd")
                        {
                        Response.Write(Name + " is a RadioButton <br/>");
                        }

                    }

这对我来说是有效的,但我发现如果单选按钮未被选择,则为null,因此不会返回任何内容,这是可以的,如果为null,则无需将任何内容写入数据库


0
        var dropDownLists = new List<DropDownList>();
        foreach (var control in this.Controls)
        {
            if (control is DropDownList)
            {
                dropDownLists.Add( (DropDownList)control );
            }
        }

8
我相信你需要进行递归。 - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接