Commentor Blog

When Quality Matters

Commentor A/S

When Quality Matters

Contact usSend mail

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

ListView Extended Styles in .NETCF

In this article I would like to demonstrate how to extend the ListViewcontrol in the .NET Compact Framework. We will focus on enabling someof the ListView Extended Styles. If we take a look at the WindowsMobile 5.0 Pocket PC SDK we will see that there are certain features ofListView that aren't provided by the .NET Compact Framework.

Anexample of the ListView extended styles is displaying gridlines arounditems and subitems, double buffering, and drawing a gradientbackground. These extended styles can be enabled in native code byusing the ListView_SetExtendedListViewStyle macro or by sendingLVM_SETEXTENDEDLISTVIEWSTYLE messages to the ListView.

Send Message

Wewill be using a lot of P/Invoking so let's start with creating aninternal static class called NativeMethods. We need a P/Invokedeclaration for SendMessage(HWND, UINT, UINT, UINT).

internal static class NativeMethods
{
  [DllImport("coredll.dll")]
  public static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
}

Enabling and Disabling Extended Styles

Nowthat we have our SendMessage P/Invoke declaration in place, we canbegin extending the ListView control. Let's start off with creating aclass called ListViewEx that inherits from ListView. We need to lookinto the native header files of the Pocket PC SDK to get the ListViewMessages. For now we will only need LVM_[GET/SET]EXTENDEDLISTVIEWSTYLEmessage which will be the main focus of all the examples. I willdeclare my class as a partial class and create all the pieces one byone for each example. Let's create a private method called SetStyle(),this method will enable/disable extended styles for the ListView

public partial class ListViewEx : ListView
{
  private const uint LVM_FIRST = 0x1000;
  private const uint LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54;
  private const uint LVM_GETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 55;

  private void SetStyle(uint style, bool enable)
  {
    uint currentStyle = NativeMethods.SendMessage(
      Handle,
      LVM_GETEXTENDEDLISTVIEWSTYLE,
      0,
      0);

    if (enable)
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle | style);
    else
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle & ~style);
  }
}

Grid Lines

Formy first example, let's enable GridLines in the ListView control. Wecan do this by using LVS_EX_GRIDLINES. This displays gridlines arounditems and sub-items and is available only in conjunction with theDetails mode.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRIDLINES = 0x00000001;

  private bool gridLines = false;
  public bool GridLines
  {
    get { return gridLines; }
    set
    {
      gridLines = value;
      SetStyle(LVS_EX_GRIDLINES, gridLines);
    }
  }
}

Whatthe code above did was add the LVS_EX_GRIDLINES style to the existingextended styles by using the SetStyle() helper method we first created.

Aninteresting discovery to this is that the Design Time attributes of theCompact Framework ListView control includes the GridLines property. Nowthat we created the property in the code, when we open the VisualStudio Properties Window for our ListViewEx we will notice thatGridLines property we created falls immediately under the "Appearance"category and even includes a description :)

Double Buffering

Doyou notice that when you populate a ListView control with a lot ofitems, the drawing flickers a lot when you scroll up and down the list?Although it is not in the Pocket PC documentation for Windows Mobile5.0, the ListView actually has an extended style calledLVS_EX_DOUBLEBUFFER. Enabling the LVS_EX_DOUBLEBUFFER solves theflickering issue and gives the user a more smooth scrolling experience.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_DOUBLEBUFFER = 0x00010000;

  private bool doubleBuffering = false;
  public bool DoubleBuffering
  {
    get { return doubleBuffering; }
    set
    {
      doubleBuffering = value;
      SetStyle(LVS_EX_DOUBLEBUFFER, doubleBuffering);
    }
  }
}

Gradient Background

Anothercool extended style is the LVS_EX_GRADIENT. This extended style draws agradient background similar to the one found in Pocket Outlook. It usesthe system colors and fades from right to left. But what is really coolabout this is that this is done by the OS. All we had to do was enablethe style.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRADIENT = 0x20000000;

  private bool gradient = false;
  public bool Gradient
  {
    get { return doubleBuffering; }
    set
    {
      gradient = value;
      SetStyle(LVS_EX_GRADIENT, gradient);
    }
  }
}

Ifyou want to look more into extended styles then I suggest you check outthe Pocket PC Platform SDK documentation. There a few other extendedstyles that I did not discuss that might be useful for you. You can getthe definitions in a file called commctrl.h in your Windows Mobile SDK"INCLUDE" directory.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, October 30, 2008 7:25 AM
Permalink | Comments (2) | Post RSSRSS comment feed

Unit Testing for Smart Devices Webcast

