Introduction
In this article, I will describe some tricks which many Windows Mobile developers may need while developing for the .NET Compact Framework platform. The reason I decided to write this article is the following tricks either cannot be achieved using pure managed code or they are not easy to find out in the big twine of classes .NET CF provides.
I will show you how to:
- Graphics
- Capture the screen (create a screenshot)
- Reset the idle timer (keep the backlight on)
- Show/hide the soft input panel button
- File System
- Create a shortcut in the Start menu
- Get path to special folders (\Program Files, \Windows, etc.)
- Get path to the folder of the currently executed assembly
- Get the names of all storage cards
Capture the screen
Capturing the screen can be a useful trick when debugging graphics programming (usually in GUI development). To capture the screen, we use a well-known trick thoroughly described in Charles Petzold's book - Programming Windows:
- Get the Device Context of the entire display
- Bit blit it to a prepared bitmap
The attached project also contains a sample application which shows how to use the code:
This is the code:
[DllImport("coredll.dll")]
internal static extern int BitBlt(IntPtr hdcDest, int nXDest,
int nYDest, int nWidth, int nHeight, IntPtr hdcSrc,
int nXSrc, int nYSrc, uint dwRop);
[DllImport("coredll.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("coredll.dll")]
private static extern IntPtr ReleaseDC(IntPtr HDC);
const int SRCCOPY = 0x00CC0020;
public static Bitmap Snapshot(Rectangle rectangle)
{
IntPtr hdcSrc = GetDC(IntPtr.Zero);
Bitmap bmpCapture = new Bitmap(rectangle.Width, rectangle.Height);
using (Graphics grCapture = Graphics.FromImage(bmpCapture))
{
IntPtr hdcDest = grCapture.GetHdc();
BitBlt(hdcDest, 0, 0,
rectangle.Width, rectangle.Height, hdcSrc,
rectangle.Left, rectangle.Top, SRCCOPY);
grCapture.ReleaseHdc(hdcDest);
}
ReleaseDC(hdcSrc);
return bmpCapture;
}
public static void Snapshot(string fileName, Rectangle rectangle)
{
Snapshot(rectangle).Save(fileName, ImageFormat.Bmp);
}
The Snapshot
method with the string parameter can be used to save the bitmap into a file.
Reset the idle timer
Windows Mobile continuously measures how long the user has been idle. When the time specified in the settings runs up, Windows will automatically turn off the backlight. This may not be desirable for some applications. Fortunately, Windows gives us a function to reset this timer whenever we want to. If we want to keep the backlight on, we should reset this idle timer every now-and-then (let's say every 500 milliseconds). For this, a Windows Timer will be handy:
public class ResetIdleTimer
{
[DllImport("coredll.dll")]
private static extern void SystemIdleTimerReset();
[DllImport("Aygshell.dll")]
private static extern void SHIdleTimerReset();
[DllImport("coredll.dll")]
private static extern int SetSystemPowerState(string pwrState,
int pwrStateFlags, int options);
private const int POWER_STATE_ON = 0x10000;
private const int POWER_STATE_OFF = 0x20000;
private const int POWER_STATE_SUSPEND = 0x200000;
private const int POWER_FORCE = 4096;
private Timer m_Timer;
public ResetIdleTimer(int milisec)
{
m_Timer = new Timer();
m_Timer.Interval = milisec; m_Timer.Tick += new EventHandler(Timer_Tick);
}
public static void ResetTimer()
{
SetSystemPowerState(null, POWER_STATE_ON, POWER_FORCE);
SystemIdleTimerReset();
SHIdleTimerReset();
}
public static void TurnOffBackLight()
{
SetSystemPowerState(null, POWER_STATE_OFF, POWER_FORCE);
}
public void StartTimer()
{
m_Timer.Enabled = true;
}
public void StopTimer()
{
m_Timer.Enabled = false;
}
private void Timer_Tick(object sender, EventArgs e)
{
ResetTimer();
}
}
To keep the backlight on, we only need to instantiate the class and then call the StartTimer()
method. To stop this functionality, simply call StopTimer()
. However, if you want to turn off the backlight immediately, call the TurnOffBackLight()
method.
Show or hide the soft input panel button
Although there is a soft input panel component in the standard shipment of Visual Studio components, it only allows us to display or hide the SIP. But if we remove the menu of the form, the SIP button disappears too. Hence the user has no chance to display the SIP if he wanted to. The SIP button is actually a child window of a window named MS_SIPBUTTON
. We will therefore first find the MS_SIPBUTTON
window, get its child window which is a button, and then either show or hide it:
[DllImport("coredll.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string caption, string className);
[DllImport("coredll.dll", SetLastError = true)]
private static extern bool ShowWindow(IntPtr hwnd, int state);
[DllImport("coredll.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
private const int GW_CHILD = 5;
static public void ShowHideSIP(int nShowOrHide)
{
IntPtr hSipWindow = FindWindow("MS_SIPBUTTON", "MS_SIPBUTTON");
if (hSipWindow != IntPtr.Zero)
{
IntPtr hSipButton = GetWindow(hSipWindow, GW_CHILD);
if (hSipButton != IntPtr.Zero)
{
bool res = ShowWindow(hSipButton, nShowOrHide);
}
}
}
And we would use the method so:
ShowHideSIP(SW_SHOW);
ShowHideSIP(SW_HIDE);
Create a shortcut in the Start menu
If you create a CAB installer for your application, you can set it to create a shortcut for your application in the Start menu. But there are times when you want to create the shortcut yourself - for example, some applications add a shortcut for uninstalling the application or a shortcut to run the settings for it. Well, there is an API function exactly for this, and this is how it looks like:
[DllImport("coredll.dll", EntryPoint = "SHCreateShortcut")]
public static extern void CreateShortcut(string target, string shortcut);
CreateShortcut(@"\windows\start menu\programs\my email.lnk", @"\windows\tmail.exe");
In this case, we created a shortcut in the Start menu whose text will be "my email", and if the user taps the shortcut in the Start menu, the tmail.exe file located in the \windows folder will be run.
Get path to special folders
In some language mutations of Windows Mobile / Windows CE, folders such as \Windows, \Windows\Start menu, \Program Files have different names. For example, the Start menu sub-folder in the Windows folder has its name in Czech language as \Windows\Nabídka Start.
If your application is (for whatever reason) accessing this folder and you have hard coded this path, it will likely fail. To find out the names of these special folders, you can use this code:
public enum Folders
{
Programs = 2, Personal = 5, Startup = 7, Startmenu = 0x0B, Fonts = 0x14, Favorites = 0x16, Program_Files = 0x26 }
public class NativeFileSystem
{
const int MAX_PATH = 50;
static bool m_sbExceptionsEnabled;
[DllImport("coredll.dll", SetLastError = true,
EntryPoint = "SHGetSpecialFolderPath")]
static extern bool SHGetSpecialFolderPath(int hwndOwner,
string lpszPath, Folders nFolder, bool fCreate);
NativeFileSystem()
{ }
public static string GetFolderPath(Folders folder)
{
string sPath = new string(' ', MAX_PATH);
bool bRet;
try
{
bRet = SHGetSpecialFolderPath(0, sPath, folder, false);
}
catch (Exception ex)
{
HandleCeError(ex, "GetFolderPath");
return null;
}
if (!bRet)
{
int errorNum = Marshal.GetLastWin32Error();
HandleCeError(new WinCeException("SHGetSpecialFolderPath " +
"returned false, likely an invalid constant", errorNum),
"GetSpecialFolderPath");
}
return sPath;
}
}
The HandleCeError
method throws a managed exception based on an error which occurred in the SHGetSpecialFolderPath
function call. It can look like this:
private static void HandleCeError(Exception ex, string method)
{
if (!ExceptionsEnabled)
{
return;
}
if (ex is NotSupportedException)
{
throw new WinCeException("Bad arguments or " +
"incorrect declaration in " + method, 0, ex);
}
if (ex is MissingMethodException)
{
throw new WinCeException("Entry point not found in " + method, 0, ex);
}
if (ex is WinCeException)
{
throw ex;
}
throw new WinCeException("Miscellaneous exception in " + method, 0, ex);
}
The sample application shows the list of special folders:
Get the names of all storage cards
Some devices may have several storage cards installed in them. What's even more important is that the storage cards don't necessarily have to be named \Storage Card. To iterate through all storage cards, we don't even need to use any Platform Invokes. The trick is to find all folders which have the Temporary
attribute set. The code is very simple:
static public List<string> GetStorageCardNames()
{
DirectoryInfo rootInfo = new DirectoryInfo("\\");
List<string> paths = new List<string>();
foreach (DirectoryInfo dirInfo in rootInfo.GetDirectories())
{
if ((dirInfo.Attributes & FileAttributes.Temporary) != 0)
{
//Debug.WriteLine("Directory Name: " + dirInfo.Name)
//Debug.WriteLine(" Full Name: " + dirInfo.FullName)
paths.Add(dirInfo.FullName);
}
}
return paths;
}
Conclusion
This concludes the first part of the article. All the source code along with a sample application can be downloaded at the beginning of this article or from the Bee Mobile web site at http://beemobile4.net/?mod=products&action=det&id=3. The whole source code is part of Bee Mobile's Free Utils project. The goal of Free Utils project is to put together source code which shows how to overcome some typical hurdles of Windows Mobile / Windows CE development in the .NET Compact Framework environment.
The second part of this article is available here.