Edit boxes and default text selection

While working on an application of mine, I had to display ‘Help’ information. I chose to use a read-only multiline edit box along with SetWindowText() to display the text, sounds simple enough. But I ran into a small but annoying problem. The text displayed was all selected, and I thought why would the text be selected by default!?

Here is a sample program to demonstrate this,

  1. #include <windows.h>
  2. #include <aygshell.h>
  3. #include "resource.h"
  4.  
  5. HINSTANCE g_hInst;
  6.  
  7. BOOL CALLBACK EditBoxSelTextDlgProc(HWND, UINT, WPARAM, LPARAM);
  8. void AlignComponents(HWND hDlg);
  9. void InitializeComponents(HWND hDlg);
  10.  
  11. int WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
  12.             LPTSTR lpCmdLine, int nCmdShow)
  13. {
  14.     g_hInst = hInst;
  15.  
  16.     DialogBox(hInst, MAKEINTRESOURCE(IDD_PPC_EDITBOXTEXTSEL), NULL, EditBoxSelTextDlgProc);
  17.     return 0;
  18. }
  19.  
  20. BOOL CALLBACK EditBoxSelTextDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
  21. {
  22.     int wmID, wmEvent;
  23.     PAINTSTRUCT ps;
  24.     HDC hdc;
  25.  
  26.     switch(uMessage)
  27.     {
  28.         case WM_INITDIALOG:
  29.             {
  30.                 SHINITDLGINFO shidi;
  31.                 SHMENUBARINFO mbi;
  32.  
  33.                 memset(&shidi, 0, sizeof(shidi));
  34.                 memset(&mbi, 0, sizeof(mbi));
  35.  
  36.                 shidi.dwMask = SHIDIM_FLAGS;
  37.                 shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
  38.                 shidi.hDlg = hDlg;
  39.                 SHInitDialog(&shidi);
  40.  
  41.                 mbi.cbSize = sizeof(mbi);
  42.                 mbi.hwndParent = hDlg;
  43.                 mbi.nToolBarId = IDR_MENU_EDITBOXTEXTSEL;
  44.                 mbi.hInstRes = g_hInst;
  45.  
  46.                 if(!SHCreateMenuBar(&mbi))
  47.                 {
  48.                     printf("Error creating menu bar, errcode:0x%x\n", GetLastError());
  49.                 }
  50.  
  51.                 AlignComponents(hDlg);
  52.  
  53.                 InitializeComponents(hDlg);
  54.             }
  55.             return TRUE;
  56.  
  57.         case WM_COMMAND:
  58.             {
  59.                 wmID = LOWORD(wParam);
  60.                 wmEvent = HIWORD(wParam);
  61.  
  62.                 switch(wmID)
  63.                 {
  64.                     case IDM_EXIT:
  65.                         EndDialog(hDlg, uMessage);
  66.                         break;
  67.                 }
  68.             }
  69.             break;
  70.  
  71.         case WM_PAINT:
  72.             {
  73.                 hdc = BeginPaint(hDlg, &ps);
  74.  
  75.                 EndPaint(hDlg, &ps);
  76.             }
  77.             break;
  78.     }
  79.     return FALSE;
  80. }
  81.  
  82. void AlignComponents(HWND hDlg)
  83. {
  84.     HWND hTemp = NULL;
  85.     RECT rect = {0, 0, 0, 0};
  86.     int x=0, y=0, width=0, height=0;
  87.     const int insetX = 3;
  88.     const int insetY = 3;
  89.  
  90.     GetClientRect(hDlg, &rect);
  91.  
  92.     hTemp = GetDlgItem(hDlg, IDC_EDITBOX);
  93.     if (hTemp)
  94.     {
  95.         x = rect.left + insetX;
  96.         y = rect.top + insetY;
  97.         width = (rect.right – rect.left – 2*insetX);
  98.         height = (rect.bottom – rect.top – 2*insetY);
  99.  
  100.         MoveWindow(hTemp, x, y, width, height, FALSE);
  101.     }
  102. }
  103.  
  104. const TCHAR message[] = TEXT("Lorem ipsum dolor sit amet, consectetur adipisicing elit, \
  105. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
  106. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris \
  107. nisi ut aliquip ex ea commodo consequat.\
  108. \r\n\r\n\
  109. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum \
  110. dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non \
  111. proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\
  112. ");
  113.  
  114. void InitializeComponents(HWND hDlg)
  115. {
  116.     HWND hTemp = GetDlgItem(hDlg, IDC_EDITBOX);
  117.  
  118.     if (hTemp)
  119.     {
  120.         SetWindowText(hTemp, message);
  121.     }
  122. }

 

This is usual stuff. Display a dialog box from WinMain(), initialize the dialog and the menubar in dialog proc under the WM_INITDIALOG message. The functions of interest would be AlignComponents() and InitializeComponents(). The dialog contains a single multiline edit box, AlignComponents() resizes the edit box so it occupies the entire screen, and InitializeComponents() calls SetWindowText() to set the text on the edit box control.

If you run this program, here is how it’ll look,

image

So you see the problem there. Skimming through the edit box messages, I came across EM_SETSEL message. This message selects a range of characters in an edit control, and if you pass –1 to it, any current selection is removed. So after the SetWindowText() call in InitializeComponents(), I put this line,

SendMessage(hTemp, EM_SETSEL, (WPARAM)-1, (LPARAM)0);

but if it was this simple I wouldn’t be writing this post, would I? In short, it didn’t work.

I thought maybe I am sending the EM_SETSEL message too early, even before the edit control is ready to process it perhaps? So the next step was to find the right place from where the message could be sent. So, one by one, I went through all the messages sent to dialog proc until the dialog is shown and found WM_ACTIVATE could be the right place. And it indeed was.

Adding the following switch case to dialog proc did the trick,

  1. case WM_ACTIVATE:
  2.     SendMessage(GetDlgItem(hDlg, IDC_EDITBOX), EM_SETSEL,
  3.         (WPARAM)-1, (LPARAM)0);
  4.     break;

 

The edit control now shows up without any selected text.

image

HTC HD2, Accelerometer and Windows Phone 7 – Source Code

If you haven’t seen the video demo in my last post, I suggest you watch it first to get a better understanding. So in this post we are going to see how the WCF service, the .NET Compact Framework application running on HD2 and the Silverlight application running on the Windows Phone 7 emulator work together.


The Objective

We are going to write three applications, a simple WCF service hosted in a windows console application which implements two functions, one called UploadAccelData() and the other DownloadAccelData(). A .NET Compact Framework application for HD2 which calls the WCF services’ UploadAccelData() function periodically and passes it the x, y and z components of the acceleration vector. A silverlight application for Windows Phone 7 which calls into DownloadAccelData() periodically and gets the accelerometer data off it.

For those of you who want to jump straight into the code, you can download it from the following links,

Download ConsoleApp.zip – VS 2008 solution which contains two projects. One, the console application which hosts the WCF service and two the Compact Framework application which runs on HD2

Download WCFClient_WP7Emu.zip – Windows Phone 7 application which runs on the emulator

Creating the WCF service
I will be using Visual Studio 2008 for creating the service and the .net cf application. Open VS 2008 in administrator mode and create a windows console application in Visual C#. It is important that you start visual studio in administrator mode otherwise you might get an error while deploying the service depending on your access rights on the machine. Now lets add a WCF service which will be hosted by this console application. Right click on the project and select “Add –> New Item..”. In the next dialog select “WCF Service” and give a name to it. I call it “AccelerometerService”.

Creating accelerometer service

With this three files will be added to your project AccelerometerService.cs, IAccelerometerService.cs and App.Config. Open App.Config and change a few things in it, first change binding="wsHttpBinding" to binding="basicHttpBinding" since .net compact framework only supports basicHttpBinding. Next change the BaseAddress from,

<add baseAddress="http://localhost:8731/Design_Time_Addresses/ConsoleApp/AccelerometerService/" /> to

<add baseAddress="http://192.168.2.2:8731/ConsoleApp/AccelerometerService/" />

Again, it is important that you change “localhost” to the ip address of your machine. This is because we will be using this address from the compact framework application to refer to the service, so we need the address of the machine where the service is running. Using “localhost” from the compact framework application would mean a totally different thing. Here I use 192.168.2.2 which is the IP address assigned to my machine by the wireless router that I am using. Be sure to change this IP address depending on your machine setup.

Open Program.cs and add the following code in the Main() function,

static void Main(string[] args)
{
    ServiceHost accelService = new ServiceHost(typeof(AccelerometerService));

    try
    {
        accelService.Open();
        Console.WriteLine("Accel service is running at:{0}", accelService.BaseAddresses[0].AbsoluteUri);
        Console.WriteLine("Press <ENTER> to terminate the service..");

        Console.ReadLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine("An exception occured while creating the serivce. {0}", ex.Message);
        accelService.Abort();
    }
}

Here we are creating an instance of our AccelerometerService and calling Open() on it to start the service. The address where the service is running is output to the console and the service waits for any key to be pressed after which it will be terminated. Now build and run your project, you should see this console window,

Console service running

Now to check our service, open internet explorer and browse to the base address that we entered above,

http://192.168.2.2:8731/ConsoleApp/AccelerometerService/

and you should see this,

sanity check for service IE

If your browser is showing the same thing then great! We are progressing well.

Now lets implement our upload and download functions. Open IAccelerometerService.cs and add the following declarations to the interface,

[OperationContract]
void UploadAccelData(int x, int y, int z);

[OperationContract]
void DownloadAccelData(out int x, out int y, out int z);

To keep things simple I am passing only the x, y and z components of the vector, other information from the accelerometer like Roll and Pitch are ignored for simplicity, though adding those shouldn’t be rocket science. We will be implementing these functions in our AccelerometerService class. Open AccelerometerService.cs and add the following member variables to the AccelerometerService class,

static int _accelX;
static int _accelY;
static int _accelZ;

and add the following function definitions to the same class,

public void UploadAccelData(int x, int y, int z)
{
        _accelX = x;
        _accelY = y;
        _accelZ = z;

        Console.WriteLine("UploadAccelData: X:{0}, Y:{1}, Z:{2}", x, y, z);

}

public void DownloadAccelData(out int x, out int y, out int z)
{
        x = _accelX;
        y = _accelY;
        z = _accelZ;
}

This completes our console app and the WCF service that it will be hosting.

Creating the Compact Framework Application for HD2
Now lets go ahead and add a smart device compact framework C# application to the same solution in VS 2008. Right click on the solution and select “Add –> New Project..”. In the Add New Project dialog select Smart Device under Visual C# and add a new project, WCFClient_HD2.

HD2 CF add new project

in the next screen select “Windows Mobile 6 Professional SDK” as the target platform and “.NET CF Version 3.5”, choose Device Application and press OK.

HD2 cf project type

I have designed the form to show the x, y and z values from the accelerometer and a button to start and stop the sensor. Also, add a timer which fires every 100ms and is initially disabled.

cf form and timer

Now to this project, we will add a reference to the WCF service. First make sure that the service is running, deploy the ConsoleApp project. Right click on WCFClient_HD2 and select “Add Web Reference..”. In the next dialog, enter the URL of the service and press Go.

 adding reference to service

If everything goes well, our AccelerometerService should be found and you will be presented with this dialog,

give a name to the web reference, in this case “AccelerometerService” and select “Add Reference” button. Now the functionalities offered by the AccelerometerService are available to our compact framework application. Next, we need to instantiate this service and use it to call the UploadAccelData() function.

Open Form1.cs and add a member variable to the Form1 class which refers to the WCF service,

WCFClient_HD2.AccelerometerService.AccelerometerService accelService =
        new WCFClient_HD2.AccelerometerService.AccelerometerService();

I am going to go a bit off topic here and talk about the Accelerometer implementation on HD2. HTC does not provide an SDK for it’s devices (which is really sad) so the functionalities like Accelerometer, Proximity sensor and other custom features are not exposed to others for use. So some folks had to find out the library, reverse engineer the implementation and figure out how to use these features. The accelerometer driver is implemented in a dll called HTCSensorSDK.dll, this dll also implements a few other sensors as well. To use the accelerometer from a native application you’ll have to load this library [using LoadLibrary()], get pointers to the exported functions and call them. And from a managed application you’ll have to P/Invoke. Take a look at this post for more information on this.

Managed code to access the accelerometer on HD2 is available online and I am using the same code in this project here. Just go ahead and add HTCSensor.cs file to your project. This file implements HTCSensor class which you can use to read data from the accelerometer. The class implements StartSensor(), ReadDataFromSensor() and StopSensor() functions. Go back to Form1.cs and add two more variables to the class,

HTCSensor htcSensor = new HTCSensor();
HTCSensorData htcSensorData;

Create handlers for the Start Sensor button and uploadTimer,

private void button1_Click(object sender, EventArgs e)
{
    Button btn = sender as Button;

    if (btn.Text.Contains("Start"))
    {
        htcSensor.StartSensor();
        uploadTimer.Enabled = true;

        btn.Text = "Stop Sensor";
    }
    else
    {
        htcSensor.StopSensor();
        uploadTimer.Enabled = false;

        lblXAxis.Text = "???";
        lblYAxis.Text = "???";
        lblZAxis.Text = "???";

        btn.Text = "Start Sensor";
    }
}

and,

private void uploadTimer_Tick(object sender, EventArgs e)
{
    htcSensor.ReadDataFromSensor(out htcSensorData);

    lblXAxis.Text = htcSensorData.tiltX.ToString();
    lblYAxis.Text = htcSensorData.tiltY.ToString();
    lblZAxis.Text = htcSensorData.tiltZ.ToString();           
}

The button handler controls the timer and starts and stops it. And whenever the timer fires, we read the data from the accelerometer and display it. Run this application on HD2, if you have one, and check if the values are being read from the sensor. I am guessing that this application should also work on HTC Touch Pro2 and HTC Diamond 2 as well, but I have not tested this on those devices. Now all that is left is hooking this with the WCF Service, so every time the timer function is called we call the UploadAccelData() function of the service and pass the x, y and z values to it. Add the following lines of code to the end of the uploadTimer_Tick() function,

try
{
    accelService.UploadAccelData(htcSensorData.tiltX, true,
        htcSensorData.tiltY, true,
        htcSensorData.tiltZ, true);
}
catch (Exception ex)
{
    Console.WriteLine("An exception occured while calling into the service.");
    Console.WriteLine("{0}", ex.Message);
}

And that is all, our compact framework application is ready! Lets test whether the values are being passed to the service. Go to the solution properties and select multiple start-up projects and make sure that both the ConsoleApp and WCFClient_HD2 are set to start,

multiple start-up projects

Press F5 to start both the console and the compact framework application. Make sure that you are deploying the compact framework application to the device and that the device is connected to the machine over Active Sync (Windows XP) or WMDC (Vista or Win 7). Once both applications are deployed press the “Start Sensor” button on the phone, the accelerometer data should be getting passed to the service and the console window should output the logs as follows,

console logs showin service accel data

 

Creating the Silverlight application for Windows Phone 7
Start Visual Studio 2010 Express for Windows Phone and create a new Silverlight Windows Phone project, WCFClient_WP7Emu.

sl app wp7 project creation

Add text blocks and a button to Mainpage.xaml. The application UI looks like this,

sl app running on emu

Now lets add to this project a reference to the WCF Service. Deploy the WCF service from Visual Studio 2008 and make sure that it is running, right click on the silverlight project and select “Add Service Reference..”, and in the next dialog enter the URL of the service and press Go,

sl adding reference to service

once the service is discovered enter a namespace “AccelerometerService” and press OK to add a reference to it.

Open Mainpage.xaml.cs and add the following member variables to the Mainpage class,

WCFClient_WP7Emu.AccelerometerService.AccelerometerServiceClient accelService;
DispatcherTimer dt;

Now in the Mainpage() constructor add the following lines of code after the InitializeComponent() call,

accelService = new WCFClient_WP7Emu.AccelerometerService.AccelerometerServiceClient();

accelService.DownloadAccelDataCompleted +=
    new EventHandler<AccelerometerService.DownloadAccelDataCompletedEventArgs>(accelService_DownloadAccelDataCompleted);

dt = new DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 100);
dt.Tick += new EventHandler(dt_Tick);

