Click here to Skip to main content
Click here to Skip to main content

Windows Mobile Programming Tricks on the .NET Compact Framework: Part 1

, 23 Feb 2011
Rate this:
Please Sign up or sign in to vote.
The article provides and describes some useful code snippets for Windows Mobile/CE developers.

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:

  1. Get the Device Context of the entire display
  2. Bit blit it to a prepared bitmap

The attached project also contains a sample application which shows how to use the code:

Form with an image and button. Form containing the screen shot of itself.

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)
{
    //Use a zeropointer to get hold of the screen context
    IntPtr hdcSrc = GetDC(IntPtr.Zero);

    Bitmap bmpCapture = new Bitmap(rectangle.Width, rectangle.Height);

    //Get graphics from bitmap
    using (Graphics grCapture = Graphics.FromImage(bmpCapture))
    {
        IntPtr hdcDest = grCapture.GetHdc();

        // Blit the image data
        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; // timer used to reset the system idle timer
    
    public ResetIdleTimer(int milisec)
    {
        m_Timer = new Timer();
        m_Timer.Interval = milisec; // in milliseconds
        m_Timer.Tick += new EventHandler(Timer_Tick);
    }
    
    /// <summary>
    /// Turns on the display and resets the idle timers.
    /// </summary>
    public static void ResetTimer()
    {
        SetSystemPowerState(null, POWER_STATE_ON, POWER_FORCE);
        SystemIdleTimerReset();
        SHIdleTimerReset();
    }
        
    /// <summary>
    /// Turns the backlight off.
    /// </summary>
    public static void TurnOffBackLight()
    {
        SetSystemPowerState(null, POWER_STATE_OFF, POWER_FORCE);
    }

    /// <summary>
    /// Call this method to keep the display lit.
    /// </summary>
    public void StartTimer()
    {
        m_Timer.Enabled = true;
    }
    
    /// <summary>
    /// Call this method to turn off
    /// the functionality provided by this class.
    /// </summary>
    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;

/// <summary>
/// Shows the SIP (Software Input Panel) button.
/// <summary>
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,           // \Windows\Start Menu\Programs
    Personal = 5,           // \My Documents
    Startup = 7,            // \Windows\StartUp
    Startmenu = 0x0B,       // \Windows\Start Menu
    Fonts = 0x14,           // \Windows\Fonts
    Favorites = 0x16,       // \Windows\Favorites
    Program_Files = 0x26    // \Program Files
}

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)
{
    // logging

    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Vladimir Gregor(Beemobile)
G&M Dynamics, s.r.o.
Slovakia Slovakia
I work for Bee Mobile.
 
Web site: http://beemobile4.net
Facebook site: http://facebook.com/BeeMobile
YouTube Channel: http://youtube.com/beemobile4dotnet
Follow on   Twitter

Comments and Discussions

 
Generalhelp about my project Pinmemberactroos22-Mar-11 23:19 
GeneralRe: help about my project PingroupBee Mobile22-Mar-11 23:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 23 Feb 2011
Article Copyright 2011 by Vladimir Gregor(Beemobile)
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid