Writing a Windows shell (explorer.exe) replacement in C#

Download the source files.
This document will teach you to make your own windows shell replacement. If you don't know what a shell replacement is, take a look at Shellfront. Before you begin, you'll need:
- A working knowledge of C#.
- A copy of Visual Studio C# Express. [Download]
- A machine running Windows 2000 and above.
Creating the project
Fire up Visual C# and create a new Windows Application project. I have named it DeeShell. The first thing to do is to get rid of all the default code. Delete Form1.cs by right clicking it in Solution Explorer and selecting Delete.

You'll be left with Program.cs. Open Program.cs and make the static void Main() method look like so:
Program.cs1 static void Main() 2 { 3 //Application.Run(); 4 }
Save the project.
Setting up the Screen
Next, we're going to set up the desktop area. Our steps will be:
- Hide the taskbar.
- Reset the desktop area.
- -- Insert Breakpoint --
- Restore the desktop area.
- Restore the taskbar.
Add a class called WinAPI.cs to the project. This file will contain all the Win32 API that we need to P/Invoke. The best place to learn about all the functions available is at pinvoke.net.
Edit WinAPI.cs to look like so,
WinAPI.cs1 /* DeeShell - A shell replacement for Windows 2 * Pravin Paratey (February 19, 2007) 3 * 4 * Article: http://pravin.insanitybegins.com/articles/deeshell 5 * 6 * Released under Creative Commons Attribution 2.5 Licence 7 * http://creativecommons.org/licenses/by/2.5/ 8 */ 9 using System; 10 using System.Text; 11 using System.Runtime.InteropServices; 12 13 namespace DeeShell 14 { 15 public class WinAPI 16 { 17 public struct RECT 18 { 19 public int left; 20 public int top; 21 public int right; 22 public int bottom; 23 } 24 25 /// <summary>For ShowWindow</summary> 26 public enum WindowShowStyle : int 27 { 28 Hide = 0, 29 ShowNormal = 1, 30 ShowMinimized = 2, 31 ShowMaximized = 3, 32 Maximize = 3, 33 ShowNormalNoActivate = 4, 34 Show = 5, 35 Minimize = 6, 36 ShowMinNoActivate = 7, 37 ShowNoActivate = 8, 38 Restore = 9, 39 ShowDefault = 10, 40 ForceMinimized = 11 41 } 42 43 /// <summary>For SystemParametersInfo</summary> 44 public enum SPI : int 45 { 46 SPI_SETWORKAREA = 0x002F, 47 SPI_GETWORKAREA = 0x0030 48 } 49 50 [DllImport("user32.dll", SetLastError = true)] 51 public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, 52 ref RECT pvParam, uint fWinIni); 53 54 [DllImport("user32.dll", SetLastError = true)] 55 public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 56 57 [DllImport("user32.dll")] 58 public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 59 60 [DllImport("user32.dll")] 61 public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, 62 int Y, int cx, int cy, uint uFlags); 63 64 [DllImport("user32.dll")] 65 [return: MarshalAs(UnmanagedType.Bool)] 66 public static extern bool SetForegroundWindow(IntPtr hWnd); 67 } 68 }
Now add another file Functions.cs with the following code,
Functions.cs1 /* DeeShell - A shell replacement for Windows 2 * Pravin Paratey (February 19, 2007) 3 * 4 * Article: http://pravin.insanitybegins.com/articles/deeshell 5 * 6 * Released under Creative Commons Attribution 2.5 Licence 7 * http://creativecommons.org/licenses/by/2.5/ 8 */ 9 using System; 10 using System.Windows.Forms; 11 12 namespace DeeShell 13 { 14 class Functions 15 { 16 #region Private variables 17 private static WinAPI.RECT m_rcOldDesktopRect; 18 private static IntPtr m_hTaskBar; 19 #endregion 20 21 /// <summary> 22 /// Resizes the Desktop area to our shells' requirements 23 /// </summary> 24 public static void MakeNewDesktopArea() 25 { 26 // Save current Working Area size 27 m_rcOldDesktopRect.left = SystemInformation.WorkingArea.Left; 28 m_rcOldDesktopRect.top = SystemInformation.WorkingArea.Top; 29 m_rcOldDesktopRect.right = SystemInformation.WorkingArea.Right; 30 m_rcOldDesktopRect.bottom = SystemInformation.WorkingArea.Bottom; 31 32 // Make a new Workspace 33 WinAPI.RECT rc; 34 rc.left = SystemInformation.VirtualScreen.Left; 35 // We reserve the 24 pixels on top for our taskbar 36 rc.top = SystemInformation.VirtualScreen.Top + 24; 37 rc.right = SystemInformation.VirtualScreen.Right; 38 rc.bottom = SystemInformation.VirtualScreen.Bottom; 39 WinAPI.SystemParametersInfo((int)WinAPI.SPI.SPI_SETWORKAREA, 0, ref rc, 0); 40 } 41 42 /// <summary> 43 /// Restores the Desktop area 44 /// </summary> 45 public static void RestoreDesktopArea() 46 { 47 WinAPI.SystemParametersInfo((int)WinAPI.SPI.SPI_SETWORKAREA, 0, ref m_rcOldDesktopRect, 0); 48 } 49 50 /// <summary> 51 /// Hides the Windows Taskbar 52 /// </summary> 53 public static void HideTaskBar() 54 { 55 // Get the Handle to the Windows Taskbar 56 m_hTaskBar = WinAPI.FindWindow("Shell_TrayWnd", null); 57 // Hide the Taskbar 58 if ((int)m_hTaskBar != 0) 59 { 60 WinAPI.ShowWindow(m_hTaskBar, (int)WinAPI.WindowShowStyle.Hide); 61 } 62 } 63 64 /// <summary> 65 /// Show the Windows Taskbar 66 /// </summary> 67 public static void ShowTaskBar() 68 { 69 if ((int)m_hTaskBar != 0) 70 { 71 WinAPI.ShowWindow(m_hTaskBar, (int)WinAPI.WindowShowStyle.Show); 72 } 73 } 74 } 75 }
Edit Program.cs to look like,
Program.cs1 static void Main() 2 { 3 // Make new Working Area 4 Functions.HideTaskBar(); 5 Functions.MakeNewDesktopArea(); 6 7 // Restore Working Area Size 8 Functions.RestoreDesktopArea(); 9 Functions.ShowTaskBar(); 10 }
Put a breakpoint at RestoreDesktopArea(), and Press F5 to compile and run. When you hit the breakpoint, observe that,
- The Windows Taskbar has disappeared.
- You can try maximizing any open windows. They will now occupy the bottom pixels and leave an empty space of 24 pixels on top.
- If you refresh your desktop icons, they will obey these rules too. Doesn't it make you feel powerful - making the desktop icons do that? Those who answered Yes - Boy! You guys sure need some serious councelling.
Press F5 again to continue and end the program. I know what you're thinking, "This is not a shell replacement! You're just hiding the taskbar. Explorer still runs in the background."
*raises eyebrow* Are you being sassy? Let me tell you a story about what happened to the little boys and girls who were sassy. Santa didn't leave them any presents that year. So there!
Coming back to the question, take a look at HideTaskBar(). DeeShell can run independently as well as on top of explorer.
Adding a Taskbar
Adding a taskbar will involve,
- Creating a new form.
- Setting its properties.
- Displaying it on startup.
Lets add a new form. Right-click the project in the solution explorer and choose Add -> Windows Form and name it TaskBar. Next, set the following properties:
- Size: 300, 24
- Start Position: Manual
- Control Box: False
- ShowInTaskbar: False
That completes your basic taskbar. Lets spruce it up a little. First, we'll add a background image. Double click Resources.resx in the solution explorer. Then click on Add Image Resource and add an image. Set this as the BackgroundImage for your Taskbar window.

Next, we'll add a TableLayoutPanel. This control will help us organize our taskbar buttons quite nicely. Set the number of rows to 1 and leave the number of columns as two. Set the size of the first column to 60px. Then, set the following properties:
- Backcolor: Transparent
- (Name): tableLayoutPanel
- ColumnCount: 2
- Dock: Fill
- Location: 0, 0
- Margin: 0, 0, 0, 0
- RowCount: 1
Now, we'll add a Button called "Exit" which will let us exit DeeShell gracefully. Add a button to column 1 of TableLayoutPanel. Set its Dock property to Fill and set Margin to 0. Add the following code to it's Click event (I've named the function OnExitClick):
private void OnExitClick(object sender, EventArgs e) { Application.Exit(); }
Change Program.cs to
Program.cs1 static void Main() 2 { 3 Application.EnableVisualStyles(); 4 5 // Make new Working Area 6 Functions.HideTaskBar(); 7 Functions.MakeNewDesktopArea(); 8 9 Application.Run(new Taskbar()); 10 11 // Restore Working Area Size 12 Functions.RestoreDesktopArea(); 13 Functions.ShowTaskBar(); 14 }
If you run the application now, you'll see that the height property of Taskbar is not respected. To fix this, add this line to Taskbar.cs:
public Taskbar() { InitializeComponent(); WinAPI.SetWindowPos(this.Handle, (IntPtr)0, 0, 0, SystemInformation.VirtualScreen.Width, 24, 0x0040); }
Listing Running Tasks
Add the following to Functions.cs,
Functions.cs1 /// <summary> 2 /// Gets a list of Active Tasks 3 /// </summary> 4 public static ArrayList GetActiveTasks() 5 { 6 ArrayList ar = new ArrayList(); 7 IntPtr child = IntPtr.Zero; 8 9 Process[] process = Process.GetProcesses(); 10 foreach (Process p in process) 11 { 12 WindowData w; 13 if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle.Length > 0) 14 { 15 w.hwnd = p.MainWindowHandle; 16 w.title = p.MainWindowTitle; 17 ar.Add(w); 18 } 19 } 20 return ar; 21 }
You will also have to add these declarations at the beginning of Functions.cs
using System.Diagnostics;
using System.Collections;
Now add a ComboBox to our TaskBar form to show the list of running processes. I've populated the ComboBox at the start with:
public Taskbar() { InitializeComponent(); WinAPI.SetWindowPos(this.Handle, IntPtr.Zero, 0, 0, SystemInformation.VirtualScreen.Width, 24, 0x0040); ArrayList ar = Functions.GetActiveTasks(); for (int i = 0; i < ar.Count; i++) { WindowData w = (WindowData)ar[i]; cboTaskList.Items.Add(w.title); } }
We're going to bring the window selected in the ComboBox to the Foreground. To the SelectedIndexChanged event, attach the code:
private void cboTaskList_SelectedIndexChanged(object sender, EventArgs e) { string windowName = cboTaskList.Text; IntPtr handle = WinAPI.FindWindow(null, windowName); WinAPI.SetForegroundWindow(handle); }
There you go! Your very own partially skinned shell replacement. It doesn't do much. It doesn't mow your lawn or fix your faucet. But it makes one hell of a story - for those times when your kids just refuse to sleep.
I can't believe the tutorial is done! 5 hours. Whoa!
F.A.Q.
- 1. How do I prevent the user from closing DeeShell through Alt+F4?
Add this code to the Taskbar's FormClosing event:
private void Taskbar_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = true; }
You'll also need to add a check to see if the exit was valid (say clicking the exit button) and allow that one to pass.
- 2. How do I ensure that only one instance of DeeShell runs?
Create a named mutex. Lock it. And let your first line in
main()check for the presence of this mutex. If present, DeeShell is already running. Another way would be to use theFindWindow()API.