Wednesday, January 13, 2010

Well I blew the VB cobwebs out of my brain this morning and wrote a very basic example of getting AP info like signal strength using the SDF.  It took me nearly 2 hours and a whole lot of search engine work to remind me of syntax to crank out a simple 70-line application (I actually had to find a machine with VB installed first, as it's no longer in my "default" install of Studio).  Hard to believe I was once fluent enough in the language to have written a book.

At any rate, here's the sample: BasicWiFiSample.rar (532.46 KB)

1/13/2010 1:26:21 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Friday, October 09, 2009

Today I got a question from a customer, that essentially was "how can I detect when the network cable has been plugged in or unplugged from my CF application."  I knew I has solved this before, and after a little bit of digging with search engines I was reminded how obscrure finding the answer to this, even from native code, is.

So the general answer for how this is detected is that you have to call down into the NDIS driver via an IOCTL and tell it that you're interested in notifications.  This is done with the IOCTL_NDISUIO_REQUEST_NOTIFICATION value.  Of course receiving the notifications isn't so straightforward - you son't just get some nice callback.  Instead you have to spin up a point to point message queue and send that in to the IOCTL call, along with a mask of which specific notifications you want.  Then, when something changes (like the cable is pulled) you'll get an NDISUIO_DEVICE_NOTIFICATION structure on the queue, which you can then parse to find the adapter that had the event and what the exact event is.

From a managed code perspective, this is actually a lot of code to have to write - CreateFile to open NDIS, all of the queueing APIs, the structures for the notifications, etc.  Fortunately, I'd already been down this road and had added it to the Smart Device Framework already.  So if you're using the SDF, getting the notifications looks like this:

public partial class TestForm : Form
{
  public TestForm()
  {
    InitializeComponent();

    this.Disposed += new EventHandler(TestForm_Disposed);

    AdapterStatusMonitor.NDISMonitor.AdapterNotification += new AdapterNotificationEventHandler(NDISMonitor_AdapterNotification);
    AdapterStatusMonitor.NDISMonitor.StartStatusMonitoring();
  }

  void TestForm_Disposed(object sender, EventArgs e)
  {
    AdapterStatusMonitor.NDISMonitor.StopStatusMonitoring();
  }

  void NDISMonitor_AdapterNotification(object sender, AdapterNotificationArgs e)
  {
    string @event = string.Empty;

    switch (e.NotificationType)
    {
      case NdisNotificationType.NdisMediaConnect:
        @event = "Media Connected";
      break;
      case NdisNotificationType.NdisMediaDisconnect:
        @event = "Media Disconnected";
      break;
      case NdisNotificationType.NdisResetStart:
        @event = "Resetting";
      break;
      case NdisNotificationType.NdisResetEnd:
        @event = "Done resetting";
      break;
      case NdisNotificationType.NdisUnbind:
        @event = "Unbind";
      break;
      case NdisNotificationType.NdisBind:
        @event = "Bind";
      break;
      default:
        return;
    }

    if (this.InvokeRequired)
    {
      this.Invoke(new EventHandler(delegate
      {
        eventList.Items.Add(string.Format("Adapter '{0}' {1}", e.AdapterName, @event));
      }));
    }
    else
    {
      eventList.Items.Add(string.Format("Adapter '{0}' {1}", e.AdapterName, @event));
    }
  }
}

 

10/9/2009 11:08:32 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, June 25, 2009

In my last blog entry, I showed a workaround for getting mouse events for the CF ListView.  Since I can't leave well enough alone, I decided I'd actually try implementing the HitTest method (now that we have a valid x,y click coordinate).  Here's the result:

namespace OpenNETCF.Windows.Forms
{
  [Flags]
  public enum ListViewHitTestLocations
  {
    None = 0x01,
    Image = 0x02,
    Label = 0x04,
    StateImage = 0x08,

    AboveClientArea = 0x08,
    BelowClientArea = 0x10,
    RightOfClientArea = 0x20,
    LeftOfClientArea = 0x40,
  }

  public class ListViewHitTestInfo
  {
    public ListViewItem Item { get; set; }
    public ListViewHitTestLocations Location { get; set; }
    public ListViewItem.ListViewSubItem SubItem { get; set; }
  }
}

namespace OpenNETCF.Core
{
  using OpenNETCF.Windows.Forms;
  using System.Runtime.InteropServices;
  using System.Diagnostics;

  public static partial class Extensions
  {
    private const int LVM_FIRST = 0x1000;
    private const int LVM_GETCOUNTPERPAGE = LVM_FIRST + 40;
    private const int LVM_SUBITEMHITTEST = LVM_FIRST + 57;

    private struct LVHITTESTINFO
    {
      public int x;
      public int y;
      public ListViewHitTestLocations flags;
      public int iItem;
      public int iSubItem;
    }

    public static int GetVisibleRowCount(this ListView lv)
    {
      return Win32Window.SendMessage(lv.Handle, LVM_GETCOUNTPERPAGE, 0, 0).ToInt32();
    }

    public static ListViewHitTestInfo HitTest(this ListView lv, int x, int y)
    {
      LVHITTESTINFO info = new LVHITTESTINFO();
      info.x = x;
      info.y = y;
      GCHandle pInfo = GCHandle.Alloc(info, GCHandleType.Pinned);
      try
      {
        Win32Window.SendMessage(lv.Handle, LVM_SUBITEMHITTEST, 0, pInfo.AddrOfPinnedObject());
        LVHITTESTINFO result = (LVHITTESTINFO)pInfo.Target;
        ListViewHitTestInfo lvhti = new ListViewHitTestInfo();

        lvhti.Location = result.flags;
        switch (lvhti.Location)
        {
          case ListViewHitTestLocations.Image:
          case ListViewHitTestLocations.Label:
          case ListViewHitTestLocations.StateImage:
            lvhti.Item = lv.Items[result.iItem];
            lvhti.SubItem = lvhti.Item.SubItems[result.iSubItem];
            break;
        }
        return lvhti;
      }
      finally
      {
        pInfo.Free();
      }
    }

    public static ListViewHitTestInfo HitTest(this ListView lv, Point point)
    {
      return lv.HitTest(point.X, point.Y);
    }
  }
}

And usage looks like this:

public partial class Foo : Form
{
  public Foo()
  {
    InitializeComponent();
    listView.Items.Add(new ListViewItem(new string[] { "Item A", "Sub A1", "Sub A2", "Sub A3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item B", "Sub B1", "Sub B2", "Sub B3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item C", "Sub C1", "Sub C2", "Sub C3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item D", "Sub D1", "Sub D2", "Sub D3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item E", "Sub E1", "Sub E2", "Sub E3" }));

    ClickFilter filter = new ClickFilter(listView);
    Application2.AddMessageFilter(filter);
    filter.MouseDown += new MouseEventHandler(filter_MouseDown);

  }

  void filter_MouseDown(object sender, MouseEventArgs e)
  {
    ListView lv = sender as ListView;
    ListViewHitTestInfo hti = lv.HitTest(e.X, e.Y);

    if (hti.Item != null)
    {
      Debug.WriteLine(string.Format("Item: {0}, SubItem: {1}", hti.Item.Text, hti.SubItem.Text));
    }
  }
}

All of this will end up in SDF v. Next, but why do we have to implement fundamental things like this that should already be there?  It was excusable to be missing in v 1.0.  Maybe even in v 2.0, but in 3.5?  Really?

 

6/25/2009 12:38:57 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

We're going to be adding a full set of custom-drawn and owner-drawn controls in the next version of the SDF.  These are not "new" controls, but wrappers around the existing native control, but we're adding the custom and owner draw hooks that the OS already provides (and that, honestly, the CF team should have given us by version 3.5). 

In testing them out, I wanted to dogfood the custom-drawn ListView in an application, but I needed to know when a user clicked on an item in the ListView.  Simple.  Or so I thought.  It turns out that the style bits that get set to make a ListView really usable means that the ListView's *parent* gets it's click actions, and to turn those into Click events in the managed control is a *lot* of work, and would likely have some performance penalty.

So that got me to wondering how the existing CF ListView deals with it.  Well after a very quick test it seems that the CF team made the same decision - they didn't support the Click event either

Well my application requires that I know the difference between when the selected index changes (that's about the only useful event you do get) due a click and and when it changes dues to a keyboard action like an up or down arrow. Being me, I refuse to change the way I want my application will work just becasue of some stupid limitation of a framework.  The OS knows when I click on that damned thing - after all, it changes the selected item - so it *will* tell my application when it happens.

The key here is pretty simple.  We know that the click event does come in, and it comes in as a Windows message.  Our application message pump routes it to the parent of the ListView - so we have two point at which we can try to get it.  Getting it from the parent would entail subclassing the parent Form, and that just does seem like fun, nor is it really extensible if I ever have another app where I want to get ListView item clicks.

So that leaves looking at the application message pump.  Unfortunately the CF team has again not seen fit in any version to provide us the ability to add an IMessageFilter, but this was something we overcame long ago in the SDF.  If you use the Application2.Run method, you can then add IMessageFilters.  So what I did here is created an IMessageFilter that looks for messages whose hWnd matches the ListView I'm interested in, and then looks for the WM_LBUTTONDOWN message:

internal class ClickFilter : IMessageFilter
{
  private Control m_control;

  public event EventHandler Click;
  public event MouseEventHandler MouseDown;

  public ClickFilter(Control control)
  {
    m_control = control;
  }

  private int LoWord(IntPtr param)
  {
    return (ushort)(param.ToInt32() & ushort.MaxValue);
  }

  private int HiWord(IntPtr param)
  {
    return (ushort)(param.ToInt32() >> 16);
  }

  public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
  {
    if (m_control.IsDisposed) return false;

    if ((m.HWnd == m_control.Handle) && (m.Msg == (int)Microsoft.Controls.WM.WM_LMOUSEDOWN))
    {
      MouseEventArgs args = new MouseEventArgs(MouseButtons.Left, 1, LoWord(m.LParam), HiWord(m.LParam), 0);

      ThreadPool.QueueUserWorkItem(ButtonInvoker, args);
    }

    return false;
  }

  void ButtonInvoker(object o)
  {
    // let the system select the clicked listview item
    Thread.Sleep(10);
    Application2.DoEvents();

    if (m_control.IsDisposed) return;

    if (m_control.InvokeRequired)
    {
      m_control.Invoke(new WaitCallback(ButtonInvoker), new object[] { o });
      return;
    }

    // not truly a click, since it's not a down/up pair. Fix this later if we feel like it
    if(Click != null) Click(m_control, null);
    if (MouseDown != null) MouseDown(m_control, o as MouseEventArgs);
  }
}

Take note of the slight kludge in there though with the ThreadPool.  The reason for this is that if you just raise the event immediately on getting the mouse down event, the ListViewItem selection won't have happened yet, so if your handler looks at the SelectedItem, it would get the item that was selected before the click, not the one that the was clicked. 

Sure, maybe that's what your app wants, but in my case I wanted to know which item is actually clicked.  The ListView doesn't support a HitTest method (again, why don't we have this yet?), so knowing the x,y coordinates of the click is still a long way from giving us the ListViewItem.  This was a kludge to easily get me the info I wanted.

So using this filter in my application was as easy as adding this to the Form that contains the ListView:

InitializeComponent();

ClickFilter filter = new ClickFilter(listView);
filter.Click += new EventHandler(filter_Click);
Application2.AddMessageFilter(filter);

No, it shouldn't be this hard.  I wish the CF team would spend more time fixing fundamental stuff like this instead of tilting at the Silverlight windmill, but it is what it is, and as developers we still have to ship solutions.

6/25/2009 11:27:26 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Wednesday, June 24, 2009

It took a long time to get here, but we've finally release what I'm calling the version 1.0 (pervious version were 0.9.x) release of the OpenNETCF.IoC Framework.  In case you've not been tracking this project, it is a public-domain-licensed (you can't get any more free and unencumbered than that) framework that provides both inversion of control and dependency injection for .NET Compact Framework applications (it can be used on the desktop as well).  It's roughly modelled after Microsoft's SCSF and CAB frameworks, but it's scaled down and optimized for running on mobile and embedded devices, plus I "fixed" stuff that I think the SCSF got wrong (like having a static, globally available RootWorkItem and the ability to insert IMessageFilters into the application's message pump).

This framework is in use in a couple of commercial applications already, so it's been pretty heavily tested and vetted.  I still want to add a few more features as well and go back through it looking for performance optimizations, but it certainly has enough features to be used in applications today.

This release also ships with a full-blown, real-world sample application, not just the typical "Northwind" type of application.  The sample is called WiFiSurvey and it can be used to survey WiFi AP coverage of a site and to monitor associated AP changes as well as network addressability of a device.

WiFiSurvey has a Configuration service, a SQL CE 3.5-backed Data Access Layer, an Infrastructure module and a an application shell all of which are fully decoupled from one another and that are all loaded dynamically using an XML definition file.  The shell makes use of both a DeckWorkspace and a TabWorkspace, showing you not just how to use them, but also how to create your own workspaces if need be.

The WiFiSurvey application has a single source base for all target platforms and has been tested on the following platforms:

  • ARM-based CE 6.0 with a 320x240 (landscape) display.
  • Pocket PC 2003 240x320 (portrait)
  • WinMo 5.0 240x320 (portrait and landscape)

The IoC framework has additionally been tested on x86-based CE 5.0 and CE 6.0 devices.

As a side note, the WiFiSurvey sample application is also a good example of using the OpenNETCF Smart Device Framework for getting wireless information.

6/24/2009 2:54:24 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Wednesday, June 17, 2009

So it seems that something has changed in CE 6.0 in the way that user inactivity is detected by the OS.  In CE 5.0 and before, if we wanted to keep the backlight on we could periodically call SystemIdleTimerReset and all would be well.  In CE 6.0, this no longer works.  Now we have to set a named event that GWE is waiting on.  Here's what it looks like (this code uses the SDF for the named EventWaitHandle - the CF doesn't provide one).

