Click here to Skip to main content
6,822,613 members and growing! (23,978 online)
Email Password   helpLost your password?
Desktop Development » Shell and IE programming » General     Intermediate License: The MIT License

Classic Shell

By Ivo Beltchev

Classic start menu and other shell features for Windows 7 and Vista
C++, Windows (Vista, Win7), Win32, Win64, ATL, COM, Dev
Revision:4 (See All)
Posted:29 Nov 2009
Updated:13 Dec 2009
Views:22,027
Bookmarked:90 times
Unedited contribution
Prize winner in Competition "Windows 7 "Windows @ Work" Article Contest"
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
75 votes for this article.
Popularity: 8.89 Rating: 4.74 out of 5

1

2
13 votes, 17.3%
3
2 votes, 2.7%
4
60 votes, 80.0%
5

Introduction

ClassicShell
Classic Shell
is a collection of features that were available in older versions of Windows but not anymore. It brings back the classic start menu that Windows 7 doesn't support, adds a toolbar for Windows Explorer, replaces the copy UI in Vista and Windows 7 with the classic UI from Windows XP and adds couple more smaller features.
Visit the Classic Shell project on Source Forge

Classic Start Menu

Classic Start Menu is a clone of the original start menu, which you can find in all versions of Windows from 95 to Vista. It has a variety of advanced features:

  • Drag and drop to let you organize your applications
  • Options to show Favorites, expand Control Panel, etc
  • Shows recently used documents. The number of documents to display is customizable
  • Translated in 35 languages, including Right-to-left support for Arabic and Hebrew
  • Does not disable the original start menu in Windows. You can access it by Shift+Click on the start button
  • Right-click on an item in the menu to delete, rename, sort, or perform other tasks
  • Available for 32 and 64-bit operating systems
  • And last but not least – it's FREE!

If you have used the start menu in older versions of Windows you’ll feel right at home:

Classic Start Menu

Classic Explorer

Classic Explorer is a plugin for Windows Explorer that:

  • Adds a toolbar to Explorer for some common operations (Go to the parent folder, Cut, Copy, Paste, Delete, Properties)
  • Replaces the copy UI in Vista and Windows 7 with the more user-friendly “classic” version similar to Windows XP
  • Handles Alt+Enter in the folder panel of Windows Explorer and shows the properties of the selected folder
  • Has options for customizing the folder panel to look more like the Windows XP version or to not fade the expand buttons

Toolbar for Windows Explorer

Windows Explorer in Vista doesn’t have a toolbar like the one in Windows XP. If you want to go to the parent folder you have to use the breadcrumbs bar. If you want to copy or delete a file with the mouse you have to right-click and look for the Delete command. The right-click menu gets bigger and bigger the more shell extensions you have installed, and finding the right command can take a while.

To solve the problem, the Classic Explorer plugin adds a new toolbar that has an Up button:

Explorer Toolbar

Hold the Control key when clicking the Up button to open the parent folder in a new Explorer window.
Hold the Shift key when clicking the Delete button to permanently delete a file.

New copy UI

In Vista when you copy files and there is a conflict you are presented with this:

Copy in Vista

What’s wrong with it?

Well, for starters it is half a screen full of text that you have to read. Also it is not immediately clear what parts of it are clickable. You have to move the mouse around to discover the UI like in a Lucas Arts adventure game. And finally the keyboard usability is awful. To tell it “yes, I know what I’m doing, I want to overwrite all files” you have to press Alt+D, up, up, up, Space! It is harder than performing the Akuma Kara Demon move in Street Fighter 3. There is a time and a place for that stuff and copying files is not it.

The Classic Explorer plugin brings back the simpler dialog box from Windows XP:

Copy in XP

It is immediately clear what is clickable (clue – the buttons at the bottom), there is easy keyboard navigation (press Y for “Yes”, A to copy all files) and you can still see which file is newer and which is larger. And of course just like in Windows XP, holding down Shift while clicking on the No button means "No to All" (or just press Shift+N).

If you click on More… you will get the original dialog from Windows. From there you will see all the details and you’ll get an extra option to “Copy, but keep both files”.

Important Note: Only the UI is replaced. The underlying system that does the actual copying is not affected.

Alt+Enter in the folder panel

Alt+Enter is universal shortcut across Windows to bring up the properties of the selection. But in Vista and Windows 7 it doesn’t work in the left panel that shows the folders. It works fine on the right where the files are. This is broken compared to Windows XP where Alt+Enter works in both places.

To solve the problem, the Classic Explorer plugin detects when you press Alt+Enter and shows the properties for the currently selected folder.


Alternative start menu implementations

