The SMSTalk Example

The following example uses a number of the techniques discussed in this chapter to create an application that sends and receives SMS messages. SMSTalk is a dialog-based, multithreaded application that monitors the SMS read queue as well as provides a method for the user to compose and send SMS messages.

The example is designed to run both on the Smartphone and the Pocket PC phone edition. The example is designed for binary compatibility, as opposed to source code compatibility. Notice that SMSTalk checks for the Smartphone and makes the necessary changes to the user interface at run time. Figure 19-9 shows the SMSTalk main dialog on both a Smartphone and a Pocket PC phone edition device.

Figure 19-9

The SMSTalk application running on both a Smartphone and a Pocket PC

The dialogs look different because the application uses different dialog box templates depending on the device the application is running on. The source code, shown in Listing 19-2, has a rather long resource file because all the dialogs must be described twice, once for each device. The resource file also contains different menu bar templates for the two devices.

SMSTalk.rc

//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h"
#include "aygshell.h"
#include "SMSTalk.h"                       // Program-specific stuff

//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON   "SMSTalk.ico"               // Program icon

//----------------------------------------------------------------------
// Main window dialog template for Pocket PC
//
SMSTalk_PPC DIALOG discardable  25, 5, 120,  98
STYLE  WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | DS_MODALFRAME 
CAPTION "SMS Talk"
FONT 8, "System"
BEGIN
    LTEXT   "Received Messages",   -1,   4,   4, 128,  10, 
    LISTBOX               IDD_MSGLIST,   4,  14, 128,  48, WS_TABSTOP

    LTEXT   "Message",             -1,   4,  62,  32,  10, 
    EDITTEXT              IDD_MSGADDR,  36,  60,  96,  12, WS_TABSTOP | 
                                                           ES_NUMBER
    EDITTEXT              IDD_MSGTEXT,   4,  74, 128,  60, WS_TABSTOP |
                          ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL
    PUSHBUTTON "&New",      ID_CMDNEW,   4, 137,  40,  12, WS_TABSTOP
    PUSHBUTTON "&Reply",  ID_CMDREPLY,  48, 137,  40,  12, WS_TABSTOP
END
//----------------------------------------------------------------------
// Main window dialog template for Smartphone
//
SMSTalk_SP DIALOG discardable  25, 5, 120,  98
STYLE  WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | 
       DS_CENTER | DS_MODALFRAME 
CAPTION "SMS Talk"
BEGIN
    LTEXT   "Unread messages",     -1,   2,   2, 96,  8,
    LISTBOX               IDD_MSGLIST,   2,  11, 96,  12, WS_TABSTOP 
    CONTROL "",                            IDD_MSGLISTUD, UPDOWN_CLASS,  
            UDS_AUTOBUDDY | UDS_HORZ | UDS_ALIGNRIGHT | UDS_ARROWKEYS |
                           UDS_SETBUDDYINT | UDS_WRAP | UDS_EXPANDABLE,
                                           0,   0,  0,   0
    LTEXT   "Number",              -1,   2,  24, 34,   8,
    EDITTEXT              IDD_MSGADDR,  36,  23, 62,  10, ES_READONLY

    LTEXT   "Message Text",         -1,  2,  34, 96,   8,
    EDITTEXT              IDD_MSGTEXT,   2,  43, 96,  40, WS_TABSTOP |
                                           ES_MULTILINE | ES_READONLY
    CONTROL "",         IDD_MSGTEXTUD, UPDOWN_CLASS,  UDS_AUTOBUDDY | 
                        UDS_HORZ | UDS_ARROWKEYS  | UDS_SETBUDDYINT | 
                        UDS_WRAP | UDS_EXPANDABLE | UDS_NOSCROLL, 
                                         0,   0,  0,   0
END
//----------------------------------------------------------------------
// Compose window dialog template for Pocket PC
//
WriteMsgDlg_PPC DIALOG discardable  25, 5, 120,  98
STYLE  WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | DS_MODALFRAME 
CAPTION "Compose Message"
BEGIN
    LTEXT   "Number",              -1,   4,   6,  32,  10, 
    EDITTEXT              IDD_MSGADDR,  36,   4,  96,  12, WS_TABSTOP | 
                                                           ES_NUMBER
    LTEXT   "Message Text",        -1,   4,  20, 128,  10, 
    EDITTEXT              IDD_MSGTEXT,   4,  30, 128,  54, WS_TABSTOP |
                          ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL
    PUSHBUTTON "&Send",    ID_CMDSEND,   4,  90,  40,  12, WS_TABSTOP
    PUSHBUTTON "&Cancel",    IDCANCEL,  48,  90,  40,  12, WS_TABSTOP