private EventWaitHandle m_activityEvent;

[DllImport("coredll", SetLastError=true)]
private static extern void SystemIdleTimerReset();

private void ResetBacklightTimer()
{
  if (Environment.OSVersion.Version.Major <= 5)
  {
    SystemIdleTimerReset();
  }
  else
  {
    if (m_activityEvent == null)
    {
      using (var key = Registry.LocalMachine.OpenSubKey("System\\GWE"))
      {
        object value = key.GetValue("ActivityEvent");
        key.Close();
        if (value == null) return;

        string activityEventName = (string)value;
        m_activityEvent = new EventWaitHandle(false, EventResetMode.AutoReset, activityEventName);
      }
    }

    m_activityEvent.Set();
  }
}

6/17/2009 5:11:25 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Tuesday, May 19, 2009

Detecting if your application has been idle (i.e. no user mouse or keyboard actions) for a certain period of time is straightforward using an IMessageFilter implementation.  The filter would look like this:

using System;

using System.Collections.Generic;
using System.Text;
using OpenNETCF.Win32;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;

namespace MessageFilterSample
{
  public class InactivityFilter : IMessageFilter
  {
    public event MethodInvoker InactivityElapsed;

    private Timer m_inactivityTimer;

    public InactivityFilter(int timeoutMilliseconds)
    {
      m_inactivityTimer = new Timer();
      m_inactivityTimer.Interval = timeoutMilliseconds;

      m_inactivityTimer.Tick += new EventHandler(m_inactivityTimer_Tick);
      Reset();
    }

    void m_inactivityTimer_Tick(object sender, EventArgs e)
    {
      m_inactivityTimer.Enabled = false;
      Elapsed = true;

      if (InactivityElapsed != null) InactivityElapsed();
    }

    public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
    {
      switch ((WM)m.Msg)
      {
        case WM.KEYUP:
        case WM.LBUTTONUP:
        case WM.MOUSEMOVE:
          // reset the timer
          m_inactivityTimer.Enabled = false;
          m_inactivityTimer.Enabled = true;
          break;
      }
      return false;
    }

    public int Timeout { get; set; }
    public bool Elapsed { get; private set; }

    public void Reset()
    {
      Elapsed = false;
      m_inactivityTimer.Enabled = true;
    }
  }
}

To use it, you must use one of the Run overloads in Application2 to start your application, then you can add your filter like this:

InactivityFilter m_filter = new InactivityFilter(5000);
m_filter.InactivityElapsed += new MethodInvoker(m_filter_InactivityElapsed);
Application2.AddMessageFilter(m_filter);
...
void m_filter_InactivityElapsed()
{
   MessageBox.Show("Inactivity timer fired");
}

Download the sample here:
 

MessageFilterSample.zip (3.92 KB)
5/19/2009 5:35:42 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Monday, April 20, 2009

I blogged about this feature of the SDF two years ago (almost to the day in fact), but it certainly bears repeating since it's so useful.  If you need to know wehn a disk (SD, CF, USB, etc) has been attached or removed from a CE device (including Windows Mobile) you can use the SDF's DeviceStatusMonitor class (which, generally speaking, wraps the RequestDeviceNotifications API).  It's really, really simple to use:

private void WatchForDrives()
{
  DeviceStatusMonitor monitor = new DeviceStatusMonitor(DeviceClass.FileSystem, false);
  monitor.StartStatusMonitoring();
  monitor.DeviceNotification += delegate(object sender, DeviceNotificationArgs e)
  {
    string message = string.Format("Disk '{0}' has been {1}.", e.DeviceName, e.DeviceAttached ? "inserted" : "removed");
    MessageBox.Show(message, "Disk Status");
  };
}

4/20/2009 4:08:38 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Tuesday, March 31, 2009

Another nice addition to the latest drop of the SDF is the ability to get the serial number and manufacturer ID of storage volumes that support it (like SD cards). We did this by simply extending the existing OpenNETCF.IO.DriveInfo class to add a couple new properties.  Here's a quick example of how it works:

foreach(var info in DriveInfo.GetDrives())
{
  Debug.WriteLine("Info for " + info.RootDirectory);
  Debug.WriteLine("\tSize: " + info.TotalSize.ToString());
  Debug.WriteLine("\tFree: " + info.AvailableFreeSpace.ToString());
  Debug.WriteLine("\tManufacturer: " + info.ManufacturerID ?? "[Not available]");
  Debug.WriteLine("\tSerial #: " + info.SerialNumber ?? "[Not available]");
  Debug.WriteLine(string.Empty);
}

3/31/2009 12:03:33 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 

The latest drop of the SDF adds a nice little feature for playing tones with the device.  It works a lot like the old Beep API on the desktop where you provide a frequency and a duration and it plays the tone.

Here's a quick example of how it works:

Tone[] scale = new Tone[]
{
  // up fast, using MIDI
  new Tone { Duration = 10, MIDINote = 63},
  new Tone { Duration = 10, MIDINote = 65},
  new Tone { Duration = 10, MIDINote = 67},
  new Tone { Duration = 10, MIDINote = 68},
  new Tone { Duration = 10, MIDINote = 70},
  new Tone { Duration = 10, MIDINote = 72},
  new Tone { Duration = 10, MIDINote = 74},
  new Tone { Duration = 10, MIDINote = 75},

  // down slow, using freq (same notes as above)
  new Tone { Duration = 100, Frequency = 622 },
  new Tone { Duration = 100, Frequency = 587 },
  new Tone { Duration = 100, Frequency = 523 },
  new Tone { Duration = 100, Frequency = 466 },
  new Tone { Duration = 100, Frequency = 415 },
  new Tone { Duration = 100, Frequency = 391 },
  new Tone { Duration = 100, Frequency = 349 },
  new Tone { Duration = 100, Frequency = 311 },
};

SoundPlayer.PlayTone(scale);

You can see that this just plays an ascending scale of notes quickly, then the same scale descending, but slower.  Note that the PlayTone method takes in an array of Tones, and a Tone can be initialized with either a Frequency or MIDI note value.

3/31/2009 11:57:42 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Tuesday, March 10, 2009