Before I decided to develop my own start menu I tried to look for alternatives. I couldn’t find something that is free, supports reordering of programs, shows the recent documents, etc. This article will not be complete without listing the “competition” with some (hopefully objective) pros and cons:

CSMenuhttp://www.csmenu.com/

CSMenu is free and provides the basic functionality – open the Programs menu, click on a program to run. It is lacking advanced functions like keyboard navigation, drag/drop, recent documents, customizability, etc. Also the localization is not quite right – for example Help and Support, Calculator, etc. are not localized, no support for right-to-left languages.

Classic Windows Start Menuhttp://usuarios.lycos.es/coreaffinity/classicwinstartmenu.htm

Classic Windows Start Menu is also free and only has basic functionality. No drag/drop, very few languages are supported, doesn’t quite work correctly when the taskbar is not at the bottom.

Classic Start Menuhttp://www.classicstartmenu.com/index.html

Classic Start Menu, while not free (20 bucks), has a variety of advanced features. You can use drag/drop to rearrange your menus (I found it to be a bit buggy), it has 2 skins to choose from (Aero and Classic). On the negative side, there is almost no keyboard navigation because there is a search box that steals all typed characters. There is some sort of shortcut system using the numeric keys but I couldn’t get it to work reliably. There is no proper "recent documents" menu. Also the localization is a bit off and right-to-left support is a bit lacking.

Vista Start Menuhttp://www.vistastartmenu.com/index.html

Vista Start Menu is another version done by the same guy as Classic Start Menu above. It tries to be much fancier but the UI was way too busy for my taste. I gave it a quick look and found the keyboard shortcut system to work a bit more reliably. There is free version and PRO version (20 bucks) that adds some more customization features. I would rate this one the highest of the bunch because of its features, if you are into this sort of UI.

Seven Classic Starthttp://www.sevenclassicstart.com/

Seven Classic Start is probably the worst of the bunch. It is the most expensive (25 bucks!) and only offers basic functionality. Even though it is advertized as “Complete with everything that makes the original Start menu beloved by so many users” there is no drag and drop, expanding Control Panel, recent documents, localization, or right-to-left support.
P.S. When you try to uninstall the trial you get an offer to use it for free if you agree to try some other software as well.

If you are not a programmer you can stop reading now. Just download the binaries and install them.



The rest of the article discusses how the different features are implemented.

I’ll try to keep it short and not repeat information that is readily available in other Code Project articles or the MSDN.


How Classic Start Menu works

So how do we create a start menu replacement? Let’s start from the beginning.

Implementing the menu

Just like with the original start menu, our implementation uses a vertical toolbar control. This gives us some advantages over a regular menu:
  • the toolbar supports images directly without the need to fiddle with owner-drawn menus
  • the toolbar can be placed in a pager control to make it scrollable if the items don’t fit on screen
  • you can right-click on a toolbar but not on a menu
  • the toolbar offers some drag/drop functionality that will come handy later
Of course there are downsides. We need to simulate parts of the menu behavior ourselves. We need to take care of opening a submenu when the mouse hovers over an item, handle focus, activation and Z-order issues, etc.

The matters get complicated further because we want to show multi-column menus when the programs don’t fit on screen. The original start menu uses the undocumented styles TBSTYLE_EX_MULTICOLUMN and TBSTYLE_EX_VERTICAL. I could not get them to work at all and had to find another solution. I’m guessing they only work under very specific conditions that I failed to replicate. If somebody has succeeded using those styles, please let me know the secret. Since we can't create a multi-column toolbar, we have to simulate it with multiple toolbars placed side by side. Simple, right? Well, there are problems. Special care is needed to make multiple toolbars work seamlessly as one. For example if you are at the bottom of the first column and press down, the focus needs to switch to the second toolbar and its first item must be selected.

Replacing the standard menu

The next problem we need to solve is how to show our menu instead of the built-in standard menu. There are 2 ways for the user to activate the menu – by pressing the Win key and by clicking on the start button (the orb). After doing some sniffing around with Spy++ you will notice a few things:
  • The taskbar is a window of class Shell_TrayWnd and no name
  • The start button is a window in the same thread as the taskbar. The text of the button is localized and depends on the current OS language
  • When you click on the start button, it receives WM_LBUTTONDOWN just like any other window
  • When you click on the area around the start button, the taskbar receives WM_NCLBUTTONDOWN message
  • There is also a window named Program Manager and class Progman. It is in the same process as the taskbar but in another thread
  • When you press the Win button the Progman window receives a message WM_SYSCOMMAND with wParam = SC_TASKLIST
So the task is simple – install WH_GETMESSAGE hooks for the threads of the start button and the Progman window. Intercept the messages that activate the start menu and show our start menu instead.
HWND g_StartButton; // the start button window
HWND g_Taskbar; // the taskbar window
UINT g_StartMenuMsg; // a private message posted when the Win key is pressed
 