END
//----------------------------------------------------------------------
// Compose window dialog template for Smartphone
//
WriteMsgDlg_SP DIALOG discardable  25, 5, 120,  98
STYLE  WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | 
       DS_CENTER | DS_MODALFRAME 
CAPTION "SMS Talk"
BEGIN
    LTEXT   "Number",              -1,   2,   2, 96,   8,
    EDITTEXT              IDD_MSGADDR,   2,  11, 96,  10, WS_TABSTOP

    LTEXT   "Message Text",         -1,  2,  24, 96,   8,
    EDITTEXT              IDD_MSGTEXT,   2,  33, 96,  50, WS_TABSTOP |
                                           ES_MULTILINE
    CONTROL "",         IDD_MSGTEXTUD, UPDOWN_CLASS,  UDS_AUTOBUDDY | 
                        UDS_HORZ | UDS_ARROWKEYS  | UDS_SETBUDDYINT | 
                        UDS_WRAP | UDS_EXPANDABLE | UDS_NOSCROLL, 
                                         0,   0,  0,   0
END
//----------------------------------------------------------------------
// String resource table
//
STRINGTABLE DISCARDABLE 
BEGIN
   IDS_EXIT    "Exit"
   IDS_MENU    "Menu"
   IDS_MSG     "Message"
   IDS_FILE    "File"
   IDS_OK      "OK"
   IDS_CANCEL  "Cancel"
   IDS_SEND    "Send"
END
//----------------------------------------------------------------------
// SoftKeyBar resource on main window for Smartphone 
//
ID_MENU_SP RCDATA MOVEABLE PURE 
BEGIN
    ID_MENU_SP, 2,
    I_IMAGENONE, IDOK,  TBSTATE_ENABLED, TBSTYLE_BUTTON | 
                                  TBSTYLE_AUTOSIZE, IDS_EXIT, 0, NOMENU,
    I_IMAGENONE, IDPOP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | 
                                  TBSTYLE_AUTOSIZE, IDS_MENU, 0, 0,
END

ID_MENU_SP MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "Reply",                      ID_CMDREPLY
        MENUITEM "New Message"                 ID_CMDNEW
        MENUITEM SEPARATOR
        MENUITEM "Delete",                     ID_CMDDEL
    END
END
//----------------------------------------------------------------------
// SoftKeyBar resource on Compose dialog for Smartphone 
//
ID_DLGMENU_SP RCDATA MOVEABLE PURE 
BEGIN
    ID_MENU_SP, 2,
    I_IMAGENONE, ID_CMDSEND,  TBSTATE_ENABLED, TBSTYLE_BUTTON | 
                                TBSTYLE_AUTOSIZE, IDS_SEND, 0, NOMENU,
    I_IMAGENONE, IDCANCEL, TBSTATE_ENABLED, TBSTYLE_BUTTON | 
                                TBSTYLE_AUTOSIZE, IDS_CANCEL, 0, NOMENU,
END
//----------------------------------------------------------------------
// Menu bar resource main window for Pocket PC
//
ID_MENU_PPC RCDATA MOVEABLE PURE 
BEGIN
    ID_MENU_PPC, 2,
    I_IMAGENONE, IDFILE, TBSTATE_ENABLED, TBSTYLE_DROPDOWN |
                                       TBSTYLE_AUTOSIZE,IDS_FILE,0,0,
    I_IMAGENONE, IDPOP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN |
                                       TBSTYLE_AUTOSIZE,IDS_MSG,0,1
END

ID_MENU_PPC MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&About",                      IDM_ABOUT
        MENUITEM "E&xit",                       IDOK
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&Delete",                     ID_CMDDEL
        MENUITEM SEPARATOR
        MENUITEM "&Reply",                      ID_CMDREPLY
        MENUITEM "New Message"                 ID_CMDNEW
    END
END

SMSTalk.h

//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0])) 

//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                            // Structure associates
    UINT Code;                                 // messages 
                                               // with a function.
    BOOL (*Fxn)(HWND, UINT, WPARAM, LPARAM);
}; 
struct decodeCMD {                             // Structure associates
    UINT Code;                                 // menu IDs with a 
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);    // function.
};

//----------------------------------------------------------------------
// Program defines used by application
//
typedef struct {
    SMS_ADDRESS smsAddr;
    SYSTEMTIME stMsg;
    int nSize;
    WCHAR wcMessage[160];
} MYMSG_STRUCT, *PMYMSG_STRUCT;

#define MAX_MSGS  250
typedef struct {
    int nMsgCnt;
    MYMSG_STRUCT pMsgs[MAX_MSGS];
} MYMSG_DBASE, *PMYMSG_DBASE;

