如何在WinForm中获取子控件的内容?

How to get content of sub-controls in WinForm?

提问人:Headshot 提问时间:11/3/2023 更新时间:11/4/2023 访问量:69

问:

我有WinForm,它有很多控件(tabcontrol,tabpage,datagridview,...等)。我想在datagridview中获取数据。设计的结构是这样的(TabControl 将有许多 TabPages,每个 TabPage 将有一个 TabControl,每个 TabControl 将有许多 TabPages,每个 TabPage 将有一个 DataGridView)。

TabControlX
+TabPage
++TabControl
+++TabPage
++++DataGridView
+++TabPage
++++DataGridView
+TabPage
++TabControl
+++TabPage
++++DataGridView
+++TabPage
++++DataGridView

因此,我想获取每个 DataGridView 的数据。我不确定这些东西是正确的还是坏的。

            foreach (TabPage tbp in tabControl_X.TabPages)
            {
                foreach (Control item in tbp.Controls)
                {
                    foreach (TabPage sub_tab in item.Controls)
                    {
                        foreach (Control dgv in sub_tab.Controls)
                        {
                            //how to get data dgv?
                        }
                    }
                }
            }
C# WinForms DataGridView 控件选项卡

评论


答:

1赞 Andrea Colleoni 11/3/2023 #1

如果要获取 dgv 控件,它必须是:

  • 在您需要的点,在代码中可访问
  • 随心所欲地命名

因此,如果通过 TabControlX 控件的设计器添加了 dgv,则应该能够通过其名称访问它们。

如果要从 TabControlX 控件外部访问它们,则必须将 dgv 的内存标记为 或 。internalpublic

除了设计器中的“属性”选项卡外,还可以尝试查看代码文件;在那里,您应该能够找到控件的变量。.Designer.cs

评论

0赞 Headshot 11/3/2023
所有通过代码创建的 DataGridView 都不会来自设计器。这意味着它们都将被创建,之后将被删除。我只是在创建后在 DataGridView 中获取数据并保留它。最后,将删除所有 DataGridView。
1赞 IVSoftware 11/3/2023 #2

要迭代 的 控制层次结构,请制作一个类似于以下内容的迭代器方法:Form

public partial class MainForm : Form
{
    IEnumerable<Control> IterateControls(Control.ControlCollection controls)
    {
        foreach (Control control in controls) 
        {
            yield return control;
            foreach (Control child in IterateControls(control.Controls))
            { 
                yield return child;
            }
        }
    }
}

首先,通过列出所有控件来演示迭代器:

// Iterate all names
Debug.WriteLine("ALL CONTROLS");
foreach (var control in IterateControls(Controls))
{
    Debug.WriteLine(
        string.IsNullOrWhiteSpace(control.Name) ?
            $"Unnamed {control.GetType().Name}"  :
            control.Name);
}

list all controls

附加扩展按深度缩进输出,请参阅存储库


您的问题是如何仅获取控件,因此请指定DataGridViewOfType()


// Iterate DataGridView only
Debug.WriteLine("\nDATA GRID VIEW CONTROLS");
foreach (var control in IterateControls(Controls).OfType<DataGridView>())
{
    Debug.WriteLine(control.Name);
}

data grid view controls

评论

0赞 Headshot 11/3/2023
基本上,所有 DataGridView 都将通过代码动态创建,而不是从设计器创建。我想在这些里面获取数据并将其保存在其他地方。之后,最后将删除所有 DataGridView。
0赞 IVSoftware 11/3/2023
没有理由动态创建的控件会与这是否有效有任何关系。如果它们可见,则它们位于控件集合中。如果它们位于控件集合中,则可通过这种方式发现它们。你用你的代码试过吗?您是否从存储库中运行了我的示例?
0赞 Headshot 11/4/2023
我以前还没有尝试过你的回购。我尝试在我的代码中调试一些东西,然后我弄清楚了。可能我在 DataGridView 中赶上了数据。无论如何,你真的很好回答我的担忧。我稍后会从你的回购回来。Tks的
1赞 Jimi 11/3/2023 #3

另一种方法,使用反射。

此方法采用一个通用 T,该 T 受限于类。
设置为要查找的控件类型,并传递作为共同祖先的控件(可能是您的 )。
你可以把它变成一个扩展方法。
ControlTtabControl_X

例如:

var childControls = GetAllChildControlsOfType<DataGridView>(tabControl_X);

如果传递 Form 实例 (),则会获得将 Form 作为上级的所有子 Type 控件,依此类推。thisDataGridView

专为 .NET 6+ 构建,启用可为 null。
如果面向 .NET Framework,请删除所有
?!

using System.Reflection;

private ReadOnlyCollection<T> GetAllChildControlsOfType<T>(Control parent) where T : Control {
    var flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
    var ctlList = new List<T>();
    var form = parent is Form ? parent : parent.FindForm();
    var childControls = form!.GetType().GetFields(flags).Select(fi => fi.GetValue(form))
                             .Where(obj => obj?.GetType() == typeof(T))
                             .Select(obj => obj as T);

    foreach (T? ctl in childControls) {
        if (HasAncestor(ctl, parent)) {
            ctlList.Add(ctl!);
        }
    }

    bool HasAncestor(Control? ctl, Control? parent) {
        if (ctl is null || parent is null) return false;
        while (ctl.Parent != null) {
            if (ctl.Parent == parent) return true;
            ctl = ctl.Parent;
        }
        return false;
    }
    return new ReadOnlyCollection<T>(ctlList);
}