Wednesday, December 5, 2007

SqlCeEngineEx - Extending the SqlCeEngine class

I use System.Data.SqlServer.SqlCeEngine() quite a lot in all my projects. I normally create the database on the fly when the application is launched for the first time and then I populate the initial data via a web service.

I often check if database objects exist before I create them. You can do this by querying the INFORMATION_SCHEMA views. I created a helper class called SqlCeEngineEx that contains the following methods for querying the INFORMATION_SCHEMA:

1) bool DoesTableExist(string table) - Checks if a table exists in the database
2) string[] GetTables() - Returns a string array of all the tables in the database
3) string[] GetTableConstraints(string table) - Returns a string array of all the constraints for a table
4) string[] GetTableConstraints() - Returns a string array of all the constraints in the database

And here is the full code:

public class SqlCeEngineEx : IDisposable
{
  private SqlCeEngine engine;

  public SqlCeEngineEx()
  {
   engine = new SqlCeEngine();
  }

  public SqlCeEngineEx(string connectionString)
  {
   engine = new SqlCeEngine(connectionString);
  }

  public bool DoesTableExist(string tablename)
  {
   bool result = false;

   using (SqlCeConnection conn = new SqlCeConnection(LocalConnectionString)) {
    conn.Open();
    using (SqlCeCommand cmd = conn.CreateCommand()) {
     cmd.CommandText =
      @"SELECT COUNT(TABLE_NAME)
       FROM INFORMATION_SCHEMA.TABLES
       WHERE TABLE_NAME=@Name"
;
     cmd.Parameters.AddWithValue("@Name", tablename);
     result = Convert.ToBoolean(cmd.ExecuteScalar());
    }
   }

   return result;
  }

  private string[] PopulateStringList(SqlCeCommand cmd)
  {
   List<string> list = new List<string>();

   using (SqlCeDataReader reader = cmd.ExecuteReader()) {
    while (reader.Read()) {
     list.Add(reader.GetString(0));
    }
   }

   return list.ToArray();
  }

  public string[] GetTables()
  {
   string[] tables;

   using (SqlCeConnection conn = new SqlCeConnection(LocalConnectionString)) {
    conn.Open();
    using (SqlCeCommand cmd = conn.CreateCommand()) {
     cmd.CommandText = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES";
     tables = PopulateStringList(cmd);
    }
   }

   return tables;
  }

  public string[] GetTableConstraints()
  {
   string[] constraints;

   using (SqlCeConnection conn = new SqlCeConnection(LocalConnectionString)) {
    conn.Open();
    using (SqlCeCommand cmd = conn.CreateCommand()) {
     cmd.CommandText = "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS";
     constraints = PopulateStringList(cmd);
    }
   }

   return constraints;
  }

  public string[] GetTableConstraints(string tablename)
  {
   string[] constraints;

   using (SqlCeConnection conn = new SqlCeConnection(LocalConnectionString)) {
    conn.Open();
    using (SqlCeCommand cmd = conn.CreateCommand()) {
     cmd.CommandText =
      @"SELECT CONSTRAINT_NAME
       FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
       WHERE TABLE_NAME=@Name"
;
     cmd.Parameters.AddWithValue("@Name", tablename);
     constraints = PopulateStringList(cmd);
    }
   }

   return constraints;
  }

  public string LocalConnectionString
  {
   get { return engine.LocalConnectionString; }
   set { engine.LocalConnectionString = value; }
  }

  public void Compact()
  {
   engine.Compact(null);
  }

  public void Compact(string connectionString)
  {
   engine.Compact(connectionString);
  }

  public void CreateDatabase()
  {
   engine.CreateDatabase();
  }

  public void Repair(string connectionString, RepairOption options)
  {
   engine.Repair(connectionString, options);
  }

  public void Shrink()
  {
   engine.Shrink();
  }

  public bool Verify()
  {
   return engine.Verify();
  }

  public void Dispose()
  {
   engine.Dispose();
   engine = null;
  }
}

Tuesday, November 27, 2007

Microsoft Development Center Copenhagen - TechFest 2007

I was at the Microsoft TechFest yesterday where we had an exhibition and a half hour presentation. I presented 2 solutions: TimeTracker which I mentioned in my previous blogs; And a successful customer project called the Mobile Inpsection Log which is designed for quality assurance and control for field workers.

The Mobile Inspection Log was fully tailored to the customers requirements. I did not design it for one handed navigation, I instead designed the mobile client to look similar to its existing desktop application counter part. I'll post some screen shots if I get permission from the customer and management.

After the presentation I was interviewed by MSDN Channel 9 where I gave an overview of what we do and what is next for us. I'll post a link to the Channel 9 interview once they publish it on their site.

During lunch break, we had the chance we watch a presentation from the Imagine Cup 2007 1st place winner in Software Design. These guys did an excellent job for a project called the LiveBook. Here's a link to it Team 3KC Returns/Project LiveBook!

Tuesday, November 20, 2007

Visual Studio 2008 Released!

Finally it's out!

http://msdn2.microsoft.com/en-us/vstudio/default.aspx


Now I can finally upgrade all my production code to the latest IDE!

Wednesday, October 24, 2007

Microsoft Dynamics Convergence 2007 - Copenhagen

It's been pretty interesting these past days where I've been hanging out at the Microsoft Dynamics Convergence 2007 Conference in Copenhagen. I had a chance to speak as a guest speaker for a session called "MOB102: Extending Your Microsoft Dynamics Solution Through Mobile Applications".

