Каким образом можно найти сфокусированный контроль в приложении 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, кажется, возвращает только активный активный дочерний элемент в дереве управления, в то время как я ищу элемент управления листьями).
Ответы
Ответ 1
Если у вас есть другие вызовы в 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;
}
Ответ 2
После поиска в Интернете я нашел следующее в Часто задаваемые вопросы о Windows Forms от George Shepherd
. Библиотеки framework.Net не предоставляют вам API для запроса сфокусированное управление. Вы должны вызовите API окон для этого:
[С#]
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;
}
}
Ответ 3
ActiveControl в форме или контейнере возвращает этот активный элемент управления независимо от того, насколько глубоко он может быть вложен в другие контейнеры.
В вашем примере, если TextBox имеет Focus: then: для Form, TableLayoutPanel и FlowLayoutPanel: свойство ActiveControl для всех из них будет TextBox!
Некоторые, но не все, "подлинные" типы ContainerControl... например Form и UserControl... выставляют ключевые события (в случае формы: только если Form.KeyPreview == true могут использоваться).
Другие элементы управления, которые по дизайну содержат другие элементы управления, такие как TableLayOutPanel, GroupBox, Panel, FlowLayoutPanel и т.д., являются не типа ContainerControl и не раскрывают KeyEvents.
Любая попытка бросить экземпляры таких объектов, как TextBox, FlowLayoutPanel, TableLayoutPanel непосредственно в ContainerControl, не будет компилироваться: они не являются типами ContainerControl.
Код в принятом ответе, а в следующем ответе, который исправляет первые ответы на орфографические ошибки, будет компилировать/принимать экземпляры вышеуказанного в качестве параметров, потому что вы "опускаете" их, чтобы набрать "Control, создав тип параметра", Контроль
Но в каждом случае приведение в ControlContainer будет возвращать значение null, а переданный экземпляр будет возвращен (опущен): по существу нет-op.
И да, измененный код ответа будет работать, если вы передадите ему "подлинный" ControlContainer, например экземпляр формы, который находится в пути родительского наследования ActiveControl, но вы по-прежнему просто теряете время, дублируя функцию "ActiveControl.
Итак, что такое "подлинный" ContainerControls: проверьте их: MS docs для ContainerControl
Только ответ Петра действительно отвечает на явный вопрос, но этот ответ несет цену использования interop, а "ActiveControl предоставит вам то, что вам нужно".
Также обратите внимание, что каждый элемент управления (контейнер или неконтейнер) имеет коллекцию элементов управления, которая никогда не является нулевой, и что много (я никогда не пробовал их всех: зачем мне?) основной элемент управления WinForms позволяет вам делайте "сумасшедшие вещи", например, добавляя Controls к ControlCollection "простых" элементов управления, таких как Button без ошибок.
Теперь, если реальное намерение вашего вопроса состояло в том, чтобы спросить, как вы находите внешний ContainerControl..., который не находится в самой форме... обычного неконтейнерного управления, вложенные некоторые произвольные уровни глубины... вы можете использовать некоторые идеи в ответе: но код может быть значительно упрощен.
Регулярные элементы управления, ContainerControls, UserControls и т.д. (но не Form!) имеют свойство "Контейнер", к которому вы можете получить доступ, чтобы получить их непосредственный контейнер, но убедитесь, что у вас есть конечный Контейнер по пути их наследования, а не форма требует некоторого кода для "ходьбы" дерева наследования, которое показано здесь.
Вы также можете проверить свойство "HasChildren" элемента управления, которое обычно полезно для решения проблем Focus, ActiveControl и Select в WinForms. Рассмотрение разницы между Select и Focus может быть здесь ценным, и у SO есть хорошие ресурсы.
Надеюсь, что это поможет.
Ответ 4
Решение 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;
}
Ответ 5
Если вы будете следовать за ActiveControl рекурсивно, это не приведет вас к управлению листьями, у которого есть фокус?
Ответ 6
ActiveForm не всегда работает, как с SplitContainer.
Так что для более надежного метода можно сделать что-то вроде этого:
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;
}