//----------------------------------------------------------------------
// Generic defines used by application

#define MYMSG_TELLNOTIFY     (WM_USER + 100)

#define  ID_ICON             1   

#define  ID_MENU_SP          100   
#define  ID_MENU_PPC         101   
#define  ID_DLGMENU_SP       102

#define  IDD_MSGLIST         110                // Control IDs
#define  IDD_MSGLISTUD       111
#define  IDD_MSGTEXT         112
#define  IDD_MSGTEXTUD       113
#define  IDD_MSGADDR         114

#define  IDM_EXIT            200
#define  ID_CMDSEND          201
#define  ID_CMDNEW           202
#define  ID_CMDREPLY         203
#define  ID_CMDDEL           204
#define  ID_CMDREAD          205
#define  IDM_ABOUT           206
#define  IDFILE              207
#define  IDPOP               208

#define IDS_EXIT             401
#define IDS_MENU             402
#define IDS_MSG              403
#define IDS_FILE             404
#define IDS_OK               405
#define IDS_CANCEL           406
#define IDS_SEND             407

//----------------------------------------------------------------------
// Function prototypes
//
void ErrorBox (HWND hWnd, LPCTSTR lpszFormat, ...);
BOOL OnSmartPhone(void);
int RefreshMessageList (HWND hWnd, int nSel);
int SetButtons (HWND hWnd);

// Window procedures
BOOL CALLBACK MainDlgProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK WriteDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);

// Message handlers
BOOL DoCreateDialogMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoInitDialogMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoCommandMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoHotKeyMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoTellNotifyMain (HWND, UINT, WPARAM, LPARAM);

// Command functions
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandDelMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandReplyMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandNewMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandMsgList (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD);

// Thread prototype
DWORD WINAPI MonitorThread (PVOID pArg);

SMSTalk.cpp

//======================================================================
// SMSTalk - Demonstrates SMS messaging system
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
#include <aygshell.h>                // Extended shell defines
#include <tpcshell.h>
#include <sms.h>                     // SMS functions
#include "SMSTalk.h"                 // Program-specific stuff

#define MY_MSGWAITING_STRING  TEXT("SMSMsgReadEvent")
#define EMPTY_MSG_LIST        TEXT("<No new messages>")
#define MAXMESSAGELEN         4096
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("SMSTalk");
const TCHAR szOtherApp[] = TEXT("Another application already \
has the SMS system open.\n\nPlease close the (email?) application");
HINSTANCE hInst;                     // Program instance handle
HWND g_hMain = 0;
HANDLE g_hReadEvent = 0; 
HANDLE g_hQuitEvent = 0;
BOOL g_fContinue = TRUE;
BOOL g_fOnSPhone = FALSE;
PMYMSG_DBASE g_pMsgDB = 0;

// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_CREATE, DoCreateDialogMain,
    WM_INITDIALOG, DoInitDialogMain,
    WM_COMMAND, DoCommandMain,
    WM_HOTKEY, DoHotKeyMain,
    MYMSG_TELLNOTIFY, DoTellNotifyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
    IDD_MSGLIST, DoMainCommandMsgList,
    IDOK, DoMainCommandExit,
    IDCANCEL, DoMainCommandExit,
    ID_CMDREPLY, DoMainCommandReplyMessage,
    ID_CMDNEW, DoMainCommandNewMessage,
    ID_CMDDEL, DoMainCommandDelMessage,
    IDM_ABOUT, DoMainCommandAbout,
};
//======================================================================
// Program entry point
// 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    INT i;
    HWND hWnd;
    HANDLE hThread;
    TCHAR szDlgTemplate[32];
    SMS_HANDLE smshHandle;

    hInst = hInstance;

    // Look to see if another instance of the app is running.
    hWnd = FindWindow (NULL, szAppName);
    // See if we were launched with a command line
    if (*lpCmdLine) {
        // Check to see if app started due to notification.
        if (lstrcmp (lpCmdLine, MY_MSGWAITING_STRING) == 0) {
            if (hWnd) {
                SendMessage (hWnd, MYMSG_TELLNOTIFY, 0, 0);
            }
        } 
    } 
    // Set first instance to the foreground and exit
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));    
        return 0;
    }
    // See if we're running on a smartphone.
    g_fOnSPhone = OnSmartPhone ();

    // Allocate message array
    g_pMsgDB = (PMYMSG_DBASE)LocalAlloc (LPTR, sizeof (MYMSG_DBASE));
    if (g_pMsgDB == 0) {
        ErrorBox (NULL, TEXT("Out of memory"));
        return -1;
    }
    g_pMsgDB->nMsgCnt = 0;

    // Create secondary thread for timer event notification.
    // then try to open an SMS Handle for reading messages.
    g_hQuitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    g_hReadEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    HRESULT hr = SmsOpen (SMS_MSGTYPE_TEXT, SMS_MODE_RECEIVE, 
                          &smshHandle, &g_hReadEvent);
    if (hr == SMS_E_RECEIVEHANDLEALREADYOPEN) {
        ErrorBox (hWnd, (LPCTSTR)szOtherApp);
        return 0;
    } else if (hr != ERROR_SUCCESS) {
        ErrorBox (hWnd, TEXT("SmsOpen fail %x %d"), hr, GetLastError());
        return 0;
    }
    hThread = CreateThread (NULL, 0, MonitorThread, (PVOID)smshHandle, 
                            0, (DWORD *)&i);
    if (hThread == 0)
        return -1;

    // Display dialog box as main window.  Use different template if
    // running on the smartphone
    if (g_fOnSPhone)
        _tcscpy (szDlgTemplate, TEXT("SMSTalk_SP"));
    else
        _tcscpy (szDlgTemplate, TEXT("SMSTalk_PPC"));

    DialogBoxParam (hInstance, szDlgTemplate, NULL, MainDlgProc, 
                    (LPARAM)lpCmdLine);
    // Signal notification thread to terminate
    g_fContinue = FALSE;
    SetEvent (g_hQuitEvent);
    WaitForSingleObject (hThread, 1000);
    CloseHandle (hThread);
    CloseHandle (g_hQuitEvent);  // Don't close ReadEvent, SMS does that
    if (g_pMsgDB) LocalFree (g_pMsgDB);
    return 0;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainDlgProc - Callback function for application window
//
BOOL CALLBACK MainDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, 
                           LPARAM lParam) {
    INT i;

    //
    // Search message list to see if we need to handle this
    // message.  If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return FALSE;
}
//----------------------------------------------------------------------
// DoCreateDialogMain - Process WM_CREATE message for window.
//
BOOL DoCreateDialogMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {

    if (!g_fOnSPhone) {
        // set up Menu bar for Pocket PC
        SHMENUBARINFO mbi;
    
        memset (&mbi, 0, sizeof(SHMENUBARINFO));
        mbi.cbSize = sizeof(SHMENUBARINFO);
        mbi.hwndParent = hWnd;
        mbi.nToolBarId = ID_MENU_PPC; // IDM_MENU; 
        mbi.hInstRes = hInst;

        // If we could not initialize the dialog box, return an error
        if (!SHCreateMenuBar(&mbi)) {
            ErrorBox (hWnd, TEXT("Menubar failed"));
            DestroyWindow (hWnd);
            return FALSE;
        }
    }
    return TRUE;
}
//----------------------------------------------------------------------
// DoInitDialogMain - Process WM_INITDIALOG message for window.
//
BOOL DoInitDialogMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {
    // Save the window handle
    g_hMain = hWnd;

    // Specify that the dialog box should stretch full screen
    SHINITDLGINFO shidi;
    memset (&shidi, 0, sizeof(shidi));
    shidi.dwMask = SHIDIM_FLAGS;
    shidi.dwFlags = SHIDIF_SIZEDLG ;//SHIDIF_SIZEDLGFULLSCREEN 
    shidi.hDlg = hWnd;
    if(!SHInitDialog(&shidi))
        return FALSE;

    // Create menubar
    SHMENUBARINFO mbi;
    memset (&mbi, 0, sizeof(SHMENUBARINFO));
    mbi.cbSize = sizeof(SHMENUBARINFO);
    mbi.hwndParent = hWnd;
    if (g_fOnSPhone) 
        mbi.nToolBarId = ID_MENU_SP;
    else
        mbi.nToolBarId = ID_MENU_PPC; 
    mbi.hInstRes = hInst;

    // If we could not initialize the dialog box, return an error
    if (!SHCreateMenuBar(&mbi)) {
        ErrorBox (hWnd, TEXT("Menubar failed"));
        DestroyWindow (hWnd);
        return FALSE;
    }

    // This is only needed on the smartphone
    if (g_fOnSPhone) {
        // Override back key since we have an edit control 
        SendMessage (SHFindMenuBar (hWnd), SHCMBM_OVERRIDEKEY, VK_TBACK,
                     MAKELPARAM (SHMBOF_NODEFAULT | SHMBOF_NOTIFY, 
                                 SHMBOF_NODEFAULT | SHMBOF_NOTIFY));
    }
    // set the title bar
    SHSetNavBarText (hWnd, TEXT("SMS Talk"));

    SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0, 
                        (LPARAM)EMPTY_MSG_LIST);
    SetButtons (hWnd);
    return TRUE;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
