50 votes

Windows : comment obtenir une liste de toutes les fenêtres visibles ?

(en tout cas, ré-étiquettez avec la technologie appropriée : Je ne sais pas lesquelles :)

Je reviendrai probablement plus tard avec des questions plus précises, sur des détails spécifiques, mais pour l'instant, j'essaie de saisir la "vue d'ensemble" : Je cherche un moyen d'énumérer les "fenêtres réellement visibles" sur Windows. Par "fenêtre visible réelle", j'entends ce qu'un utilisateur appelle une "fenêtre". Je cherche un moyen d'obtenir une liste de toutes ces fenêtres visibles, dans l'ordre Z.

Notez que I faire Il faut vraiment le faire. Je l'ai déjà fait sous OS X (où c'est un vrai casse-tête à faire, surtout si vous voulez prendre en charge OS X 10.4, car OS X n'a pas d'API Windows pratique) et maintenant je dois le faire sous Windows.

Voici un exemple, supposons qu'il y ait trois fenêtres visibles à l'écran, comme ceci :

 +------------------------------------------+
 |                                          |
 |           +=============+                |
 |           |             |                |
 |           |    A   +--------------------------+
 |           |        |                          |
 |    C      |        |             B            |
 |           |        +--------------------------+
 |           |             |                |
 +-----------|             |----------------+
             |             |
             +-------------+

Alors je dois récupérer une liste comme celle-ci :

 windows B is at (210,40)
 windows A is at (120,20)
 windows C is at (0,0)

Ensuite, si l'utilisateur (ou le système d'exploitation) met la fenêtre A à l'avant, cela devient :

 +------------------------------------------+
 |                                          |
 |           +=============+                |
 |           |             |                |
 |           |    A        |---------------------+
 |           |             |                     |
 |    C      |             |        B            |
 |           |             |---------------------+
 |           |             |                |
 +-----------|             |----------------+
             |             |
             +-------------+

Et je reçois (idéalement) un rappel me donnant ceci :

windows A is at (120,20)
windows B is at (210,40)
windows C is at (0,0)

Faire cela sous OS X nécessite l'utilisation de bidouillages étonnamment bizarres (comme demander à l'utilisateur d'allumer la fonction "Activer l'accès pour les appareils fonctionnels" !) mais je l'ai fait sous OS X et ça marche (sous OS X, je n'ai pas réussi à obtenir un callback à chaque fois qu'un changement de fenêtre se produit, donc je sonde, mais j'ai réussi à le faire fonctionner).

Maintenant, je veux faire cela sous Windows (je le veux vraiment, sans aucun doute) et j'ai quelques questions :

  • Est-ce possible ?

  • Existe-t-il des API Windows bien documentées (et fonctionnant conformément à leurs spécifications) permettant de faire cela ?

  • est-il facile d'enregistrer un callback chaque fois qu'une fenêtre change ? (si elle est redimensionnée, déplacée, mise en avant ou en arrière, ou si une nouvelle fenêtre apparaît, etc.)

  • quels seraient les écueils ?

Je sais que cette question n'est pas spécifique, c'est pourquoi j'ai essayé de décrire mon problème aussi clairement que possible (y compris une belle illustration ASCII pour laquelle vous pouvez voter en faveur de cette question) : pour l'instant, je cherche à avoir une vue d'ensemble. Je veux savoir ce que cela implique de faire une telle chose sous Windows.

Question bonus : imaginez que vous ayez besoin d'écrire un minuscule .exe écrire le nom/la position/la taille de Windows dans un fichier temporaire chaque fois qu'il y a un changement de fenêtre à l'écran, combien de temps un tel programme serait-il approximativement dans la langue de votre choix et combien de temps vous faudrait-il pour l'écrire ?

(une fois encore, j'essaie d'avoir une vue d'ensemble pour comprendre ce qui se passe ici).

0 votes

Personne ? Déjà +1 upvote et 1 favori... :)

0 votes

Je pense que vous devriez commencer par FindWindowEx pour énumérer toutes les fenêtres dans l'ordre Z, puis utiliser GetWindowLong pour obtenir le style de la fenêtre. Évidemment, vous ne tiendriez compte que des fenêtres avec WS_VISIBLE, et peut-être WS_CAPTION ou autre.

0 votes

.net framework est-il une option ? J'ai une solution simple pour ça, ça prend 10 secondes.

30voto

mdma Points 33973

Pour énumérer les fenêtres de premier niveau, vous devez utiliser EnumWindows plutôt que GetTopWindow/GetNextWindow, puisque EnumWindows renvoie une vue cohérente de l'état des fenêtres. Avec GetTopWindow/GetNextWindow, vous risquez d'obtenir des informations incohérentes (telles que les rapports sur les fenêtres supprimées) ou des boucles infinies, lorsque les fenêtres changent d'ordre z pendant l'itération.