I also had the chance to show a demo of a new product that I have been working on lately. A Time, Material, and Mileage registration application called the TimeTracker. One of the key features that the crowd really loved was the one handed navigation or thumb navigation design. Of course the graphics, performance, and the integration with Microsoft Dynamics NAV was also a huge hit.

The last day of the conference is tomorrow and I'm looking forward to what tomorrow has to offer

Friday, October 5, 2007

Official Windows Mobile 6.0 Upgrade for HTC P3300

Finally, the Windows Mobile 6.0 Upgrade is released for the HTC P3300 device. You will have to sign up for the HTC e-Club though (which is free)

http://member.htc.com/member/login.aspx

Sunday, September 30, 2007

ButtonEx - Owner Drawn Button Control

Wow 2 posts in 1 day. I must have a lot of spare time today! Actually I'm down with a cold and I just feel sorry for myself when I stay in bed.

In this article I'd like to share another piece of code that I use quite often. Its a simple owner drawn button control that can have a nice 3D shadow. Drawing the 3D shadow effect was by complete accident though. I was trying to just draw a simple control that acts like a button. When I was drawing the borders the first time I drawn the rectangle a pixel bigger hence the control had a 1 pixel line of just black on the right and bottom side. Anyway, my graphic artist loved it and decided that we keep it.

The control has the following properties added:
  1) PushedColor (Color) - The color the control will be filled with once the MouseDown is fired
  2) DrawShadow (Boolean) - A flag whether to draw the 3D border

The default settings of the control is best on a form with a blue-ish background.

And here's the code:

public class ButtonEx : Control
{
  private Bitmap off_screen;
  private SolidBrush back_brush;
  private SolidBrush selected_brush;
  private SolidBrush text_brush;
  private Pen border_pen;
  private bool pushed;
  private bool shadow;

  public ButtonEx()
  {
    BackColor = Color.FromArgb(2, 32, 154);
    ForeColor = Color.White;

    back_brush = new SolidBrush(Color.FromArgb(48, 88, 198));
    selected_brush = new SolidBrush(Color.FromArgb(15, 51, 190));
    border_pen = new Pen(Color.White);
    text_brush = new SolidBrush(Color.White);
  }

  public override Color BackColor
  {
    get { return base.BackColor; }
    set
    {
      base.BackColor = value;
      back_brush = new SolidBrush(value);
    }
  }

  public override Color ForeColor
  {
    get { return base.ForeColor; }
    set
    {
      base.ForeColor = value;
      border_pen = new Pen(value);
      text_brush = new SolidBrush(value);
    }
  }

  public Color PushedColor
  {
    get { return selected_brush.Color; }
    set { selected_brush = new SolidBrush(value); }
    }

  public bool DrawShadow
  {
    get { return shadow; }
    set { shadow = value; }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (off_screen == null) {
      off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
    }

    using (Graphics gxOff = Graphics.FromImage(off_screen)) {
      gxOff.DrawRectangle(
        border_pen,
        0,
        0,
        ClientSize.Width - (shadow ? 0 : 1),
        ClientSize.Height - (shadow ? 0 : 1));

      Rectangle rect = new Rectangle(1, 1,
        ClientRectangle.Width - 2, ClientRectangle.Height - 2);

      gxOff.FillRectangle(
        pushed ? back_brush : selected_brush, rect);

      if (!string.IsNullOrEmpty(Text)) {
        SizeF size = gxOff.MeasureString(Text, Font);
        gxOff.DrawString(
          Text,
          Font,
          text_brush,
          (ClientSize.Width - size.Width) / 2,
          (ClientSize.Height - size.Height) / 2);
      }
    }

    e.Graphics.DrawImage(off_screen, 0, 0);
  }

  protected override void OnPaintBackground(PaintEventArgs e) { }

  protected override void OnMouseDown(MouseEventArgs e)
  {
    base.OnMouseDown(e);

    pushed = true;
    Invalidate();
  }

  protected override void OnMouseUp(MouseEventArgs e)
  {
    base.OnMouseUp(e);

    pushed = false;
    Invalidate();
  }

  protected override void OnTextChanged(EventArgs e)
  {
    base.OnTextChanged(e);

    Invalidate();
  }
}

Attaching Photos to a .NETCF Application

Sorry for not posting any articles lately. I've been quite busy and didn't have time to follow up on blogs.

Anyway, lately I've been working on a Mobile Inspection Log application for a customer. The application is rather simple and works like a small check list. The back-end server provides the mobile client with a list of Observation types where the User can set each observation to "OK", "Questionable", "Fail", and "N/A". If an Observation is questionable or failed then the user can attach a comment and a photo. The photo is then saved to a local database which can be synchronized with an access database on the users desktop computer or be sent to a web service.

The user has 2 options for attaching photos to their observation: 1) Opening an existing photo on the file system; 2) taking a photo themselves using the built-in camera. Since the one of the requirements for the application is Windows Mobile 6.0 Professional, my job got a lot easier with the Microsoft.WindowsMobile.Forms.CameraCaptureDialog and the Microsoft.WindowsMobile.Forms.SelectPictreDialog forms

Here's a small sample of how to use the CameraCaptureDialog:

string image_filename = null;
using (CameraCaptureDialog camera = new CameraCaptureDialog()) {
  camera.Owner = this;
  camera.Title = base.Text;
  camera.Mode = CameraCaptureMode.Still;
  camera.StillQuality = CameraCaptureStillQuality.High;

  if (camera.ShowDialog() == DialogResult.OK) {
    image_filename = camera.FileName;
  }
}

And for the SelectPictureDialog:

