tags:

views:

1546

answers:

5

What is the preferred/easiest way to find the control that is currently receiving user (keyboard) input in WinForms?

So far I have come up with the following:

public static Control FindFocusedControl(Control control)
{
    var container = control as ContainerControl;
    return (null != container
        ? FindFocusedControl(container.ActiveControl)
        : control);
}

From a form, this can be called simply as (in .NET 3.5+ this could even be defined as an extension method on the form) -

var focused = FindFocusedControl(this);

Is this appropriate?

Is there a built-in method that I should be using instead?

Note that a single call to ActiveControl is not enough when hierarchies are used. Imagine:

Form
    TableLayoutPanel
        FlowLayoutPanel
            TextBox (focused)

(formInstance).ActiveControl will return reference to TableLayoutPanel, not the TextBox (because ActiveControl seems to only be returning immediate active child in the control tree, while I'm looking for the leaf control).

+1  A: 

After searching the Internet, I found the following on George Shepherd's Windows Forms FAQ

The .Net framework libraries does not provide you an API to query for the focused Control. You have to invoke a windows API to do so:

[C#]

public class MyForm : Form
{
          [DllImport("user32.dll", CharSet=CharSet.Auto, CallingConvention=CallingConvention.Winapi)]
          internal static extern IntPtr GetFocus();

          private Control GetFocusedControl()
          {
               Control focusedControl = null;
               // To get hold of the focused control:
               IntPtr focusedHandle = GetFocus();
               if(focusedHandle != IntPtr.Zero)
                    // Note that if the focused Control is not a .Net control, then this will return null.
                    focusedControl = Control.FromHandle(focusedHandle);
               return focusedControl;
          }
}
Peter
Would using interop call be preferrable to using built-in functionality like the ActiveControl method? What about code-access-security ramifications of such a solutioN?
Milan Gardian
There are even Problems in the Vista API doing everything just with .NET e.g. try changing the colour of a progressbar under vista. There are just 3 colours available (States: Error, Warning, Default) and you're only able to change it with API Calls. Or the Glass-Effect...
Peter
+1  A: 

If you follow ActiveControl out recursively it doesn't take you to the leaf control that has focus?

sliderhouserules
That's what the function "FindFocusedControl" in my question does, eh? ;-)
Milan Gardian
Ah yes. :) That or Hinek's method are the ticket. Had to do this in a Smart Client app a few years ago.
sliderhouserules
+6  A: 

If you have other calls to the Windows API already, there's no harm in using Peters solution. But I understand your worries about it and would tend to a similar solution as yours, using only the Framework functionalities. After all, the performance difference (if there is one) shouldn't be significant.

I would take a non recursive approach:

public static Control FindFocusedControl(Control control)
{
    var container = control as ContainerControl;
    while (container != null)
    {
        control = container.ActiveControl;
        container = control as ContainerControl;
    }
    return control;
}
Hinek
The LISPer/F#-er in me still prefers recursion ;-)
Milan Gardian
+2  A: 

Hinek's solution works well for me, except it is ContainerControl, not ControlContainer. (Just in case you were scratching your head about that red squiggly line.)

    public static Control FindFocusedControl(Control control)
    {
        ContainerControl container = control as ContainerControl;
        while (container != null)
        {
            control = container.ActiveControl;
            container = control as ContainerControl;
        }
        return control;
    }
Nate
+2  A: 

ActiveControl on a Form or Container will return that entity's active control no matter how deeply it might be nested inside other containers.

In your example if the TextBox has Focus : then : for Form, TableLayoutPanel, and FlowLayoutPanel : the 'ActiveControl property of all of them will be the TextBox !

Some, but not all, "genuine" ContainerControl types ... like Form and UserControl ... expose Key Events (in the case of Form : only if Form.KeyPreview == true can they be used) .

Other controls which, by design, contain other controls like TableLayOutPanel, GroupBox, Panel, FlowLayoutPanel, etc. are not type ContainerControl, and they do not expose KeyEvents.

Any attempt to cast instances of objects like TextBox, FlowLayoutPanel, TableLayoutPanel directly to ContainerControl will not compile : they are not type ContainerControl.

The code in the accepted answer, and in the next answer that corrects the first answer's spelling errors, will compile/accept instances of the above as parameters because you are "downcasting" them to type 'Control by making the parameter type 'Control

But in each case the cast to ControlContainer will return null, and the passed in instance will be returned (downcasted) : essentially a no-op.

And, yes, the modified answer code will work if you pass it a "genuine" ControlContainer, like a Form instance, which is in the parent inheritance path of the ActiveControl, but you are still just wasting time duplicating the function of 'ActiveControl.

So what are "genuine" ContainerControls : check them out : MS docs for ContainerControl

Only the answer by Peter really answers the explicit question, but that answer carries the price of using interop, and 'ActiveControl will give you what you need.

Also note that every Control (container or non-container) has a Controls Collection that is never null, and that a lot of (I've never tried all of them : why would I ?) the basic WinForms control let you do "crazy stuff" like adding Controls to the ControlCollection of 'simple' controls like Button without an error.

Now if the real intent of your question was to ask how you find the outermost ContainerControl ... that is not on the Form itself ... of a regular non-container Control nested some arbitrary levels deep ... you can use some of the ideas in the answer : but the code can be greatly simplified.

Regular Controls, ContainerControls, UserControls, etc. (but not Form !) all have a 'Container property you can access to get their immediate container, but making sure you have the 'final Container in their inhertance path that's not a Form requires some code to "walk-up" the inheritance tree, which is demonstrated here.

You may also wish to check out the 'HasChildren property of 'Control which is usually useful in dealing with issues of Focus, ActiveControl, and Select in WinForms. Reviewing the difference between Select and Focus can be valuable here, and SO has some good resources on that.

Hope this helps.

BillW
Thank you for your answer. +1 for your research and thought you put into it. I verified that WinForms now indeed return the leaf focused control, not just immediate container containing focused control. However, I can't mark it as the chosen answer because when I wrote the question, this was definitely NOT the behaviour I was observing (the whole reason for this question was that ActveControl did NOT behave as expected). The incorrect behaviour must have been fixed in one of the .NET service packs, but it also means that there exist .NET releases that behave incorrectly.
Milan Gardian
Both recursive and non-recursive solutions offered here are still valid for the current WinForms behaviour, but they have the added benefit of correcting the invalid behaviour where needed.
Milan Gardian
Hi Milton, Honestly it doesn't matter to me whether my answer is "the chosen one" or not; what matters to me is the technical accuracy of the question and the answers. Of course I do not question your experience as stated ! All I can distill from the question and the answers is the hypothesis that there may have been an anomaly with TableLayoutPanel at a certain point in time. best,
BillW
Hi Milton, I've worked with WinForms for > 3 years, helped edit Sell's books on WinForms, etc., never observed what you report, seen it reported on MSDN, MS usenet forums, CodeProject, etc. I wrote code to find a top-level non-Form Container of ActiveControl long ago, never had a problem with it. I'm sure neither you nor I have time to be "forensic investigators" :), and go back and research this, but I must say, respectfully : question and accepted answer(s) "as is" just don't "hold up," and actually would confuse and mis-inform newcomers to .NET WinForms.
BillW