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.