void ToggleStartMenu(); // function that opens/closes our start menu
 
STARTMENUAPI LRESULT CALLBACK HookProgMan( int code, WPARAM wParam, LPARAM lParam )
{
   if (code==HC_ACTION) {
      MSG *msg=(MSG*)lParam;
      if (msg->message==WM_SYSCOMMAND && (msg->wParam&0xFFF0)==SC_TASKLIST) {
         PostMessage(g_StartButton,g_StartMenuMsg,0,0);
         msg->message=WM_NULL; // stop the window from processing the message
      }
  }
  return CallNextHookEx(NULL,code,wParam,lParam);
}
 
STARTMENUAPI LRESULT CALLBACK HookStartButton( int code, WPARAM wParam, LPARAM lParam )
{
   if (code==HC_ACTION && !g_bInMenu) {
      MSG *msg=(MSG*)lParam;
      if (msg->message==g_StartMenuMsg && msg->hwnd==g_StartButton) {
         // activated by keyboard
         ToggleStartMenu();
         msg->message=WM_NULL;
      }
      if (msg->message==WM_LBUTTONDOWN && msg->hwnd==g_StartButton) {
         // activated by mouse
         ToggleStartMenu();
         msg->message=WM_NULL;
      }
      if (msg->message==WM_NCLBUTTONDOWN && msg->hwnd==g_Taskbar) {
         // activated by mouse
         ToggleStartMenu();
         msg->message=WM_NULL;
      }
   }
   return CallNextHookEx(NULL,code,wParam,lParam);
}
The hooks can be installed by a shell extension that is auto-loaded by Explorer or by an external exe. I chose an external exe for few reasons. First, it is simpler since it doesn’t require registering a shell extension with all the hassles that come with it. Second, when the exe is killed it cleans up its hooks automatically and the Explorer is restored to its original state. And finally, when Explorer is restarted the exe gets a message TaskbarCreated and it can re-install the hooks.

Of course there are more details to consider. For example when the mouse is over the start button, a tooltip pops up with the text “Start”. We don’t want this text to show up when our start menu is opened. So while the menu is visible, temporarily disable the tooltip.

Next thing to keep in mind is drag and drop. When the user drags a program to add to the start menu he will hover over the start button and expect the menu to open. One way to support this is to replace the IDropTarget object associated with the start button. We get the old drop target from the OleDropTargetInterface property of the start button window, set a new drop target, and when it’s time to unhook Explorer we restore the original drop target:
CComPtr<IDropTarget> g_pOriginalTarget;
 
// hook
g_pOriginalTarget=(IDropTarget*)GetProp(g_StartButton,L"OleDropTargetInterface");
if (g_pOriginalTarget)
   RevokeDragDrop(g_StartButton);
CStartMenuTarget *pNewTarget=new CStartMenuTarget();
RegisterDragDrop(g_StartButton,pNewTarget);
pNewTarget->Release();
 
// unhook
if (g_pOriginalTarget)
{
   RevokeDragDrop(g_StartButton);
   RegisterDragDrop(g_StartButton,g_pOriginalTarget);
   g_pOriginalTarget=NULL;
}

Icons

The start menu needs to display icons for all the shell items, as well as for command items like Run, Shutdown, etc.

For shell items we can get the icons using the IExtractIcon interface. First call IExtractIcon::GetIconLocation, then pass the received location to IExtractIcon::Extract. If Extract returns S_FALSE you have to additionally call the ExtractIconEx function.
// Retrieves an icon from a shell folder and child ID
int CIconManager::GetIcon( IShellFolder *pFolder, PITEMID_CHILD item,
 bool bLarge )
{
   // get the IExtractIcon object
   CComPtr<IExtractIcon> pExtract;
   HRESULT hr=pFolder->GetUIObjectOf(NULL,1,&item,
 IID_IExtractIcon,NULL,(void**)&pExtract);
   if (FAILED(hr))
      return 0;
 
   // get the icon location
   wchar_t location[_MAX_PATH];
   int index=0;
   UINT flags=0;
 
 hr=pExtract->GetIconLocation(0,location,_countof(location),&index,&flags);
   if (hr!=S_OK)
      return 0;
 
   // extract the icon
   HICON hIcon;
   hr=pExtract->Extract(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,
 MAKELONG(LARGE_ICON_SIZE,SMALL_ICON_SIZE));
   if (hr==S_FALSE) {
     // the IExtractIcon object didn't do anything - use ExtractIconEx instead
     if (ExtractIconEx(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,1)==1)
         hr=S_OK;
   }
 
   // add to the image list
   index=0;
   if (hr==S_OK) {
      index=ImageList_AddIcon(bLarge?m_LargeIcons:m_SmallIcons,hIcon);
      DestroyIcon(hIcon);
   }
 
   return index;
}
Extracting icons can be expensive because the containing exe or dll needs to be loaded in memory first. The Control Panel is the worst offender because it contains a long list of items, each in its own file, and they are all needed at the same time.

