web-dev-qa-db-fra.com

Parcourez tous les contrôles d'un formulaire, même ceux des groupes

J'aimerais ajouter un événement à toutes les zones de texte de mon formulaire:

foreach (Control C in this.Controls)
{
    if (C.GetType() == typeof(System.Windows.Forms.TextBox))
    {
        C.TextChanged += new EventHandler(C_TextChanged);
    }
}

Le problème est qu'ils sont stockés dans plusieurs boîtes de groupe et que ma boucle ne les voit pas. Je pourrais parcourir les contrôles de chaque groupe individuellement, mais est-il possible de tout faire de manière simple en une boucle?

15
RRM

La collection Controls de formulaires et de contrôles de conteneur contient uniquement les enfants immédiats. Pour obtenir tous les contrôles, vous devez parcourir l'arborescence des contrôles et appliquer cette opération de manière récursive.

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

Remarque: Le formulaire dérive (indirectement) de Control également et tous les contrôles ont une collection Controls. Donc, vous pouvez appeler la méthode comme ceci dans votre formulaire:

AddTextChangedHandler(this);

Une solution plus générale consisterait à créer une méthode d'extension appliquant une action de manière récursive à tous les contrôles. Dans une classe statique (par exemple, WinFormsExtensions), ajoutez cette méthode:

public static void ForAllControls(this Control parent, Action<Control> action)
{
    foreach (Control c in parent.Controls) {
        action(c);
        ForAllControls(c, action);
    }
}

L'espace de noms des classes statiques doit être "visible", c'est-à-dire ajouter une déclaration using appropriée si elle se trouve dans un autre espace de noms.

Ensuite, vous pouvez l'appeler comme ceci, où this est la forme; vous pouvez également remplacer this par un formulaire ou une variable de contrôle dont les contrôles imbriqués doivent être affectés:

this.ForAllControls(c =>
{
    if (c.GetType() == typeof(TextBox)) {
        c.TextChanged += C_TextChanged;
    }
});

Quelques outils simples à usage général rendent ce problème très simple. Nous pouvons créer une méthode simple qui parcourt l'arborescence d'un contrôle entier, renvoyant une séquence de tous ses enfants, de tous leurs enfants, etc., en couvrant tous les contrôles, pas seulement à une profondeur fixe. Nous pourrions utiliser la récursivité, mais en l’évitant, elle fonctionnera mieux.

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

En utilisant cela, nous pouvons obtenir tous les enfants, filtrer ceux du type dont nous avons besoin, puis attacher le gestionnaire très facilement:

foreach(var textbox in GetAllChildren().OfType<Textbox>())
    textbox.TextChanged += C_TextChanged;
11
Servy

Essaye ça

AllSubControls(this).OfType<TextBox>().ToList()
    .ForEach(o => o.TextChanged += C_TextChanged);

où est AllSubControls

private static IEnumerable<Control> AllSubControls(Control control)
    => Enumerable.Repeat(control, 1)
       .Union(control.Controls.OfType<Control>()
                              .SelectMany(AllSubControls)
             );

LINQ est génial!

5
Georg

N'ayant vu personne utiliser linq et/ou céder, voici:

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

Vous pouvez ensuite l'utiliser à votre guise:

someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);
1
XDS

vous pouvez uniquement parcourir les formulaires ouverts dans les formulaires Windows à l'aide de la collection de formulaires, par exemple, pour définir la position de début des fenêtres pour tous les formulaires ouverts:

public static void setStartPosition()
        {
            FormCollection fc = Application.OpenForms;

            foreach(Form f in fc)
            {
                f.StartPosition = FormStartPosition.CenterScreen;
            }
        }
0
Ashraf Abusada

Comme vous l'avez dit, vous devrez aller plus loin que de simplement passer en revue chaque élément de votre formulaire. Ceci, malheureusement, implique l'utilisation d'une boucle imbriquée.

Dans la première boucle, parcourez chaque élément. SI l'élément est de type GroupBox, vous savez qu'il vous faudra parcourir chaque élément de la zone de groupe avant de poursuivre. sinon ajouter l'événement comme d'habitude.

Vous semblez avoir une bonne compréhension de C #, je ne vous donnerai donc aucun code; purement pour vous assurer de développer tous les concepts importants impliqués dans la résolution de problèmes :)

0
christopher

Je sais qu'il s'agit d'un sujet plus ancien, mais dirais que l'extrait de code de http://backstreet.ch/coding/code-snippets/mit-c-rekursiv-durch-form-controls-loopen/ est un solution intelligente pour ce problème.

Il utilise une méthode d'extension pour 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);
        }
    }
}

Maintenant, pour assigner un événement à tous les contrôles TextBox, vous pouvez écrire une instruction comme (où 'ceci' est la forme):

this.Controls.ApplyToAll<TextBox>("", control =>
{
    control.TextChanged += SomeEvent
});

Vous pouvez éventuellement filtrer les contrôles en fonction de leurs balises.

0
Roger