string image_filename = null;
using (SelectPictureDialog open = new SelectPictureDialog()) {
  open.Filter = "Pictures (*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF";

  if (open.ShowDialog() == DialogResult.OK) {
    image_filename = open.FileName;
  }
}

For saving to the local database I simply convert an Image to byte[]. I do this by:

public static byte[] ImageToByteArray(Image image, ImageFormat format) {
  MemoryStream stream = new MemoryStream();
  image.Save(stream, format);

  byte[] buffer = stream.GetBuffer();

  stream.Close();
  stream = null;

  return buffer;
}

or

public static byte[] ImageToByteArray(string image_file) {
  FileStream stream = new FileStream(image_file, FileMode.Open);
  int size = (int)stream.Length;
  byte[] buffer = new byte[size];
  stream.Read(buffer, 0, size);

  return buffer;
}

For loading image from the database to the application I simply convert the byte[] to an Image. I do that by:

public static Image ByteArrayToImage(byte[] raw_data) {
  MemoryStream stream = new MemoryStream(raw_data.Length);
  stream.Write(raw_data, 0, raw_data.Length - 1);

  Bitmap image = new Bitmap(stream);

  stream.Close();
  stream = null;

  return image;
}

Since each inspection report will contain around 40-50 observation, it can also take around 40-50 images. For synchronizing the application via web services, I had to split up the inspection report in parts, 1 part will contain all the textual information, and all the other parts will be for the photos. The Mobile Client Software Factory made life easier for having this architecture run on an occasionally connected environment.

Other things that are keeping me busy is integrating mobile applications with popular out-of-the-box ERP systems, such as Navision, Axapta, and Visma. I'll post more info and how-to's once I get more familiar with these systems.

Monday, July 23, 2007

Displaying the Calendar view on a DateTimePicker Control in .NETCF

I've recently made a solution where the customer requested to be able to bring up the calendar view in a DateTimePicker control by pressing on a specific button on the screen. The solution to that was really simple: Create a control that inherits from System.Windows.Forms.DateTimePicker and add a method called ShowCalendar() which I call to bring up the Calendar view.


public class DateTimePickerEx : DateTimePicker
{
  [DllImport("coredll.dll")]
  static extern int SendMessage(
    IntPtr hWnd, uint uMsg, int wParam, int lParam);

  const int WM_LBUTTONDOWN = 0x0201;

  public void ShowCalendar() {
    int x = Width - 10;
    int y = Height / 2;
    int lParam = x + y * 0x00010000;

    SendMessage(Handle, WM_LBUTTONDOWN, 1, lParam);
  }
}

Monday, July 16, 2007

Generic Multiple CAB File Installer for the Desktop

In this article I would like to share a nice and simple multiple CAB file installer application for the desktop that I have been using for a few years now. The tool can install up to 10 CAB files and is written in C and has less than a 100 lines of code.

Before we get started with installing multiple CAB files with a generic installer, let's try to go through the process of installing a CAB file from the desktop.

Installing a CAB file from the desktop to the mobile device is pretty simple. All you need to do is create a .INI file that defines the CAB files you wish to install and launch the Application Manager (CeAppMgr.exe) passing the .INI file (full path) as the arguments. Application Manager is included when installing ActiveSync or the Windows Mobile Device Center (Vista)

An Application Manager .INI file contains information that registers an application with the Application Manager. The .INI file uses the following format:

[CEAppManager]
Version = 1.0
Component = component_name

[component_name]
Description = descriptive_name
CabFiles = cab_filename [,cab_filename]

Here's an MSDN link for more details on Creating an .ini File for the Application Manager

Before launching the Application Manager, we should first make sure that it's installed. Once we know that then we get the location of the file. The easiest way to do this programmatically is to look into the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAppMgr.exe. If this key doesn't exist, then the Application Manager is not installed.

The next step is quite interesting yet very simple. In this installer application, I use a configuration file called Setup.ini which defines which CAB files I want to install. Setup.ini will look like this:

[CabFiles]
CABFILE1 = MyApp1.ini
CABFILE2 = SQLCE.ini
CABFILE3 = NETCF.ini
CABFILE4 =
CABFILE5 =
CABFILE6 =
CABFILE7 =
CABFILE8 =
CABFILE9 =
CABFILE10 =

You can easily modifiy the code to install more than 10 CAB files if you think it's appropriate. I used GetPrivateProfileString() to read the values from my configuration file.

Setup.ini, the .INI files, and the actual CAB files are required to be in the same directory as the generic installer.

Ok, now we have Application Manager command-line arguments ready, we now just have to launch it. I used CreateProcess() to launch the application manager and used WaitForSingleObject() to wait for the process to end.

Here's the full source code:

[C CODE]

#include "windows.h"
#include "tchar.h"

#define CE_APP_MGR TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\CEAppMgr.exe")

