Getting Mouse Events in a Compact Framework TextBox

Yesterday I got a support request for the Smart Device Framework.  The user was after a seemingly simple behavior – they wanted to get a Click or MouseDown event for a TextBox is their Compact Framework application so they could select the full contents of the TextBox when a user tapped on it.

Of course on the desktop this is pretty simple, you’d just add a handler to the Click event and party on. Well, of course the Compact Framework can’t be that simple. The TextBox has no Click, MouseUp or MouseDown events. Thanks CF team. There are some published workarounds – one on CodeProject and one on MSDN – but they involve subclassing the underlying native control to get the WM_LBUTTONDOWN and WM_LBUTTONUP messages, and honestly that’s just not all that fun. Nothing like making your code look like C++ to kill readability.

For whatever reason (I can’t give a good one offhand) the TextBox2 in the Smart Device Framework also doesn’t give any of the mouse events, *but* it does offer a real easy way to add them since it does allow simply overriding of the WndProc. Basically you just have to create a new derived control like this:

    public class ClickableTextBox : TextBox2
    {
        public event EventHandler MouseDown;

        protected override void WndProc(ref Microsoft.WindowsCE.Forms.Message m)
        {
            base.WndProc(ref m);

            switch ((WM)m.Msg)
            {
                // do this *after* the base so it can do the focus, etc. for us
                case WM.LBUTTONDOWN:
                    var handler = MouseDown;
                    if (handler != null) handler(this, EventArgs.Empty);
                    break;
            }
        }
    }

And then using it becomes as simple as this:

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

            textBox2.Text = "lorem ipsum";
            textBox2.MouseDown += new EventHandler(textBox2_MouseDown);
        }

        void textBox2_MouseDown(object sender, EventArgs e)
        {
            textBox2.SelectAll();
        }
    }

SDF Sample: IPC with Memory Mapped Files

Transferring data between processes in Windows CE is not difficult, and you can avoid using passing data through the message pump using WM_COPYDATA (which I’ve never considered a good idea, BTW).  Sockets and point to point queues are pretty straightforward and well documented for IPC. 


One mechanism that isn’t well covered, and that works well for sharing larger blobs of data, is a memory-mapped file.  The SDF contains a MemoryMappedFile class (in the OpenNETCF.IO namespace) that allows you to use a shared memory region (in ram or on disk – your choice) and to access it just like you would any other stream with Write, Read and Seek.  Here’s a simple example. Yes I realize my synchronization mechanism here isn’t the cleanest, but it conveys the idea with the least code.


 

using System;
using System.Text;
using OpenNETCF.IO;
using OpenNETCF.Threading;
using System.Diagnostics;
using System.Reflection;
using System.IO;

