Перебирайте все элементы управления в форме, даже в групповых ящиках
Я хочу добавить событие ко всем текстовым полям в моей форме:
foreach (Control C in this.Controls)
{
if (C.GetType() == typeof(System.Windows.Forms.TextBox))
{
C.TextChanged += new EventHandler(C_TextChanged);
}
}
Проблема в том, что они хранятся в нескольких групповых ящиках, а мой цикл их не видит. Я мог бы управлять элементами управления каждого группового ящика индивидуально, но можно ли все это сделать простым способом в одном цикле?
Ответы
Ответ 1
Коллекция элементов Controls
форм и контейнеров содержит только непосредственные дочерние элементы. Чтобы получить все элементы управления, вам нужно пройти дерево управления и применить эту операцию рекурсивно
private void AddTextChangedHandler(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += new EventHandler(C_TextChanged);
} else {
AddTextChangedHandler(c);
}
}
}
Примечание. Форма также выводится (косвенно) из Control
, а все элементы управления имеют коллекцию Controls
. Таким образом, вы можете вызвать метод следующим образом:
AddTextChangedHandler(this);
Более общим решением было бы создать метод расширения, который рекурсивно применяет действие ко всем элементам управления. В статическом классе (например, WinFormsExtensions
) добавьте этот метод:
public static void ForAllControls(this Control parent, Action<Control> action)
{
foreach (Control c in parent.Controls) {
action(c);
ForAllControls(c, action);
}
}
Пространство имен статических классов должно быть "видимым", т.е. добавить соответствующее объявление using
, если оно находится в другом пространстве имен.
Затем вы можете вызвать его так, где this
- форма; вы также можете заменить this
на форму или контрольную переменную, вложенные элементы которой должны быть затронуты:
this.ForAllControls(c =>
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += C_TextChanged;
}
});
Ответ 2
Несколько простых инструментов общего назначения делают эту проблему очень простой. Мы можем создать простой метод, который будет проходить через все дерево управления, возвращая последовательность всех его детей, всех их детей и т.д., Охватывая все элементы управления, а не только фиксированную глубину. Мы могли бы использовать рекурсию, но, избегая рекурсии, она будет работать лучше.
public static IEnumerable<Control> GetAllChildren(this Control root)
{
var stack = new Stack<Control>();
stack.Push(root);
while (stack.Any())
{
var next = stack.Pop();
foreach (Control child in next.Controls)
stack.Push(child);
yield return next;
}
}
Используя это, мы можем получить все дочерние элементы, отфильтровать те, которые нам нужны, а затем легко присоединить обработчик:
foreach(var textbox in GetAllChildren().OfType<Textbox>())
textbox.TextChanged += C_TextChanged;
Ответ 3
Попробуйте это
AllSubControls(this).OfType<TextBox>().ToList()
.ForEach(o => o.TextChanged += C_TextChanged);
где AllSubControls
private static IEnumerable<Control> AllSubControls(Control control)
=> Enumerable.Repeat(control, 1)
.Union(control.Controls.OfType<Control>()
.SelectMany(AllSubControls)
);
LINQ отлично!
Ответ 4
Не видел никого, использующего linq и/или урока, так вот:
public static class UtilitiesX {
public static IEnumerable<Control> GetEntireControlsTree(this Control rootControl)
{
yield return rootControl;
foreach (var childControl in rootControl.Controls.Cast<Control>().SelectMany(x => x.GetEntireControlsTree()))
{
yield return childControl;
}
}
public static void ForEach<T>(this IEnumerable<T> en, Action<T> action)
{
foreach (var obj in en) action(obj);
}
}
Затем вы можете использовать его в своем сердце:
someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);
Ответ 5
Как вы уже сказали, вам нужно идти глубже, чем просто ездить на велосипеде по каждому элементу в вашей форме. Это, к сожалению, подразумевает использование вложенного цикла.
В первом цикле цикл через каждый элемент. ЕСЛИ элемент имеет тип GroupBox, тогда вы знаете, что вам нужно будет циклически перебирать каждый элемент внутри группового ящика, прежде чем продолжить; иначе добавьте событие как обычно.
У вас, похоже, есть приличное понимание С#, поэтому я не дам вам никакого кода; исключительно для того, чтобы вы разработали все важные концепции, которые участвуют в решении проблем:)
Ответ 6
вы можете только прокручивать открытые формы в виде окон, используя коллекцию форм, например, чтобы установить начальную позицию окна для всех открытых форм:
public static void setStartPosition()
{
FormCollection fc = Application.OpenForms;
foreach(Form f in fc)
{
f.StartPosition = FormStartPosition.CenterScreen;
}
}
Ответ 7
Я знаю, что это более старая тема, но, скажем, фрагмент кода из http://backstreet.ch/coding/code-snippets/mit-c-rekursiv-durch-form-controls-loopen/ является умным решением этой проблемы.
Он использует метод расширения для ControlCollection.
public static void ApplyToAll<T>(this Control.ControlCollection controlCollection, string tagFilter, Action action)
{
foreach (Control control in controlCollection)
{
if (!string.IsNullOrEmpty(tagFilter))
{
if (control.Tag == null)
{
control.Tag = "";
}
if (!string.IsNullOrEmpty(tagFilter) && control.Tag.ToString() == tagFilter && control is T)
{
action(control);
}
}
else
{
if (control is T)
{
action(control);
}
}
if (control.Controls != null && control.Controls.Count > 0)
{
ApplyToAll(control.Controls, tagFilter, action);
}
}
}
Теперь, чтобы назначить событие всем элементам управления TextBox, вы можете написать инструкцию типа (где 'this' - это форма):
this.Controls.ApplyToAll<TextBox>("", control =>
{
control.TextChanged += SomeEvent
});
По желанию вы можете фильтровать элементы управления своими тегами.