CONTENT
 PRODUCTS
 ARTICLES
 DOWNLOAD
 SOURCE CODE
 BOOK
 ABOUT
 HOME

MVP

ADS




Smartphone Task Manager with .NET CF
The Task Manager for Smartphone is a .NET CF application that is provided as open source written in C#. It provides the basic task management functionality that can be used on both the emulator and a physical device.

As a Smartphone power user and also developer, I was missing a task manager for the Smartphone. Although there are many both commercial and free task managers out there, I couldn't find a single one that was distributed for the emulator (x86 processors). Even if my physical Smartphone comes with something called a resource manager that allow me to stop processes, I often want to do application testing on the emulator as it provides more speed and therefore makes debugging more efficient. When doing application development, file management is essential. Just like when I was building the File Manager, Christoph Wille's excellent initiative to publish an open source .NET CF registry editor for Smartphone made me do the same for task management. A special thanks also goes to fellow MVP Alex Yakhnin whose blog and article provided valuable input.

Task Manager
Task Manager
Get the sample source code!

User Interface
The Smartphone Task Manager application was built for .NET CF with Visual Studio .NET 2003 and when installed in the Start Menu's Accessories folder, it looks like this:

Task Manager icon in Start Menu
Figure 1. Task Manager icon in Start Menu

When the Task Manager icon is selected in the Start Menu, the task manager is started with the currently applications listed that looks like this:

Running applications list
Figure 2. Running applications list

The user interface is very simple and the look and feel is aligned with the standard Smartphone applications. It provides a clean user experience without any non-standard title bar, status bar, splitters, or other alien user interface elements.

Any of the listed applications can be activated by simply navigating the list (using the hardware navigation buttons) and selecting the Activate menu option (left soft key). Doing so will activate the selected application and terminate the task manager. The list can be updated at any time by selecting the Refresh menu option. An application can be stopped by selecting menu option Stop, and to stop all running applications (except the task manager itself), selecting the Stop All menu option. The menu option Done simply ends the task manager.

Selecting the Processes menu option will open the process list that looks like this:

Active processes list
Figure 3. Active processes list

The list can be updated at any time by selecting the Refresh menu option, and to kill a process, navigate to it in the list and select the Kill menu option. To view details about a process, select menu option View. Doing so will bring up the process details screen:

Process details screen
Figure 4. Process details screen

Here the information about the process is shown, like the process identity, the number of threads running in the process and the process' base address.

That concludes the walkthrough of the user interface, so let's move on and look at some of the code.

Code
The code to fill the list of running applications in the main form (MainForm) looks like this:
  windows = WindowHelper.EnumerateTopWindows();

  // Fill the ListView
  ListViewItem lvi;
  listView.BeginUpdate();
  listView.Items.Clear();
  foreach(Window w in windows)
  {
    lvi = new ListViewItem(w.ToString());
    listView.Items.Add(lvi);
  }
  listView.EndUpdate();
  if(listView.Items.Count > 0)
  {
    listView.Items[0].Selected = true;
    listView.Items[0].Focused = true;
  }
First, the running applications are retrieved from the window helper class (WindowHelper). Then, the list is cleared and filled with the (title bar) text for each of the running applications. If there are any running applications, the first item in the list is selected. The window helper method (EnumerateTopWindows) has the following code:
  public static Window[] EnumerateTopWindows()
  {
    ArrayList windowList = new ArrayList();
    IntPtr hWnd = IntPtr.Zero;
    Window window = null;
    
    // Get first window
    hWnd = GetActiveWindow();
    hWnd = GetWindow(hWnd, GW_HWNDFIRST);
    
    while(hWnd != IntPtr.Zero)
    {
      if(IsWindow(hWnd) && IsWindowVisible(hWnd))
      {
        IntPtr parentWin = GetParent(hWnd);
        if ((parentWin == IntPtr.Zero))
        {
          int length = GetWindowTextLength(hWnd);
          if (length > 0)
          {
            string s = new string('\0', length + 1);
            GetWindowText(hWnd, s, length + 1);
            s = s.Substring(0, s.IndexOf('\0'));
            if(s != "Tray" && s != "Start" && s != "Task Manager")
            {
              window = new Window();
              window.Handle = hWnd;
              window.Text = s;
              windowList.Add(window);
            }
          }
        }
      }
      hWnd = GetWindow(hWnd, GW_HWNDNEXT);
    }
    return (Window[])windowList.ToArray(typeof(Window));
  }