For the command items we can extract the icons from the shell32.dll. It is already loaded in the Explorer process:

Programs- Programs is # 326
Settings - Settings is # 330
Run - Run is # 328

This is a bit hacky because the icon resources can certainly change between Windows versions. I have verified that the icons we need have the same resource IDs for Vista and Windows 7. We’ll see if the next version of Windows (or the next service pack) breaks that. The documented way to access the shell icons is with the SHGetStockIconInfo function. Unfortunately it doesn’t give us all icons we need. Also the interesting icons like SIID_STFIND and SIID_STRUN are marked as “Do not use” in the documentation. In the latest online docs they are not even listed! So for now looking at shell32.dll with a resource viewer seems to be the only workable solution (short of drawing our own icons).

It is possible for multiple items to share the same icon – for example all text files use the same text file icon. To reuse icons, the start menu has a global cache CIconManager that associates each icon with a key value that is a hash of the icon’s location and index. To improve performance the icon manager starts a background thread that crawls through the shell and pre-caches the icons. So by the time you need to open the Control Panel, all icons should already be loaded.

The Programs menu

The Programs menu is a combination of 2 folders – one for the current user and one shared by all users. The start menu should combine the 2 folder trees and present them as one tree. Items with identical names should be combined into one item.

It is important to compare the internal names of the items as returned by GetDisplayNameOf(SHGDN_INFOLDER|SHGDN_FORPARSING) but show the normal display name GetDisplayNameOf(SHGDN_INFOLDER|SHGDN_NORMAL) in the UI. Some items can have the same display name but should be treated as separate items - for example foo.exe and foo.exe.lnk. The opposite is also true - some items can have the same internal name but different display names - for example the display name of the user's Startup folder will be translated to the current language but the common Startup folder will not be. Even though their display names differ, the 2 folders should be combined.

Why not use IShellMenu?

The shell has support for displaying a menu for a given shell folder. You create an instance of IShellMenu, give it IShellFolder and it works quite great. The icons show up properly, drag and drop works, everything is rosy, everybody is happy. Not quite.

There are few downsides, for which I was unable to find a workaround even after a week of trying. First is that we want to show a combination of 2 shell folders – one for the user’s programs and one for the common programs. I created a virtual IShellFolder that presented 2 folders as one. I had to use my own private PIDL structure and the IShellMenu didn’t quite like that. It assumes the PIDLs it gets are compatible with the shell file system. Another problem is that the start menu is much more than just the Programs menu. It has recent documents, system commands, separators, etc. Shoehorning that into a single IShellFolder hierarchy turned out to be an impossible task.

So I gave up on IShellMenu and decided to implement the menu control from scratch. On the bright side, since we are implementing the menu ourselves the possibilities for tweaking the look and feel are endless.

Recent Documents

Windows stores links to the recently accessed documents in the %APPDATA%\Microsoft\Windows\Recent folder. Not every link in the folder points to a document though. Some point to recently accessed folders. So we need to filter those out. How do we distinguish between links to files and links to folders? Get an IShellLink object, convert it to IPersistFile, load the contents of the link with IPersistFile::Load, get the target path and attributes with IShellLink::GetPath and check if it is a file or a folder.

Also there can be quite a few items and enumerating them using the IShellFolder API can be very slow. In my case it was taking about 5-8 seconds. I found it is much faster to go through them using the FindNextFile API, sort them by time and pick the first 15 documents.

System commands

Besides the Programs menu, the start menu contains many items that execute specific commands. We should try to implement as many as possible. Here’s the current list:

Command Implementation
Shutdown IShellDispatch::ShutdownWindows()
Log Off ExitWindowsEx(EWX_LOGOFF,0)
Undock IShellDispatch::EjectPC()
Run IShellDispatch::FileRun()
Help and Support IShellDispatch::Help()
Taskbar Properties IShellDispatch::TrayProperties()
Search for Files and Folders IShellDispatch::FindFiles() (or execute the Command.SearchFile setting)
Search for Printer IShellDispatch2::FindPrinter(CComBSTR(L””), CComBSTR(L””), CComBSTR(L””))
Search for Computers IShellDispatch::FindComputer()
Search for People this one is a bit trickier. Its implementation is part of Windows Mail.
The command line is %ProgramFiles%\Windows Mail\wab.exe /find

Drag and drop