So I was reading the Windows Mobile Team Blog and to my surprise I saw "How to capture a screen shot with .NET CF" as a topic that actually made it to the top of their "upcoming articles" list.  Seriously?  I know the SDF provides that.  So I did some basic Googling and sure enough, unless you know what you're looking for, it's tough to find (but it's been out for over a year).  For the record, the code is way too simple for a full article.  Even with the P/Invokes directly it's pretty basic, but with the SDF, it looks like this (pulled from a sample web page for our Padarn web server):

// create a bitmap and graphics objects for the capture
Drawing.Bitmap destinationBmp = new Drawing.Bitmap(Forms.Screen.PrimaryScreen.Bounds.Width, Forms.Screen.PrimaryScreen.Bounds.Height);
Drawing.Graphics g = Drawing.Graphics.FromImage(destinationBmp);
GraphicsEx gx = GraphicsEx.FromGraphics(g);

// capture the current screen
gx.CopyFromScreen(0, 0, 0, 0, Forms.Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);

// save the file
destinationBmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png);

// clean house
gx.Dispose();
g.Dispose();
destinationBmp.Dispose();

Simple enough, right?

3/10/2009 10:59:06 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Tuesday, February 24, 2009
Every time we release a new version of the SDF, even minor releases, we often sneak in a few added features. For example the latest release (2.3.0.21) added a static OpenNETCF.Net.Dns class with a couple of methods:QueryARecord and ReplaceARecord (the source is itself is actually CE and desktop compliant if you're interested). These correspond to the APIs DnsQuery_W and DnsReplaceRecordSetW and can be quite useful when your device is on a public network getting assigned a dynamic IP address. (For the record, the MSDN documentation on the APIs is abysmal - and even the headers don't provide much clarity for usage).

Here's a quick example of usage:

static void Main(string[] args)
{
  System.Net.IPAddress dnsServer = System.Net.IPAddress.Parse(DNS_ADDRESS);

  try
  {
    string nameToResolve = PC_NAME;

    Console.WriteLine(string.Format("Getting an A record for '{0}'...", nameToResolve));
    ARecord[] records = Dns.QueryARecord(dnsServer, nameToResolve, QueryOptions.BypassCache);
    Console.WriteLine(string.Format("\r\n Name: {0}", records[0].Name));
    Console.WriteLine(string.Format(" Address: {0}", records[0].Address.ToString()));
    Console.WriteLine(string.Format(" TTL: {0}", records[0].TTL));

    string replacementAddress = REPLACE_ADDRESS;
    Console.WriteLine(string.Format("\r\nReplacing address {0} with {1}...", records[0].Address.ToString(), replacementAddress));
    records[0].Address = System.Net.IPAddress.Parse(replacementAddress);
    Dns.ReplaceARecord(dnsServer, records[0], DnsUpdateSecurity.On, UID, DOMAIN, PWD);

    Console.WriteLine(string.Format("\r\n Getting an A record for '{0}' again...", nameToResolve));
    records = Dns.QueryARecord(dnsServer, nameToResolve, QueryOptions.BypassCache);
    Console.WriteLine(string.Format("\r\n Name: {0}", records[0].Name));
    Console.WriteLine(string.Format(" Address: {0}", records[0].Address.ToString()));
    Console.WriteLine(string.Format(" TTL: {0}", records[0].TTL));
  }
  catch (DnsException ex)
  {
    Console.WriteLine(string.Format("\r\n!! FAILED with error code {0} : '{1}'", ex.ErrorCode, ex.Message));
  }

}


2/24/2009 10:25:32 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Tuesday, December 23, 2008
We still need to put together a list of "what's new" in SDF 2.3 - it's a bit of a challenge, and we're a little busy here - but in the meantime I'll try to blog about some of the features that come to mind.

One simple new item if the ability to change radio power states (WiFi, Bluetooth and Phone) on WinMo devices.  Here's a quick example of how it might be done:

using OpenNETCF.WindowsMobile;

namespace SmartDeviceProject1
{
   public static class Utility
   {
     public static void SetWifiRadioPower(bool turnOn)
     {
       foreach (IRadio radio in Radios.GetRadios())
       {
         if (radio is WiFiRadio)
         {
           radio.RadioState = turnOn ? RadioState.On : RadioState.Off;
         }
       }
     }
   }
}


12/23/2008 1:52:59 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Wednesday, September 03, 2008
Here's another quick peek at an upcoming class in the next release of the SDF:

Object Model

keyhook2.PNG

Usage

    private KeyboardHook m_keyHook;
    
    public Form1()
    {
      m_keyHook = new KeyboardHook();
      m_keyHook.KeyDetected += OnKeyDetected;
      m_keyHook.Enabled = true;
    }

    void OnKeyDetected(OpenNETCF.Win32.WM keyMessage, KeyData keyData)
    {
      // Do Stuff
    } 


Sample App in the SDF

keyhook1.PNG

9/3/2008 1:40:08 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Thursday, August 28, 2008
This afternoon I learned of a few APIs that I was completely unaware of (that would have made my life a lot easier on some earlier projects).  3 hours later, We have this fully implemented:

radios.PNG

Usage looks like this:

Radios radios = Radios.GetRadios();

Debug.WriteLine("\nBefore\r\n--------");
foreach (IRadio radio in radios)
{
  Debug.WriteLine(string.Format("Name: {0}, Type: {1}, State: {2}", radio.DeviceName, radio.RadioType.ToString(), radio.RadioState.ToString()));

  // toggle all radio states
  radio.RadioState = (radio.RadioState == RadioState.On) ? RadioState.Off : RadioState.On;
}

// give the radios enough time to change state - some (like BT) seem to be slow
Thread.Sleep(1000);

radios.Refresh();

// display again
Debug.WriteLine("\r\nAfter\r\n--------");
foreach (IRadio radio in radios)
{
  Debug.WriteLine(string.Format("Name: {0}, Type: {1}, State: {2}", radio.DeviceName, radio.RadioType.ToString(), radio.RadioState.ToString()));
}
Debug.WriteLine("\r\n\n");
Thread.Sleep(100);


The only down side is that they are WinMo 5.0 and later only, so sorry CE devs.

I'm going to add it to the SDF source tree so it will be in the next release (soon, I promise).

8/28/2008 5:25:21 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Friday, February 15, 2008

So, you want network statistics for your Windows CE or Windows Mobile device?  I think the new Smart Device Framework 2.2's NetworkInformation namespace might just give you what you're after.  It's got almost everything the desktop gives, plus we even provide access to the ARP and routing tables (something you *don't* get on the desktop).  Here's a complete sample on getting the supported statistics:

IPGlobalProperties globalProps = IPGlobalProperties.GetIPGlobalProperties();
Debug.WriteLine("\r\nGlobal IP Properties\r\n====================");
Debug.WriteLine(string.Format(" Host Name: {0}", globalProps.HostName));
Debug.WriteLine(string.Format(" Domain Name: {0}", globalProps.DomainName));
Debug.WriteLine(string.Format(" DHCP Scope Name: {0}", globalProps.DhcpScopeName));

IPGlobalStatistics globalStatsv4 = globalProps.GetIPv4GlobalStatistics();
Debug.WriteLine("\r\nGlobal IPv4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv4.ReceivedPackets));
Debug.WriteLine(string.Format(" Packets Delivered: {0}", globalStatsv4.ReceivedPacketsDelivered));
Debug.WriteLine(string.Format(" Packets Discarded: {0}", globalStatsv4.ReceivedPacketsDiscarded));
Debug.WriteLine(string.Format(" Packets Forwarded: {0}", globalStatsv4.ReceivedPacketsForwarded));
Debug.WriteLine(string.Format(" Packets Received w/ Address Errors: {0}", globalStatsv4.ReceivedPacketsWithAddressErrors));
Debug.WriteLine(string.Format(" Packets Received w/ Header Errors: {0}", globalStatsv4.ReceivedPacketsWithHeadersErrors));
Debug.WriteLine(string.Format(" Packets Received w/ Protocol Errors: {0}", globalStatsv4.ReceivedPacketsWithUnknownProtocol));
Debug.WriteLine(string.Format(" Default TTL: {0}", globalStatsv4.DefaultTtl));
Debug.WriteLine(string.Format(" Forwarding {0} Enabled", globalStatsv4.ForwardingEnabled ? "IS" : "IS NOT"));
Debug.WriteLine(string.Format(" Interfaces: {0}", globalStatsv4.NumberOfInterfaces));
Debug.WriteLine(string.Format(" IP Addresses: {0}", globalStatsv4.NumberOfIPAddresses));
Debug.WriteLine(string.Format(" Routes: {0}", globalStatsv4.NumberOfRoutes));
Debug.WriteLine(string.Format(" Output Packet Requests: {0}", globalStatsv4.OutputPacketRequests));
Debug.WriteLine(string.Format(" Output Routing Discards: {0}", globalStatsv4.OutputPacketRoutingDiscards));
Debug.WriteLine(string.Format(" Output Packet Discards: {0}", globalStatsv4.OutputPacketsDiscarded));
Debug.WriteLine(string.Format(" Output Packets w/o Route: {0}", globalStatsv4.OutputPacketsWithNoRoute));
Debug.WriteLine(string.Format(" Fragmented Packets: {0}", globalStatsv4.PacketsFragmented));
Debug.WriteLine(string.Format(" Fragment Failures: {0}", globalStatsv4.PacketFragmentFailures));
Debug.WriteLine(string.Format(" Reassemblies Required: {0}", globalStatsv4.PacketReassembliesRequired));
Debug.WriteLine(string.Format(" Reassemblies Succeeded: {0}", globalStatsv4.PacketsReassembled));
Debug.WriteLine(string.Format(" Reassembly Failures: {0}", globalStatsv4.PacketReassemblyFailures));
Debug.WriteLine(string.Format(" Reassembly Timeouts: {0}", globalStatsv4.PacketReassemblyTimeout));

#region --- IPv6 stats ---
try
{
    Debug.WriteLine("\r\nGlobal IPv6 Statistics\r\n====================");
    IPGlobalStatistics globalStatsv6 = globalProps.GetIPv6GlobalStatistics();
    Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv6.ReceivedPackets));
    Debug.WriteLine(string.Format(" Packets Delivered: {0}", globalStatsv6.ReceivedPacketsDelivered));
    Debug.WriteLine(string.Format(" Packets Discarded: {0}", globalStatsv6.ReceivedPacketsDiscarded));
    Debug.WriteLine(string.Format(" Packets Forwarded: {0}", globalStatsv6.ReceivedPacketsForwarded));
    Debug.WriteLine(string.Format(" Packets Received w/ Address Errors: {0}", globalStatsv6.ReceivedPacketsWithAddressErrors));
    Debug.WriteLine(string.Format(" Packets Received w/ Header Errors: {0}", globalStatsv6.ReceivedPacketsWithHeadersErrors));
    Debug.WriteLine(string.Format(" Packets Received w/ Protocol Errors: {0}", globalStatsv6.ReceivedPacketsWithUnknownProtocol));
    Debug.WriteLine(string.Format(" Default TTL: {0}", globalStatsv6.DefaultTtl));
    Debug.WriteLine(string.Format(" Forwarding {0} Enabled", globalStatsv6.ForwardingEnabled ? "IS" : "IS NOT"));
    Debug.WriteLine(string.Format(" Interfaces: {0}", globalStatsv6.NumberOfInterfaces));
    Debug.WriteLine(string.Format(" IP Addresses: {0}", globalStatsv6.NumberOfIPAddresses));
    Debug.WriteLine(string.Format(" Routes: {0}", globalStatsv6.NumberOfRoutes));
    Debug.WriteLine(string.Format(" Output Packet Requests: {0}", globalStatsv6.OutputPacketRequests));
    Debug.WriteLine(string.Format(" Output Routing Discards: {0}", globalStatsv6.OutputPacketRoutingDiscards));
    Debug.WriteLine(string.Format(" Output Packet Discards: {0}", globalStatsv6.OutputPacketsDiscarded));
    Debug.WriteLine(string.Format(" Output Packets w/o Route: {0}", globalStatsv6.OutputPacketsWithNoRoute));
    Debug.WriteLine(string.Format(" Fragmented Packets: {0}", globalStatsv6.PacketsFragmented));
    Debug.WriteLine(string.Format(" Fragment Failures: {0}", globalStatsv6.PacketFragmentFailures));
    Debug.WriteLine(string.Format(" Reassemblies Required: {0}", globalStatsv6.PacketReassembliesRequired));
    Debug.WriteLine(string.Format(" Reassemblies Succeeded: {0}", globalStatsv6.PacketsReassembled));
    Debug.WriteLine(string.Format(" Reassembly Failures: {0}", globalStatsv6.PacketReassemblyFailures));
    Debug.WriteLine(string.Format(" Reassembly Timeouts: {0}", globalStatsv6.PacketReassemblyTimeout));
    Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv6.ReceivedPackets));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