namespace MMFPeer
{
class Program
{
public const string SharedMapName = “MMF_PEER_NAME”;
public const long MaxMapSize = 1024;
public const string SharedMutexName = “MMF_PEER_MUTEX”;
public const string DataReadyEventName = “MMF_PEER_DATA_READY”;

private MemoryMappedFile m_mmf;
private NamedMutex m_mutex;
private EventWaitHandle m_dataReady;

private bool Sending { get; set; }

static void Main(string[] args)
{
new Program().Run();
}

public void Run()
{
var processName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().GetName().CodeBase);

// create the MMF
m_mmf = MemoryMappedFile.CreateInMemoryMap(SharedMapName, MaxMapSize);

// create a shared mutex
m_mutex = new NamedMutex(false, SharedMutexName);

// create a data-ready event
m_dataReady = new EventWaitHandle(false, EventResetMode.ManualReset, DataReadyEventName);

// fire up a “listener”
new System.Threading.Thread(ReadProc)
{
IsBackground = true,
Name = “MMF Peer Reader”
}
.Start();

Console.WriteLine(“Memory Mapped File Created. Enter text to send to peer(s)”);

// wait for user input
while (true)
{
var input = Console.ReadLine();

if (input == null)
{
Thread2.Sleep(5000);
input = GetMockInput();
Debug.WriteLine(string.Format(“Platform does not have a Console installed. Sending mock data ‘{0}'”, input));
}

if (input == “exit”) break;

// prefix our process name so we can tell who sent the data
input = processName + “:” + input;

// grab the mutex
if (!m_mutex.WaitOne(5000, false))
{
Console.WriteLine(“Unable to acquire mutex. Send Abandoned”);
Debug.WriteLine(“Unable to acquire mutex. Send Abandoned”);
continue;
}

// mark as “sending” so the listener will ignore what we send
Sending = true;

// create a “packet” (length + data)
var packet = new byte[4 + input.Length];
Buffer.BlockCopy(BitConverter.GetBytes(input.Length), 0, packet, 0, 4);
Buffer.BlockCopy(Encoding.ASCII.GetBytes(input), 0, packet, 4, input.Length);

// write the packet at the start
m_mmf.Seek(0, System.IO.SeekOrigin.Begin);
m_mmf.Write(packet, 0, packet.Length);

// notify all clients that data is ready (manual reset events will release all waiting clients)
m_dataReady.Set();

// yield to allow the receiver to unblock and check the “Sending” flag
Thread2.Sleep(1);

// reset the event
m_dataReady.Reset();

// unmark “sending”
Sending = false;

// release the mutex
m_mutex.ReleaseMutex();
}
}

private int m_inputIndex = -1;
private string[] m_inputs = new string[]
{
“Hello”,
“World”
};

private string GetMockInput()
{
if (++m_inputIndex >= m_inputs.Length) m_inputIndex = 0;
return m_inputs[m_inputIndex];
}

private void ReadProc()
{
var lengthBuffer = new byte[4];
var dataBuffer = new byte[m_mmf.Length – 4];

while (true)
{
// use a timeout so if the app ends, this thread can exit
if(!m_dataReady.WaitOne(1000, false)) continue;

// avoid receiving our own data
if (Sending)
{
// wait long enough for the sender to reset the m_dataReady flag
Thread2.Sleep(5);
continue;
}

// grab the mutex to prevent concurrency issues
m_mutex.WaitOne(1000, false);

// read from the start
m_mmf.Seek(0, System.IO.SeekOrigin.Begin);

// get the length
m_mmf.Read(lengthBuffer, 0, 4);
var length = BitConverter.ToInt32(lengthBuffer, 0);

// get the data
m_mmf.Read(dataBuffer, 0, length);

// release the mutex so any other clients can receive
m_mutex.ReleaseMutex();

// convert to a string
var received = Encoding.ASCII.GetString(dataBuffer, 0, length);

Console.WriteLine(“Received: ” + received);
Debug.WriteLine(“Received: ” + received);
}
}
}
}

SDF and WiFi: Connecting to APs

One of the more common question groups I see about the Smart Device Framework really revolves around connecting to an access point.  Connecting to an Open AP is pretty straightforward, but as soon as you need to start adding encryption, privacy, passcodes and all that fun stuff, people tend to go off the rails.  And I can’t blame them, the WirelessZeroConfigNetworkInterface class isn’t the most intuitive interface.  What I’ve done in my own projects is to create a WiFiService class that wraps all of this (and a lot of other stuff too).  I’ll try to get that full service to a publishable state in the not-too-distant future, but the core pieces for connectivity boil down to these few methods:


 

public bool ConnectToOpenNetwork(string ssid)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return ConnectToNetwork(ssid, null, false, AuthenticationMode.Open, WEPStatus.WEPDisabled);
}

public bool ConnectToOpenNetwork(IAccessPoint accessPoint)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return m_wzc.AddPreferredNetwork(accessPoint);
}

public bool ConnectToWEPNetwork(string ssid, string wepKey)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return ConnectToNetwork(ssid, wepKey, false, AuthenticationMode.Open, WEPStatus.WEPEnabled);
}

public bool ConnectToWEPNetwork(IAccessPoint accessPoint, string wepKey)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return ConnectToWEPNetwork(accessPoint.Name, wepKey);
}

///


/// This connects to a WPA network using TKIP encryption.
///

