To avoid any confusion, let me say that this article will talk about the taskbar buttons that represent the visible windows of currently running applications. I will not discuss the system tray or Quick Launch. Most information given here is applicable only to Windows XP and possibly newer versions of Windows (though I am not able to verify that). Windows 2000 has a different taskbar structure, and probably so do other – older – versions.

Note : most functions that access the toolbar will expect you to provide the data/buffers from the adress space of explorer.exe process. So you will need to either use WriteProcessMemory() & ReadProcessMemory() or inject you DLL into the target process. I will assume the latter, as it will notably simplify the task. The former approach is described in this article on CodeProject, which served as basis and inspiration for this post. I included a simple DLL-injection example in my Hiding from NT Task Manager article.

The toolbar buttons can be either visible or hidden. This mostly has to do with groups, which I’ll discuss later. For now, lets look at some basic functions and structures that you’ll need to work with these buttons.

This will fill the aButton structure with some information about the button at position ButtonIndex. Indexes are zero-based. SendMessage will return zero if there is no button with that index.

The TBButton structure
This structure is described quite well in MSDN and SDK helpfiles, so I’ll focus on parts that are relevant to this particular case.

TTBBUTTON is a record-type structure that contains the following elements :

iBitmap: Integer;
A zero-based index of a bitmap in an image list associted with the toolbar. I will discuss this in more detail in Icons, below.

idCommand: Integer;
A zero-based command identifier for the button. Explorer reuses old identifiers, so if you wish to assign your own identifier to a button, start with a large number to avoid conflicts (1000 should be fine). All buttons should have unique identifiers.

fsState: Byte;
A collection of flags that describes the button’s state. All buttons have the TBSTATE_ENABLED flag. Hidden buttons also have the TBSTATE_HIDDEN flag. Visible buttons commonly have a TBSTATE_WRAP flag, and sometimes TBSTATE_ELLIPSES (indicates that button text is too long and the last part is replaced by “…”).

fsStyle: Byte;
A collection of flags describing the style of button. All buttons have BTNS_NOPREFIX and BTNS_CHECK flags, meaning that they can be in “checked” and “unchecked” states. A button representing the currently active window has the TBSTATE_CHECKED state flag. Buttons that function as groups have BTNS_DROPDOWN and BTNS_WHOLEDROPDOWN flags, meaning that they display a dropdown menu when clicked.

bReserved: array[1..2] of Byte;
As far as I know, this isn’t used.

iString: Integer;
A pointer to an Unicode string that represents the button title. See below for a more convienent ways of retrieving and setting the title.

dwData: Longint;
MSDN defines this as user-defined data. In this particular case it is a pointer to a structure described in the next paragraph…

The undocumented data structure
I used Delphi built-in debugger to look at where dwData was pointing and this is what I could decipher : dwData points to an undocumented array of seven dword/cardinal values. The values are such :

Data[0] is the handle of a window associated with that button. This is always zero for groups.

Data[1] is $40 for the button and the (in)visible group button of an active window. It is zero for all other buttons.

Data[2] looks like a pointer to a structure of approximately 32 bytes. I didn’t find out what it was. Curiously the third dword of that structure is equal to Data[4]…

Data[3] – unknown. Not a pointer. May change during the existance of the button.

Data[4] – unknown. Often equal to Data[3].

Data[5] For groups it is a pointer to an Unicode string that contains the executable path of application that the grouped windows belong to. For example, if you have a group of Notepad windows, this will point to “C:\WINDOWS\NOTEPAD.EXE”. This element is always zero for all buttons that are not window groups.

Data[6] appears to be a flag of some kind. It is zero for invisible groups, usually 3 for visible groups, 0, 5 or 8 for other buttons. It appears to be related to the type of application.

However, if it is a single button, you must also move the button representing its group. If the button is a group, you must also move the invisible buttons representing the contents of the window group. You have to be very careful not to mess up the ordering of buttons, or some of them might disappear, become incorrectly grouped etc. See Groups below.

Setting a new icon
To set a new icon for a button, you will need to add this icon to the Image List that is associated with the toolbar (which will give you the index of the newly added image) and update the button information by a TB_SETBUTTONINFO call. MSDN tells us to use TB_ADDBITMAP to add a new bitmap, but that didn’t seem to work correctly for me (the icon gets added, but the index is wrong). So instead I used TB_GETIMAGELIST to retrieve the handle of the image list and added the icon with ImageList_Add. This worked okay.

Retrieving an icon
I expect you could do it by retrieving the ImageList handle as show above and using ImageList_GetIcon(). Another way is to use the window handle (see Data[0] above) to get the icon associated with the window, but that doesn’t always work. For example :