#region --- Icmpv4 stats ---
IcmpV4Statistics icmpstats = globalProps.GetIcmpV4Statistics();
Debug.WriteLine("\r\nICMPv4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Address Mask Requests Received: {0}", icmpstats.AddressMaskRequestsReceived));
Debug.WriteLine(string.Format(" Address Mask Requests Sent: {0}", icmpstats.AddressMaskRequestsSent));
Debug.WriteLine(string.Format(" Address Mask Replies Received: {0}", icmpstats.AddressMaskRepliesReceived));
Debug.WriteLine(string.Format(" Address Mask Replies Sent: {0}", icmpstats.AddressMaskRepliesSent));
Debug.WriteLine(string.Format(" Dest Unreachable Messages Received: {0}", icmpstats.DestinationUnreachableMessagesReceived));
Debug.WriteLine(string.Format(" Dest Unreachable Messages Sent: {0}", icmpstats.DestinationUnreachableMessagesSent));
Debug.WriteLine(string.Format(" Echo Replies Received: {0}", icmpstats.EchoRepliesReceived));
Debug.WriteLine(string.Format(" Echo Replies Sent: {0}", icmpstats.EchoRepliesSent));
Debug.WriteLine(string.Format(" Echo Requests Received: {0}", icmpstats.EchoRequestsReceived));
Debug.WriteLine(string.Format(" Echo Requests Sent: {0}", icmpstats.EchoRequestsSent));
Debug.WriteLine(string.Format(" Errors Received: {0}", icmpstats.ErrorsReceived));
Debug.WriteLine(string.Format(" Errors Sent: {0}", icmpstats.ErrorsSent));
Debug.WriteLine(string.Format(" Messages Received: {0}", icmpstats.MessagesReceived));
Debug.WriteLine(string.Format(" Messages Sent: {0}", icmpstats.MessagesSent));
Debug.WriteLine(string.Format(" Parameter Problems Received: {0}", icmpstats.ParameterProblemsReceived));
Debug.WriteLine(string.Format(" Parameter Problems Sent: {0}", icmpstats.ParameterProblemsSent));
Debug.WriteLine(string.Format(" Redirects Received: {0}", icmpstats.RedirectsReceived));
Debug.WriteLine(string.Format(" Redirects Sent: {0}", icmpstats.RedirectsSent));
Debug.WriteLine(string.Format(" Source Quenches Received: {0}", icmpstats.SourceQuenchesReceived));
Debug.WriteLine(string.Format(" Source Quenches Sent: {0}", icmpstats.SourceQuenchesSent));
Debug.WriteLine(string.Format(" Time Exceeded Messages Received: {0}", icmpstats.TimeExceededMessagesReceived));
Debug.WriteLine(string.Format(" Time Exceeded Messages Sent: {0}", icmpstats.TimeExceededMessagesSent));
Debug.WriteLine(string.Format(" Timestamp Replies Received: {0}", icmpstats.TimestampRepliesReceived));
Debug.WriteLine(string.Format(" Timestamp Replies Sent: {0}", icmpstats.TimestampRepliesSent));
Debug.WriteLine(string.Format(" Timestamp Requests Received: {0}", icmpstats.TimestampRequestsReceived));
Debug.WriteLine(string.Format(" Timestamp Requests Sent: {0}", icmpstats.TimestampRequestsSent));
#endregion

#region --- TCPIPv4 stats ---
TcpStatistics tcpstats = globalProps.GetTcpIPv4Statistics();
Debug.WriteLine("\r\nTCP/IP v4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Connections Accepted: {0}", tcpstats.ConnectionsAccepted));
Debug.WriteLine(string.Format(" Connections Initiated: {0}", tcpstats.ConnectionsInitiated));
Debug.WriteLine(string.Format(" Cumulative Connections: {0}", tcpstats.CumulativeConnections));
Debug.WriteLine(string.Format(" Current Connections: {0}", tcpstats.CurrentConnections));
Debug.WriteLine(string.Format(" Errors Received: {0}", tcpstats.ErrorsReceived));
Debug.WriteLine(string.Format(" Failed Connection Attempts: {0}", tcpstats.FailedConnectionAttempts));
Debug.WriteLine(string.Format(" Maximum Connections: {0}", tcpstats.MaximumConnections));
Debug.WriteLine(string.Format(" Max Transmission Timeout: {0}", tcpstats.MaximumTransmissionTimeout));
Debug.WriteLine(string.Format(" Min Transmission Timeout: {0}", tcpstats.MinimumTransmissionTimeout));
Debug.WriteLine(string.Format(" Reset Connections: {0}", tcpstats.ResetConnections));
Debug.WriteLine(string.Format(" Resets Sent: {0}", tcpstats.ResetsSent));
Debug.WriteLine(string.Format(" Segments Received: {0}", tcpstats.SegmentsReceived));
Debug.WriteLine(string.Format(" Segments Resent: {0}", tcpstats.SegmentsResent));
Debug.WriteLine(string.Format(" Connections Sent: {0}", tcpstats.SegmentsSent));
#endregion

