简体   繁体   English

如何遍历Windows窗体表单中的所有控件或如何查找特定控件是否是容器控件?

[英]How to loop through all controls in a Windows Forms form or how to find if a particular control is a container control?

I will tell my requirement. 我会告诉我的要求。 I need to have a keydown event for each control in the Windows Forms form. 我需要为Windows窗体表单中的每个控件都有一个keydown事件。 It's better to do so rather than manually doing it for all controls if what I have to do for all keydown events is the same. 如果我必须为所有keydown事件做的事情是相同的,那么最好这样做而不是为所有控件手动执行它。

So I could basically do this: 所以我基本上可以这样做:

foreach (Control c in this.Controls)
    c.KeyDown+= new KeyEventHandler(c_KeyDown);

But here, the foreach doesn't loop inside those controls which reside inside a groupBox or a tabControl. 但是在这里,foreach不会循环驻留在groupBox或tabControl中的那些控件。 I mean if the form (this) contains a groupBox or some other container control, then I can get a keydown event for that particular container control. 我的意思是如果表单(this)包含groupBox或其他一些容器控件,那么我可以获得该特定容器控件的keydown事件。 And the foreach doesn't loop through controls that reside inside that container control. 并且foreach不会foreach驻留在容器控件内的控件。

Question 1: How do I get a keydown event for "all" the controls in a form? 问题1:如何为表单中的“所有”控件获取keydown事件?

If the above puzzle is solved, then my problem is over. 如果上面的谜题得到解决,那么我的问题就结束了。

This is what I can otherwise do: 这是我可以做的事情:

Mainly pseudo code 主要是伪代码

foreach (Control c in this.Controls)
{
     c.KeyDown += new KeyEventHandler(c_KeyDown);

     if (c is Container control)
           FunctionWhichGeneratesKeyDownForAllItsChildControls(c)
}

I know I will have to go through FunctionWhichGeneratesKeyDownForAllItsChildControls(c) many times over to get keydown for all controls if there are groupboxes inside a groupbox or so. 我知道如果组框内有组框,我将不得不多次通过FunctionWhichGeneratesKeyDownForAllItsChildControls(c)来获取所有控件的keydown。 I can do it. 我能做到。 My question is, 我的问题是,

Question 2: How do I check if c is a container control? 问题2:如何检查c是否为容器控件?

A simple recursive function should do it. 一个简单的递归函数应该这样做。

private void AddEvent(Control parentCtrl)
{
  foreach (Control c in parentCtrl.Controls)
  {
    c.KeyDown += new KeyEventHandler(c_KeyDown);
    AddEvent(c);
  }
}

This is the same as Magnus' correct answer but a little more fleshed out. 这与马格努斯的正确答案相同,但更加充实。 Note that this adds the handler to every control, including labels and container controls. 请注意,这会将处理程序添加到每个控件,包括标签和容器控件。 Those controls do not appear to raise the event, but you may want to add logic to only add the handler to controls that accept user input. 这些控件似乎不会引发事件,但您可能希望添加逻辑以仅将处理程序添加到接受用户输入的控件。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        RegisterKeyDownHandlers(this);
    }

    private void RegisterKeyDownHandlers(Control control)
    {
        foreach (Control ctl in control.Controls)
        {
            ctl.KeyDown += KeyDownFired;
            RegisterKeyDownHandlers(ctl);
        }
    }

    private void KeyDownFired(object sender, EventArgs e)
    {
        MessageBox.Show("KeyDown fired for " + sender);
    }
}

Here are some non-recursive options for traversing the control collection. 以下是遍历控件集合的一些非递归选项。 My particular implementation is doing interface validation, but could be adapted to your purpose. 我的具体实现是进行界面验证,但可以根据您的目的进行调整。