This is one of the most useful features of the start menu. It lets the user rearrange the installed programs and make them easier to find.

How is it done? When the user drags a menu item the toolbar sends the TBN_DRAGOUT notification. We have to create a data object and a drop source and run the SHDoDragDrop function. We can get the data object from IShellFolder::GetUIObjectOf(IID_IDataObject). The drop source has nothing special about it.

To enable dropping items on the menu we use the TBSTYLE_REGISTERDROP style. This causes the toolbar to send the TBN_GETOBJECT notification to request an IDropTarget interface from us and use it during drag/drop. Our IDropTarget must take care of things such as:
  • showing the insert mark where an item can be dropped (use the TB_SETINSERTMARK message)
  • detect when the mouse hovers over a submenu and open it after some delay
  • reorder the menu when an item is dragged and dropped into the same menu
  • move/copy the item if it is dropped in a different menu. For that we can get IDropTarget from the target folder, and manually call IDropTarget::DragOver and IDropTarget::Drop to get it to perform the operation
Usually the drop operation is asynchronous and happens in a background thread. This is not good for us because we want to know immediately when an item is moved so we can update the menu. The simplest solution is to disable asynchronous operations (that's what Windows' own start menu does):
CComQIPtr<IAsyncOperation> pAsync=pDataObj;
if (pAsync)
    pAsync->SetAsyncMode(FALSE);
This is not a big problem because the start menu contains mostly shortcuts and they are fast to copy. I can think of 2 alternative solutions. Ideally we can add an IAdviseSink to the data object and be notified when the drag operation is complete. Unfortunately the shell data objects don't support such notifications (IDataObject::DAdvise returns OLE_E_ADVISENOTSUPPORTED). The other way is to install a directory watcher with FindFirstChangeNotification. This will work but is way too complex for such little benefit.

Context menu

The standard start menu has another feature worth supporting. The user can right-click on an item, get its shell context menu and click on commands. It is quite tricky to host the context menu correctly; fortunately Raymond Chen has covered that in great detail on his blog: http://blogs.msdn.com/oldnewthing/archive/2004/09/20/231739.aspx

Of course there is still work to do because we want some custom behavior. We want special handling for the “rename”, “delete” and “link” commands. For “rename” we are going to show our own renaming dialog box because the default implementation does nothing. For “delete” and “link” we want to refresh the menu after the operation finishes.

How Classic Explorer works

Classic Explorer is a single DLL that registers as 3 different shell extensions – a drag and drop handler, a browser helper object and a desk band. The drag and drop handler is used for the copy UI, the browser helper object hooks into the folder tree view and the desk band adds a toolbar to the Explorer.

Classic Copy

Classic Copy replaces the dialog box that Explorer shows when there is a conflict during a copy/move operation. It uses a drag and drop handler to ensure the DLL is loaded when a copy operation is in progress. Once loaded, the DLL installs WH_CBT hooks into all threads of the process and waits for a dialog box with a specific title to be created.

When Explorer creates a dialog box with title "Copy File" or "Move File" we hide it before it has a chance to be displayed and we show our own dialog box. After the user has picked the selection (Yes, No, Cancel, etc) we have to communicate that selection to the original dialog box.

The original dialog is not an ordinary window but rather a task dialog box. This makes it very difficult to control programmatically because instead of regular buttons with control IDs it uses windowless buttons that are painted directly on the dialog's surface. I have found the only way to control the task dialog is through the active accessibility API. We locate the IAccessible interfrace for each item, find the important buttons by their label and activate the right button using IAccessible::accDoDefaultAction. It would have been so much easier if the buttons have any other feature to distinguish them besides their label, but I couldn’t find any. The labels of course depend on the currently selected language. So we can’t just look for a button called “Don’t Copy”. We have to find the text for the current language. The text is located in the string table of shell32.dll.mui file. For example “Don’t Copy” is string 13606, "Move" is 13610, etc.

So there you have it – hide the original dialog, show our own instead and then use the accessibility API to control the original dialog based on the user selection. It’s that simple.

Alt+Enter in the folder view

