| ||
|
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: ![]() 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: ![]() 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: ![]() 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: ![]() 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 |
||