On the 28th of February 2008, Microsoft Denmark will have the first and largest online launch for Windows Server 2008, SQL Server 2008, and Visual Studio 2008. We made a few webcasts related to the products and technologies to be released. Here's one that I made entitled "Unit Testing for Smart Devices"

http://blogs.commentor.dk/downloads/smart_device_unit_testing.wmv

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, February 21, 2008 2:44 AM
Permalink | Comments (2) | Post RSSRSS comment feed

Integrating with Garmin Mobile XT

Half a year ago, I wrote an article about Integrating with TomTom Navigator. This time I'm gonna discuss how you can integrate a .NET Compact Framework application with the Garmin Mobile XT navigation software. The process is a bit similar to integrating with TomTom because the Garmin SDK only provides a native API.

Before we get started, we need to have the Garmin Mobile XT for the Windows Mobile platform. Unlike TomTom's SDK, Garmin's SDK is available free of charge for download.

Before we can get more into detail, we will need the following:
1) Visual Studio 2005 or 2008
2) Windows Mobile 5.0 SDK for Pocket PC
3) A windows mobile device a GPS receiver and the Garmin Mobile XT (and Maps)
4) Garmin Mobile XT SDK for the Windows Mobile platform

We will be making the same projects we made for Integrating with TomTom Navigator:
1) Native wrapper for the Garmin XT SDK
2) Managed Garmin XT SDK wrapper
3) Device application that will call the Garmin XT SDK wrapper methods


Let's get started...


Native wrapper for the Garmin XT SDK

The Garmin SDK ships with C++ header files and a static library that a native application can link to. For that reason we need to create a native DLL that exposes the methods that we need as C type funtions. Let's call this Garmin.Native.dll.

In this article, we will implement a managed call to the Garmin Mobile XT to allow us to launch the Garmin Mobile XT, Navigate to a specific address or GPS coordinate, and to Show an address on the Map. These tasks will be performed on a native wrapper and which will be called from managed code.

We will be using the following methods from the Garmin Mobile XT SDK:
- QueLaunchApp
- QueAPIOpen
- QueAPIClose
- QueCreatePointFromAddress
- QueCreatePoint
- QueRouteToPoint
- QueViewPointOnMap

These methods return specific error codes describing whether the command executed successfully or not. This error information is translated to a .NET Framework enum which we will see later.


#include "QueAPI.h"

#define EXPORTC extern "C" __declspec(dllexport)

long DecimalDegreesToSemicircles(double degrees);

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;
}

static void QueCallback(QueNotificationT8 aNotification)
{
  // Used for debugging purposes
}

EXPORTC int CloseAPI()
{
  QueErrT16 err = QueAPIClose(QueCallback);
  return err;
}

EXPORTC int OpenNavigator()
{
  QueErrT16 err = QueLaunchApp(queAppMap);
  return err;
}

EXPORTC int NavigateToAddress(
  const wchar_t *streetAddress,
  const wchar_t *city,
  const wchar_t *postalCode,
  const wchar_t *state,
  const wchar_t *country)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QueSelectAddressType address;
  QuePointHandle point = queInvalidPointHandle;

  memset(&address, 0, sizeof(QueSelectAddressType));
  address.streetAddress = streetAddress;
  address.city = city;
  address.postalCode = postalCode;
  address.state = state;
  address.country = country;

  err = QueCreatePointFromAddress(&address, &point);
  if (err == queErrNone && point != queInvalidPointHandle) {
    err = QueRouteToPoint(point);
  }

  QueAPIClose(QueCallback);
  return err;
}

long DecimalDegreesToSemicircles(double degrees)
{
  return degrees * (0x80000000 / 180);
}

EXPORTC int NavigateToCoordinates(double latitude, double longitude)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QuePointType point;
  QuePositionDataType position;

  memset(&position, 0, sizeof(QuePositionDataType));
  position.lat = DecimalDegreesToSemicircles(latitude);
  position.lon = DecimalDegreesToSemicircles(longitude);

  memset(&point, 0, sizeof(QuePointType));
  point.posn = position;

  QuePointHandle hPoint;
  memset(&hPoint, 0, sizeof(QuePointHandle));

  err = QueCreatePoint(&point, &hPoint);
  if (err == queErrNone && hPoint != queInvalidPointHandle) {
    err = QueRouteToPoint(hPoint);
  }

  QueAPIClose(QueCallback);
  return err;
}