This was easy. We subclass the tree view and listen for WM_SYSKEYDOWN message with wParam = VK_RETURN. When the message comes we find the selected item. The user data stored in the tree control item is the PIDL of the corresponding shell item. We need to go up the tree hierarchy and assemble a full PIDL. Finally, we call ShellExecuteEx with the “properties” verb to display the properties:
HTREEITEM hItem=TreeView_GetSelection(hwndTree);
LPITEMIDLIST pidl=NULL;
while (hItem) {
   TVITEMEX info={TVIF_PARAM,hItem};
   TreeView_GetItem(hwndTree,&info);
   LPITEMIDLIST **pidl1=(LPITEMIDLIST**)info.lParam;
   if (!pidl1 || !*pidl1 || !**pidl1) {
      if (pidl) ILFree(pidl);
      pidl=NULL;
      break;
   }
   LPITEMIDLIST pidl2=pidl?ILCombine(**pidl1,pidl):ILClone(**pidl1);
   if (pidl) ILFree(pidl);
   pidl=pidl2;
   hItem=TreeView_GetParent(hwndTree,hItem);
}
if (pidl) {
   SHELLEXECUTEINFO execute={sizeof(execute),
      SEE_MASK_IDLIST|SEE_MASK_INVOKEIDLIST,NULL,L"properties"};
   execute.lpIDList=pidl;
   execute.nShow=SW_SHOWNORMAL;
   ShellExecuteEx(&execute);
   ILFree(pidl);
   msg->message=WM_NULL;
}

Tweaking the folder view look and feel

Since we are subclassing the folder view for the Alt+Enter feature, let's see if we can get it to look like the folder view in Windows XP:
Different folder views


To get the classic look, add the TVS_HASLINES style, remove TVS_SINGLEEXPAND and TVS_TRACKSELECT styles, and remove the TVS_EX_FADEINOUTEXPANDOS and TVS_EX_AUTOHSCROLL extended styles.

To get the simple look, add the
TVS_SINGLEEXPAND and TVS_TRACKSELECT styles, remove the TVS_HASLINES style, and remove the TVS_EX_FADEINOUTEXPANDOS and TVS_EX_AUTOHSCROLL extended styles.

Or if you simply don't want the buttons to fade, just remove the
TVS_EX_FADEINOUTEXPANDOS extended style.

Operation Folder tree command File list command
Cut 41025 28696
Copy 41026 28697
Paste 41027 28698
Delete 40995 28689
Properties no command, see Alt+Enter 28691

Adding the toolbar

The Classic Explorer Bar is a simple desk band with a toolbar inside. You can find out how to create desk bands from this article: ietoolbartutorial.aspx. There are 2 tricky pieces that I needed to discover by myself.

First, I want the desk band to be hosted in Windows Explorer but not Internet Explorer. You do that by checking the exe name in DllMain and return FALSE if the exe is named “iexplore.exe”. Why can’t we just return TRUE only for “explorer.exe”? Our dll may need to be loaded by other executables like regsvr32.exe or msiexec.exe. So it’s better to exclude iexplore.exe specifically, than to provide a specific list of allowed hosts.
extern "C" BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason,
 LPVOID lpReserved )
{
   if (dwReason==DLL_PROCESS_ATTACH) {
      wchar_t path[_MAX_PATH];
      GetModuleFileName(NULL,path,_countof(path));
      if (_wcsicmp(PathFindFileName(path),L"iexplore.exe")==0)
         return FALSE;
   }
   return _AtlModule.DllMain(dwReason, lpReserved);
}
Second, there appears to be a bug in Windows 7. Each desk band is forced to be on its own row in Windows Explorer. The Explorer forces the RBBS_BREAK style for every band. To combat that, the solution is to subclass the rebar control and force a specific value for the RBBS_BREAK style. We remember the state when the band is hidden (IDockingWindow::ShowDW is called with FALSE) so we can restore it correctly the next time Explorer is opened or the band is shown. All this is specific to Windows 7. None of that hackery is needed for Vista because it restores the band states correctly.


Now that we have a toolbar we have to write the code for each button. The Up button navigates to the parent folder:
pBrowser->BrowseObject(NULL,(GetKeyState(VK_CONTROL)<0?SBSP_NEWBROWSER:SBSP_SAMEBROWSER)|SBSP_DEFMODE|SBSP_PARENT);
The Cut, Copy, Paste, Delete and Properties buttons simply send WM_COMMAND messages to Explorer. You can discover the command codes with Spy++. The command code is different if you want to operate on a folder in the tree view or a file in the list view:

Localization

Windows Vista and Windows 7 support 35 languages (if you have the Ultimate version you can install more than one). We want to localize the UI to make it integrate seamlessly into the OS.

The first task is to localize the text. Classic Shell has 2 files that contain localization data. ExplorerL10N.ini has the text for Classic Explorer and StartMenuL10N.ini has the text for the start menu. Each language has its own section:
[ar-SA] - Arabic (Saudi Arabia)
Menu.Programs = البرا&مج
Menu.Favorites = المف&ضلة
 
[bg-BG] - Bulgarian (Bulgaria)
Menu.Programs = &Програми
Menu.Favorites = Пре&дпочитани
 
[el-GR] - Greek (Greece)
Menu.Programs = &Προγράμματα
Menu.Favorites = Αγαπ&ημένα
 