Le EnumWindows utilise un callback. A chaque appel de la callback, vous obtenez un handle de fenêtre. Les coordonnées de l'écran de la fenêtre peuvent être récupérées en passant ce handle à la fonction GetWindowRect . Votre callback construit une liste des positions de la fenêtre dans l'ordre z.

Vous pouvez utiliser l'interrogation, et construire la liste des fenêtres de manière répétée. Vous pouvez également configurer un CBTHook pour recevoir les notifications de changement de fenêtre. Toutes les notifications CBT ne se traduiront pas par des modifications de l'ordre, de la position ou de la visibilité des fenêtres de niveau supérieur, il est donc judicieux de relancer EnmWindows pour construire une nouvelle liste de positions de fenêtres dans l'ordre z et de la comparer à la liste précédente avant de poursuivre le traitement de la liste, de sorte que le traitement ultérieur ne soit effectué que lorsqu'un véritable changement s'est produit.

Notez qu'avec le hooking, vous ne pouvez pas mélanger 32 et 64 bits. Si vous exécutez une application 32 bits, vous recevrez des notifications de processus 32 bits. De même pour les processus 64 bits. Ainsi, si vous voulez surveiller l'ensemble du système sur une machine 64 bits, il semblerait qu'il soit nécessaire d'exécuter deux applications. Mon raisonnement vient de la lecture de ceci :

SetWindowsHookEx peut être utilisé pour injecter une DLL dans un autre processus. Une DLL 32 bits ne peut pas être injectée dans un processus 64-bit 64 bits, et une DLL 64 bits ne peut pas être être injectée dans un processus 32 bits. Si une application nécessite l'utilisation de crochets dans d'autres processus, il est nécessaire qu'une application 32 bits appelle SetWindowsHookEx pour injecter une DLL dans les processus 32 bits, et qu'une application 64 bits appelle SetWindowsHookEx pour injecter une DLL 64-bit DLL 64 bits dans des processus 64 bits. Les DLL 32 bits et 64 bits doivent avoir des noms noms différents. (Extrait de la page de l'api SetWindowsHookEx).

Comme vous mettez en œuvre ce système en Java, vous pouvez regarder dans JNA - il rend l'écriture de l'accès aux bibliothèques natives beaucoup plus simple (appel de code en java) et supprime la nécessité de votre propre DLL JNI native.

EDIT : Vous avez demandé combien de code il faut écrire et combien de temps. Voici le code en java

import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.win32.StdCallLibrary;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        Main m = new Main();
        final List<WindowInfo> inflList = new ArrayList<WindowInfo>();
        final List<Integer> order = new ArrayList<Integer>();
        int top = User32.instance.GetTopWindow(0);
        while (top != 0) {
            order.add(top);
            top = User32.instance.GetWindow(top, User32.GW_HWNDNEXT);
        }

        User32.instance.EnumWindows(new WndEnumProc() {
            public boolean callback(int hWnd, int lParam) {
                if (User32.instance.IsWindowVisible(hWnd)) {
                    RECT r = new RECT();
                    User32.instance.GetWindowRect(hWnd, r);
                    if (r.left > -32000) {     // If it's not minimized
                        byte[] buffer = new byte[1024];
                        User32.instance.GetWindowTextA(hWnd, buffer, buffer.length);
                        String title = Native.toString(buffer);
                        inflList.add(new WindowInfo(hWnd, r, title));
                    }
                }
                return true;
            }
        }, 0);

        Collections.sort(inflList, new Comparator<WindowInfo>() {
            public int compare(WindowInfo o1, WindowInfo o2) {
                return order.indexOf(o1.hwnd)-order.indexOf(o2.hwnd);
            }
        });
        for (WindowInfo w : inflList) {
            System.out.println(w);
        }
    }

    public static interface WndEnumProc extends StdCallLibrary.StdCallCallback {
        boolean callback(int hWnd, int lParam);
    }

    public static interface User32 extends StdCallLibrary {
        final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
        final int GW_HWNDNEXT = 2;

        boolean EnumWindows(WndEnumProc wndenumproc, int lParam);
        boolean IsWindowVisible(int hWnd);
        int GetWindowRect(int hWnd, RECT r);
        void GetWindowTextA(int hWnd, byte[] buffer, int buflen);
        int GetTopWindow(int hWnd);
        int GetWindow(int hWnd, int flag);
    }

    public static class RECT extends Structure {
        public int left, top, right, bottom;
    }

    public static class WindowInfo {
        public final int hwnd;
        public final RECT rect;
        public final String title;
        public WindowInfo(int hwnd, RECT rect, String title) {
            this.hwnd = hwnd;
            this.rect = rect;
            this.title = title;
        }

        public String toString() {
            return String.format("(%d,%d)-(%d,%d) : \"%s\"",
                rect.left, rect.top,
                rect.right, rect.bottom,
                title);
        }
    }
}

