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

时间:2022-04-29 19:16:17

I will tell my requirement. I need to have a keydown event for each control in the Windows Forms form. 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.

我会告诉我的要求。我需要在Windows窗体表单中为每个控件创建一个keydown事件。如果我必须为所有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. 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. And the foreach doesn't loop through controls that reside inside that container control.

但是在这里,foreach不会在位于groupBox或tabControl中的那些控件内循环。我的意思是如果表单(this)包含groupBox或其他一些容器控件,那么我可以获得该特定容器控件的keydown事件。并且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. I can do it. My question is,

我知道如果组框内有组框,我将不得不多次通过FunctionWhichGeneratesKeyDownForAllItsChildControls(c)来获取所有控件的keydown。我能做到。我的问题是,

Question 2: How do I check if c is a container control?

问题2:如何检查c是否为容器控件?

4 个解决方案

#1


17  

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);
  }
}

#2


3  

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);
    }
}

#3


1  

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;
    }

#4


0  

The answer to question 2 is to use the GetType() method of the control you are checking.

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

#1


17  

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);
  }
}

#2


3  

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);
    }
}

#3


1  

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;
    }

#4


0  

The answer to question 2 is to use the GetType() method of the control you are checking.

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