[en-US] - English (United States)
Menu.Programs = &Programs
Menu.Favorites = F&avorites
The name of the section ([en-US], [bg-BG], etc) comes from the GetThreadPreferredUILanguages function. It returns a list of language names in the order of preference. To get a localized string call the FindSetting function:
const wchar_t *FindSetting( const char *name, const wchar_t *def );
If a string is not found in the first prefered language, the next language is used. If none of the languages is recognized, the [default] section is used. And if the text is nowhere to be found, FindSetting returns the def parameter.

Part of the localization is to support right-to-left languages like Arabic and Hebrew. We check if the language is RTL with this code:
bool IsLanguageRTL( void )
{
   LOCALESIGNATURE localesig;
   LANGID language=GetUserDefaultUILanguage();
   if (GetLocaleInfoW(language,LOCALE_FONTSIGNATURE,(LPWSTR)&localesig,
 (sizeof(localesig)/sizeof(wchar_t))) && (localesig.lsUsb[3]&0x08000000))
      return true;
   return false;
}
For RTL languages there are few things to consider:
  • The dialog boxes need to be mirrored. This is done by having a separate dialog resource with the RTL style
  • The start menu needs to be mirrored. So we set the RTL style for the menu container window. The toolbar is smart enough to reverse its orientation
  • Since the toolbars are reversed, all icons are mirrored. We don’t want that so we need to set the ILC_MIRROR flag for the toolbar’s image list to mirror the icons back to normal
  • Context menus also need to be mirrored. This is done by passing the TPM_LAYOUTRTL flag to TrackPopupMenu
  • AnimateWindow has some problems with RTL layout. It uses the WM_PRINT and WM_PRINTCLIENT messages and they don’t play nice with RTL. Unfortunately I haven’t found a fix, so for now the menu animation is disabled for RTL languages

The Installer

The installer for Classic Shell is split into 3 projects. ClassicShellSetup32 builds a 32-bit msi file, ClassicShellSetup64 builds a 64-bit msi file. The 64-bit package contains both 32-bit and 64-bit versions of the ClassicExplorer.dll because you can run a 32-bit Explorer on 64-bit Windows.

ClassicShellSetup builds an exe that combines the 2 msi files into one convenient package. Certainly it is possible to distribute 2 separate msi files but the exe gives are some more advantages besides just being a convenient package:
  • The exe can check if we are running on older version of Windows and complain about it
  • The exe can check if the OS is 32-bit or 64-bit and will run the correct msi
  • After installation we want to launch the start menu exe. It is not possible to do so from the msi package because it runs in an elevated environment and the start menu needs to run as the current user
  • The exe can have a nice non-default icon

Building the solution

The included solution is for Visual Studio 2008. It needs to be built in 2 steps.

First, do a full build for the Setup|Win32 configuration. That will build the 32-bit modules and create the 32-bit msi file.
Second, do a full build for the Setup|x64 configuration. That will build the 64-bit modules, create the 64-bit msi file and build the final package ClassicShellSetup\Release\ClassicShellSetup.exe. Run the exe to install.

Note: The first time you build the setup projects you may get some warning about adding dependencies for some system files like OLEACC.dll and UxTheme.dll. Make sure all Detected Dependencies are excluded and build again.

And finally some tips for developers


Debugging the start menu will be easier if you disable the #define HOOK_EXPLORER line in ClassicStartMenu.cpp. Then the menu will run in its own process and will not interfere with the Explorer process. Some of the functionality will be disabled but it is good enough for most purposes.

Building Classic Explorer for Release or Debug configuration will register it as a shell extension. The next time you open an Explorer window it will be activated. You can attach a debugger to the explorer.exe process and debug the shell extension.

During development it is inconvenient that the DLLs are loaded by Explorer and can't be rebuilt. So we need a way to restart Explorer. You may restart Explorer by killing it in Task Manager and running explorer.exe again. But an easier way to restart Explorer is to make it crash. In Release and Debug if you hold down Shift and click on the Settings button it will force a crash in Explorer.

Another workaround for restarting Explorer is available if you develop on 64-bit Vista. You can run the 32-bit Explorer and close it when you are done. I have found that opening Explorer from the Perforce client runs the 32-bit version. This doesn't seem to work on Windows 7 for some reason.

Room for improvement

Of course there is always room for improvement. Here’s what I have planned for the future:
  • It would be nice if the start menu fades the selected item like a regular menu does. This is controlled by the SPI_GETSELECTIONFADE setting
  • The start menu needs to be available through the active accessibility API. Accessibility currently thinks it is a "toolbar" with "buttons". It should be a "menu" with "menu items"
  • The start menu may have a new sub-menu for the 10 recently started programs
  • Some glass effect may look cool on the start menu. I experimented a bit with glass, but was unable to get something to look acceptable. I can give it one more try for the next major version
  • Maybe the start menu can hide the rarely used items (so called "personalized menus") and highlight the newly installed programs

