Thursday, October 30, 2008

Source Code Download

I've published a few articles and quite a deal of code in my blog. I realized that I never properly published the source code as Visual Studio solutions.

I will upload all source code as Visual Studio solutions to this URL:
http://cid-ca531e7fb4762c70.skydrive.live.com/browse.aspx/Code%20Samples

Special thanks to Caspar de Zinger for porting my Transparent Controls solution to VB.NET

Tuesday, October 28, 2008

ListView Extended Styles in .NETCF

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

An example of the ListView extended styles is displaying gridlines around items and subitems, double buffering, and drawing a gradient background. These extended styles can be enabled in native code by using the ListView_SetExtendedListViewStyle macro or by sending LVM_SETEXTENDEDLISTVIEWSTYLE messages to the ListView.

Send Message

We will be using a lot of P/Invoking so let's start with creating an internal static class called NativeMethods. We need a P/Invoke declaration 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

Now that we have our SendMessage P/Invoke declaration in place, we can begin extending the ListView control. Let's start off with creating a class called ListViewEx that inherits from ListView. We need to look into the native header files of the Pocket PC SDK to get the ListView Messages. For now we will only need LVM_[GET/SET]EXTENDEDLISTVIEWSTYLE message which will be the main focus of all the examples. I will declare my class as a partial class and create all the pieces one by one 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

For my first example, let's enable GridLines in the ListView control. We can do this by using LVS_EX_GRIDLINES. This displays gridlines around items and sub-items and is available only in conjunction with the Details 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);
    }
  }
}

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

An interesting discovery to this is that the Design Time attributes of the Compact Framework ListView control includes the GridLines property. Now that we created the property in the code, when we open the Visual Studio Properties Window for our ListViewEx we will notice that GridLines property we created falls immediately under the "Appearance" category and even includes a description :)

Double Buffering

Do you notice that when you populate a ListView control with a lot of items, the drawing flickers a lot when you scroll up and down the list? Although it is not in the Pocket PC documentation for Windows Mobile 5.0, the ListView actually has an extended style called LVS_EX_DOUBLEBUFFER. Enabling the LVS_EX_DOUBLEBUFFER solves the flickering 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

Another cool extended style is the LVS_EX_GRADIENT. This extended style draws a gradient background similar to the one found in Pocket Outlook. It uses the system colors and fades from right to left. But what is really cool about this is that this is done by the OS. All we had to do was enable the style.

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

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

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

Thursday, April 10, 2008

Chris' Puzzle Game for Windows Mobile

I wrote this casual game in my easter holiday. It's an excellent way to pass time while waiting in the airport or sitting in a train :)

Chris' Puzzle Game

Try it out!

Thursday, February 21, 2008

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"

You can watch it from the danish MSDN community site:
http://www.msdncommunity.dk/udviklere/webcasts/smart-device-unit-testing-in-visual-studio-team-system-

or get it here:
http://blogs.commentor.dk/downloads/smart_device_unit_testing.wmv

or here:
http://cid-ca531e7fb4762c70.skydrive.live.com/self.aspx/Videos/smart%5E_device%5E_unit%5E_testing.wmv

Thursday, February 7, 2008

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.

Sunday, January 13, 2008

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.

Tuesday, January 1, 2008

Cepa Mobility - Enabling the Disabled

About a year ago, I had the chance to sit down with a brilliant developer with Muscular Atrophy. I was doing some mentoring that time and was helping them in solving some minor issues regarding a Windows Mobile based solution.

Yesterday, while I was checking out what's new on MSDN's Channel 9 website I saw that the guys at Cepa Mobility were interviewed by Channel 9. These guys are doing some really great stuff for enabling people with disabilities.

Here are some very interesting and inspiring videos of the guys and their work:

YouTube - Mobile Communicator for People with Disabilities
Channel 9 - Cepa Mobility: Enabling the Disabled with Mobile Communication
Channel 9 - CepaCom Demo