This is the way to simulate the native Windows API EnumWindows as it cannot be called from .NET CF 1.0 (but it will be possible with .NET CF 2.0 as it allow native callbacks). All windows are added to the list that does not have a parent (top) is checked to be a visible window, have a (title bar) text, and not being the task or title bar, and not the task manager itself. An array of windows is returned.

To activate a running application, a native API (SetForegroundWindow) is called with the window handle, and to stop an application, another native API (SendMessage) is used to send a close message (WM_CLOSE) to the window. Yet another native API (SHCloseApps) is used to close all running applications (except the task manager itself).

Back in the main form, this is the code to fill the list of running processes:
  processes = Process.GetProcesses();

  // Fill the ListView
  ListViewItem lvi;
  listView.BeginUpdate();
  listView.Items.Clear();
  foreach(Process p in processes)
  {
    lvi = new ListViewItem(p.ProcessName);
    listView.Items.Add(lvi);
  }
  listView.EndUpdate();
  if(listView.Items.Count > 0)
  {
    listView.Items[0].Selected = true;
    listView.Items[0].Focused = true;
  }
The active processes are retrieved from the process class (Process), and then the process names are added to the list with the first item selected. The method called on the process class (GetProcesses) is implemented like this:
  public static Process[] GetProcesses()
  {
    ArrayList procList = new ArrayList();

    IntPtr handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if((int)handle > 0)
    {
      try
      {
        PROCESSENTRY32 peCurrent;
        PROCESSENTRY32 pe32 = new PROCESSENTRY32();
        byte[] peBytes = pe32.ToByteArray();
        int retval = Process32First(handle, peBytes);

        while(retval == 1)
        {
          peCurrent = new PROCESSENTRY32(peBytes);
          Process proc = new Process(new IntPtr((int)peCurrent.PID),
            peCurrent.Name, (int)peCurrent.ThreadCount,
            (int)peCurrent.BaseAddress);
          procList.Add(proc);
          retval = Process32Next(handle, peBytes);
        }
      }
      catch(Exception ex)
      {
        throw new Exception("Exception: " + ex.Message);
      }
      CloseToolhelp32Snapshot(handle); 
      return (Process[])procList.ToArray(typeof(Process));
    }
    else
    {
      throw new Exception("Unable to get processes!");
    }
  }
For each of the active processes, a instance of the process class (Process) is created with the details about the process (identity, name, number of threads, etc), and an array of processes is returned.

In the process class (Process) the method to kill a process (Kill) looks like this:
  IntPtr hProcess = OpenProcess(PROCESS_TERMINATE, false,
    (int) processId);

  if(hProcess != (IntPtr) INVALID_HANDLE_VALUE) 
  {
    bool bRet;
    bRet = TerminateProcess(hProcess, 0);
    CloseHandle(hProcess);
  }
The process handle is retrieved by passing the process identity to a native API (OpenProcess) and with that handle, the process can be terminated with another native API (TerminateProcess).

For a more complete example, see the sample source code.

Conclusion
When the functionality is not available in the.NET Compact Framework, there is almost always a way to solve the problem by making some P/Invokes to the native APIs. With the plumbing in place, the user interface can quickly be put together to create a useful and missed (at least by me) utility.

Any comments?


©2001-2009 Christian Forsberg & Andreas Sjöström