73 votes

Quelle est la meilleure façon de trouver un contrôle ciblé dans une application WinForms ?

Quel est le moyen le plus simple de trouver le contrôle qui reçoit actuellement une entrée utilisateur (clavier) dans WinForms ?

Jusqu'à présent, j'ai obtenu les résultats suivants :

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

À partir d'un formulaire, on peut l'appeler simplement de la façon suivante (dans .NET 3.5+, cela pourrait même être défini comme une méthode d'extension du formulaire)

var focused = FindFocusedControl(this);

Est-ce approprié ?

Existe-t-il une méthode intégrée que je devrais utiliser à la place ?

Notez qu'un seul appel à ActiveControl n'est pas suffisant lorsque des hiérarchies sont utilisées. Imaginez :

Form
    TableLayoutPanel
        FlowLayoutPanel
            TextBox (focused)

(formInstance).ActiveControl renverra la référence à TableLayoutPanel, et non à TextBox (car ActiveControl semble ne renvoyer que l'enfant actif immédiat dans l'arbre de contrôle, alors que je cherche le contrôle feuille).

79voto

Hinek Points 4598

Si vous disposez déjà d'autres appels à l'API Windows, il n'y a aucun inconvénient à utiliser la solution de Peters. Mais je comprends vos inquiétudes à ce sujet et j'opterais pour une solution similaire à la vôtre, en utilisant uniquement les fonctionnalités du Framework. Après tout, la différence de performance (s'il y en a une) ne devrait pas être significative.

J'adopterais une approche non récursive :

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

1 votes

La récursion ne semble pas nécessaire puisque la seule chose requise est le contrôle réel avec le focus. La traversée en profondeur qui se produit (dans l'algorithme de l'OP) n'apporte rien en termes de localisation de l'objet ciblé, de sorte que la surcharge de la pile d'appels ne semble pas en valoir la peine (même si c'est un outil génial et cool). Cela vaut également pour un décent breadth-first, vous avez tout autant de chances de trouver un objet ciblé en parcourant directement la liste des contrôles. Je suppose que si une structure de données arborescente était annotée pendant un décentrement et pouvait être décorée lors de clics sur des événements, il pourrait y avoir une certaine optimisation au prix de l'espace.

0 votes

Cela pose un problème si vous testez votre UserControl pour qu'il ait le focus. Ce n'est pas le cas. C'est l'un des contrôles sans conteneur qui se trouve à l'intérieur qui aura le focus. Si vous voulez tester si un UserControl a le focus, vous pouvez étendre cette méthode pour inclure l'UC que vous testez au fur et à mesure qu'il descend dans le conteneur.

27voto

Xn0vv3r Points 7778

Après avoir fait des recherches sur Internet, j'ai trouvé ce qui suit sur FAQ sur les formulaires Windows de George Shepherd

Les bibliothèques du cadre .Net ne fournissent pas d'API permettant de rechercher le contrôle ciblé. Vous devez invoquer une API Windows pour le faire :

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

1 votes

L'utilisation d'un appel interop est elle préférable à l'utilisation d'une fonctionnalité intégrée comme la méthode ActiveControl ? Quelles sont les ramifications d'une telle solution en matière de sécurité d'accès au code ?

0 votes

Il y a même des problèmes dans l'API de Vista qui font tout simplement avec .NET. Par exemple, essayez de changer la couleur d'une barre de progression sous Vista. Il n'y a que 3 couleurs disponibles (états : Erreur, Avertissement, Défaut) et vous ne pouvez les changer qu'avec des appels API. Ou l'effet de verre...

0 votes

J'ai essayé le code ci-dessus mais je n'arrive pas à trouver la méthode "GetFocus()" à la ligne 10.

23voto

BillW Points 2303

ActiveControl sur un formulaire ou un conteneur sera renvoie le contrôle actif de cette entité, quelle que soit la profondeur de son imbrication dans d'autres conteneurs.

Dans votre exemple, si le TextBox a le Focus : alors : pour le Form, le TableLayoutPanel et le FlowLayoutPanel : la propriété 'ActiveControl de tous sera la TextBox !

Certains, mais pas tous les "véritables" types de ContainerControl ... comme Form et UserControl ... exposent des événements clés (dans le cas de Form : ils ne peuvent être utilisés que si Form.KeyPreview == true) .

D'autres contrôles qui, par conception, contiennent d'autres contrôles comme TableLayOutPanel, GroupBox, Panel, FlowLayoutPanel, etc. no de type ContainerControl, et ils n'exposent pas les KeyEvents.

Toute tentative de lancer instances d'objets comme TextBox, FlowLayoutPanel, TableLayoutPanel directement à ContainerControl ne compileront pas : ils ne sont pas de type ContainerControl.

Le code dans la réponse acceptée, et dans la réponse suivante qui corrige les fautes d'orthographe de la première réponse, compilera/acceptera les instances des éléments ci-dessus en tant que paramètres parce que vous les "downcasting" au type 'Control en faisant du paramètre le type 'Control

Mais dans chaque cas, le cast vers ControlContainer retournera null, et l'instance passée sera retournée (downcasted) : essentiellement un no-op.

Et, oui, le code de réponse modifié fonctionnera si vous lui passez un "véritable" ControlContainer, comme une instance de Form, qui se trouve dans le chemin d'héritage parent de l'ActiveControl, mais vous perdez toujours du temps à dupliquer la fonction de 'ActiveControl'.

Qu'est-ce qu'un "vrai" ContainerControls : vérifiez-le : Documentation MS pour ContainerControl

Seule la réponse de Peter répond réellement à la question explicite, mais cette réponse implique l'utilisation de l'interopérabilité, et 'ActiveControl vous donnera ce dont vous avez besoin.

Notez également que chaque contrôle (conteneur ou non-conteneur) possède une collection de contrôles qui n'est jamais nulle, et que beaucoup (je ne les ai jamais tous essayés : pourquoi le ferais-je ?) des contrôles WinForms de base vous permettent de faire des "trucs fous" comme ajouter des contrôles à la collection de contrôles de contrôles "simples" comme les boutons sans erreur.

Maintenant, si le véritable intention de votre question était de savoir comment trouver le ContainerControl le plus à l'extérieur. ... qui n'est pas sur le formulaire lui-même ... d'un non-conteneur régulier Contrôle imbriqué à quelques niveaux arbitraires de profondeur ... vous pouvez utiliser certains des idées dans la réponse : mais le code peut être grandement simplifié.

Les contrôles réguliers, les ContainerControls, les UserControls, etc. (mais pas les Form !) ont tous une propriété 'Container à laquelle vous pouvez accéder pour obtenir leur conteneur immédiat, mais s'assurer que vous avez le 'conteneur final dans leur chemin d'héritage qui n'est pas un Form nécessite du code pour "remonter" l'arbre d'héritage, ce qui est démontré ici.

Vous pouvez également consulter la propriété 'HasChildren' de 'Control' qui est généralement utile pour traiter les questions de Focus, ActiveControl et Select dans WinForms. L'examen de la différence entre Select et Focus peut être utile ici, et SO a quelques bonnes ressources à ce sujet.

J'espère que cela vous aidera.

1 votes

Merci pour votre réponse. +1 pour votre recherche et la réflexion que vous y avez mise. J'ai vérifié que WinForms renvoie bien le contrôle focalisé de la feuille, et pas seulement le conteneur immédiat contenant le contrôle focalisé. Cependant, je ne peux pas le marquer comme la réponse choisie parce que lorsque j'ai écrit la question, ce n'était certainement PAS le comportement que j'observais (la raison entière de cette question était que ActveControl ne se comportait PAS comme prévu). Le comportement incorrect doit avoir été corrigé dans l'un des Service Packs .NET, mais cela signifie également qu'il existe des versions de .NET qui se comportent de manière incorrecte.

0 votes

Les solutions récursives et non récursives proposées ici sont toujours valables pour le comportement actuel de WinForms, mais elles ont l'avantage supplémentaire de corriger le comportement invalide si nécessaire.

0 votes

Bonjour Milton, Honnêtement, il m'importe peu que ma réponse soit "l'élue" ou non ; ce qui m'importe, c'est l'exactitude technique de la question et des réponses. Bien sûr, je ne remets pas en question votre expérience telle qu'elle est présentée ! Tout ce que je peux tirer de la question et des réponses, c'est l'hypothèse qu'il y a pu y avoir une anomalie avec TableLayoutPanel à un certain moment dans le temps. best,

9voto

Nate Cook Points 1472

La solution de Hinek fonctionne bien pour moi, sauf qu'elle est ContainerControl et non pas ControlContainer. (Juste au cas où vous vous seriez gratté la tête à propos de cette ligne rouge en forme de vague).

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

2voto

sliderhouserules Points 2693

Si vous suivez ActiveControl de manière récursive, cela ne vous amène pas au contrôle de la feuille qui a le focus ?

1 votes

C'est ce que fait la fonction "FindFocusedControl" dans ma question, hein ? ;-)

0 votes

Ah oui. :) C'est ça ou la méthode de Hinek. J'ai dû faire cela dans une application Smart Client il y a quelques années.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X