Каков предпочтительный способ найти сфокусированный контроль в приложении 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;
}