|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This tool only works on Windows XP and later!
IntroductionThis tool allows you to alter the order of the window buttons in your taskbar. BackgroundI normally have about 20 windows open, and switching between them is a lot quicker if they are in my "normal" order. For instance, I always like Outlook to be the first button in my taskbar, so I know where to click when I get mail. The problem is that when Outlook hangs, I have to kill its process. Then when I restart it, it appears at the end of the list, and is soon hidden among my other windows. With this tool, I can easily move Outlook back to the start where it belongs, without closing and re-opening all my other windows. This problem is a pet peeve of mine - if the order of your windows doesn't bother you, then you don't need this tool :) How it worksThis tool uses a lot of PInvoke calls - if you want to see the details, look in the "Common/Kernel32.cs" and "Common/User32.cs" files in the source. It also uses an The basic idea is to get a list of the windows in the taskbar, show them and allow them to be reordered, and then apply the new ordering. Sounds easy :) Getting a list of the windowsThis proved to be the hardest part. Fortunately, I found a way to do it, but it only works on Windows XP and later. Windows 2000 uses a different window hierarchy, and I don't know (or care :) about earlier OSs. Getting the buttons window handleUsing Spy++, I found that the buttons window in the taskbar is actually a IntPtr hDesktop = User32.GetDesktopWindow();
IntPtr hTray = User32.FindWindowEx( hDesktop , 0, "Shell_TrayWnd" , null );
IntPtr hReBar = User32.FindWindowEx( hTray , 0, "ReBarWindow32" , null );
IntPtr hTask = User32.FindWindowEx( hReBar , 0, "MSTaskSwWClass" , null );
IntPtr hToolbar = User32.FindWindowEx( hTask , 0, "ToolbarWindow32" , null );
This just walks down the window hierarchy from the desktop window. We now have a handle on the Getting the number of buttonsThis is easy, as we know about the UInt32 count = User32.SendMessage( _ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0 );
Note that the window count is more than we would expect. See Groups for an explanation. Getting each button's infoThis also should be easy, because we know about the TBBUTTON tbButton; TBBUTTON* ipRemoteBuffer = & tbButton; // unsafe for ( int i = 0 ; i < count ; i++ ) { User32.SendMessage( hToolbar, TB.GETBUTTON, ( IntPtr ) i, ipRemoteBuffer ); } Note that this is a pseudo-code - it doesn't compile, it just gives you an idea of the algorithm. We now have a Getting the button textThe int chars = ( int ) User32.SendMessage(
hToolbar,
TB.GETBUTTONTEXTW,
( IntPtr ) tbButton.idCommand,
ipRemoteBuffer );
Note that this message takes a Getting the window handleThis was the really lucky part. I thought to myself: "Where would I keep the window handle?". They must keep it somewhere, to enable the activation of the correct window when a button is selected. The obvious place to keep it would be in a structure for each button, and the obvious place to keep the pointer to this structure would be in the So I had a look at the Using the window handleThe rest is easy. I used Thomas Caudal [^] 's TreeListView [^] which made the UI easy. A bit of glue to handle moving the items around, and then the "Apply" button. All we have to do to apply the new order is to hide all the windows using Points of interestA few things could do with a bit more explanation. GroupsIn XP and later, you have the option of grouping similar taskbar buttons together. This tool works with this switched on or off. In either case, Explorer adds a dummy button, which is hidden, at the start of each group. This is why Out of interest, the behaviour is controlled by a couple of registry entries:
Cross-process memory accessThis was a tricky one. Some of the required Win32 functions use structures to move information around. This works well if you and your target are in the same process space, but falls apart in our case. The naive implementation doesn't work. If you declare a structure, and get a pointer to it to pass into your function, the pointer is to some address in your virtual memory space. The function can't know this, and thinks that it is pointing to a block of its virtual memory. This can only lead to General Protection Faults at best. The solution is to allocate space for your structure in Explorer's virtual memory, call the function, and then copy the data back into your virtual memory so you can use it. This works without elevated privileges because Explorer runs under the local user account. As we use a few functions one after the other, I just allocate a page of memory, and use this for all the structures. The basic algorithm is this:
I will just show one call here, the others are similar: private unsafe bool GetTBButton(
IntPtr hToolbar,
int i, // button id
ref TBBUTTON tbButton,
... )
{
// One page
const int BUFFER_SIZE = 0x1000;
byte[] localBuffer = new byte[ BUFFER_SIZE ];
UInt32 processId = 0;
UInt32 threadId =
User32.GetWindowThreadProcessId(
hToolbar,
out processId );
IntPtr hProcess =
Kernel32.OpenProcess(
ProcessRights.ALL_ACCESS,
false,
processId );
if ( hProcess == IntPtr.Zero ) return false;
IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
hProcess,
IntPtr.Zero,
new UIntPtr( BUFFER_SIZE ),
MemAllocationType.COMMIT,
MemoryProtection.PAGE_READWRITE );
if ( ipRemoteBuffer == IntPtr.Zero ) return false;
// TBButton
fixed ( TBBUTTON* pTBButton = & tbButton )
{
IntPtr ipTBButton = new IntPtr( pTBButton );
int b = ( int ) User32.SendMessage(
hToolbar,
TB.GETBUTTON,
( IntPtr ) i,
ipRemoteBuffer );
if ( b == 0 ) { Debug.Assert( false ); return false; }
Int32 dwBytesRead = 0;
IntPtr ipBytesRead = new IntPtr( & dwBytesRead );
bool b2 = Kernel32.ReadProcessMemory(
hProcess,
ipRemoteBuffer,
ipTBButton,
new UIntPtr( ( uint ) sizeof( TBBUTTON ) ),
ipBytesRead );
if ( ! b2 ) { Debug.Assert( false ); return false; }
}
...
Kernel32.VirtualFreeEx(
hProcess,
ipRemoteBuffer,
UIntPtr.Zero,
MemAllocationType.RELEASE );
Kernel32.CloseHandle( hProcess );
return true;
}
The IconsThis is just a bit of fluff. The Shameless plugsI used the code from a couple of my previous articles in this project. I used my OSVersion [^] class to check that we're running on XP or later. And I used my TreeCollection [^] as a backing store. I also used the ConclusionWell, that's about it. If you just want to use this tool, that's fine - I hope you find it useful. If you want to dig a little deeper, then you can look at the code as an example of P/Invoke and cross-process memory access. I just want to mention the pinvoke.net [^] wiki site - very handy and mostly accurate :) History
|
||||||||||||||||||||||