Windows Explorer in XP: Now with Checkboxes and Fullrowselect






4.13/5 (9 votes)
Customizing XP with Vista features

Introduction
Windows Explorer is a great little program. Millions use it, few have any complaints. But unless you've moved to Vista, there are things you can't do in Windows Explorer:
- Checkboxes: you can't get a checkbox column to select files with. So you either select one file at a time, use a selection rectangle to "lasso" some adjoining files, or you Multi-Select. Which stops working when you accidentally lift the Ctrl finger and have to start all over again. Tedious.
-
Fullrowselect: you can't get the selection color to encompass all columns in the file list. Which would have been helpful if your selection criteria depends on, for instance, the file date.
With TrayProdder, these two features are no longer missing.
Background
The ListView control of the Win32 API is heavily used in any number of run-of-the-mill Windows applications. It is of course also used in Windows Explorer. And CheckBoxes and FullRowSelect have been ListView properties since time immemorial, as far as I know. So it irked me that none of them can be changed in the settings for Windows Explorer (pre-Vista, that is). I scoured the web for some arcane registry key, but nooo....
What to do? Well, roll your own, of course!
Using TrayProdder
Normally you will have TrayProdder running in the system tray (i.e the form above will be invisible). To show the form, just double-click the TP icon in the tray.
Then:
- check none, any or both of the two checkboxes Checkboxes and Full row select. This will prod any running instances of Windows Explorer to attain the desired features.
- click any of the eight buttons to make full use of both checkboxes and selection status. This will speed file selection to no end.
You can also click the ? button to show some overall information and - more importantly - command line options.
Please note: TrayProdder does not change the Windows Explorer executable or installation in any way, shape or form.
When you quit TrayProdder, everything resorts back to its original state. Nothing is permanently changed. You may well ask "how is it then even possible to make Explorer get up on its hind legs and do tricks like a circus pony?" The answer: TrayProdder does it's magic by lobbing messages over the process boundry to Explorer, right at the ListView controls in it. In the absence of hooks (technically "better" but way more invasive), a 5-times-a-second timer keeps Explorer reasonably synched to options chosen in TrayProdder.
Points of Interest
Using enums to Pass Task Information
The main form (class TrpMain
) is the controller for all the action of TrayProdder. Here we have a timer object/event that keeps checkboxes and fullrowselects active as new Explorer instances appear, and the event handlers for the eight button actions possible.
However, for the sake of modularization, the main form is kept clean of any actual low-level interaction with Explorer. That stuff is contained in an "engine" class named TrpBase
.
So, we now need a way for TrpMain
to tell TrpBase
what to do. What better way than to define a couple of enums:
public enum enAction
{
MaintainStyle, // maintain wanted visibility of fullrowselect and
// checkboxes (timer)
HitCheckedRows, // perform action based on checked items
HitSelectedRows // perform action based on selected items
}//enAction
public enum enChangeState
{
TrueMirror, // set "mirror" to true if hit
FalseMirror, // set "mirror" to false if hit
InverseSelf, // toggle self from true to false or vice versa
ClearSelf, // set self to false
Dummy // used by enAction.MaintainStyle (none above applies)
}//enChangeState
The enAction
enum defines the major task to be performed (at timer tick or button press). The enChangeState
is in effect an argument to enAction
, giving one of four possible things to do once you have hit on either a checked row or a selected row. The "mirror" moniker is just an awkward way of saying that if we hit a SELECTED row, we want to do something with it's CHECKED status. And vice versa. In effect, the two properties mirror each other.
The timer task (MaintainSyle
) does not need any supplemental argument but is given one (Dummy
), just for the sake of symmetry.
The Innards of the Engine
Now that we've defined an enum interface between the GUI (trpMain
) and the engine (trpBase
), we need a public method in trpBase
that can be called by trpMain
, using the enums above as arguments.
public void fnActionTop ( enAction nAction, enChangeState nChangeState )
{
if ( !m_bBusy ) //one at a time....
{
m_bBusy = true;
if (nAction != enAction.MaintainStyle)
{
m_hMainForm.Enabled = false;
Application.DoEvents();
}
fnActionWinClass ( "CabinetWClass", nAction, nChangeState );
fnActionWinClass ( "ExploreWClass", nAction, nChangeState );
if (nAction != enAction.MaintainStyle)
{
m_hMainForm.Enabled = true;
Application.DoEvents();
}
m_bBusy = false;
}//!Busy
}
Here we have a busy flag to ensure that the last action was completely taken care of, before any new action is possible. Since actions (except MaintainStyle
) may be time-consuming (say in lists of 4000 rows or more), we disable the main form until tbe action has ended. The duo "CabinetWClass
" and "ExploreWClass
" stems from the fact that any one of these can exist as a top level container in an instance of Explorer, depending on the way it's been invoked.
Finding ListView in a Nest of Containers
Here is what may be the hairiest piece of the TrayProdder source. It's called by fnActionTop
(above) to find the ListView lurking somewhere in the container structure of the specified top window class, and then taking the indicated action.
private void fnActionWinClass ( string sWinClass, enAction nAction,
enChangeState nChangeState )
{
int hMain = 0; int hShell = 0; int hList = 0;
int hLevel1 = 0; int hLevel2 = 0; int hLevel3 = 0;
while ( ( hMain = FindWindowEx ( 0, hMain, sWinClass, null ) ) > 0 )
{//winclass found
while ( ( hShell = FindWindowEx ( hMain, hShell,
"SHELLDLL_DefView", null ) ) > 0 )
{//shell found
// if listview is not found directly under the shell,
// look three levels down
bool bListFound = false;
while ( ( hList = FindWindowEx ( hShell, hList,
"SysListView32", null ) ) > 0 )
{
bListFound = true;
fnStyleOrAction(hList,nAction,nChangeState);
}//Syslistview32 found under shell
if (!bListFound)
{//check next three levels
hList = 0;
while ((hLevel1 = FindWindowEx(hShell, hLevel1,
"DUIViewWndClassName", null)) > 0)
{
while ((hLevel2 = FindWindowEx(hLevel1, hLevel2,
"DirectUIHWND", null)) > 0)
{
while ((hLevel3 = FindWindowEx(hLevel2,
hLevel3, "CtrlNotifySink", null)) > 0)
{
while ((hList = FindWindowEx(hLevel3,
hList, "SysListView32", null)) > 0)
{
fnStyleOrAction(hList, nAction,
nChangeState);
}//listview found on third level
}//check third level
}//check second level
}//check first level
}//check next three levels
}//shell found
}//winclass found
}//fnActionWinClass
The Real Challenge: Gathering Information
The actual programming may be rather pedestrian, but finding the information needed was an interesting exercise in software forensics.
- Google helped me figure out names of various Explorer container objects:
CabinetWClass
,ExploreWClass
,SHELLDLL_DefView
,DUIViewWndClassName
,DirectUIHWND
,CtrlNotifySink
andSysListView32
(a ListView subclass). Where Google couldn't help me, the Microsoft utility Spy++ could. - Much of the message definitions came from ancient Microsoft C header files, such as COMMCTRL.H and WINUSER.H. A .NET developer is normally discouraged from mucking about too much with low-level stuff (for sound architectural reasons) so these may well be missing from his IDE. However, if you google any one of them, you'll easily find them on the web. Example: COMMCTRL.H
- Important insights, as well as core parts of the code (though somewhat modified), came from this page: Crossing the process boundry with .NET
Other parts of Windows could conceivably be customized in a similar, non-intrusive fashion. Any takers?
All source code for the project is contained in the zip file above. A ready-to-use executable can be found here: TrayProdder Home Page