Hello, I am an Engineering Manager at Facebook with 13+ years in Ad Technology, Natural Language Processing and Data mining. (Learn More)
by Pravin Paratey

Windows shell (explorer.exe) replacement in C#

![DeeShell running](/img/posts/deeshell0.gif)

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:

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.

Deleting default stuff

You’ll be left with Program.cs. Open Program.cs and make the static void Main() method look like so:

static void Main()
{
    //Application.Run();
}

Save the project.

Setting up the Screen

Next, we’re going to set up the desktop area. Our steps will be:

  1. Hide the taskbar.
  2. Reset the desktop area.
  3. – Insert Breakpoint –
  4. Restore the desktop area.
  5. 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,

/* DeeShell - A shell replacement for Windows
 * Pravin Paratey (February 19, 2007)
 *
 * Article: http://pravin.paratey.com/posts/deeshell
 *
 * Released under Creative Commons Attribution 2.5 Licence
 * http://creativecommons.org/licenses/by/2.5/
 */
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace DeeShell
{
    public class WinAPI
    {
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

        /// <summary>For ShowWindow</summary>
        public enum WindowShowStyle : int
        {
            Hide = 0,
            ShowNormal = 1,
            ShowMinimized = 2,
            ShowMaximized = 3,
            Maximize = 3,
            ShowNormalNoActivate = 4,
            Show = 5,
            Minimize = 6,
            ShowMinNoActivate = 7,
            ShowNoActivate = 8,
            Restore = 9,
            ShowDefault = 10,
            ForceMinimized = 11
        }

        /// <summary>For SystemParametersInfo</summary>
        public enum SPI : int
        {
            SPI_SETWORKAREA = 0x002F,
            SPI_GETWORKAREA = 0x0030
        }

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool SystemParametersInfo(uint uiAction, uint uiParam,
            ref RECT pvParam, uint fWinIni);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll")]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
            int Y, int cx, int cy, uint uFlags);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
    }
}

Now add another file Functions.cs with the following code,

/* DeeShell - A shell replacement for Windows
 * Pravin Paratey (February 19, 2007)
 *
 * Article: http://pravin.paratey.com/posts/deeshell
 *
 * Released under Creative Commons Attribution 2.5 Licence
 * http://creativecommons.org/licenses/by/2.5/
 */
using System;
using System.Windows.Forms;

namespace DeeShell
{
    class Functions
    {
        #region Private variables
        private static WinAPI.RECT m_rcOldDesktopRect;
        private static IntPtr m_hTaskBar;
        #endregion

        /// <summary>
        /// Resizes the Desktop area to our shells' requirements
        /// </summary>
        public static void MakeNewDesktopArea()
        {
            // Save current Working Area size
            m_rcOldDesktopRect.left = SystemInformation.WorkingArea.Left;
            m_rcOldDesktopRect.top = SystemInformation.WorkingArea.Top;
            m_rcOldDesktopRect.right = SystemInformation.WorkingArea.Right;
            m_rcOldDesktopRect.bottom = SystemInformation.WorkingArea.Bottom;

            // Make a new Workspace
            WinAPI.RECT rc;
            rc.left = SystemInformation.VirtualScreen.Left;
            // We reserve the 24 pixels on top for our taskbar
            rc.top = SystemInformation.VirtualScreen.Top + 24;
            rc.right = SystemInformation.VirtualScreen.Right;
            rc.bottom = SystemInformation.VirtualScreen.Bottom;
            WinAPI.SystemParametersInfo((int)WinAPI.SPI.SPI_SETWORKAREA, 0, ref rc, 0);
        }

        /// <summary>
        /// Restores the Desktop area
        /// </summary>
        public static void RestoreDesktopArea()
        {
            WinAPI.SystemParametersInfo((int)WinAPI.SPI.SPI_SETWORKAREA, 0, ref m_rcOldDesktopRect, 0);
        }

        /// <summary>
        /// Hides the Windows Taskbar
        /// </summary>
        public static void HideTaskBar()
        {
            // Get the Handle to the Windows Taskbar
            m_hTaskBar = WinAPI.FindWindow("Shell_TrayWnd", null);
            // Hide the Taskbar
            if ((int)m_hTaskBar != 0)
            {
                WinAPI.ShowWindow(m_hTaskBar, (int)WinAPI.WindowShowStyle.Hide);
            }
        }

        /// <summary>
        /// Show the Windows Taskbar
        /// </summary>
        public static void ShowTaskBar()
        {
            if ((int)m_hTaskBar != 0)
            {
                WinAPI.ShowWindow(m_hTaskBar, (int)WinAPI.WindowShowStyle.Show);
            }
        }
    }
}

Edit Program.cs to look like,

static void Main()
{
    // Make new Working Area
    Functions.HideTaskBar();
    Functions.MakeNewDesktopArea();

    // Restore Working Area Size
    Functions.RestoreDesktopArea();
    Functions.ShowTaskBar();
}

Put a breakpoint at RestoreDesktopArea(), and Press F5 to compile and run. When you hit the breakpoint, observe that,

  1. The Windows Taskbar has disappeared.
  2. You can try maximizing any open windows. They will now occupy the bottom pixels and leave an empty space of 24 pixels on top.
  3. 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,

  1. Creating a new form.
  2. Seting its properties.
  3. 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.

![Adding Image Resource](/img/posts/deeshell2.gif)

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:

PropertyValue
BackcolorTransparent
(Name)tableLayoutPanel
ColumnCount2
DockFill
Location0, 0
Margin0, 0, 0, 0
RowCount1

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();
}</pre>

Change `Program.cs` to

<pre class="prettyprint lang-cs linenums">
static void Main()
{
    Application.EnableVisualStyles();

    // Make new Working Area
    Functions.HideTaskBar();
    Functions.MakeNewDesktopArea();

    Application.Run(new Taskbar());

    // Restore Working Area Size
    Functions.RestoreDesktopArea();
    Functions.ShowTaskBar();
}

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,

/// <summary>
/// Gets a list of Active Tasks
/// </summary>
public static ArrayList GetActiveTasks()
{
    ArrayList ar = new ArrayList();
    IntPtr child = IntPtr.Zero;

    Process[] process = Process.GetProcesses();
    foreach (Process p in process)
    {
        WindowData w;
        if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle.Length > 0)
        {
            w.hwnd = p.MainWindowHandle;
            w.title = p.MainWindowTitle;
            ar.Add(w);
        }
    }
    return ar;
}

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.

Q. How do I prevent the user from closing DeeShell through Alt+F4?
Add the following code to the Taskbar’s FormClosing event. 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.
private void Taskbar_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = true;
}
Q. 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 the FindWindow() API.
Q. Windows 7 issues
Thank you Iain Brearton for the solution - In Windows 7, the Aero-style title bar is still displayed even with ControlBox being set to False; the solution is to also set the FormBorderStyle property to None, after which the Windows 7 title bar disappears as expected (images attached).

Resources