EXPORTC int ShowAddressOnMap(
  const wchar_t *streetAddress,
  const wchar_t *city,
  const wchar_t *postalCode,
  const wchar_t *state,
  const wchar_t *country)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QueSelectAddressType address;
  QuePointHandle point = queInvalidPointHandle;

  memset(&address, 0, sizeof(QueSelectAddressType));
  address.streetAddress = streetAddress;
  address.city = city;
  address.postalCode = postalCode;
  address.state = state;
  address.country = country;

  err = QueCreatePointFromAddress(&address, &point);
  if (err == queErrNone && point != queInvalidPointHandle) {
    err = QueViewPointOnMap(point);
  }

  QueAPIClose(QueCallback);
  return err;
}

The Garmin Mobile XT SDK works in a straight forward way. Before making any calls to the API, you first need to Open it. Once open, you can start executing a series of methods and then once you're done you must Close the API. The Garmin Mobile XT has to be running before you can execute commands to it, otherwise you will get a communication error.

You might notice in the code above an empty static method called QueCallback(QueNotificationT8 aNotification). This is a callback method that receives the information about the application state. You can use this for making callbacks from native to managed code. You can pass a delegate method from managed code to the native methods that expect QueNotificationCallback as a parameter. We will only use it for debugging purposes in this example. We will not dig more into that in this article.

Normally when reverse geocoding an address to a GPS coordinates using some free service, you will get the coordinates in decimal degrees (WGS84 decimal format). Navigating to a coordinate using the Garmin Mobile XT SDK requires the coordinates to be in semicircles (2^31 semicircles equals 180 degrees).

To convert decimal degrees to semicircles we use the following formula:
semicircles = decimal degrees * (2^31 / 180)


Managed wrapper

In my article Integrating with TomTom Navigator, I created a Generic Navigator wrapper that uses the INavigator interface for defining methods to be used by the managed wrapper. The purpose of the Generic Navigator was to allow the application to integrate with several navigation solutions without changing any of the existing code. As I already discussed this in the past, I will skip this part and only focus on how to integrate with Garmin Mobile XT.

We first need to create an enumeration containing error codes we receive from the native wrapper.

public enum GarminErrorCodes : int
{
  None = 0,
  NotOpen = 1,
  InvalidParameter,
  OutOfMemory,
  NoData,
  AlreadyOpen,
  InvalidVersion,
  CommunicationError,
  CmndUnavailable,
  LibraryStillOpen,
  GeneralFailure,
  Cancelled,
  RelaunchNeeded
}

We of course need to create our P/Invoke declarations. This time let's put them in an internal class called NativeMethods()

internal class NativeMethods
{
  [DllImport("Garmin.Native.dll")]
  internal static extern int CloseAPI();

  [DllImport("Garmin.Native.dll")]
  internal static extern int OpenNavigator();

  [DllImport("Garmin.Native.dll")]
  internal static extern int NavigateToAddress(
    string address,
    string city,
    string postalcode,
    string state,
    string country);

  [DllImport("Garmin.Native.dll")]
  internal static extern int NavigateToCoordinates(
    double latitude,
    double longitude);

  [DllImport("Garmin.Native.dll")]
  internal static extern int ShowAddressOnMap(
    string address,
    string city,
    string postalcode,
    string state,
    string country);
}

Let's create a .NET exception that we can throw which contains native error details when a native method call fails. Let's call it GarminNativeException()

[Serializable]
public class GarminNativeException : Exception
{
  public GarminNativeException() { }

  public GarminNativeException(GarminErrorCodes native_error) { }

  public GarminNativeException(
    string message,
    GarminErrorCodes native_error) : base(message) { }
}

Now we need a class that we can use for calling the wrapped managed methods to the Garmin mobile XT. Let's call it GarminXT()

public class GarminXT : IDisposable
{
  public void Dispose()
  {
    NativeMethods.CloseAPI();
  }