Why even mess with a non-recursive solution you say? 为什么甚至搞乱你说的非递归解决方案呢? Well I got a stack overflow error when debugging one day, so I looked at replacing it with a loop (which is considerably more difficult). 好吧,我在调试一天时遇到堆栈溢出错误,所以我看着用循环替换它(这要困难得多)。 As it turns out that error was a fluke and has never happened again 事实证明,错误是一种侥幸,并且再也没有发生过

    //recursive
    //This is the simplest implementation, but the most memory hungry
    private IEnumerable<DataObjects.Error> CheckErrors(Control.ControlCollection controls, ErrorProvider errorProvider)
    {
        var errors = new List<DataObjects.Error>();
        foreach (var control in controls.Cast<System.Windows.Forms.Control>())
        {
            //insert your own business logic in here
            var error = errorProvider.GetError(control);
            if (!string.IsNullOrEmpty(error))
            {
                errors.Add(new DataObjects.Error(error, DataObjects.ErrorLevel.Validation));
            }
            //recursive call
            errors.AddRange(CheckErrors(control.Controls, errorProvider));
            //insert your own business logic in here
        }
        return errors;
    }

    //Breadth first - Does NOT require child node to have knowledge of parent
    //Read through the controls at a given level and then blindly delve 
    //deeper until you reach the end of the rainbow
    //order(max-tree-level-size) memory usage?
    //tree-level-size, as in the # of nodes at a given depth
    private IEnumerable<DataObjects.Error> CheckErrors_NonRecursive_NeverLookBack(Control control, ErrorProvider errorProvider)
    {
        var currentControls = control.Controls.Cast<Control>();
        var errors = new List<DataObjects.Error>();

        while (currentControls.Count() > 0)
        {
            foreach (var currentControl in currentControls)
            {
                //insert your own business logic in here
                var error = errorProvider.GetError(currentControl);
                if (!string.IsNullOrEmpty(error))
                {
                    errors.Add(new DataObjects.Error(error, DataObjects.ErrorLevel.Validation));
                }
                //insert your own business logic in here
            }
            //replace currentControls with ALL of the nodes at a given depth
            currentControls = currentControls.SelectMany(x => x.Controls.Cast<Control>());
        }

        return errors;
    }

    //Depth first - Does NOT require child to have knowledge of parent
    //Approximate recursion by keeping a stack of controls, instead of a call stack.
    //Traverse the stack as you would have with recursion
    //order(tree-branch-size) memory usage? tree-branch-size as in the number of nodes 
    //that it takes to get from the root to the bottom of a given branch
    private IEnumerable<DataObjects.Error> CheckErrors_NonRecursive(Control.ControlCollection controls, ErrorProvider errorProvider)
    {
        var controlStack = new Stack<Control.ControlCollection>();
        var controlIndicies = new Stack<int>();
        var errors = new List<DataObjects.Error>();

        controlStack.Push(controls);
        controlIndicies.Push(0);

        while(controlStack.Count() > 0)
        {
            while(controlIndicies.First() < controlStack.First().Count)
            {
                var controlIndex = controlIndicies.Pop();
                var currentControl = controlStack.First()[controlIndex];
                //insert your own business logic in here
                var error = errorProvider.GetError(currentControl);
                if (!string.IsNullOrEmpty(error))
                {
                    errors.Add(new DataObjects.Error(error, DataObjects.ErrorLevel.Validation));
                }
                //insert your own business logic in here

                //update the fact that we've processed one more control
                controlIndicies.Push(controlIndex + 1);
                if(currentControl.Controls.Count > 0)
                {
                    //traverse deeper
                    controlStack.Push(currentControl.Controls);
                    controlIndicies.Push(0);
                }
                //else allow loop to continue uninterrupted, to allow siblings to be processed
            }
            //all siblings have been traversed, now we need to go back up the stack
            controlStack.Pop();
            controlIndicies.Pop();
        }

        return errors;
    }

    //Depth first - DOES require child to have knowledge of parent.
    //Approximate recursion by keeping track of where you are in the control 
    //tree and use the .Parent() and .Controls() methods to traverse the tree.
    //order(depth(tree)) memory usage? 
    //Best of the bunch as far as I can (in memory usage that is)
    private IEnumerable<DataObjects.Error> CheckErrors_NonRecursiveIndicesOnly(Control control, ErrorProvider errorProvider)
    {
        var errors = new List<DataObjects.Error>();
        var controlIndicies = new Stack<int>();
        var controlCount = new Stack<int>();
        Control currentControl = control;
        var currentControls = currentControl.Controls;

        controlCount.Push(currentControls.Count);
        controlIndicies.Push(0);
        while (controlCount.Count() > 0)
        {
            while (controlIndicies.First() < controlCount.First())
            {
                var controlIndex = controlIndicies.Pop();
                currentControl = currentControls[controlIndex];
                //insert your own business logic in here
                var error = errorProvider.GetError(currentControl);
                if (!string.IsNullOrEmpty(error))
                {
                    errors.Add(new DataObjects.Error(error, DataObjects.ErrorLevel.Validation));
                }
                //insert your own business logic in here

                //update the fact that we've processed one more control
                controlIndicies.Push(controlIndex + 1);
                if (currentControl.Controls.Count > 0)
                {
                    //traverse deeper
                    currentControls = currentControl.Controls;
                    controlCount.Push(currentControl.Controls.Count);
                    controlIndicies.Push(0);
                }
                else
                {
                    //allow loop to continue uninterrupted, to allow siblings to be processed
                }
            }
            //all siblings have been traversed, now we need to go back up the stack
            controlCount.Pop();
            controlIndicies.Pop();

            //need to check our position in the stack... once we get back to the top there is no parent of parent.
            if (controlCount.Count() > 0)
            {
                currentControls = currentControl.Parent.Parent.Controls;
            }
            //do nothing, believe it or not once you've gotten to this level you have traversed the entire stack
        }

        return errors;
    }

问题2的答案是使用您正在检查的控件的GetType()方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM