如何循环遍历Windows窗体中的所有控件,或者如何确定特定控件是否是容器控件?

12

我来说明我的需求。我需要为 Windows Forms 表单中的每个控件都添加一个 keydown 事件。如果我必须对所有的 keydown 事件执行相同的操作,那么最好这样做,而不是手动为所有控件添加事件。

所以,基本上我可以这样做:

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

但是问题在于,foreach循环无法遍历groupBox或tabControl内部的控件。我的意思是,如果表单(this)包含groupBox或其他容器控件,则可以为该特定容器控件获取keydown事件。而foreach无法循环遍历驻留在该容器控件内部的控件。

问题1:如何获得表单中“所有”控件的keydown事件?

如果上述难题解决了,那么我的问题就解决了。

这是我可以用的另一种方法:

主要是伪代码

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

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

我知道如果有嵌套的groupbox,我需要多次使用FunctionWhichGeneratesKeyDownForAllItsChildControls(c)来获取所有控件的keydown事件。我可以做到这一点。我的问题是,

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


1
所有控件都是"容器控件",因为它们都有一个子控件集合(从Control继承而来)。 - Magnus
@Magnus 噢,这对我来说是个新消息.. 给你点赞。 - nawfal
1
Magnus的答案对于问题1是可以的。对于问题2:容器控件只是一个Controls.Count>0的控件,因此没有必要显式地检查它,因为如果Controls.Count==0,则在Controls上的foreach循环不执行任何操作。 - Doc Brown
也许你真正感兴趣的是 KeyPreview 属性,它可以让你集中处理键盘事件。详见:http://msdn.microsoft.com/zh-cn/library/system.windows.forms.form.keypreview.aspx - Ben Voigt
@BenVoigt 是的,那对于键盘事件是有效的,但我想要一个更通用的方法。 - nawfal
4个回答

19
一个简单的递归函数就可以做到这一点。
private void AddEvent(Control parentCtrl)
{
  foreach (Control c in parentCtrl.Controls)
  {
    c.KeyDown += new KeyEventHandler(c_KeyDown);
    AddEvent(c);
  }
}

@Magnus,我对这段代码能否正常工作有些怀疑,但让我试一下 :) 待会儿回复你。 - nawfal
1
@nawfal:持怀疑态度是你应该更多地了解递归的一个迹象。我非常确定这段代码会起作用(所以+1)。 - Doc Brown
@Doc Brown,是的,这确实是一个信号。仅仅通过看代码,我就发现它失败了 :) 我知道这是我的知识不足...让我试试。我还没有尝试过... - nawfal
在递归调用AddEvent方法之前,检查控件是否有子元素不是很好吗? - user4624979
1
@nocomprende 这不应该有太大影响,因为for循环不会迭代任何子级,只会离开函数。 - Magnus

3

这与Magnus的正确答案相同,但更加详细。请注意,这将处理程序添加到每个控件,包括标签和容器控件。这些控件似乎不会引发事件,但您可能需要添加逻辑,仅将处理程序添加到接受用户输入的控件。

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

1
以下是遍历控件集合的非递归选项。我的具体实现正在进行接口验证,但可以根据您的目的进行调整。
你为什么要使用非递归解决方案呢?嗯,有一天在调试时我遇到了堆栈溢出错误,所以我考虑用循环替换它(这要困难得多)。结果证明那个错误是偶然的,从那以后再也没有发生过。
    //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;
    }

0
问题2的答案是使用GetType()方法来检查控件类型。

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