#region --- TCPIPv6 stats ---
Debug.WriteLine("\r\nTCP/IP v6 Statistics\r\n====================");
try
{
    tcpstats = globalProps.GetTcpIPv6Statistics();
    Debug.WriteLine(string.Format(" Connections Accepted: {0}", tcpstats.ConnectionsAccepted));
    Debug.WriteLine(string.Format(" Connections Initiated: {0}", tcpstats.ConnectionsInitiated));
    Debug.WriteLine(string.Format(" Cumulative Connections: {0}", tcpstats.CumulativeConnections));
    Debug.WriteLine(string.Format(" Current Connections: {0}", tcpstats.CurrentConnections));
    Debug.WriteLine(string.Format(" Errors Received: {0}", tcpstats.ErrorsReceived));
    Debug.WriteLine(string.Format(" Failed Connection Attempts: {0}", tcpstats.FailedConnectionAttempts));
    Debug.WriteLine(string.Format(" Maximum Connections: {0}", tcpstats.MaximumConnections));
    Debug.WriteLine(string.Format(" Max Transmission Timeout: {0}", tcpstats.MaximumTransmissionTimeout));
    Debug.WriteLine(string.Format(" Min Transmission Timeout: {0}", tcpstats.MinimumTransmissionTimeout));
    Debug.WriteLine(string.Format(" Reset Connections: {0}", tcpstats.ResetConnections));
    Debug.WriteLine(string.Format(" Resets Sent: {0}", tcpstats.ResetsSent));
    Debug.WriteLine(string.Format(" Segments Received: {0}", tcpstats.SegmentsReceived));
    Debug.WriteLine(string.Format(" Segments Resent: {0}", tcpstats.SegmentsResent));
    Debug.WriteLine(string.Format(" Connections Sent: {0}", tcpstats.SegmentsSent));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

#region --- UDPv4 stats ---
UdpStatistics udpstats = globalProps.GetUdpIPv4Statistics();
Debug.WriteLine("\r\nUDP v4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" UDP Listeners: {0}", udpstats.UdpListeners));
Debug.WriteLine(string.Format(" Datagrams Received: {0}", udpstats.DatagramsReceived));
Debug.WriteLine(string.Format(" Datagrams Sent: {0}", udpstats.DatagramsSent));
Debug.WriteLine(string.Format(" Datagrams Discarded: {0}", udpstats.IncomingDatagramsDiscarded));
Debug.WriteLine(string.Format(" Datagrams with Errors: {0}", udpstats.IncomingDatagramsWithErrors));
#endregion

#region --- UDPv6 stats ---
Debug.WriteLine("\r\nUDP v6 Statistics\r\n====================");
try
{
    udpstats = globalProps.GetUdpIPv6Statistics();
    Debug.WriteLine(string.Format(" UDP Listeners: {0}", udpstats.UdpListeners));
    Debug.WriteLine(string.Format(" Datagrams Received: {0}", udpstats.DatagramsReceived));
    Debug.WriteLine(string.Format(" Datagrams Sent: {0}", udpstats.DatagramsSent));
    Debug.WriteLine(string.Format(" Datagrams Discarded: {0}", udpstats.IncomingDatagramsDiscarded));
    Debug.WriteLine(string.Format(" Datagrams with Errors: {0}", udpstats.IncomingDatagramsWithErrors));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

ArpTable table = ArpTable.GetArpTable();
if (table.Count > 0)
{
    Debug.WriteLine("\r\n*ARP Table\r\n====================");
    Debug.WriteLine(string.Format("{0}{1}{2}{3}",
   
"[Interface]".PadRight(16),
    "[Type]".PadLeft(10),
    "[IP Address]".PadLeft(17),
    "[MAC Address]".PadLeft(22)));
    foreach (ArpEntry entry in table)
    {
        Debug.WriteLine(string.Format("{0}{1}{2}{3}"
            entry.NetworkInterface.Name.PadRight(16), 
            entry.ArpEntryType.ToString().PadLeft(10), 
            entry.IPAddress.ToString().PadLeft(17), 
            entry.PhysicalAddress.ToString().PadLeft(22)));
    }
}
else
{
    Debug.WriteLine(" No ARP table entries");
}

IPRoutingTable routingTable = IPRoutingTable.GetRoutingTable();
if(routingTable.Count > 0)
{
    Debug.WriteLine("\r\n*IP Routing Table\r\n====================");
    Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
        "[Interface]".PadRight(16),
        "[Type]".PadRight(12),
        "[Destination]".PadLeft(17),
        "[NetMask]".PadLeft(17),
        "[Next Hop]".PadLeft(17)));
    foreach (IPForwardEntry entry in routingTable)
    {
        string intfname = entry.NetworkInterface == null ? intfname = "{Loopback}" : entry.NetworkInterface.Name;
        Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
        intfname.PadRight(16),
        entry.RouteType.ToString().PadRight(12),
        entry.Destination.ToString().PadLeft(17),
        entry.SubnetMask.ToString().PadLeft(17),
        entry.NextHop.ToString().PadLeft(17))); 
    }
}
else
{
    Debug.WriteLine(" No IP Routing table entries");
}

TcpConnectionInformation[] connections = globalProps.GetActiveTcpConnections();
if (connections.Length > 0)
{
    Debug.WriteLine("\r\nActive TCP Connections\r\n====================");
    Debug.WriteLine(string.Format("{0}:{1}<---->{2}{3}[{4}]",
        "[local IP]".PadLeft(16),
        "[port]".PadRight(6),
        "[remote IP]".PadLeft(16),
        "[port]".PadRight(6),
        "[state]"));
    foreach (TcpConnectionInformation info in connections)
    {
        Debug.WriteLine(string.Format("{0}:{1}<---->{2}:{3}[{4}]",
        info.LocalEndPoint.Address.ToString().PadLeft(16),
        info.LocalEndPoint.Port.ToString().PadRight(6),
        info.RemoteEndPoint.Address.ToString().PadLeft(16),
        info.RemoteEndPoint.Port.ToString().PadRight(6),
        info.State.ToString()));
    }
}
else
{
    Debug.WriteLine(" No active TCP connections");
}

2/15/2008 11:57:23 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 

Smart Device Framework 2.2 refactored a lot of our network interface classes.  Yes, it's likely to casue some angst with people that used the older stuff, but it really is for the best.  I never liked the older mechanism that exposed things like SSID for network interfaces that weren't even wireless.  The new model is much friendlier and object-oriented.  Here's a quick (and very thorough) example of how to use the new classes.  As you can see, there's *nothing* to be known about the interface that isn't exposed.

NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
WirelessZeroConfigNetworkInterface wzcInterface = null;

foreach (NetworkInterface ni in interfaces)
{
    Debug.WriteLine(string.Format("===========\r\nInterface {0}\r\n===========", ni.Id));
    Debug.WriteLine(string.Format(" Type: {0}", ni.GetType().Name));
    Debug.WriteLine(string.Format(" Name: {0}", ni.Name));
    Debug.WriteLine(string.Format(" Description: {0}", ni.Description));
    Debug.WriteLine(string.Format(" Network Interface Type: {0}", ni.NetworkInterfaceType.ToString()));
    PhysicalAddress address = ni.GetPhysicalAddress();
    Debug.WriteLine(string.Format(" MAC: {0}", address == null ? "{none}" : address.ToString()));
    Debug.WriteLine(string.Format(" Operational Status: {0}", ni.OperationalStatus.ToString()));
    Debug.WriteLine(string.Format(" Speed: {0}bps", ni.Speed));
    Debug.WriteLine(string.Format(" *Interface Operational Status: {0}", ni.InterfaceOperationalStatus.ToString()));
    Debug.WriteLine(string.Format(" *Current IP: {0}", ni.CurrentIpAddress.ToString()));
    Debug.WriteLine(string.Format(" *Current Subnet: {0}", ni.CurrentSubnetMask.ToString()));

    IPInterfaceProperties ipProps = ni.GetIPProperties();
    Debug.WriteLine("\r\n IP Properties\r\n -------------");

#region
--- Server Lists ---
    if (ipProps.DhcpServerAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.DhcpServerAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" DHCP Server {0}: {1}", i, ipProps.DhcpServerAddresses[i].ToString()));
        }
    }
    else
    {
        Debug.WriteLine(" No DHCP Servers");
    }

    if (ipProps.DnsAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.DnsAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" DNS Server {0}: {1}", i, ipProps.DnsAddresses[i].ToString()));
        }
    }
    else
    {
        Debug.WriteLine(" No DNS Servers");
    }

    if (ipProps.GatewayAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.GatewayAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" Gateway {0}: {1}", i, ipProps.GatewayAddresses[i].Address.ToString()));
        }
    }
    else
    {
        Debug.WriteLine(" No Gateways");
    }
    
    if (ipProps.WinsServersAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.WinsServersAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" WINS Server {0}: {1}", i, ipProps.WinsServersAddresses[i].ToString()));
        }
    }
    else
    {
        Debug.WriteLine(" No WINS Servers");
    }
#endregion

#region --- Unicast Addresses ---
    if (ipProps.UnicastAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.UnicastAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" Unicast address {0}: {1}", i, ipProps.UnicastAddresses[i].Address.ToString()));
            Debug.WriteLine(string.Format(" Address Family: {0}", ipProps.UnicastAddresses[i].Address.AddressFamily));
            if (ipProps.UnicastAddresses[i].Address.AddressFamily == AddressFamily.InterNetworkV6)
            {
                Debug.WriteLine(string.Format(" Scope ID: {0}", ipProps.UnicastAddresses[i].Address.ScopeId));
            }
            else // ipv4
            {
                Debug.WriteLine(string.Format(" IPv4 Mask: {0}", ipProps.UnicastAddresses[i].IPv4Mask == null 
                        ? "{none}" : ipProps.UnicastAddresses[i].IPv4Mask.ToString()));
            }
            Debug.WriteLine(string.Format(" Preferred Limetime: {0}", ipProps.UnicastAddresses[i].AddressPreferredLifetime));
            Debug.WriteLine(string.Format(" Valid Limetime: {0}", ipProps.UnicastAddresses[i].AddressValidLifetime));
            Debug.WriteLine(string.Format(" DHCP Lease Limetime: {0}", ipProps.UnicastAddresses[i].DhcpLeaseLifetime));
            Debug.WriteLine(string.Format(" DAD State: {0}", ipProps.UnicastAddresses[i].DuplicateAddressDetectionState));
            Debug.WriteLine(string.Format(" {0} DNS Eligible", ipProps.UnicastAddresses[i].IsDnsEligible ? "IS" : "IS NOT"));
            Debug.WriteLine(string.Format(" {0} Transient", ipProps.UnicastAddresses[i].IsTransient ? "IS" : "IS NOT"));
            Debug.WriteLine(string.Format(" Prefix Origin: {0}", ipProps.UnicastAddresses[i].PrefixOrigin.ToString()));
            Debug.WriteLine(string.Format(" Suffix Origin: {0}", ipProps.UnicastAddresses[i].SuffixOrigin.ToString()));
        }
    }
    else
    {
        Debug.WriteLine(" No Unicast addresses");
    }
#endregion

#region
--- Anycast Addresses ---
    if (ipProps.AnycastAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.AnycastAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" Anycast address {0}: {1}", i, ipProps.AnycastAddresses[i].Address.ToString()));
            Debug.WriteLine(string.Format(" Address Family: {0}", ipProps.AnycastAddresses[i].Address.AddressFamily));
            if (ipProps.AnycastAddresses[i].Address.AddressFamily == AddressFamily.InterNetworkV6)
            {
                Debug.WriteLine(string.Format(" Scope ID: {0}", ipProps.AnycastAddresses[i].Address.ScopeId));
            }
            Debug.WriteLine(string.Format(" {0} DNS Eligible", ipProps.UnicastAddresses[i].IsDnsEligible ? "IS" : "IS NOT"));
            Debug.WriteLine(string.Format(" {0} Transient", ipProps.UnicastAddresses[i].IsTransient ? "IS" : "IS NOT"));
        }
    }
    else
    {
        Debug.WriteLine(" No Anycast addresses");
    }
#endregion