///
///
///
public bool ConnectToWPANetwork(string ssid, string passphrase)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return ConnectToNetwork(ssid, passphrase, false, AuthenticationMode.WPAPSK, WEPStatus.TKIPEnabled);
}

///


/// This method will only connect to a WPA2 network using AES.
/// If you need fallback to WPA (using TKIP), use the overload that takes in an AccessPoint
///

///
///
///
public bool ConnectToWPA2Network(string ssid, string passphrase)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

return ConnectToNetwork(ssid, passphrase, false, AuthenticationMode.WPA2PSK, WEPStatus.AESEnabled);
}

///


/// Connects to a WPA or WPA2 (AES or TKIP)
///

///
///
///
public bool ConnectToWPANetwork(IAccessPoint accessPoint, string passphrase)
{
if (!WiFiSupported) throw new NotSupportedException(“No WZC WiFi Adapter detected on this device”);

// quick validation
var valid = accessPoint.AuthenticationMode == AuthenticationMode.WPA
|| accessPoint.AuthenticationMode == AuthenticationMode.WPAPSK
|| accessPoint.AuthenticationMode == AuthenticationMode.WPA2
|| accessPoint.AuthenticationMode == AuthenticationMode.WPA2PSK;

if (!valid)
{
throw new InvalidOperationException(“The provided AP is not set up for WPA”);
}

return ConnectToNetwork(accessPoint.Name, passphrase, false, accessPoint.AuthenticationMode, accessPoint.Privacy);
}

private bool ConnectToNetwork(string ssid, string passphrase, bool adhoc, AuthenticationMode mode, WEPStatus encryption)
{
EAPParameters eap = null;

switch (mode)
{
case AuthenticationMode.WPA:
case AuthenticationMode.WPAPSK:
case AuthenticationMode.WPA2:
case AuthenticationMode.WPA2PSK:
eap = new EAPParameters()
{
Enable8021x = true,
EapType = EAPType.Default,
EapFlags = EAPFlags.Enabled,
};
break;
}

// stop scanning while connecting
var wasScanning = Scanning;
StopScanning();

try
{
lock (m_syncRoot)
{
return m_wzc.AddPreferredNetwork(ssid,
!adhoc,
passphrase,
1,
mode,
encryption,
eap);
}
}
catch (ArgumentException ae)
{
return false;
}
finally
{
if (wasScanning) StartScanning();
}
}


I apologize that the above code won’t just compile right out of the box – it’s got some dependencies to other bits in the Service class, but my hope is that it at least gives the essence of what needs to be done to get the connectvitiy you need working.

A service for Scanning and Connecting to WiFi Networks from the CF

