La barre des tâches a bien évolué depuis Windows 95, et avec Windows 7 a fait un bond en avant avec l'ajout de nombreuses fonctionnalités comme l'Aero Peek, les Jumplists, les Thumbnail Buttons et la barre de progression intégrée. Intégrer l'ensemble de ces fonctionnalités dans des logiciels comme MusicPeek est fort utile, toutefois pourquoi ne pas aller plus loin?

J'ai eu la vision d'un spectre audio accroché au bouton de MusicPeek, suivant celui-ci au gré de ses déplacements dans la barre des tâches. Oui, mais comment faire? Ayant joué avec Spy++ afin de destructurer la barre des tâches, j'avais remarqué que les boutons n'étaient pas des fenêtres enfants ciblables. Il était temps de faire quelques recherches.

Des images, pas des contrôles

Cet article datant de 2011 m'apprit qu'il ne pouvait y avoir d'arborescence et d'obtention des boutons de la barre des tâches par FindWindow car les boutons ne sont au final que des images générées et apposées sur la fenêtre MSTaskListWClass, descendante de ReBarWindow32 elle même s'emboitant dans Shell_TrayWnd, et ayant pour poupée-russe mère le Bureau Windows. Il propose, code à l'appui, de localiser ingénieusement chaque bouton en se servant du liseré blanc bordant chaque bouton de Windows 7 (cela signifierait que sous Windows 10 nous devrions détecter la fine bordure du dessous de chaque bouton, l'interface graphique ayant été "flatdesignée"). Alors que j'allais me lancer dans cette vaste entreprise en C# (le code de l'article est en Qt/C++), je notais le premier et seul commentaire de l'article suggérant de se tourner plutôt vers l'API Microsoft MSAA (Microsoft Active Accessibility), qui permet d'obtenir l'arborescence des boutons de la barre des tâches, ainsi que leur coordonnées. Pour ce faire, il suffit de nous munir des dépendances fournies dans AccChecker_v2.0_x86 que l'on peut télécharger ici.

Je télécharge l'archive, crée une nouvelle application console C# .NET 4.5, ajoute AccCheck.dll en référence, et c'est parti! D'après l'exemple d'obtention d'arborescence fourni dans le package, chaque descendant d'un handle s'obtient sous forme d'objet UI Accessible ayant pour attributs .Name, .Handle, .State, .ChildId et... .Location (en type Rectangle)! Il est temps de passer au code:

Tout d'abord, on fait les imports:

        [DllImport("user32.dll", SetLastError = false)]
        static extern IntPtr GetDesktopWindow();
        [DllImport("user32.dll", CharSet=CharSet.Unicode)]
        static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle); 

On met le grappin sur MSTaskSwWClass en ouvrant les "poupées russes" les unes après les autres:

            IntPtr hDesktop = GetDesktopWindow();
            IntPtr hTray    = FindWindowEx( hDesktop , IntPtr.Zero, "Shell_TrayWnd"   , null );
            IntPtr hReBar   = FindWindowEx( hTray    , IntPtr.Zero, "ReBarWindow32"   , null );
            IntPtr hTask    = FindWindowEx( hReBar   , IntPtr.Zero, "MSTaskSwWClass"  , null );

que cibler pour lister les boutons de la barre des tâches? Ces posts nous mettent sur la bonne piste.

            //please adapt the part below to your locale
            string loc = CultureInfo.CurrentCulture.Name == "fr-FR" ? "applications en cours" : "running applications";

            Accessible root;
            Accessible.FromWindow(hTask, out root);      
            root.Children(out children);
            children.FirstOrDefault(c => c.Name.StartsWith(loc, StringComparison.OrdinalIgnoreCase)).Children(out children);
            children.FirstOrDefault(c => c.Name.StartsWith(loc, StringComparison.OrdinalIgnoreCase)).Children(out children);
            children.FirstOrDefault(c => c.Name.StartsWith(loc, StringComparison.OrdinalIgnoreCase)).Children(out children);

voilà, maintenant qu'on a la liste des boutons de la barre des tâches, nous pouvons obtenir leur position! Pour faciliter le ciblage d'une appli spécifique, pourquoi ne pas créer une fonction afin d'obtenir les coordonnées par titre de fenêtre?

        public Rectangle getLocation(string Text){
            try{
                return children.FirstOrDefault(c => {
                    try {
                        return c.Name.Equals(Text);
                    } catch {
                        //Handling COM exceptions    
                        return false;
                    }
                }).Location;
            }catch{ //can't find the damn button
                return new Rectangle();
            }
        }

ex.

    getLocation("KeePass 2") -> {X=120,Y=998,Width=62,Height=40};

Maintenant que nous voilà arrivés à nos fins, nous pouvons aisément suivre une application et son emplacement dans la barre des tâches, afin de lui accoler des animations ou informations supplémentaires =)

    Rectangle curLoc = getLocation(this.Text);
    Size curSize = curLoc.Size;
    Point destPos = curLoc.Location;
    destPos.Y -= curSize.Height; //we place a new form on top of the taskbar button

    Form f = new Form();
    f.Location = destPos;
    f.Size = curSize;   

N'oubliez pas de prendre en considération l'emplacement de la barre des tâches

Téléchargement de la classe

AccCheck.dll

TBButtonHooker.cs

Article précédent