#region --- Multicast Addresses ---
    if (ipProps.MulticastAddresses.Count > 0)
    {
        for (int i = 0; i < ipProps.MulticastAddresses.Count; i++)
        {
            Debug.WriteLine(string.Format(" Anycast address {0}: {1}", i, ipProps.MulticastAddresses[i].Address.ToString()));
            Debug.WriteLine(string.Format(" Address Family: {0}", ipProps.MulticastAddresses[i].Address.AddressFamily));
            if (ipProps.MulticastAddresses[i].Address.AddressFamily == AddressFamily.InterNetworkV6)
            {
                Debug.WriteLine(string.Format(" Scope ID: {0}", ipProps.MulticastAddresses[i].Address.ScopeId));
            }
            Debug.WriteLine(string.Format(" {0} DNS Eligible", ipProps.MulticastAddresses[i].IsDnsEligible ? "IS" : "IS NOT"));
            Debug.WriteLine(string.Format(" {0} Transient", ipProps.MulticastAddresses[i].IsTransient ? "IS" : "IS NOT"));
        }
    }
    else
    {
        Debug.WriteLine(" No Multicast addresses");
    }
#endregion

#region --- IPv4 Stats ---
    IPv4InterfaceStatistics ipstats = ni.GetIPv4Statistics();
    Debug.WriteLine("\r\n IPv4 Statistics\r\n -------------");
    Debug.WriteLine(string.Format(" Bytes Received: {0}", ipstats.BytesReceived));
    Debug.WriteLine(string.Format(" Bytes Sent: {0}", ipstats.BytesSent));
    Debug.WriteLine(string.Format(" Incoming Packets Discarded: {0}", ipstats.IncomingPacketsDiscarded));
    Debug.WriteLine(string.Format(" Incoming Packets with errors: {0}", ipstats.IncomingPacketsWithErrors));
    Debug.WriteLine(string.Format(" Incoming Packets Unk. Protocol: {0}", ipstats.IncomingUnknownProtocolPackets));
    Debug.WriteLine(string.Format(" Non Unicast Packets Received: {0}", ipstats.NonUnicastPacketsReceived));
    Debug.WriteLine(string.Format(" Non Unicast Packets Sent: {0}", ipstats.NonUnicastPacketsSent));
    Debug.WriteLine(string.Format(" Outgoing Packets Discarded: {0}", ipstats.OutgoingPacketsDiscarded));
    Debug.WriteLine(string.Format(" Outgoing Packets With Errors: {0}", ipstats.OutgoingPacketsWithErrors));
    Debug.WriteLine(string.Format(" Output Queue Length: {0}", ipstats.OutputQueueLength));
    Debug.WriteLine(string.Format(" Unicast Packets Received: {0}", ipstats.UnicastPacketsReceived));
    Debug.WriteLine(string.Format(" Unicast Packets Sent: {0}", ipstats.UnicastPacketsSent));
#endregion

#region --- IPv4 Properties ---
    IPv4InterfaceProperties ipv4props = ipProps.GetIPv4Properties();
    Debug.WriteLine("\r\n IPv4 Properties\r\n -------------");
    Debug.WriteLine(string.Format(" Index: {0}", ipv4props.Index));
    Debug.WriteLine(string.Format(" Auto Addressing {0}", ipv4props.IsAutomaticPrivateAddressingActive 
        ? "ACTIVE" : "INACTIVE"));
    Debug.WriteLine(string.Format(" Auto Addressing {0}", ipv4props.IsAutomaticPrivateAddressingEnabled 
        ? "ENABLED" : "DISABLED"));
    Debug.WriteLine(string.Format(" DHCP {0}", ipv4props.IsDhcpEnabled ? "ENABLED" : "DISABLED"));
    Debug.WriteLine(string.Format(" MTU: {0}", ipv4props.Mtu));
    Debug.WriteLine(string.Format(" {0} use WINS", ipv4props.UsesWins ? "DOES" : "DOES NOT"));
#endregion

    if (ni is WirelessZeroConfigNetworkInterface)
    {
        WirelessZeroConfigNetworkInterface wzc = (WirelessZeroConfigNetworkInterface)ni;

        // save for later operations
        wzcInterface = wzc;

        Debug.WriteLine("\r\n *WZC Properties\r\n -------------");
        Debug.WriteLine(string.Format(" *Associated AP: {0}", wzc.AssociatedAccessPoint));
        Debug.WriteLine(string.Format(" *Associated AP MAC: {0}", wzc.AssociatedAccessPointMAC));
        Debug.WriteLine(string.Format(" *Authentication Mode: {0}", wzc.AuthenticationMode.ToString()));
        Debug.WriteLine(string.Format(" *WZC Fallback Enabled: {0}", wzc.FallbackEnabled));
        Debug.WriteLine(string.Format(" *WEP Status: {0}", wzc.WEPStatus));
        Debug.WriteLine(string.Format(" *Signal Strength: {0}db ({1})", wzc.SignalStrength.Decibels, 
                wzc.SignalStrength.Strength.ToString()));
        Debug.WriteLine(string.Format(" *Authentication Mode: {0}", wzc.AuthenticationMode.ToString()));
        Debug.WriteLine(string.Format(" *Infrastructure Mode: {0}", wzc.InfrastructureMode.ToString()));
        Debug.WriteLine(string.Format(" *WEP Status: {0}", wzc.WEPStatus.ToString()));
        foreach (int rate in wzc.SupportedRates)
        {
            Debug.WriteLine(string.Format(" *Supports {0}kbps", rate));
        }
        Debug.WriteLine(string.Format(" *Radio ATIM Window: {0}Kµsec", wzc.RadioConfiguration.ATIMWindow));
        Debug.WriteLine(string.Format(" *Radio beacon Period: {0}Kµsec", wzc.RadioConfiguration.BeaconPeriod));
        Debug.WriteLine(string.Format(" *Radio Frequency: {0}kHz", wzc.RadioConfiguration.Frequency));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Dwell Time: {0}Kµsec",         
                wzc.RadioConfiguration.FrequencyHopDwellTime));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Pattern: {0}", wzc.RadioConfiguration.FrequencyHopPattern));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Set: {0}", wzc.RadioConfiguration.FrequencyHopSet));

#region --- Preferred APs ---
        if (wzc.PreferredAccessPoints.Count > 0)
        {
            Debug.WriteLine("\r\n *Preferred APs\r\n -------------");
            foreach (AccessPoint ap in wzc.PreferredAccessPoints)
            {
                Debug.WriteLine(string.Format(" *SSID: {0}", ap.Name));
                Debug.WriteLine(string.Format(" *MAC {0}", ap.PhysicalAddress.ToString()));
                Debug.WriteLine(string.Format(" *Mode {0}", ap.InfrastructureMode.ToString()));
                Debug.WriteLine(string.Format(" *Network Type {0}", ap.NetworkTypeInUse.ToString()));
                Debug.WriteLine(string.Format(" *Privacy {0}", ap.Privacy));
                Debug.WriteLine(string.Format(" *Signal Strength: {0}db ({1})", ap.SignalStrength.Decibels,                 
                        ap.SignalStrength.Strength.ToString()));
                // supported rates
                foreach (int rate in ap.SupportedRates)
                {
                    Debug.WriteLine(string.Format(" *Supports {0}kbps", rate));
                }
            }
        }
        else
        {
            Debug.WriteLine("\r\n *No Preferred APs");
        }
#endregion
#region --- Nearby APs ---
        if (wzc.NearbyAccessPoints.Count > 0)
        {
            Debug.WriteLine("\r\n *Nearby APs\r\n -------------");
            foreach (AccessPoint ap in wzc.NearbyAccessPoints)
            {
                Debug.WriteLine(string.Format(" *SSID: {0}", ap.Name));
                Debug.WriteLine(string.Format(" *MAC {0}", ap.PhysicalAddress.ToString()));
                Debug.WriteLine(string.Format(" *Mode {0}", ap.InfrastructureMode.ToString()));
                Debug.WriteLine(string.Format(" *Network Type {0}", ap.NetworkTypeInUse.ToString()));
                Debug.WriteLine(string.Format(" *Privacy {0}", ap.Privacy));
                Debug.WriteLine(string.Format(" *Signal Strength: {0}db ({1})", ap.SignalStrength.Decibels, 
                        ap.SignalStrength.Strength.ToString()));
                // supported rates
                foreach (int rate in ap.SupportedRates)
                {
                    Debug.WriteLine(string.Format(" *Supports {0}kbps", rate));
                }
            }
        }
        else
        {
            Debug.WriteLine("\r\n *No Nearby APs");
        }
#endregion
#region --- Preferred APs ---
        if (wzc.NearbyPreferredAccessPoints.Count > 0)
        {
            Debug.WriteLine("\r\n *Preferred Nearby APs\r\n -------------");
            foreach (AccessPoint ap in wzc.NearbyPreferredAccessPoints)
            {
                Debug.WriteLine(string.Format(" *SSID: {0}", ap.Name));
                Debug.WriteLine(string.Format(" *MAC {0}", ap.PhysicalAddress.ToString()));
                Debug.WriteLine(string.Format(" *Mode {0}", ap.InfrastructureMode.ToString()));
                Debug.WriteLine(string.Format(" *Network Type {0}", ap.NetworkTypeInUse.ToString()));
                Debug.WriteLine(string.Format(" *Privacy {0}", ap.Privacy));
                Debug.WriteLine(string.Format(" *Signal Strength: {0}db ({1})", ap.SignalStrength.Decibels,
                        ap.SignalStrength.Strength.ToString()));
                // supported rates
                foreach (int rate in ap.SupportedRates)
                {
                    Debug.WriteLine(string.Format(" *Supports {0}kbps", rate));
                }
            }
        }
        else
        {
            Debug.WriteLine("\r\n *No Nearby Preferred APs");
        }