History

  • Version 0.9.6 beta (Dec, 2009) - more improvements:
    • Added Properties button to the toolbar
    • Added settings for the look of the folder tree in Explorer - XP Classic, XP simple, etc.
    • Added support for the start menu group policies like "Hide Run", "Hide Help", "Always show Log Off", etc.
    • for more improvements and bug fixes check out the HISTORY.txt file
  • Version 0.9.5 beta (Dec, 2009) - few improvements:
    • Added option to remove the Documents menu and option to use an alternative search application (requested by johnohn)
    • Added more buttons to the toolbar - Cut, Copy, Paste, Delete
    • Fixed a crash in the start menu (thanks to AlexG)
    • The selected menu item is drawn using the current theme (still more eye-candy is needed for a native Aero look)
    • for more improvements check out the HISTORY.txt file
  • Version 0.9 beta (Nov, 2009) - first public version:
    • Classic start menu
    • Replacement for the Copy UI in Vista
    • Fix for Alt+Enter in Explorer
    • Toolbar for Explorer with Up button

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Ivo Beltchev


Member
Ivo started programming in 1985 on an Apple ][ clone. He graduated from Sofia University, Bulgaria with a MSCS degree. Ivo has been working as a professional programmer for over 12 years, and as a professional game programmer for over 10. He is currently employed in Pandemic Studios, a video game company in Los Angeles, California.
Occupation: Software Developer (Senior)
Location: United States United States

Other popular Shell and IE programming articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 100 (Total in Forum: 100) (Refresh)FirstPrevNext
GeneralA suggestion for the copy dialog PinmemberAndromeda Shun22hrs 25mins ago 
GeneralRe: A suggestion for the copy dialog PinmemberIvo Beltchev14hrs 46mins ago 
GeneralRe: A suggestion for the copy dialog PinmemberAndromeda Shun14hrs 8mins ago 
GeneralRe: A suggestion for the copy dialog PinmemberIvo Beltchev13hrs 30mins ago 
GeneralRe: A suggestion for the copy dialog PinmemberAndromeda Shun12hrs 40mins ago 
GeneralHilarious description... PinmemberMember 367367519:35 8 Feb '10  
GeneralWhy oh why? PinmemberScott Beamer16:08 8 Feb '10  
GeneralRe: Why oh why? PinmemberIvo Beltchev16:26 8 Feb '10  
GeneralGreat Job PinmemberEng. Jalal13:07 8 Feb '10  
GeneralHOWTO: Customize the toolbar PinmemberIvo Beltchev21:11 25 Jan '10  
GeneralVersion 0.9.10 is available on Source Forge PinmemberIvo Beltchev17:57 24 Jan '10  
GeneralRe: Version 0.9.10 is available on Source Forge Pinmemberc62762719:19 25 Jan '10  
GeneralRe: Version 0.9.10 is available on Source Forge PinmemberIvo Beltchev19:55 25 Jan '10  
GeneralVersion 0.9.9 is available on Source Forge PinmemberIvo Beltchev12:33 18 Jan '10  
GeneralWhy are Favorites auto alphabetized and other first impressions Pinmemberc62762711:10 17 Jan '10  
GeneralRe: Why are Favorites auto alphabetized and other first impressions PinmemberIvo Beltchev11:40 17 Jan '10  
GeneralRe: Why are Favorites auto alphabetized and other first impressions Pinmemberc62762712:48 17 Jan '10  
GeneralRe: Why are Favorites auto alphabetized and other first impressions Pinmemberc62762712:54 17 Jan '10  
GeneralRe: Why are Favorites auto alphabetized and other first impressions PinmemberIvo Beltchev14:11 17 Jan '10  
GeneralRe: Why are Favorites auto alphabetized and other first impressions PinmemberIvo Beltchev14:09 17 Jan '10  
GeneralExcellent ideas Pinmemberc62762719:28 17 Jan '10  
GeneralRe: Excellent ideas PinmemberIvo Beltchev19:40 17 Jan '10  
GeneralOne request Pinmembercasperguy5:51 12 Jan '10  
GeneralRe: One request PinmemberIvo Beltchev6:08 12 Jan '10  
QuestionNot Showing All Installed Programs [modified] PinmemberKuP1237:56 10 Jan '10  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.

PermaLink | Privacy | Terms of Use
Last Updated: 13 Dec 2009
Editor:
Copyright 2009 by Ivo Beltchev
Everything else Copyright © CodeProject, 1999-2010
Web21 | Advertise on the Code Project