BOOL DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){
    WORD idItem, wNotifyCode;
    HWND hwndCtl;
    INT  i;

    // Parse the parameters.
    idItem = (WORD) LOWORD (wParam);
    wNotifyCode = (WORD) HIWORD (wParam);
    hwndCtl = (HWND) lParam;

    // Call routine to handle control message.
    for (i = 0; i < dim(MainCommandItems); i++) {
        if (idItem == MainCommandItems[i].Code) {
            (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl, 
                                       wNotifyCode);
            return TRUE;
        }
    }
    return FALSE;
}
//----------------------------------------------------------------------
// DoHotKeyMain - Process WM_HOTKEY message for window.
//
BOOL DoHotKeyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    SHSendBackToFocusWindow (wMsg, wParam, lParam); 
    return 0;
}
//----------------------------------------------------------------------
// DoTellNotifyMain - Process MYMSG_TELLNOTIFY message for window.
//
BOOL DoTellNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam, 
                       LPARAM lParam) {
    RefreshMessageList (hWnd, lParam);
    SetButtons (hWnd);
    return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl, 
                          WORD wNotifyCode) {
    EndDialog (hWnd, 0);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandDelMessage - Process Read message button.
//
LPARAM DoMainCommandDelMessage (HWND hWnd, WORD idItem, HWND hwndCtl, 
                                 WORD wNotifyCode) {
    int i, nSel;
    nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL,0,0);
    if (nSel != LB_ERR) {
        for (i = nSel; i < g_pMsgDB->nMsgCnt-1; i++) 
            g_pMsgDB->pMsgs[i] = g_pMsgDB->pMsgs[i+1];
        g_pMsgDB->nMsgCnt--;
        RefreshMessageList (hWnd, -1);
    }
    SetButtons (hWnd);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandReplyMessage - Process Reply message button.
//
LPARAM DoMainCommandReplyMessage (HWND hWnd, WORD idItem, HWND hwndCtl, 
                                  WORD wNotifyCode) {
    int nSel;
    LPCTSTR lpTemplate;
    LPARAM lp = 0;
    nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL,0,0);
    if (nSel != LB_ERR)
        lp = (LPARAM)&g_pMsgDB->pMsgs[nSel].smsAddr;

    // Display reply dialog box.
    if (g_fOnSPhone) 
        lpTemplate = TEXT("WriteMsgDlg_SP");
    else
        lpTemplate = TEXT("WriteMsgDlg_PPC");

    DialogBoxParam (hInst, lpTemplate, NULL, WriteDlgProc, lp);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandNewMessage - Process New message button.
//
LPARAM DoMainCommandNewMessage (HWND hWnd, WORD idItem, HWND hwndCtl, 
                                WORD wNotifyCode) {
    LPCTSTR lpTemplate;
    // Display reply dialog box.
    if (g_fOnSPhone) 
        lpTemplate = TEXT("WriteMsgDlg_SP");
    else
        lpTemplate = TEXT("WriteMsgDlg_PPC");

    DialogBoxParam (hInst, lpTemplate, NULL, WriteDlgProc, 0);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandAbout - Process About menu item.
//
LPARAM DoMainCommandAbout (HWND hWnd, WORD idItem, HWND hwndCtl, 
                           WORD wNotifyCode) {
    TCHAR szAbout[] = TEXT("SMS Talk\nCopyright 2003\nDouglas Boling");

    // Display about information in a message box.
    MessageBox (hWnd, szAbout, TEXT("About"), MB_OK | MB_ICONASTERISK);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandMsgList - Process message list listbox.
//
LPARAM DoMainCommandMsgList (HWND hWnd, WORD idItem, HWND hwndCtl, 
                             WORD wNotifyCode) {
    if (wNotifyCode == LBN_SELCHANGE) 
        SetButtons (hWnd);
    return 0;
}
//----------------------------------------------------------------------
// RefreshMessageList - Fill in message listbox from message array
//
int RefreshMessageList (HWND hWnd, int nSel) {
    TCHAR szStr[256];
    int i;
    
    SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_RESETCONTENT, 0, 0);
    for (i = 0; i < g_pMsgDB->nMsgCnt; i++) {
        wsprintf (szStr, TEXT("%d/%02d/%02d %d:%02d:%02d  %s"),
                  g_pMsgDB->pMsgs[i].stMsg.wMonth,
                  g_pMsgDB->pMsgs[i].stMsg.wDay,
                  g_pMsgDB->pMsgs[i].stMsg.wYear%100,
                  g_pMsgDB->pMsgs[i].stMsg.wHour,
                  g_pMsgDB->pMsgs[i].stMsg.wMinute,
                  g_pMsgDB->pMsgs[i].stMsg.wSecond, 
                  g_pMsgDB->pMsgs[i].wcMessage);
        SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0, 
                            (LPARAM)szStr);
    }
    if (g_pMsgDB->nMsgCnt == 0)
        SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0, 
                            (LPARAM)EMPTY_MSG_LIST);
    else { 
        if (nSel != -1)
            SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_SETCURSEL,0,nSel);
        else
            SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_SETCURSEL, 0, i-1);
    }
    return 0;
}
//----------------------------------------------------------------------
// SetButtons - Utility function to compute enabled state of btns
//
int SetButtons (HWND hWnd) {
    int nSel;
    BOOL fReply = FALSE;
    TCHAR szText[128];
    LPTSTR pMsg = TEXT(""), pNum = TEXT("");

    nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL, 0, 0);
    if (nSel != LB_ERR) {
        SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETTEXT, nSel, 
                            (LPARAM)szText);
        if (_tcscmp (szText, EMPTY_MSG_LIST)) {
            fReply = TRUE;
            pNum = g_pMsgDB->pMsgs[nSel].smsAddr.ptsAddress;
            pMsg = g_pMsgDB->pMsgs[nSel].wcMessage;
        }
    }
    EnableWindow (GetDlgItem (hWnd, ID_CMDREPLY), fReply);

    // Set the text in the number and message fields
    SetWindowText (GetDlgItem (hWnd, IDD_MSGADDR), pNum);
    SetWindowText (GetDlgItem (hWnd, IDD_MSGTEXT), pMsg);

    // Disable the menu bar button if necessary
    TBBUTTONINFO tbi;
    HWND hwndMB = SHFindMenuBar (hWnd);
    memset (&tbi, 0, sizeof (tbi));
    tbi.cbSize = sizeof (tbi);
    tbi.dwMask = TBIF_STATE;
    if(SendMessage (hwndMB, TB_GETBUTTONINFO, IDPOP, (LPARAM)&tbi)) {
        if (fReply)
            tbi.fsState |= TBSTATE_ENABLED;
        else
            tbi.fsState &= ~TBSTATE_ENABLED;
        SendMessage (hwndMB, TB_SETBUTTONINFO, IDPOP, (LPARAM)&tbi);
    }
    return nSel;
}
//----------------------------------------------------------------------
// SendSmsMessage - Send an SMS message
//
HRESULT SendSmsMessage (HWND hWNd, SMS_ADDRESS smsDest, LPTSTR pMsg) {
    HRESULT hr;
    SMS_HANDLE smshHandle;
    TEXT_PROVIDER_SPECIFIC_DATA tpsd;
    SMS_MESSAGE_ID smsmidMessageID = 0;

    // try to open an SMS Handle
    hr = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, &smshHandle, NULL);
    if (hr != ERROR_SUCCESS) 
        return hr;

    // Set up provider specific data
    tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
    tpsd.psMessageClass = PS_MESSAGE_CLASS0;
    tpsd.psReplaceOption = PSRO_NONE;
    tpsd.dwHeaderDataSize = 0;

    // Send the message, indicating success or failure
    hr = SmsSendMessage (smshHandle, NULL, &smsDest, NULL, (PBYTE)pMsg, 
                         lstrlen(pMsg) * sizeof (TCHAR), 
                         (PBYTE) &tpsd, 12, SMSDE_OPTIMAL, 
                         SMS_OPTION_DELIVERY_NONE, &smsmidMessageID);
    SmsClose (smshHandle);
    return hr;
}
//======================================================================
// WriteMsg Dialog procedure
//
BOOL CALLBACK WriteDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, 
                            LPARAM lParam) {
    SHINITDLGINFO shidi;
    static SMS_ADDRESS smsDest;
    TCHAR szMsg[SMS_DATAGRAM_SIZE+2];
    HRESULT hr;

    SHInputDialog (hWnd, wMsg, wParam);
    switch (wMsg) {
    case WM_INITDIALOG:
        // Specify that the dialog box should stretch full screen
        memset (&shidi, 0, sizeof(shidi));
        shidi.dwMask = SHIDIM_FLAGS;
        shidi.dwFlags = SHIDIF_SIZEDLGFULLSCREEN;
        if (!g_fOnSPhone) shidi.dwFlags |= SHIDIF_DONEBUTTON;
        shidi.hDlg = hWnd;
        if(!SHInitDialog(&shidi))
            return FALSE;

        // This is only needed on the smartphone
        if (g_fOnSPhone) {
            // Create MenuBar
            SHMENUBARINFO mbi;
            memset (&mbi, 0, sizeof(SHMENUBARINFO));
            mbi.cbSize = sizeof(SHMENUBARINFO);
            mbi.hwndParent = hWnd;
            mbi.nToolBarId = ID_DLGMENU_SP;
            mbi.hInstRes = hInst;

            // If we could not initialize the dialog box, return an error
            if (!SHCreateMenuBar(&mbi)) {
                ErrorBox (hWnd, TEXT("Menubar failed"));
                DestroyWindow (hWnd);
                return FALSE;
            }

            // Override back key since we have an edit control 
            SendMessage (SHFindMenuBar (hWnd), SHCMBM_OVERRIDEKEY, VK_TBACK,
                         MAKELPARAM (SHMBOF_NODEFAULT | SHMBOF_NOTIFY, 
                                     SHMBOF_NODEFAULT | SHMBOF_NOTIFY));
            // Set input mode of number field to numbers
            SendDlgItemMessage (hWnd, IDD_MSGADDR, EM_SETINPUTMODE, 0, 
                                EIM_NUMBERS);
        }

        SendDlgItemMessage (hWnd, IDD_MSGTEXT, EM_LIMITTEXT, 
                            SMS_DATAGRAM_SIZE, 0);
        // If there is a reply address passed, place it in control
        if (lParam) {
            // Copy dest address
            smsDest = *(SMS_ADDRESS *)lParam;
            SetDlgItemText (hWnd, IDD_MSGADDR, smsDest.ptsAddress);
            SetFocus (GetDlgItem (hWnd, IDD_MSGTEXT));
            return FALSE;
        } else {
            smsDest.smsatAddressType = SMSAT_INTERNATIONAL;
            memset (smsDest.ptsAddress, 0, 
                    sizeof (smsDest.ptsAddress));
        }
        return TRUE;

    case WM_HOTKEY:
        SHSendBackToFocusWindow (wMsg, wParam, lParam); 
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD (wParam)) {
            case ID_CMDSEND:
                GetDlgItemText (hWnd,IDD_MSGADDR, smsDest.ptsAddress, 
                                dim (smsDest.ptsAddress));
                GetDlgItemText (hWnd,IDD_MSGTEXT, szMsg, dim (szMsg));
                hr = SendSmsMessage (hWnd, smsDest, szMsg);
                if (hr == 0)
                    MessageBox (hWnd, TEXT("Message Sent"), szAppName,
                                MB_OK | MB_ICONASTERISK);
                else
                    ErrorBox (hWnd, TEXT ("Send message fail %x %d"),
                              hr, GetLastError());
            case IDOK:
            case IDCANCEL:
                EndDialog (hWnd, 0);
                return TRUE;
        }
    break;
    }
    return FALSE;
}
//----------------------------------------------------------------------
// ErrorBox - Displays a message box with a formatted string
//
void ErrorBox (HWND hWnd, LPCTSTR lpszFormat, ...) {
    int nBuf;
    TCHAR szBuffer[512];

    va_list args;
    va_start(args, lpszFormat);

    nBuf = _vstprintf(szBuffer, lpszFormat, args);
    MessageBox (hWnd, szBuffer, TEXT("Error Msg"), MB_OK);
    va_end(args);
}
//----------------------------------------------------------------------
// OnSmartPhone - Determines if we're running on a smartphone
//
BOOL OnSmartPhone (void) {
    TCHAR szPlat[128];
    int rc;
    rc = SystemParametersInfo(SPI_GETPLATFORMTYPE, dim(szPlat),szPlat,0);
    if (rc) {
        if (lstrcmpi (szPlat, TEXT("Smartphone")) == 0)
            return TRUE;
    }
    return FALSE;
}
//======================================================================
// MonitorThread - Monitors event for timer notification 
//
DWORD WINAPI MonitorThread (PVOID pArg) {
    TEXT_PROVIDER_SPECIFIC_DATA tpsd;
    SMS_HANDLE smshHandle = (SMS_HANDLE)pArg;
    PMYMSG_STRUCT pNextMsg;
    BYTE bBuffer[MAXMESSAGELEN];
    PBYTE pIn;
    SYSTEMTIME st;
    HANDLE hWait[2];
    HRESULT hr;
    int rc;
    DWORD dwInSize, dwSize, dwRead = 0;

    hWait[0] = g_hReadEvent;        // Need two events since it isn't
    hWait[1] = g_hQuitEvent;        // allowed for us to signal SMS event.

    while (g_fContinue) {
        rc = WaitForMultipleObjects (2, hWait, FALSE, INFINITE);
        if (!g_fContinue || (rc != WAIT_OBJECT_0))
            break;
        // Point to the next free entry in the array
        pNextMsg = &g_pMsgDB->pMsgs[g_pMsgDB->nMsgCnt];

        // Get the message size
        hr = SmsGetMessageSize (smshHandle, &dwSize);
        if (hr != ERROR_SUCCESS) continue;

        // Check for message larger than std buffer
        if (dwSize > sizeof (pNextMsg->wcMessage)) {
            if (dwSize > MAXMESSAGELEN) 
                continue;
            pIn = bBuffer;
            dwInSize = MAXMESSAGELEN;
        } else {
            pIn = (PBYTE)pNextMsg->wcMessage;
            dwInSize = sizeof (pNextMsg->wcMessage);
        }
        // Set up provider specific data
        tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
        tpsd.psMessageClass = PS_MESSAGE_CLASS0;
        tpsd.psReplaceOption = PSRO_NONE;
        tpsd.dwHeaderDataSize = 0;
        // Read the message
        hr = SmsReadMessage (smshHandle, NULL, &pNextMsg->smsAddr, &st,
                             (PBYTE)pIn, dwInSize, (PBYTE)&tpsd, 
                             sizeof(TEXT_PROVIDER_SPECIFIC_DATA),
                             &dwRead);
        if (hr == ERROR_SUCCESS) {
            // Convert GMT message time to local time
            FILETIME ft, ftLocal;
            SystemTimeToFileTime (&st, &ft);
            FileTimeToLocalFileTime (&ft, &ftLocal);
            FileTimeToSystemTime (&ftLocal, &pNextMsg->stMsg);

            // If using alt buffer, copy to std buff 
            if ((DWORD)pIn == (DWORD)pNextMsg->wcMessage) {
                pNextMsg->nSize = (int) dwRead;
            } else {
                memset (pNextMsg->wcMessage, 0, 
                        sizeof(pNextMsg->wcMessage));
                memcpy (pNextMsg->wcMessage, pIn, 
                        sizeof(pNextMsg->wcMessage)-2);
                pNextMsg->nSize = sizeof(pNextMsg->wcMessage);
            }
            // Increment message count
            if (g_pMsgDB->nMsgCnt < MAX_MSGS-1) {
                if (g_hMain)
                    PostMessage (g_hMain, MYMSG_TELLNOTIFY, 1, 
                                 g_pMsgDB->nMsgCnt);
                g_pMsgDB->nMsgCnt++;
            }
        } else {
            ErrorBox (g_hMain, TEXT("Error %x (%d) reading msg"), 
                      hr, GetLastError());
            break;
        }
    }
    SmsClose (smshHandle);
    return 0;
}