#endregion
    }
    else if (ni is WirelessNetworkInterface)
    {
        WirelessNetworkInterface wni = (WirelessNetworkInterface)ni;

        Debug.WriteLine("\r\n *Wireless Properties\r\n -------------");
        Debug.WriteLine(string.Format(" *Associated AP: {0}", wni.AssociatedAccessPoint));
        Debug.WriteLine(string.Format(" *Associated AP MAC: {0}", wni.AssociatedAccessPointMAC));
        Debug.WriteLine(string.Format(" *Signal Strength: {0}db ({1})", wni.SignalStrength.Decibels,         
            wni.SignalStrength.Strength.ToString()));
        Debug.WriteLine(string.Format(" *Authentication Mode: {0}", wni.AuthenticationMode.ToString()));
        Debug.WriteLine(string.Format(" *Infrastructure Mode: {0}", wni.InfrastructureMode.ToString()));
        Debug.WriteLine(string.Format(" *WEP Status: {0}", wni.WEPStatus.ToString()));
        foreach (int rate in wni.SupportedRates)
        {
            Debug.WriteLine(string.Format(" *Supports {0}kbps", rate));
        }
        Debug.WriteLine(string.Format(" *Radio ATIM Window: {0}Kµsec", wni.RadioConfiguration.ATIMWindow));
        Debug.WriteLine(string.Format(" *Radio beacon Period: {0}Kµsec", wni.RadioConfiguration.BeaconPeriod));
        Debug.WriteLine(string.Format(" *Radio Frequency: {0}kHz", wni.RadioConfiguration.Frequency));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Dwell Time: {0}Kµsec"
                wni.RadioConfiguration.FrequencyHopDwellTime));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Pattern: {0}", wni.RadioConfiguration.FrequencyHopPattern));
        Debug.WriteLine(string.Format(" *Radio Frequency Hop Set: {0}", wni.RadioConfiguration.FrequencyHopSet));
    }
} // foreach (NetworkInterface ni in interfaces)

if (wzcInterface != null)
{
        byte[] key = null;
        bool b = wzcInterface.AddPreferredNetwork("MyOpenNetwork1", true, key, 1, AuthenticationMode.Open, 
                WEPStatus.WEPDisabled, null);
        b = wzcInterface.AddPreferredNetwork("MyOpenNetwork2", true, key, 1, AuthenticationMode.Open, 
                WEPStatus.WEPDisabled, null);
        b = wzcInterface.AddPreferredNetwork("MyOpenNetwork3", true, key, 1, AuthenticationMode.Open, 
                WEPStatus.WEPDisabled, null);
}

2/15/2008 11:48:52 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 

Need to view or update the Netowrk Routing  Table on your CE/ WinMo device?  Smart Device Framework 2.2's updated NetworkInformation namespace adds a whole lot of new classes, including a full set of classes for routing table access.  Here's a quick example of displaying the current routes:

IPRoutingTable table = IPRoutingTable.GetRoutingTable();

Debug.WriteLine("\r\n*IP Routing Table\r\n====================");
Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
    "[Interface]".PadRight(16),
    "[Type]".PadRight(12),
    "[Destination]".PadLeft(17),
    "[NetMask]".PadLeft(17),
    "[Next Hop]".PadLeft(17)));

foreach (IPForwardEntry entry in table)
{
    string intfname = entry.NetworkInterface == null ? intfname = "{Loopback}" : entry.NetworkInterface.Name;
    Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
    intfname.PadRight(16),
    entry.RouteType.ToString().PadRight(12),
    entry.Destination.ToString().PadLeft(17),
    entry.SubnetMask.ToString().PadLeft(17),
    entry.NextHop.ToString().PadLeft(17)));
}

Can't be much simpler than that.

2/15/2008 11:31:31 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
SDF 2.2 has a new namespace called OpenNETCF.AppSettings.  We created this group of classes for those times when you'd like to be able to retrieve and store values in an XML settings file.  Here's a quick peek at how it might be used:

AppSettings settings = new AppSettings("\\MyApp\\ApplicationSettings.xml");

if (!settings.Groups.Contains("MyGroup"))
{
    settings.Groups.Add("MyGroup");
    SettingGroup group = settings.Groups["MyGroup"];
    group.Settings.Add("BoolSetting", true);
    group.Settings.Add("IntSetting", 21);
    group.Settings.Add("StringSetting", "hello");

    settings.Save();
}

// update a setting
settings.Groups["MyGroup"].Settings["IntSetting"].Value = 42;

// save back to the file
settings.Save();

string myValue = settings.Groups["MyGroup"].Settings["StringSetting"].Value
bool myOtherValue = settings.Groups["MyGroup"].Settings["BoolSetting"].Value

2/15/2008 11:21:07 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Thursday, February 14, 2008
Today we released version 2.2 of our Smart Device Framework.  Below is a quick list of changes (this list might not be 100% complete).

Fixes/changes

  • Bug fix in CRC class
  • Bug fixes in audio classes
  • ConnMgr EnumDestinations fix
  • ConnMgr RequestDisconnect fix
  • Adapter fixes for setting IPAddress, DNSServer, Gateway
  • StreamInterfaceDriver fixes for IOCTL calls with null data
  • TextDataAdapter fixes
  • AccesPoint and AccessPointCollection moved to NetworkInformation namespace
  • Many OpenNETCF.Net enums moved to OpenNETCF.Net.NetworkInformation to align with FFx model
  • NetworkInterfaceWatcher reworked for new networking classes
  • ConfigurationSettings.GetConfig added overload to allow passing context to ConfigSectionHandlers
  • See our Buzilla Database for possibly more fixes

 

Additions

  • GraphicsEx.CopyFromScreen
  • Drawing.CopyPixelOperation
  • MemoryMappedFile class
  • StreamInterfaceDriver Activate/DeactivateDevice methods
  • ToolHelp.ModuleEntry class
  • EventWaitHandle.GetData / Set(int) methods
  • EventWaitHandle.Pulse method
  • Application2.Run allows main form to remain hidden on startup
  • DelimitedTextWriter class
  • FileSystemMonitor class (works in console apps)
  • Toolhelp namespace is now desktop compatible
  • Messaging namespace refactored to allow stand-alone use
  • OpenNETCF.WindowsCE.Services namespace
  • NetworkInformation namespace additions
    • ArpEntry
    • ArpTable
    • GatewayIPAddressInformation
    • GatewayIPAddressInformationCollection
    • IcmpV4Statistics
    • IcmpV6Statistics
    • IPAddressInformation
    • IPAddressInformationCllection
    • IPAddressCollection
    • IPForwardEntry
    • IPGlobalProperties
    • IPGlobalStatistics
    • IPInterfaceProperties
    • IPProtocol
    • IPRoutingTable 
    • IPStatus
    • IPv4InterfaceProperties
    • IPv4InterfaceStatistics
    • IPv6InterfaceProperties
    • MulticastIPAddressInformation
    • MulticastIPAddressInformationCollection
    • NetworkInterface
    • OperationalStatus
    • PhysicalAddress
    • TcpConnectionInformation
    • TcpState
    • TcpStatistics
    • UdpStatistics
    • UnicastIPAddressInformation
    • UnicastIPAddressInformationCollection
    • WEPStatus
    • WirelessNetworkInterface
    • WirelessZeroConfigNetworkInterface
  • OpenNETCF.AppSettings namespace (desktop compatible code)
    • SettingsFile
    • Setting
    • Settings
    • SettingGroup
    • SettingGroups

2/14/2008 1:48:22 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Monday, May 28, 2007

Problem
I'd like to adjust the creation/last access/last modification property of a file.  The Compact Framework's FileSystemInfo class has fields that seem appropriate for these, but they are read only.  While I'm modifying properties of the firl, I'd also like to mark the file as read-only to help prevent accidental deletion. What can the SDF do for me?

Solution
The OpenNETCF.IO.FileHelper class provides methods to alter all of these file properties (as well as simple methods for reading files without having to create streams first).  Here is an example of how you could use the FileHelper:

string myFilePath = @"\My Documents\myfile.txt";

// alter the file creation time
OpenNETCF.IO.FileHelper.SetCreationTime(myFilePath, DateTime.Now);

// make the file hidden and read-only
OpenNETCF.IO.FileHelper.SetAttributes(myFilePath, FileAttributes.Hidden | FileAttributes.ReadOnly);

5/28/2007 10:42:43 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, May 24, 2007

Problem
My Appllication needs to detect when the time of timezone has been changed on the device.  It needs to work if the user changes it manually or if something else, such as a time server sync, ActiveSync or DST adjustment, changes it.  What can the SDF do for me?

Solution
The OpenNETCF.WindowsCE.DeviceManagement class provides events for these, as well as many other device notifications.

using OpenNETCF.WindowsCE;

void
TimeOrTZChanged()
{
    MessageBox.Show("Device time has been altered");
}

void MyForm()
{

    InitializeComponents();
   
DeviceManagement.TimeChanged += TimeOrTZChanged;
    DeviceManagement.TimeZoneChanged += TimeOrTZChanged;
}

5/24/2007 9:59:46 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, May 16, 2007

Problem
I have some fairly complex data I want to put into a list.  I'd like to use different fonts, icons and maybe even some custom lines and polygons.  Unfortunately the ListBox control in the CF pretty much sucks for this type of thing.  What can the SDF do for me?

Solution
Yes, the default ListBox control is a bit challenged, but all hope is not lost.  The OpenNETCF.Windows.Forms.ListBox2 follows the desktop model and provides owner-drawn capabilities.  Here are some screen shots of a sample application we have for the ListBox2 as well as some of the OpenNETCF.Net networking classes.

   

As you can see, we're drawing items with different font sizes, colors and weights on both screens, plus we're adding some icons in each item in the second and I have that nice and ugly custom "selected" color.

Download the full sample (NetUI) here.

5/16/2007 7:14:46 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Monday, May 07, 2007

Problem
In my application I really need to know when and where the mouse button is clicked/stylus is tapped on some controls like the Button control.  Unfortunately the Compact Framework doesn't expose MouseDown and MouseUp for all controls. What can the SDF do for me?

Solution
[Tested on the PPC2003SE Emulator with CF 2.0 SP1]

The CF doesn't expose managed events for all Windows messages that the control gets during the course of execution, but those messages still occur.  You can intercept them using an OpenNETCF.Windows.Forms.IMessageFilter-derived class coupled with the OpenNETCF.Windows.Forms.Application2.Run method.

Here is an example. Create an app with a single Form containing a single Button.  Note that you must modify the project properties to set the startup object to Sub Main.

Imports OpenNETCF.Windows.Forms
Imports OpenNETCF.Win32.WM
Imports System.Diagnostics

Public Class Form1
  Private noClickFont As Font
  Private clickFont As Font

 
