The taskbar has noticeably evolved since Windows 95, with a great lap coming with Windows 7, the latest adding numerous features like Aero Peek, Taskbar Jumplists, Thumbnail Buttons and the embedded progressbar. Implementing this panel of functionnalities into software like MusicPeek is pretty useful, but why not going further?

I had a vision of an audio spectrum anchored to the MusicPeek button, following it through the taskbar. But how to make it real? Having played with Spy++ in the aim to disassemble the taskbar, I then noticed the buttons weren't regular children windows. It was time to do some research.

Picture, no control

This 2011 article taught me we can't get any taskbar buttons tree using FindWindow because they just are generated images printed on the MSTaskListWClass window, child of ReBarWindow32, nested itself into Shell_TrayWnd, with the Desktop as top matryoshka. The article suggests, providing code, to cleverly locate the buttons using the light border wrapping each Windows 7 taskbar button (meaning that with the border missing from the flat designed Windows 10 buttons, we'd have to detect the bottom border). While I was going to start this ambitious project in C# (the article code being Qt/C++) I noticed the article's first and only comment, suggesting to better use the Microsoft MSAA API (Microsoft Active Accessibility), allowing us to get the taskbar buttons tree, along with their respective coordinates. To do so, we have to get the dependencies provided into the AccChecker_v2.0_x86 archive.

I download the archive, create a new C# .NET 4.5 console application, add AccCheck.dll as reference, and let's go! According to the tree sample provided into the package, each descendant of a handle can be obtained under the Accessible UI object, with attributes like .Name, .Handle, .State, .ChildId and... .Location (as Rectangle type)!

Time to do some code:

First, the 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); 

We get our hands on MSTaskSwWClass and proceed to open the "russian dolls" one by one:

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

what's the path to list taskbar buttons? These posts guide us on the right track:

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

Success! Now we have the taskbar buttons list, we can obtain their positions! To facilitate targeting of a specific process, why not creating a function to get coordinates by window title?

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

We can now easily track an application and its location through the taskbar, in order to stick it extra animations or informations =)

    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;   

Don't forget to consider the taskbar position for an optimal hook

Download

AccCheck.dll

TBButtonHooker.cs

Previous Post