  public void OpenNavigator()
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.OpenNavigator();

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void NavigateToAddress(
    string address,
    string city,
    string postalcode,
    string state,
    string country)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.NavigateToAddress(
      address,
      city,
      postalcode,
      state,
      country);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void NavigateToCoordinates(double latitude, double longitude)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.NavigateToCoordinates(
      latitude,
      longitude);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void ShowAddressOnMap(
    string address,
    string city,
    string postalcode,
    string state,
    string country)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.ShowAddressOnMap(
      address,
      city,
      postalcode,
      state,
      country);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  private GarminNativeException ThrowGarminException(GarminErrorCodes err)
  {
    string message = string.Empty;

    switch (err) {
      case GarminErrorCodes.NotOpen:
        message = "Close() called without having Open() first";
        break;
      case GarminErrorCodes.InvalidParameter:
        message = "Invalid parameter was passed to the function";
        break;
      case GarminErrorCodes.OutOfMemory:
        message = "Out of Memory";
        break;
      case GarminErrorCodes.NoData:
        message = "No Data Available";
        break;
      case GarminErrorCodes.AlreadyOpen:
        message = "The API is already open";
        break;
      case GarminErrorCodes.InvalidVersion:
        message = "The API is an incompatible version";
        break;
      case GarminErrorCodes.CommunicationError:
        message = "There was an error communicating with the API";
        break;
      case GarminErrorCodes.CmndUnavailable:
        message = "Command is unavailable";
        break;
      case GarminErrorCodes.LibraryStillOpen:
        message = "API is still open";
        break;
      case GarminErrorCodes.GeneralFailure:
        message = "General Failure";
        break;
      case GarminErrorCodes.Cancelled:
        message = "Action was cancelled by the user";
        break;
      case GarminErrorCodes.RelaunchNeeded:
        message = "Relaunch needed to load the libraries";
        break;
      default:
        break;
    }

    throw new GarminNativeException(message, err);
  }
}

The managed wrapper GarminXT() implements IDisposible for ensuring that the API will be closed when the GarminXT object gets disposed. I check the return code of every method to verify if the native method call succeeded or failed. If the native method call failed then I throw a GarminNativeException containing a text description of the error and the GarminErrorCode returned by the native method call.


Using the Managed Wrapper

Now that we have a managed wrapper for the Garmin Mobile XT SDK we can start testing it with a simple smart device application. Let's say that we created a simple application that accepts street address, city, postal code, country, latitude, longitude. We also have some buttons or menu items for: Navigating to an address, Navigating to coordinates, Showing an address on the map, and for launching Garmin Mobile XT.

Since the managed wrapper implements IDisposable, we surround our calls to it with the using statement:

using (GarminXT xt = new GarminXT()) {
  xt.OpenNavigator();
}

As I mentioned before, it is important that Garmin Mobile XT is running in the background for executing certain commands. Otherwise the managed Garmin XT wrapper will throw a GarminNativeException saying that there was an error communicating with the API. I would suggest handling the GarminNativeException everytime calls to the managed wrapper are made.

For launching Garmin Mobile XT:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.OpenNavigator();
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For navigating to an address:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.NavigateToAddress(
      "Hørkær 24",
      "Herlev",
      "2730",
      null,
      "Denmark");
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For navigating to coordinates:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.NavigateToCoordinates(
      55.43019,
      12.26075);
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For showing an address on the map:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.ShowAddressOnMap(
      "Hørkær 24",
      "Herlev",
      "2730",
      null,
      "Denmark");
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}


That wasn't that hard was it?

But there is one thing that I don't quite understand. Why do we have to wrap SDK's like this ourselves? Why don't they just provide managed SDK's? Hopefully this will change in the near future. Until then, I guess I can just write a few more articles about it.

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Sunday, February 17, 2008 9:16 AM
Permalink | Comments (1) | Post RSSRSS comment feed

Transparent Controls in .NETCF

I work a lot with a graphic artist for developing solutions. The better the graphic artist you work with is, the harder it is to implement their designs to your application. One thing that my solutions have in common is that they all require transparent controls. My graphic artist loves having a image buttons on top of fancy background.

Here's some screen shots of what I've made with my graphic artist:

 

 

 

 

 

 

 

 



 

 

 

 

 

 

 

 

 

In these screen shots I heavily use a transparent label control over a Form that has a background image. I normally set my controls to be designer visible so I can drag and drop while designing. Visual Studio 2005 and 2008 will automatically load all Custom Controls and UserControls

Implementing Transparent Controls

For creating transparent controls, I need the following:

1) IControlBackground interface - contains BackgroundImage { get; }
2) TransparentControlBase - draws the BackgroundImage of an IControlBackground form
3) Transparent Control - Inherits from TransparentControlBase
4) FormBase Form - implements IControlBackground and draws the background image to the form

Let's start off with the IControlBackground interface. Like I mentioned above, it only contains a property called BackgroundImage.

public interface IControlBackground
{
  Image BackgroundImage { get; }
}

Next we will need to create the TransparentControlBase. Let's create a class that inherits from Control. We then need to override the OnPaintBackground() event to draw the IControlBackground.BackgroundImage of the Parent control. To do this, we create an instance of IControlBackground from the Parent. Once we have the BackgroundImage, we draw part of the BackgroundImage where the transparent control is lying on.

We also override the OnTextChanged() and OnParentChanged() events to force a re-draw whenever the text or parent of the control is changed.

public class TransparentControlBase : Control
{
  protected bool HasBackground = false;