Listing 19-2

The example illustrates a number of techniques used in a Smartphone application. The dialog templates use both expandable edit fields and spinner controls. The Back button is overridden in both the main dialog and the Compose dialog because both contain edit controls. The edit control that holds the destination number in the Compose dialog has its input mode overridden to numeric mode because the address to be entered is more than likely a phone number.

The SMS code in the application uses a separate thread, appropriately named MonitorThread, to monitor incoming SMS messages and uses a send routine, SendSmsMessage, that sends messages. Before the monitor thread is launched, the SmsOpen is called to request a read access handle. This call will fail if another application currently has an open SMS handle with read access. If the open fails, SMSTalk displays a message box notifying the user of the problem and terminates. On the Pocket PC, this failure is quite likely because the e-mail program Pocket Inbox stays running in the background. Pocket Inbox can be closed by selecting the Inbox from the Start menu and then entering Ctrl+Q from the soft keyboard.

Sending an SMS message is accomplished by selecting the Reply or the New menu item. First a dialog is displayed so that the message can be composed. If the user selects to send the message, the program calls SendSmsMessage. This routine opens an SMS handle for write access, fills in the appropriate structures, and sends the message in the simple text format.

Incoming messages are saved in an array in memory. The messages can then be viewed by highlighting a message in the list box on the Pocket PC, or in the spinner on the Smartphone. SMSTalk does not save the messages because the goal is to demonstrate the features of the SMS system and the Smartphone with the least amount of clutter. Because of this arrangement, any messages received by SMSTalk will be lost when the program terminates. Saving the messages in a file would be a great enhancement and is left as a task for the reader.

This topic is from Programming Microsoft Windows CE, Third Edition, by Douglas Boling, published by Microsoft Press. © 2003 by Douglas McConnaughey Boling. Reprinted here by permission of the author.