I’m going through the Smart Device Framework‘s WZC code doing testing and hardening (yes, I realize it’s long overdue) and I realize that it’s not all that clear how to add a preferred network to a wireless adapter.  I’ve created the beginnings of a new Service class for the OpenNETCF IoC framework (though it’s not required that you use IoC to use the service) that will help applications manage things.  I’ll come back and update this code as I expand the service due to dogfooding it, but here’s the start:


 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace OpenNETCF.Net.NetworkInformation
{
public delegate void ScanCompleteDelegate(AccessPoint[] newNetworks, AccessPoint[] lostNetworks, AccessPoint[] stillAvailableNetworks);

public class WiFiService
{
private List m_knownAPs = new List();
private object m_syncRoot = new object();
private AutoResetEvent m_stopScanEvent = new AutoResetEvent(false);
private WirelessZeroConfigNetworkInterface m_wzc;

public event ScanCompleteDelegate ScanComplete;

public int ScanPeriod { get; set; }
public bool Scanning { get; private set; }

public WiFiService(WirelessZeroConfigNetworkInterface netInterface)
{
m_wzc = netInterface;

// default to a 2 second network scan period
ScanPeriod = 2000;
}

~WiFiService()
{
StopScanning();
m_knownAPs.Clear();
}

public void StartScanning()
{
if (Scanning) return;

new Thread(ScanThreadProc) { IsBackground = true }
.Start();
}

public void StopScanning()
{
if (!Scanning) return;

m_stopScanEvent.Set();
m_knownAPs.Clear();
}

public bool ConnectToOpenNetwork(string ssid)
{
return ConnectToNetwork(ssid, null, false, AuthenticationMode.Open, WEPStatus.WEPDisabled);
}

public bool ConnectToOpenNetwork(AccessPoint accessPoint)
{
return m_wzc.AddPreferredNetwork(accessPoint);
}

public bool ConnectToWEPNetwork(string ssid, string wepKey)
{
return ConnectToNetwork(ssid, wepKey, false, AuthenticationMode.Open, WEPStatus.WEPEnabled);
}

public bool ConnectToWEPNetwork(AccessPoint accessPoint, string wepKey)
{
return ConnectToWEPNetwork(accessPoint.Name, wepKey);
}

///


/// This connects to a WPA network using TKIP encryption.
///

///
///
///
public bool ConnectToWPANetwork(string ssid, string passphrase)
{
return ConnectToNetwork(ssid, passphrase, false, AuthenticationMode.WPAPSK, WEPStatus.TKIPEnabled);
}

///


/// This method will only connect to a WPA2 network using AES.
/// If you need fallback to WPA (using TKIP), use the overload that takes in an AccessPoint
///

///
///
///
public bool ConnectToWPA2Network(string ssid, string passphrase)
{
return ConnectToNetwork(ssid, passphrase, false, AuthenticationMode.WPA2PSK, WEPStatus.AESEnabled);
}

///


/// Connects to a WPA or WPA2 (AES or TKIP)
///

///
///
///
public bool ConnectToWPANetwork(AccessPoint accessPoint, string passphrase)
{
// quick validation
var valid = accessPoint.AuthenticationMode == AuthenticationMode.WPA
|| accessPoint.AuthenticationMode == AuthenticationMode.WPAPSK
|| accessPoint.AuthenticationMode == AuthenticationMode.WPA2
|| accessPoint.AuthenticationMode == AuthenticationMode.WPA2PSK;

if (!valid)
{
throw new InvalidOperationException(“The provided AP is not set up for WPA”);
}

return ConnectToNetwork(accessPoint.Name, passphrase, false, accessPoint.AuthenticationMode, accessPoint.Privacy);
}

private void ScanThreadProc()
{
lock (m_syncRoot)
{
Scanning = true;

do
{
var et = Environment.TickCount;

var current = m_wzc.NearbyAccessPoints;

var added = current.Except(m_knownAPs);
var lost = m_knownAPs.Except(current);
var updated = m_knownAPs.Intersect(current);

m_knownAPs = current.ToList();

var handler = ScanComplete;

et = Environment.TickCount – et;
Debug.WriteLine(string.Format(“Network scan took {0}ms”, et));

if (handler != null)
{
handler(added.ToArray(), lost.ToArray(), updated.ToArray());
}
} while (!m_stopScanEvent.WaitOne(ScanPeriod, false));

Scanning = false;
}
}

private bool ConnectToNetwork(string ssid, string passphrase, bool adhoc, AuthenticationMode mode, WEPStatus encryption)
{
EAPParameters eap = null;

switch (mode)
{
case AuthenticationMode.WPA:
case AuthenticationMode.WPAPSK:
case AuthenticationMode.WPA2:
case AuthenticationMode.WPA2PSK:
eap = new EAPParameters()
{
Enable8021x = true,
EapType = EAPType.Default,
EapFlags = EAPFlags.Enabled,
};
break;
}

// stop scanning while connecting
var wasScanning = Scanning;
StopScanning();

try
{
lock (m_syncRoot)
{
return m_wzc.AddPreferredNetwork(ssid,
!adhoc,
passphrase,
1,
mode,
encryption,
eap);
}
}
finally
{
if (wasScanning) StartScanning();
}
}
}
}

Loading parts of large images in the Compact Framework

Even at version 3.5, the image manipulation capabilities of the Compact Framework are seriously crappy lacking.  I’ve had all sorts of challenges over the years getting apps to do exactly what I want.  Fortuantely the CF also allows you to do just about anything you can imagine as long as you’re willing to roll up your sleeves and dive into the ugliness of interop.

Today I decided to to to create a simple app that would use large images.  Let’s say you have a modern camera – you know, something made after 1990 that actually generates pictures of a resolution a bit above 240×480.  The challenge with these images is that in order to display them, they have to be uncompressed to a Bitmap, so if you have a picture that is 2048×1536 at a depth of 24 bits per pixel, you’re talking almost 10MB or RAM just to hold that image.  It doesn’t take long before you’re going to have memory pressure issues (in fact you’ll probably have them just trying to load it).

My goal was to display the entire image on my device screen and then allow the user to zoom in on a small area of the image (the zoom is really just a “show this section at the images native resolution” operation).  This simply cannot be done with the System.Drawing namespace.  If you do manage to load the image, you can’t get it to paint both the stretched image as the zoom becasue you simply don’t have the RAM for it.

This is where the SDF’s Imaging namespace comes in.  The Imaging namespace wraps the COM objects in the Imaging library.  It took a little work, but I generated a quick helper class to generate both thumbnails and clips:

public static class ImageHelper
{
    private static ImagingFactory m_factory;
    private static ImagingFactory GetFactory()
    {
        if (m_factory == null)
        {
            m_factory = new ImagingFactory();
        }

        return m_factory;
    }

    public static Bitmap CreateClip(StreamOnFile sof, int x, int y, int width, int height)
    {
        IBitmapImage original = null;
        IImage image = null;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            GetFactory().CreateBitmapFromImage(image, info.Width, info.Height,
            info.PixelFormat, InterpolationHint.InterpolationHintDefault, out original);

            try
            {
                var ops = (IBasicBitmapOps)original;
                IBitmapImage clip = null;

                try
                {
                    var rect = new RECT(x, y, x + width, y + height);
                    ops.Clone(rect, out clip, true);

                    return ImageUtils.IBitmapImageToBitmap(clip);
                }
                finally
                {
                    Marshal.ReleaseComObject(clip);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(original);
            }
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }

    public static Bitmap CreateThumbnail(StreamOnFile sof, int width, int height)
    {
        IBitmapImage thumbnail;
        IImage image;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            GetFactory().CreateBitmapFromImage(image, (uint)width, (uint)height,
            info.PixelFormat, InterpolationHint.InterpolationHintDefault, out thumbnail);
            try
            {
                return ImageUtils.IBitmapImageToBitmap(thumbnail);
            }
            finally
            {
                Marshal.ReleaseComObject(thumbnail);
            }
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }

    public static Size GetRawImageSize(StreamOnFile sof)
    {
        IImage image;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            return new Size((int)info.Width, (int)info.Height);
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }
}

With that, creating the actual application became nearly trivial:

public partial class Form1 : Form
{
    StreamOnFile m_stream;
    Size m_size;
    public Form1()
    {
        InitializeComponent();
        thumbnail.MouseDown += new MouseEventHandler(thumbnail_MouseDown);
    }

    private void load_Click(object sender, EventArgs e)
    {
        var stream = File.Open("\Program Files\ThumbnailExample\bigimage.jpg", FileMode.Open);
        m_stream = new StreamOnFile(stream);

        m_size = ImageHelper.GetRawImageSize(m_stream);
        thumbnail.Image = ImageHelper.CreateThumbnail(m_stream, thumbnail.Width, thumbnail.Height);

        size.Text = string.Format("{0}x{1}", m_size.Width, m_size.Height);
        load.Enabled = false;
    }

    void thumbnail_MouseDown(object sender, MouseEventArgs e)
    {
        // scale
        int x = (e.X * (m_size.Width / thumbnail.Width)) - (clip.Width / 2);
        int y = (e.Y * (m_size.Height / thumbnail.Height)) - (clip.Height / 2);

        if (clip.Image != null) clip.Image.Dispose();

        clip.Image = ImageHelper.CreateClip(m_stream, x, y, clip.Width, clip.Height);
    }
}

And the output looks something like this:

Getting Access Point information from Visual Basic

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)

Detecting network cable connect and disconnect in the Compact Framework

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