Каков предпочтительный способ найти сфокусированный контроль в приложении WinForms?

Каков предпочтительный / самый простой способ найти элемент управления, который в данный момент получает пользовательский ввод (ввод с клавиатуры) в WinForms?

До сих пор я придумал следующее:

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

Из формы это можно назвать просто как (в.NET 3.5+ это можно даже определить как метод расширения в форме) -

var focused = FindFocusedControl(this);

Это уместно?

Есть ли встроенный метод, который я должен использовать вместо этого?

Обратите внимание, что при использовании иерархий одного вызова ActiveControl недостаточно. Представить:

Form
    TableLayoutPanel
        FlowLayoutPanel
            TextBox (focused)

(formInstance).ActiveControl будет возвращать ссылку на TableLayoutPanel, а не TextBox (потому что ActiveControl, похоже, возвращает только непосредственный активный дочерний элемент в дереве элемента управления, в то время как я ищу элемент управления листа).

5 ответов

Решение

Если у вас уже есть другие вызовы Windows API, использование решения Peters не повредит. Но я понимаю, что вы беспокоитесь по этому поводу и хотел бы использовать такое же решение, как ваше, используя только функциональные возможности Framework. В конце концов, разница в производительности (если она есть) не должна быть значительной.

Я бы взял нерекурсивный подход:

public static Control FindFocusedControl(Control control)
{
    var container = control as IContainerControl;
    while (container != null)
    {
        control = container.ActiveControl;
        container = control as IContainerControl;
    }
    return control;
}

После поиска в Интернете я нашел следующее в FAQ по Windows Forms Джорджа Шепарда

.NET Framework библиотеки не предоставляет вам API для запроса для целевого элемента управления. Вы должны вызвать Windows API для этого:

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

ActiveControl в форме или контейнере вернет активный элемент управления этой сущности, независимо от того, насколько глубоко он может быть вложен в другие контейнеры.

В вашем примере, если TextBox имеет Focus: then: for Form, TableLayoutPanel и FlowLayoutPanel: свойство ActiveControl для всех них будет TextBox!

Некоторые, но не все, "подлинные" типы ContainerControl... такие как Form и UserControl ... предоставляют ключевые события (в случае Form: только если Form.KeyPreview == true они могут использоваться) .

Другие элементы управления, которые по своей конструкции содержат другие элементы управления, такие как TableLayOutPanel, GroupBox, Panel, FlowLayoutPanel и т. Д., Не относятся к типу ContainerControl и не предоставляют KeyEvents.

Любая попытка привести экземпляры таких объектов, как TextBox, FlowLayoutPanel, TableLayoutPanel непосредственно к ContainerControl, не будет компилироваться: они не являются типом ContainerControl.

Код в принятом ответе и в следующем ответе, который исправляет орфографические ошибки первого ответа, скомпилирует / примет экземпляры вышеупомянутого как параметры, потому что вы "понижаете" их до типа "Управление, делая тип параметра" Управление

Но в каждом случае приведение к ControlContainer будет возвращать нуль, а переданный экземпляр будет возвращен (понижен): по сути, нет операции.

И да, измененный код ответа будет работать, если вы передадите ему "подлинный" ControlContainer, например экземпляр Form, который находится в родительском пути наследования ActiveControl, но вы все еще просто тратите время, дублируя функцию ActiveControl.

Итак, что же такое "подлинные" ContainerControls: посмотрите их: документы MS для ContainerControl

Только ответ Питера действительно отвечает на явный вопрос, но этот ответ несет цену за использование взаимодействия, и "ActiveControl даст вам то, что вам нужно.

Также обратите внимание, что каждый элемент управления (контейнер или не контейнер) имеет коллекцию элементов управления, которая никогда не равна нулю, и что многие (я никогда не пробовал все из них: зачем мне это?) Базового элемента управления WinForms позволяют вам делать "сумасшедшие" такие вещи, как добавление Controls к ControlCollection "простых" элементов управления, таких как Button без ошибок.

Теперь, если реальная цель вашего вопроса состояла в том, чтобы спросить, как вы находите самый внешний ContainerControl... который находится не в самой Форме... обычного неконтейнерного элемента управления, вложенного в несколько произвольных уровней глубоко... вы можете использовать некоторые из идеи в ответе: но код может быть значительно упрощен.

Регулярные элементы управления, ContainerControls, UserControls и т. Д. (Но не Form!) Имеют свойство "Container", к которому вы можете получить доступ к их непосредственному контейнеру, но чтобы убедиться, что у вас есть "final Container" в пути наследования, который не является Form, требуется некоторый код "подойти" к наследственному дереву, которое демонстрируется здесь.

Вы также можете проверить свойство "HasChildren" элемента управления, которое обычно полезно для решения проблем с Focus, ActiveControl и Select в WinForms. Здесь может быть полезно рассмотреть разницу между Select и Focus, и у SO есть на это хорошие ресурсы.

Надеюсь это поможет.

Решение Hinek хорошо работает для меня, за исключением того, что это ContainerControl, а не ControlContainer. (На случай, если вы почесываете голову об этой красной волнистой линии.)

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

Если вы будете следовать ActiveControl рекурсивно, это не приведет вас к конечному элементу управления, который имеет фокус?

ActiveControl не всегда работает, как и в случае с SplitContainer, ActiveControl.Focused имеет значение false.

Так что для более надежного метода можно сделать что-то вроде этого:

private IEnumerable<Control> _get_all_controls(Control c)
{
    return c.Controls.Cast<Control>().SelectMany(item =>
        _get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
        control.Name != string.Empty);
}

var _controls = _get_all_controls(this);
foreach (Control control in _controls) 
    if (control.Focused)
    {
        Console.WriteLine(control.Name);
        break;
    }
Другие вопросы по тегам