Private
Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ' wire up the event handlers
    AddHandler MyApplication.buttonFilter.MouseDown, AddressOf ButtonMouseDown
    AddHandler MyApplication.buttonFilter.MouseUp, AddressOf ButtonMouseUp

    ' add our button to the filter
    MyApplication.buttonFilter.WatchButton(Me.Button1)

    ' save some font info for later use
    noClickFont = Button1.Font
    clickFont = New Font(noClickFont.Name, 14, FontStyle.Bold Or FontStyle.Italic)
  End Sub

  Private Sub ButtonMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
    Debug.WriteLine(String.Format("Button Down at ({0},{1})", e.X, e.Y))

    ' change the font while the button is down

    Dim s As Button = CType(sender, Button)
    s.Font
= clickFont
 
End
Sub

  Private
Sub ButtonMouseUp(ByVal sender As Object, ByVal e As MouseEventArgs)
    Debug.WriteLine(String.Format("Button Up at ({0},{1})", e.X, e.Y))

    ' revert the font while the button is up

    Dim s As Button = CType(sender, Button)
    s.Font
= noClickFont
 
End
Sub
End Class

Public Class MyApplication
  ' global filter object
  Public Shared buttonFilter As New ButtonEventFilter()

  Public Shared Sub Main()
    ' add the filter
    Application2.AddMessageFilter(buttonFilter)
    ' start the app's main Form
    Application2.Run(New Form1())
  End Sub
End Class

Public Class ButtonEventFilter
  Implements IMessageFilter

  Private m_buttonList As New List(Of Button)()

  Public Sub WatchButton(ByVal buttonToWatch As Button)
    m_buttonList.Add(buttonToWatch)
  End Sub

  Public Event MouseUp As MouseEventHandler
  Public Event MouseDown As MouseEventHandler

  Public Function PreFilterMessage(ByRef m As Microsoft.WindowsCE.Forms.Message) As Boolean 
      Implements OpenNETCF.Windows.Forms.IMessageFilter.PreFilterMessage
    
    PreFilterMessage = False

    ' only look for mouse button down and up
    If m.Msg <> LBUTTONDOWN And m.Msg <> LBUTTONUP Then Exit Function

    ' see if the control is a watched button
    For Each watchedButton As Button In m_buttonList
      If m.HWnd = watchedButton.Handle Then
        Dim i As Integer = CType(m.LParam, Integer)
        Dim x As Integer = i And &HFFFF
        Dim y As Integer = (i And &HFFFF0000) >> 16
        Dim args As New MouseEventArgs(CType(m.WParam, MouseButtons), 0, x, y, 0)

        If m.Msg = LBUTTONDOWN Then
          RaiseEvent MouseDown(watchedButton, args)
          ' alter the BackColor
          watchedButton.BackColor = Color.Red
       
ElseIf
m.Msg = LBUTTONUP Then
          RaiseEvent MouseUp(watchedButton, args)
          ' alter the BackColor
          watchedButton.BackColor = Color.Green
       
End
If

        ' refresh

        watchedButton.Refresh()
        ' prevent the default behavior (which will be a black background on click
        PreFilterMessage = True
     
End
If
    Next
  End Function
End Class

5/7/2007 11:09:03 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, May 03, 2007

Problem
I've got a text file on the device that's stored in [ASCII/Unicode]. Sure, I can create a System.IO.Stream derivative, read the data and close it, but I'd like something simpler. What can the SDF do for me?

Solution
The OpenNETCF.IO.FileHelper class provides some useful methods to explore.  Take a look at these snippets:

string fileContents;
fileContents = OpenNETCF.IO.FileHelper.ReadAllText("MyASCIIFile.txt", Encoding.ASCII);
fileContents = OpenNETCF.IO.FileHelper.ReadAllText("MyUnicodeFile.txt", Encoding.Unicode);

string[] linesOfTextFile;
linesOfTextFile = OpenNETCF.IO.FileHelper.ReadAllLines("MyASCIIFile.txt", Encoding.ASCII);
linesOfTextFile = OpenNETCF.IO.FileHelper.ReadAllLines("MyUnicodeFile.txt", Encoding.Unicode);

5/3/2007 1:45:49 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

Problem
I have an assembly that is in the GAC on my device.  The assembly is used by a couple different applications, but often through other intermediate assemblies.  I'd like to do some logging of the actual application using my assembly. What can the SDF do for me?

Solution
The OpenNETCF.Reflection.Assembly2 class provides the key:

OpenNETCF.Reflection.Assembly2.GetEntryAssembly();


 

5/3/2007 1:40:54 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, April 25, 2007

Problem A
I have an application that uses an enum whos values aren't nicely readable.  Given a value of the enum, I'd like to get a nice, readble text name for it.  I'd like the solution to be easily extensible and maintanable.  What can the SDF do for me?

Problem B
I have an application with an enum and I need to populate a combobox with its value names. What can the SDF do for me?

Problem C
I have an application with an enum and I need to get a list of all of the values in the enum. What can the SDF do for me?

Solution
The solution to all of these is found in the OpenNETCF.Enum2 class.  Here's a snippet from a project I'm working on:
[Tested on the PPC 2003 Emulator with SDF 2.1]

public class DescriptionAttribute : Attribute
{
  public string Description;

  public DescriptionAttribute(string description)
  {
    Description = description;
  }
}

public enum Places
{
  [Description("Missoula, Montana")]
  MissoulaMT,
  [Description("Denton, Texas")]
  DentonTX,
  [Description("Berwyn, Illinois")]
  BerwynIL,
  [Description("Frederick, Maryland")]
  FrederickMD
}

...

using System.Reflection;
using System.ComponentModel;
using OpenNETCF;
...
private void button1_Click(object sender, EventArgs e)
{
  int maxPlaces = Enum2.GetValues(typeof(Places)).Length;
  Places p = (Places)(new System.Random().Next(maxPlaces));
  FieldInfo fi = p.GetType().GetField(Enum2.GetName(typeof(Places), p));
  DescriptionAttribute desc = (DescriptionAttribute)fi.GetCustomAttributes(typeof(DescriptionAttribute), false)[0];
  textBox1.Text = desc.Description;
}

4/25/2007 11:48:28 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Sunday, April 22, 2007

Problem A
I have an application that starts up a worker thread.  I have code that properly kills that thread during normal shutdown, but if I'm debugging and stop the app, the thread keeps the app alive and I can't debug the app again without resetting teh device.  What can the SDF do for me?

Problem B
I have a CE device that is running a process without a window (console or otherwise) and I'd like to kill it.  What can the SDF do for me?

Solution
The solution to both is found in the OpenNETCF.ToolHelp.ProcessEntry class.  Add this to an app and run it.

ProcessEntry[] currentProcesses = ProcessEntry.GetProcesses();

foreach (ProcessEntry p in currentProcesses)
{
  if (p.ExeFile.ToLower() == "MyAppName.exe".ToLower())
  {
    p.Kill();
    return;
  }
}

4/22/2007 11:23:10 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, April 20, 2007

Problem A
I have an application that transfers data to another app via (serial/CAN/Ethernet/can-and-string/cheese stream ion a macaroni pipe/whatever).  I have set up a send and acknowlege paradigm so I know when the receiver has all of the data, but I need to know that what the other end received is correct.  What does the SDF do for me?

Problem B
I have an application that receives a file from a native application.  The application send me a 'checksum' along with the data and the native guys tell me that I must use that checksum value to make sure that the data I received has not been corrupted.  I vaguely understand what a checksum is, but I have no clue how to calculate one from the file.  What does the SDF do for me?

Solution
[Tested on a custom PXA270-based Windows CE 5.0 device *and* the desktop (full framework 2.0)]

Nicely enough, the SDF solves both of these with the use of the OpenNETCF.CRC class. The CRC (short for Cyclic Redundancy Check) class supports generating a CRC for a byte array or a FileStream.

Here's what usage for a file looks like.  This creates a 32-bit checksum (we support 8-64 bit) using a standard ploynomial (we support any custom polynomial too):

FileStream fs = File.OpenRead(sourceFileName);
uint checksum = (uint)OpenNETCF.CRC.GenerateChecksum(fs, 32, (ulong)OpenNETCF.CRCPolynomial.CRC_CCITT32);
fs.Close();

 

4/20/2007 2:40:28 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, April 18, 2007

Sometimes we tend to forget just how useful the Smart Device Framework really is.  There are tons of little gems in it that we put in there, some times years ago, and then we forget about it.  I have a general idea of what all is there, but by no means can I tell you everything, so when I need a feature in an app, I always go look to see if we've already done it.

Unfortunately we tend to lack concrete examples of how a lot of it can help you in your everyday work.  In an effort to rectify that, over the coming months all of us here at OpenNETCF will be blogging short snippets of cool, useful stuff you can do.  We also are taking requests.

So here's one.  

Problem
I have a device and we want to know when a storage device (USB, CF, PCMCIA, SD, etc.) is either inserted or removed.  What does the SDF do for me?

Solution

[Developed and tested on an OLDI 56SAM-400 800MHz x86 device running CE 5.0]

private DeviceStatusMonitor m_diskMonitor;

public MyClass()
{
    ...

    m_diskMonitor = new DeviceStatusMonitor(DeviceStatusMonitor.FATFS_MOUNT_GUID, false);
    m_diskMonitor.DeviceNotification += FATMounted;
    m_diskMonitor.StartStatusMonitoring();
    ...
}

~MyClass()
{
    m_diskMonitor.StopStatusMonitoring();
}

void FATMounted(object sender, DeviceNotificationArgs e)
{
    Trace2.WriteLine(string.Format("FAT device '{0}' {1}mounted", e.DeviceName, e.DeviceAttached ? "" : "un"));
}

The DeviceStatusMonitor ctor's first parameter can be a few things - I've chosen FAT file system (we can detect a CD too!).

The thing I don't understand is how the hell this class ended up in the OpenNETCF.Net namespace.  Expect that to change in 2.2.  I'll probably deprecate the existing classes with a warning and move them somewhere that it makes sense.  I may also streamline the usage as well.

4/18/2007 10:37:04 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  |