LPTSTR GetParameters()
{
  TCHAR szParams[2048];
  szParams[0] = TCHAR(0);

  TCHAR szCurrDir[MAX_PATH];
  GetCurrentDirectory(MAX_PATH, szCurrDir);

  for (int i = 1; i < 11; i++) {
    TCHAR buffer[16];
    TCHAR szKey[16];

    strcpy(szKey, TEXT("CABFILE"));
    itoa(i, buffer, 16);
    strcat(szKey, buffer);

    TCHAR szSetupIni[MAX_PATH];
    strcpy(szSetupIni, szCurrDir);
    strcat(szSetupIni, TEXT("\\"));
    strcat(szSetupIni, TEXT("Setup.ini"));

    TCHAR szCabFile[MAX_PATH];
    ::GetPrivateProfileString(TEXT("CabFiles"), szKey,
      (TCHAR*)"", szCabFile, sizeof(szCabFile), szSetupIni);

    if (0 != strcmp(szCabFile, (TCHAR*)"")) {
      strcat(szParams, TEXT(" \""));
      strcat(szParams, szCurrDir);
      strcat(szParams, TEXT("\\"));
      strcat(szParams, szCabFile);
      strcat(szParams, TEXT(" \""));
    }
  }

  return szParams;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  HKEY hkey;
  DWORD dwDataSize = MAX_PATH;
  DWORD dwType = REG_SZ;
  TCHAR szCEAppMgrPath[MAX_PATH];

  if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, CE_APP_MGR, 0, KEY_READ, &hkey)) {
    if(ERROR_SUCCESS != RegQueryValueEx(hkey, NULL, NULL,
      &dwType, (PBYTE)szCEAppMgrPath, &dwDataSize))
    {
      MessageBox(NULL, TEXT("Unable to find Application Manager for Pocket PC Applications"),
        TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
      return 1;
    }
  } else {
    MessageBox(NULL, TEXT("Unable to find Application Manager for Pocket PC Applications"),
      TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
    return 1;
  }


  RegCloseKey(hkey);

  STARTUPINFO startup_info = {0};
  PROCESS_INFORMATION pi = {0};
  startup_info.cb = sizeof(startup_info);

  if (CreateProcess(szCEAppMgrPath, GetParameters(), NULL,
    NULL, FALSE, 0, NULL, NULL, &startup_info, &pi))
  {
    WaitForSingleObject(pi.hProcess, INFINITE);
  } else {
    MessageBox(NULL, TEXT("Unable to Launch Application Manager for Pocket PC Applications"),
      TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
    return 2;
  }

  return 0;
}


When deplying my applications in this manner, I like packaging them in a self-extracting zip file that is configured for software installation. I'm still holding to an old version of Winzip Self-Extractor to accomplish this task.

Another reason I use WaitForSingleObject is because a self-extracting zip installer file deletes the all the temporary files it has extracted once the installer process ends. This means that your .INI and CAB files will be deleted even before they get copied to the device.

Monday, July 9, 2007

Accessing Windows Mobile 6.0 Sound API's through .NETCF

A new set of APIs were introduced in Windows Mobile 6 to make it easier to manage and play sound. The new API's support playing sounds in a variety of formats that Windows Media Player supports

These API's are really easy to use. You can play a sound file with a single function call. Let's try to do that through .NETCF by P/Invoking SndPlaySync(LPCTSTR pszSoundFile, DWORD dwFlags).


[DllImport("aygshell.dll")]
static extern uint SndPlaySync(string pszSoundFile, uint dwFlags);

void PlaySound() {
  SndPlaySync("\\Storage Card\\Intro.mp3", 0);
}


In the previous sample, we are playing a sound file synchronously. Now, this is interesting in a way that its very very easy to play an audio file. But what really gets interesting is that the new Sound API provides methods for playing sound files asynchronously.

To play audio files asynchronously, we will need to call 4 methods from the Sound API.

  SndOpen(LPCTSTR pszSoundFile, HSOUND* phSound)
  SndPlayAsync(HSOUND hSound, DWORD dwFlags)
  SndClose(HSOUND hSound)
  SndStop(SND_SCOPE SoundScope, HSOUND hSound)

Let's start by declare our P/Invokes


[DllImport("aygshell.dll")]
static extern uint SndOpen(string pszSoundFile, ref IntPtr phSound);

[DllImport("aygshell.dll")]
static extern uint SndPlayAsync(IntPtr hSound, uint dwFlags);

[DllImport("aygshell.dll")]
static extern uint SndClose(IntPtr hSound);

[DllImport("aygshell.dll")]
static extern uint SndStop(int SoundScope, IntPtr hSound);


Now that we have our P/Invokes ready. Let's start playing with the Sound API in .NETCF. In the sample below, the application will play the audio file Intro.mp3 located in the Storage Card. To play an Audio file asynchronously, we will first need a handle to the audio file. We use SndOpen(string, IntPtr) to accomplish that. Once we have the handle to the audio file, we can call SndPlayAsync(IntPtr, int) to start playing the audio file. To stop playing the audio we just have to close the handle and call SndStop(SND_SCOPE_PROCESS, IntPtr.Zero) to stop the playback of the sound.


IntPtr hSound = IntPtr.Zero;
const string AUDIO_FILE = "\\Storage Card\\Intro.mp3";
const int SND_SCOPE_PROCESS = 0x1;

void Play() {
  SndOpen(AUDIO_FILE, ref hSound);
  SndPlayAsync(hSound, 0);
}

void Stop() {
  SndClose(hSound);
  SndStop(SND_SCOPE_PROCESS, IntPtr.Zero);
}


How cool is that? You can now easily add some cool sound effects to your application. Maybe even use the Sound API for one of those annoying startup sounds!

Friday, June 29, 2007

Saturday, June 16, 2007

Copying Files from the Device to the Desktop using .NET

Recently, I’ve been working on a tool that creates a backup of a SQL Server Compact Edition database on the device to the desktop. To accomplish this, I used the Remote API (RAPI). Unfortunately, the Remote API is not yet available in managed code. In this article I would like to demonstrate how to P/Invoke methods from the Remote API for copying files from the device to the desktop using managed code.

First, we’ll need some P/Invokes to rapi.dll

[DllImport("rapi.dll")]
static extern int CeRapiInit();

[DllImport("rapi.dll")]
static extern int CeRapiUninit();

[DllImport("rapi.dll")]
static extern int CeCloseHandle(IntPtr hObject);

[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CeCreateFile(
  string lpFileName,
  uint dwDesiredAccess,
  int dwShareMode,
  int lpSecurityAttributes,
  int dwCreationDisposition,
  int dwFlagsAndAttributes,
  int hTemplateFile);

[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
static extern int CeReadFile(
  IntPtr hFile,
  byte[] lpBuffer,
  int nNumberOfbytesToRead,
  ref int lpNumberOfbytesRead,
  int lpOverlapped);

const int ERROR_SUCCESS = 0;
const int OPEN_EXISTING = 3;
const int INVALID_HANDLE_VALUE = -1;
const int FILE_ATTRIBUTE_NORMAL = 0x80;
const uint GENERIC_READ = 0x80000000;


Now let’s create a method called CopyFromDevice(string remote_file, string local_file). The remote_file parameter is the source file on the device that you wish to copy. The local_file parameter is the destination filename on the desktop.

public static void CopyFromDevice(string remote_file, string local_file)
{
  bool rapi = CeRapiInit() == ERROR_SUCCESS;
  if (!rapi) {
    return;
  }

  IntPtr remote_file_ptr = CeCreateFile(
    remote_file,
    GENERIC_READ,
    0,
    0,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    0);

  if (remote_file_ptr.ToInt32() == INVALID_HANDLE_VALUE) {
    return;
  }

  FileStream local_file_stream = new FileStream(
    local_file,
    FileMode.Create,
    FileAccess.Write);

  int read = 0;
  int size = 1024 * 4;
  byte[] data = new byte[size];

  CeReadFile(remote_file_ptr, data, size, ref read, 0);

  while (read > 0) {
    local_file_stream.Write(data, 0, read);
    if (CeReadFile(remote_file_ptr, data, size, ref read, 0) == 0) {
      CeCloseHandle(remote_file_ptr);
      local_file_stream.Close();
      return;
    }
  }

  CeCloseHandle(remote_file_ptr);
  local_file_stream.Flush();
  local_file_stream.Close();

  if (rapi) {
    CeRapiUninit();
  }

  if (!File.Exists(local_file)) {
    throw new FileNotFoundException("The file was not copied to the desktop");
  }
}

To use the code above you will have to know the full path of the file on the device. The way I did it was to read the registry on the device and check if my application was installed, if it was then I get the path of the application and pass as the path in my remote_file parameter.

Tuesday, June 12, 2007

Retrieving the Icon Image within the System Image List in .NETCF 2.0

Here's a nice trick for retrieving the icon image of a file or folder from the system image list. All we actually need is to P/Invoke SHGetFileInfo and use Icon.FromHandle() to get the Icon.

First, we need to declare our P/Invokes.

[StructLayout(LayoutKind.Sequential)]
struct SHFILEINFO
{
  public IntPtr hIcon;
  public IntPtr iIcon;
  public uint dwAttributes;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
  public string szDisplayName;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
  public string szTypeName;
}

const uint SHGFI_ICON = 0x000000100;
const uint SHGFI_LARGEICON = 0x000000000;
const uint SHGFI_SMALLICON = 0x000000001;
const uint SHGFI_SELECTICON = 0x000040000;

[DllImport("coredll.dll")]
static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes,
  ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);


To get an instance of System.Drawing.Icon for the small icon of a file

Icon GetSystemIconSmall(string file)
{
  SHFILEINFO shinfo = new SHFILEINFO();
  IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
    (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);

  return Icon.FromHandle(shinfo.hIcon);
}


For the large icon of a file

Icon GetSystemIconLarge(string file)
{
  SHFILEINFO shinfo = new SHFILEINFO();
  IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
    (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);

  return Icon.FromHandle(shinfo.hIcon);
}


For the small icon of a file when it is selected

Icon GetSystemIconSmallSelected(string file)
{
  SHFILEINFO shinfo = new SHFILEINFO();
  IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
    (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_SELECTICON);

  return Icon.FromHandle(shinfo.hIcon);
}


And last for the large icon of a file when it is selected

Icon GetSystemIconLargeSelected(string file)
{
  SHFILEINFO shinfo = new SHFILEINFO();
  IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
    (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SELECTICON);

  return Icon.FromHandle(shinfo.hIcon);
}

Ok, now how is this helpful? Well if you want to implement a File Explorer-ish control, then wouldn't have to include Icons and other images in your application. You can just use the icons in the system image list

Saturday, June 9, 2007

Logging Unhandled Exceptions

Bugs are sometimes unavoidable. They're best caught during the development or testing phase. There might be some cases where the developer forgot to handle possible exceptions in a function. It could be possible that this exception isn't handled anywhere at all. But even so, it is still possible to catch this exception. To do this we handle the UnhandledException event of the current AppDomain. We should do this in our static void Main() before calling Application.Run([Main Form])

Here's a small snippet to accomplish this task.

[C# CODE]

static void Main()
{
  AppDomain.CurrentDomain.UnhandledException +=
    new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
  Application.Run(new MainForm());
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
  Exception exception = (Exception)e.ExceptionObject;
  if (exception != null) {
    Error.Append(exception.Message, exception.StackTrace);
  }
}

Now we need a mechanism for saving to a Error log file. Let's create a simple class called Error() and add a function called Append(string message, string stacktrace)

[C# CODE]

public class Error
{
  internal static void Append(string message, string stacktrace)
  {
    string file = string.Format("{0}\\Errors.txt",
      Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase));
    if (File.Exists(file)) {
      FileInfo fi = new FileInfo(file);
      if (fi.Length > 100 * 1024) {
        fi.Delete();
      }
    }
    StreamWriter sw = new StreamWriter(file, true, Encoding.UTF8);
    sw.WriteLine(string.Format(
      "-=-=-=-=-=--=-=-\n{0}\nMESSAGE:\n{1}\nSTACK TRACE:\n{3}\n",
        DateTime.Now, message, stacktrace));
    sw.Close();
    }
  }
}

The Error() class should be put in a namespace that is accessible throughout the application. This will be very helpful tool for finding those nasty almost impossible to reproduce bugs.

Thursday, June 7, 2007

Integrating with TomTom Navigator

PDA's are used for pretty much everything these days. From the bunch of devices the I work (play) with, I took a great liking to devices that have built in GPS receivers. These devices are usually bundled with really cool navigation software from various vendors. Some of these navigation software have SDK's that you can buy separately. By using these SDK's, you can fully integrate navigation features to your mobile solutions.

In this article, I would like to discuss how to integrate a .NET Compact Framework application with TomTom Navigator. I will also demonstrate an example of making a generic navigator wrapper so your application is not just bound to one kind of navigation software.

Before we get started, we need to have the TomTom Navigator SDK. Unfortunately this is not free, but can be easily purchased from the TomTom Pro website.

Before we dig into more detail, let's go through our software requirements. We need the following:

    1. Visual Studio 2005
    2. Windows Mobile 5.0 SDK for Pocket PC
    3. A device running Windows Mobile 5.0 with TomTom Navigator 5 installed
    4. The TomTom Navigator SDK
    5. ActiveSync 4.2 or higher (for Vista, the Mobile Device Center)


Now, Lets get started...


Here is what we need to make:

    1. native wrapper for the TomTom SDK (native dll)
    2. generic navigator wrapper in .NET CF
    3. managed TomTom wrapper
    4. device application that will call TomTom SDK wrapper methods

Sounds pretty simple doesn't it?


1. Native Wrapper for the TomTom SDK

We will first need a little help from native code to access the TomTom SDK. We cannot access the TomTom SDK directly from .NET due to the architecture of the SDK. We have to wrap around the TomTom SDK C++ classes and methods and expose them as C type functions.

In your native wrapper, lets say we want to wrap the following TomTom SDK functions:
  - GetApplicationVersion(TError* err, TVersion* ver)
  - FlashMessage(TError* err, char* msg, int ms)
  - NavigateToAddress(TError* aError, char* aCity, char* aStreet, char* aHouseNr, char* aPostcode)


[C++ CODE]

#include "sdkconstants.h"
#include "TomTomAPI.h"
#include "TomTomGoFileLayer.h"

#define CLIENT_NAME "client"

CTomTomAPI::TError err;
int res = 0;

BOOL APIENTRY DllMain(
  HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }
  return TRUE;
}

extern "C" __declspec(dllexport) int TTN_GetApplicationVersion(
  int* iError, LPTSTR szVersion, int *iBuildNumber )
{
  MTomTomCommunicationLayerInterface *comms =
    DEFAULT_TRANSPORTATION_LAYER(CLIENT_NAME,2005,TOMTOM_TCPIP_PORT);

  CTomTomAPI api(*comms);
  CTomTomAPI::TVersion version;
  res = api.GetApplicationVersion(&err, &version);
  *iError = err.iError;

  TCHAR str[16];
  _stprintf(str, TEXT("%S"), version.iVersion);
  lstrcpy( szVersion, (LPTSTR)str );
  *iBuildNumber = version.iBuildNumber;

  delete comms;
  return res;
}

extern "C" __declspec(dllexport) int TTN_FlashMessage(
   int* iError, char* aMessage, int aMilliSeconds )
{
  char message[256];
  sprintf(message, "%S", aMessage);

  MTomTomCommunicationLayerInterface *comms =
    DEFAULT_TRANSPORTATION_LAYER(CLIENT_NAME,2005,TOMTOM_TCPIP_PORT);

  CTomTomAPI api(*comms);
  res = api.FlashMessage(&err, message, aMilliSeconds);
  *iError = err.iError;

  delete comms;
  return res;
}

extern "C" __declspec(dllexport) int TTN_NavigateToAddress(
   int* iError, char* aCity, char* aStreet, char* aHouseNr, char* aPostcode )
{
  char city[256];
  char street[256];
  char houseNr[16];
  char postcode[32];

  sprintf(city, "%S", aCity);
  sprintf(street, "%S", aStreet);
  sprintf(houseNr, "%S", aHouseNr);
  sprintf(postcode, "%S", aPostcode);

  MTomTomCommunicationLayerInterface *comms =
    DEFAULT_TRANSPORTATION_LAYER(CLIENT_NAME,2005,TOMTOM_TCPIP_PORT);

  CTomTomAPI api(*comms);
  res = api.NavigateToAddress(&err, city, street, houseNr, postcode);
  *iError = err.iError;

  delete comms;
  return res;
}

Let's set the output of the project to be called TTSDK.dll

2. Generic Navigator Wrapper in .NET CF

Once we've gotten our native wrapper up and running, we create a generic navigator wrapper. We start off by creating a smart device class library project. Once the project is created, add the following classes: INavigator.cs, Navigator.cs, and Common.cs

Lets go and define the common objects we want to use in Common.cs

[C# CODE]

public struct NVersion {
  string Version;
  int BuildNumber;
}

INavigator.cs will be an interface defining the how the wrapper will look like. Lets add methods for the 3 TomTom SDK methods we want to use.

[C# CODE]

public interface INavigator
{
  NVersion GetApplicationVersion();
  void FlashMessage(string text, int duration);
  void NavigateToAddress(string city, string street, string houseno, string zipcode);
}

Navigator.cs will be the class your application will call. This will load the managed TomTom wrapper as an instance of INavigator. Navigator itself will implement INavigator and will return calls from the TomTom wrapper.

[C# CODE]

public class Navigator : INavigator
{
  private INavigator instance;

  public Navigator(string typeName)
  {
    Type type = Type.GetType(typeName);
    if (type == null) {
      throw new TypeLoadException();
    } else {
      instance = (INavigator)Activator.CreateInstance(type);
      if (instance == null) {
        throw new TypeLoadException();
      }
    }
  }

  public TVersion GetApplicationVersion()
  {
    return instance.GetApplicationVersion();
  }

  public void FlashMessage(string text, int duration)
  {
    instance.FlashMessage(text, duration);
  }

  public void NavigateToAddress(string city, string street, string houseno, string zipcode)
  {
    instance.NavigateToAddress(city, street, houseno, zipcode);
  }
}

The default constructor for Navigator accepts a type name. The format for type name is "[Namespace].[ClassName], [AssemblyName]"

3. Managed TomTom Wrapper

Here we create a new smart device class library. Once the project is created, add a reference to the generic navigator wrapper since we will implement the INavigator interface and add a class called TomTom.cs

Lets implement TomTom.cs as INavigator

[C# CODE]

[DllImport("TTSDK.dll", EntryPoint="TTN_GetApplicationVersion")]
internal static extern int TTN_GetApplicationVersion(
  ref int iError, StringBuilder szVersion, ref int iBuildNumber);

[DllImport("TTSDK.dll", EntryPoint="TTN_FlashMessage")]
internal static extern int TTN_FlashMessage(
  ref int iError, string aMessage, int aMilliseconds);

[DllImport("TTSDK.dll", EntryPoint="TTN_NavigateToAddress")]
internal static extern int TTN_NavigateToAddress(
  ref int iError, string aCity, string aStreet, string aHouseNo, string aPostcode);

public void FlashMessage(string aMessage, int aMilliseconds)
{
  if (0 != TTN_FlashMessage(ref iError, aMessage, aMilliseconds)) {
    throw new InvalidOperationException();
  }
}

public NVersion GetApplicationVersion()
{
  NVersion version = new TVersion();
  StringBuilder szVersion = new StringBuilder();
  int iBuildNumber = 0;
  int iError = 0;

  if (0 != TTN_GetApplicationVersion(ref iError, szVersion, ref iBuildNumber)) {
    throw new InvalidOperationException();
  } else {
    version.iVersion = szVersion.ToString();
    version.iBuildNumber = iBuildNumber;
  }
}

public void NavigateToAddress(string sCity, string sStreet, string sHouseNo, string sPostcode)
{
  int iError = 0;

  if (0 != TTN_NavigateToAddress(ref iError, sCity, sStreet, sHouseNo, sPostcode)) {
    throw new InvalidOperationException();
  }
}

Now our TomTom wrapper is pretty much ready

4. Device Application

In our application, we want to integrate with TomTom Navigator for navigating to a specific address. The address can could be retrieved from a web service, or stored in your pocket outlook. For this article, we're going to retrieve address information of a customer from Pocket Outlook.

In order to do this, we will need the Windows Mobile 5.0 SDK for Pocket PC to be installed. Let's start off by creating a Windows Mobile 5.0 device application project. Once the project is created, add a reference to the Navigator wrapper and the TomTom wrapper. Next we have to build the Native wrapper project, and add the output file TTSDK.dll to our project. Set TTSDK.dll to be "Copied if Newer". To retrieve address information from contacts, we must add a reference to Microsoft.WindowsMobile.PocketOutlook.dll.

Once the references and files are in place, we can start adding some code to Form1.cs. No need to change the name of the main form since this is only a small demo. We need to have a control that can contain the contacts, lets use the ComboBox control for now. Add a ComboBox control to the form and call it cbContacts. Lets add a "Navigate to" button to the form as well and call it btnNavigate.

To retrieve a list of contacts we need to create a global instance of Microsoft.WindowsMobile.PocketOutlook.OutlookSession and Microsoft.WindowsMobile.PocketOutlook.ContactsCollection, once we instantiate our OutlookSession, we can then retrieve a list of Contacts through OutlookSession.Contacts.Items.

To communicate with TomTom, we create an instance of Navigator(). The default constructor for Navigator will need a typeName for loading the TomTom wrapper as INavigator. It would be a smart idea to store the typeName in a seperate file, text or xml would be perfect. Once again, we do this so that if we want our application to integrate with different navigation software, we don't have to re-write everything. In this demo, the typeName will just be a hard coded string constant.

[C# CODE]

private Microsoft.WindowsMobile.PocketOutlook.OutlookSession session;
private Microsoft.WindowsMobile.PocketOutlook.ContactsCollection contacts;

private const string TYPENAME="[The namespace].[The class name], [The assembly name]";
private Navigator navigator;

public Form1()
{
  InitializeComponent();

  btnNavigate.Click += new EventHandler(btnNavigate_Click);
  Closing += new CancelEventHandler(Form1_Closing);

  navigator = new Navigator(TYPENAME);

  string restriction = "[BusinessAddressStreet] <> \" \" OR [HomeAddressStreet] <> \" \"";
  session = new OutlookSession();
  contacts = session.Contacts.Items.Restrict(restriction);
  cbContacts.DataSource = contacts;
}

private void Form1_Closing(object sender, CancelEventArgs e)
{
  contacts.Dispose();
  contacts = null;

  session.Dispose();
  session = null;
}

private void btnNavigate_Click(object sender, EventArgs e)
{
  Contact contact = cbContacts.SelectedItem as Contact;
  navigator.FlashMessage("Navigating...", 1500);
  navigator.NavigateToAddress(contact.BusinessAddressCity,
    contact.BusinessAddressStreet,
    "PARSE THE HOUSE NUMBER...",
    contact.BusinessAddressPostalCode);
}

When the application launches, your pocket outlook contacts that have a valid address will be loaded into our ComboBox control. Let's select an item in the ComboBox. Once you click on the Navigate button it will launch TomTom Navigator and display the message "Navigating" for 1.5 seconds, after that it will start calculating the route from your current location to your destination (in this case, the selected contact).

That wasn't too hard was it?

Wednesday, June 6, 2007

Programmatically Minimize an Application in .NET CF 2.0

I once made a solution that runs on full screen. The solution was written completely in managed code (except for the CE Setup and other small stuff..). Since I took over the screen completely, I don't have access to the (X) button in the upper right corner of the screen. I wanted the application running at all times, but I also wanted the user to be able to get in and out of the application. Since the user won't be able to access the Start button, I added a "Close" button to my application. This "Close" button won't exit the application, instead it will just Minimize the application.

In the .NET Compact Framework 2.0, you can't just set the form's WindowState to WindowState.Minimized since the WindowState enum only contains Normal and Maximized. Currently, the only way you can programmatically minimize an application is by doing a P/Invoke to ShowWindow and passing SW_MINIMIZE to specify how the window will be displayed. It is also required that your Form has the Taskbar visible, this is done by setting the following properties:

  FormBorderStyle = FormBorderStyle.FixedDialog;
  WindowState = FormWindowState.Normal;
  ControlBox = true;
  MinimizeBox = true;
  MaximizeBox = true;

Here's a small code snippet of how to minimize your application

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

const int SW_MINIMIZED = 6;

void Minimize() {
  // The Taskbar must be enabled to be able to do a Smart Minimize
  this.FormBorderStyle = FormBorderStyle.FixedDialog;
  this.WindowState = FormWindowState.Normal;
  this.ControlBox = true;
  this.MinimizeBox = true;
  this.MaximizeBox = true;

  // Since there is no WindowState.Minimize, we have to P/Invoke ShowWindow
  ShowWindow(this.Handle, SW_MINIMIZED);
}

Programmatically Refreshing the Today Screen

A simple trick for forcing the today screen to re-read from the registry (or refresh) is by sending the message WM_WININICHANGE with the parameter 0x000000F2 to a window called the DesktopExplorerWindow.

Here's a small code snippet on how to accomplish this programmatically:

void RefreshTodayScreen() {
  HWND hWnd = FindWindow(_T("DesktopExplorerWindow"), _T("Desktop"));
  SendMessage(hWnd, WM_WININICHANGE, 0x000000F2, 0);
}

and in managed code...

[DllImport("coredll.dll")]
static extern IntPtr FindWindow(string class_name, string caption);

[DllImport("coredll.dll")]
static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

const uint WM_WININICHANGE = 0x1a;

void RefreshTodayScreen() {
  IntPtr hWnd = FindWindow("DesktopExplorerWindow", "Desktop");
  SendMessage(hWnd, WM_WININICHANGE, 0x000000F2, 0);
}

Monday, June 4, 2007

Querying Overridden Check-in Policies

In the Team Foundation Server, you can enable certain policies for checking in files. The default install will contain policies that can verify that the check-in is associated with a work item, or that unit tests were created for the changes made, etc etc. But even though certain rules were made for checking in, the user is still given the possibility to override these policies, if the user decides to override the policy then the user is prompted with a dialog, where the user can input their "reason" for ignoring such policies. This action is logged to the TFS databases.

In this article I will show you how to query the database "TfsVersionControl" and "TfsWarehouse" to get more information for the overridden check-in policy.

let's start off with openning a query to the database server that TFS uses then type in the following query:

SELECT
    'Changeset ID'=p.ChangeSetId,
    'Creation Date'=cs.CreationDate,
    'Check-in Comment'=cs.Comment,
    'Override Reason'=p.Comment,
    'Owner'=pr.Person,
    'Email'=pr.Email
FROM
    TfsVersionControl..tbl_PolicyOverride p
INNER JOIN
    TfsVersionControl..tbl_ChangeSet cs ON p.ChangeSetId=cs.ChangeSetId
INNER JOIN
    TfsVersionControl..tbl_Identity i ON cs.OwnerId=i.IdentityId
INNER JOIN
    TfsWarehouse..Person pr ON i.DisplayName=(pr.Domain+'\'+pr.Alias)
ORDER BY
    cs.ChangeSetId DESC

This query will provide you with the change set number, date, check-in comment, override reason, change set owner and email address for every overridden check-in policy.

My first ever blog post

After being asked numerous times why I don't have a blog, I finally decided that maybe I should have one.

For a short introduction:

My name is Christian Resma Helle, I was born and raised in the Philippines. I currently reside and work in Copenhagen, Denmark. I work as a Developer/Consultant specializing in Windows Mobile based solutions and have taken an interest in the Visual Studio Team System.

My blog would probably contain technical articles regarding Windows Mobile development and the VSTS.