Buttons with style BTNS_DROPDOWN represent groups. They also don’t have an associated window handle. They can be visible or invisible. These group buttons are created regardless of whether window grouping is on of off. The overall taskbar structure is like this :

Gray shapes represent invisible elements, green – visible buttons.

If window grouping is turned off, there is a separate invisible “group button” + a visible button for every top-level window. If grouping is on, there may be more than one button in a group (a “group” in this context is a button with BTNS_DROPDOWN style, followed by one or more buttons without that flag). Up until a given treshold these simple buttons are visible and the group button – invisible (this treshold is stored in registry at HKEY_CURRENT_USER \Software\ Microsoft\ Windows\ CurrentVersion\ Explorer\ Advanced\ TaskbarGroupSize). When there are TaskbarGroupSize windows of the same kind (belonging to the same application), they are grouped – the group button is made visible and the simple buttons following it are hidden. Apparently Explorer also somehow retrieves an icon suitable for the group at that moment, as invisible groups don’t initially have icons assigned.

To programmatically create a group you need to send a TB_ADDBUTTONS message (see MSDN for details), creating a button with the apropriate style, state and other values. You must also initialize the dwData member and the structure it points to. Then you can make this new button visible (use the TB_HIDEBUTTON message), move some simple buttons behind it and hide them to create a group. When doing this you must consider a lot of special cases, so that no buttons end up in wrong groups or states.

To ungroup a group of windows you can unhide all the simple buttons it contains and create a new, invisible group button (you could copy the original group button) right before each of them, so that every button has its own group.

Sometimes, if you do something that changes the number of visible buttons (hide some, create a new button, etc) the size of new buttons will be wrong. Explorer uses a nifty (and – obviously – undocumented) algorithm to resize the buttons, but this algorithm doesn’t get triggered when your application/DLL manipulates the taskbar. You can trigger by making Explorer think that the taskbar has been moved :

if punkts1.X<0 then punkts1.X:=0;
if punkts1.y<0 then punkts1.y:=0;
//set it to exactly the same position and size
SetWindowPos(aBar, HWND_TOP,
punkts1.X, punkts1.Y,
abs(aRect.Right – aRect.Left),
abs(arect.Bottom – aRect.Top),
SWP_NOOWNERZORDER or SWP_FRAMECHANGED); //these flags are important!
end;

This entry was posted on Sunday, October 1st, 2006 at 15:56 and is filed under Miscellany.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.

Ugh, I hoped something like that wouldn’t happen. This is most likely a false positive. If you really want to delete the file (though I’m sure it’s completely harmless) you should uncheck the “Enable…” checkbox under “Options” and close the program (I think that should work).

madCHook.dll sometimes gets falsely identified as a virus/trojan, because it is used for API hooking/DLL injection – techniques that some viruses use, too.

Hi. I am having a hard time getting the icon. It seems wm_geticon does not ever work, and GetClassLong(wHnd, GCL.HICONSM); or GetClassLong(wHnd, GCL.HICON); does. But I noticed in a rare case (remote desktops input capture window) does not get the icon and it crashes. I tried every method you suggested but I cant seem to get the information I need. I tried using ImageList_GetIcon but it always returns 0. I was giving it a valid handle from TB_GETIMAGELIST. I then tried to use ExtractIcon but I was unable to get the path to the executable from data[5]. I tried to read all the bytes from dwdata but the only readable text I found was part of the buttons name. I’m probably doing something wrong so I was wondering if you could help. Btw I am using c# and the readprocessmemory method of code injection.

My coworker found this while trying to prove me wrong about MS’ documentation on this subject. He succeeded in proving me wrong about the point I was trying to make, but at the same time proved the larger issue of MS documentation sucking right =P

This is the dwData structure in the TTBButtonInfo when talking about the taskbar, it says user defined because it really is…user defined. It can be any type of structure but by default it is an NMTOOLBAR for explorer.exe’s taskbar.

As a sidenote, please update this guide. It has been invalueable for me in writing an app I’m working on right now, and I’m sure it will be for anyone else.

Hi, I was wondering how can u succeed 2 set a new ICON as u said, I ‘ve tried 2 do that use the same CODE, however, I failed! OR if u did this by “Inject dll”? or what else? Can u pls kindly give mi some more suggestions? 3Q!

Search

This site uses cookies to improve your experience, to personalize ads and to analyze traffic. It also shares information about your use of this site with social media, advertising and analytics partners. By using this site, you agree to its use of cookies. AcceptSee Details