lblXAxis.Text = "???";
lblYAxis.Text = "???";
lblZAxis.Text = "???";

Here we create an instance of the WCF service and register a handler for the DownloadAccelDataCompleted event. In silverlight for windows phone the web service api’s will be called asynchronously, and when the web service function returns, accelService_DownloadAccelDataCompleted() will be called. So we will be getting the accelerometer values in this function. Also, create a dispatch timer which fires every 100ms. Pressing TAB while creating the handlers for DownloadAccelDataCompleted and dt.Tick event will also create the function definitions for the handlers,

void accelService_DownloadAccelDataCompleted(object sender, AccelerometerService.DownloadAccelDataCompletedEventArgs e)
{

}

void dt_Tick(object sender, EventArgs e)
{

}

Add a handler for the button “Start reading data from web service”,

private void button1_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;

    if (btn.Content.ToString().Contains("Start"))
    {
        dt.Start();
        btn.Content = "Stop reading data from web service";
    }
    else
    {
        dt.Stop();
        btn.Content = "Start reading data from web service";

        lblXAxis.Text = "???";
        lblYAxis.Text = "???";
        lblZAxis.Text = "???";
    }
}

So again, this handler just starts and stops the dispatch timer. Now fill in the handlers for dt.Tick and the DownloadAccelDataCompleted() functions as below,

void accelService_DownloadAccelDataCompleted(object sender, AccelerometerService.DownloadAccelDataCompletedEventArgs e)
{
    lblXAxis.Text = e.Result.ToString();
    lblYAxis.Text = e.y.ToString();
    lblZAxis.Text = e.z.ToString();
}

void dt_Tick(object sender, EventArgs e)
{
    accelService.DownloadAccelDataAsync();
}

So everytime the dispatch timer fires, we call the DownloadAccelData() function (the asynchronous version is created when we add a reference to the WCF service). And in the DownloadAccelDataCompleted() function we update the text blocks to display the accelerometer values.

And we are done, phew! Time to test’em all. Go to visual studio 2008 and start both the ConsoleApp and WCFClient_HD2 projects, once deployed press the “Start Sensor” button on the device. Now start the silverlight application from Visual Studio 2010 and press the “Start reading data from web service” button, and if you did everything right the accelerometer data should be read on the emulator and you should be seeing something like this,

service and sl app final

The other application which I showed in the video demo where a marble moved around on the screen of the emulator can now be easily implemented using the accelerometer values. I will not be going into the details of it. If you want to know how to implement one take a look at my other post where I have the source code of the application attached, the same logic holds here as well.

It’s been a really long post, if you have an HD2 and happen to try out the source code do let me know how it goes. And if you have any suggestions on how to make this code better please do leave a comment!