  protected override void OnPaintBackground(PaintEventArgs e)
  {
    IControlBackground form = Parent as IControlBackground;
    if (form == null) {
      base.OnPaintBackground(e);
      return;
    } else {
      HasBackground = true;
    }

    e.Graphics.DrawImage(
      form.BackgroundImage,
      0,
      0,
      Bounds,
      GraphicsUnit.Pixel);
  }

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

  protected override void OnParentChanged(EventArgs e)
  {
    base.OnParentChanged(e);
    Invalidate();
  }
}

Now we need to create a control that inherits from TransparentControlBase. I'll create a simple TransparentLabel control for this example. The control will have the same behavior as the standard Label control, except that it can be transparent when used over a form or control that implements IControlBackground.

public class TransparentLabel : TransparentControlBase
{
  ContentAlignment alignment = ContentAlignment.TopLeft;
  StringFormat format = null;
  Bitmap off_screen = null;

  public TransparentLabel()
  {
    format = new StringFormat();
  }

  public ContentAlignment TextAlign
  {
    get { return alignment; }
    set
    {
      alignment = value;
      switch (alignment) {
        case ContentAlignment.TopCenter:
          format.Alignment = StringAlignment.Center;
          format.LineAlignment = StringAlignment.Center;
          break;
        case ContentAlignment.TopLeft:
          format.Alignment = StringAlignment.Near;
          format.LineAlignment = StringAlignment.Near;
          break;
        case ContentAlignment.TopRight:
          format.Alignment = StringAlignment.Far;
          format.LineAlignment = StringAlignment.Far;
          break;
      }
    }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (!base.HasBackground) {
      if (off_screen == null) {
        off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
      }
      using (Graphics g = Graphics.FromImage(off_screen)) {
        using (SolidBrush brush = new SolidBrush(Parent.BackColor)) {
          g.Clear(BackColor);
          g.FillRectangle(brush, ClientRectangle);
        }
      }
    } else {
      using (SolidBrush brush = new SolidBrush(ForeColor)) {
        e.Graphics.DrawString(
          Text,
          Font,
          brush,
          new Rectangle(0, 0, Width, Height),
          format);
      }
    }
  }
}

Now that we have our transparent controls, we need to create a Form that will contain these controls. First we need to create a base class that will implement IControlBackground and inherit from Form.

In this example, I added a background image to the solution and as an embedded resource. My default namespace is called TransparentSample and my background image is located at the root folder with the filename background.jpg

public class FormBase : Form, IControlBackground
{
  Bitmap background;

  public FormBase()
  {
    background = new Bitmap(
      Assembly.GetExecutingAssembly().GetManifestResourceStream(
      "TransparentSample.background.jpg"));
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    e.Graphics.DrawImage(background, 0, 0);
  }

  public Image BackgroundImage
  {
    get { return background; }
  }
}

For the last step, we need to create a Form that will contain these transparent controls. To start, let's add a new Form to our project and let it inherit from FormBase instead of Form.

Now we can add our transparent controls to the main form.

public class MainForm : FormBase
{
  TransparentLabel label;

  public MainForm()
  {
    label = new TransparentLabel();
    label.Font = new Font("Arial", 16f, FontStyle.Bold);
    label.ForeColor = Color.White;
    label.Text = "Transparent Label";
    label.Bounds = new Rectangle(20, 60, 200, 50);
    Controls.Add(label);
  }
}

That wasn't very complicated, was it? Having a nice and intuitive UI offers a very good user experience. Being creative, imaginative, and learning to work with a graphic artist can really pay off.

Currently rated 5.0 by 5 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Sunday, January 13, 2008 9:30 AM
Permalink | Comments (14) | Post RSSRSS comment feed

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;
  }
}

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Wednesday, December 05, 2007 9:40 AM
Permalink | Comments (0) | Post RSSRSS comment feed

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);
  }
}

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Tuesday, July 24, 2007 4:11 PM
Permalink | Comments (1) | Post RSSRSS comment feed

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.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Windows Mobile
Posted by christian.resma.helle on Monday, July 16, 2007 9:26 AM
Permalink | Comments (0) | Post RSSRSS comment feed

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() {
  SndPlaySnyc("\\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!

Currently rated 2.5 by 2 people

  • Currently 2.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Monday, July 09, 2007 4:08 PM
Permalink | Comments (1) | Post RSSRSS comment feed

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

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

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Tuesday, June 12, 2007 4:06 PM
Permalink | Comments (1) | Post RSSRSS comment feed

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?

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, June 07, 2007 9:20 AM
Permalink | Comments (0) | Post RSSRSS comment feed