J'ai fait de la plupart des classes et interfaces liées des classes internes afin de garder l'exemple compact et collable pour une compilation immédiate. Dans une implémentation réelle, il s'agirait de classes de premier niveau ordinaires. L'application en ligne de commande affiche les fenêtres visibles et leur position. Je l'ai exécutée sur jvm 32-bit et 64-bit, et j'ai obtenu les mêmes résultats pour chacun.

EDIT2 : Mise à jour du code pour inclure l'ordre z. Il utilise GetNextWindow. Dans une application de production, vous devriez probablement appeler GetNextWindow deux fois pour les valeurs suivantes et précédentes et vérifier qu'elles sont cohérentes et que ce sont des poignées de fenêtre valides.

0 votes

Juste pour être sûr, si je fais simplement un sondage, alors je peux utiliser EnumWindows et il n'y a pas de problème de 32-/64-bit ? JNA était le plan (ils avaient un exemple énumérant les fenêtres sur Windows btw, je dois juste le retrouver) mais d'abord je voulais savoir ce qui était impliqué "sous le capot". Je ne sais toujours pas si j'aurai les compétences pour le faire moi-même (je n'ai jamais fait de programmation Windows) mais toutes ces réponses m'aident grandement à comprendre ce que cela implique !

0 votes

@NoozNooz42 - C'est exact, l'interrogation évite l'utilisation de crochets et le problème des 32/64 bits. Voici un article qui appelle EnumWindows depuis JNA. javajeff.mb.ca/cgi-bin/mp.cgi?/java/javase/articles/mjnae

0 votes

@mdna : Le problème avec EnumWindows est qu'il ne renvoie pas les fenêtres dans l'ordre Z. (ou du moins pas selon la documentation). (Ou du moins ne le fait pas selon la documentation).

7voto

Billy ONeal Points 50631

Est-ce possible ?

Oui, bien que vous deviez enregistrer une carte de crédit. crochet afin d'obtenir ce que vous voulez en matière de rappel. Vous aurez probablement besoin d'utiliser un Crochet de rappel CBTProc qui est appelé à chaque fois :

avant d'activer, de créer, de détruire, de minimiser, de maximiser, de déplacer ou de dimensionner une fenêtre ; avant de terminer une commande système ; avant de retirer un événement de souris ou de clavier de la file d'attente des messages système ; avant de mettre le focus sur le clavier ; ou avant de se synchroniser avec la file d'attente des messages système.

Notez cependant que je ne crois pas que de tels crochets fonctionnent sur Console Windows car ils sont du domaine du noyau, pas de Win32.

Existe-t-il des API Windows bien documentées (et fonctionnant conformément à leurs spécifications) permettant de faire cela ?

Oui. Vous pouvez utiliser le GetTopWindow y GetNextWindow pour obtenir toutes les poignées de fenêtre sur le bureau dans l'ordre Z correct.

est-il facile d'enregistrer un callback à chaque fois qu'une fenêtre change ? (si elle est redimensionnée, déplacée, mise en avant ou en arrière, ou si une nouvelle fenêtre apparaît, etc.)

Voir la première réponse :)

quels seraient les écueils ?

Voir la première réponse :)

Question bonus : imaginez que vous ayez besoin d'écrire un petit .exe écrivant les noms/positions/tailles de Windows dans un fichier temporaire chaque fois qu'il y a un changement de fenêtre à l'écran, quelle serait la longueur approximative d'un tel programme dans la langue de votre choix et combien de temps vous faudrait-il pour l'écrire ?

Quelques centaines de lignes de C, et quelques heures. Bien que je doive utiliser une certaine forme d'interrogation - je n'ai jamais fait de crochets avant moi-même. Si j'avais besoin des hooks, ça prendrait un peu plus de temps.

0 votes

Merci beaucoup pour cette réponse... Donc si je comprends bien, soit vous faites du polling (c'est ce que je fais sous OS X) et alors pas de hooks et pas de callback... OU vous utiliseriez des hooks, permettant d'avoir un callback ? (Encore une fois, sous OS X, il n'y a pas de moyen facile de faire cela en utilisant un callback, c'est pourquoi je pose des questions).

1 votes

@Nooz : Un crochet est un rappel.

1 votes

@Nooz : En d'autres termes, oui. Vous pouvez soit sonder, soit utiliser le crochet comme callback. Le crochet est meilleur mais j'estimais juste le temps que cela prendrait car je n'ai jamais utilisé de crochets auparavant.

1voto

Miro A. Points 1679

Je me souviens qu'en 2006, il y avait un utilitaire WinObj faisant partie de sysinternals qui faisait peut-être ce que vous voulez. Une partie de ces utilitaires étaient fournis avec le code source par l'auteur (Mark Russinovich).

Depuis, sa société a été rachetée par Microsoft et je ne sais donc pas si la source serait encore disponible.

Les points suivants méritent également d'être vérifiés :

http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx

http://www.codeproject.com/KB/dialog/windowfinder.aspx

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