Alarm
By Alexander Shargin, April 16, 2002.
Introduction
Alarm is a very simple application for cell phones. It allows the user to create any
number of alarms and edit their properties (time, description, mode etc.). When an alarm
expires it notifies the user with sound signal and message box on the screen.
Alarm sample demonstrates how to create and use main windows, main menus, dialog boxes and
standard controls (edit box, list view, date time picker etc.). Also it shows how to
use ATL and WTL for WinCE to program user interface. Since there is no MFC support for
smartphones these libraries may prove very useful.
What You Need
Alarm design
User interface of Alarm application consists of two windows: main window and alarm
properties dialog box. Main window contains a list of existing alarms. Menu bar at
the bottom of the screen allows to add new alarms or edit or delete existing ones.
I decided to use virtual list view (list view with LVS_OWNERDATA style set) to implement
the list of alarms.
When the user chooses to create a new alarm or edit existing one, properties dialog is
displayed. This dialog contains controls to set or modify alarm's properties: name,
description, time and mode (disabled, notify once, notify every day). I used edit boxes
for name and description and date time picker for time. As to mode, I tried to use radio
buttons first but found that they are not supported for smartphones. So I used a list box
with buddy spin control instead. Also I found that a simple edit box was too small for
alarm description. This problem was solved by using expanding edit box.
When an alarm goes off the program plays a predefined wav file and displays message box
with alarm name and description. Currently I use the standard windows timer (WM_TIMER)
to implement alarms. This design is not perfect for a real-world application (if the
device is turned off the application will be suspended and the standard timer will have
no chance to trigger the alarm). But I could not test power off issues on smartphone
emulator or even find information as to how smartphone behaves when turned off.
I believe that the standard timer is quite sufficient for a sample application anyway.
OK, that's all. Let's see how to implement all those stuff.
Alarm implementation
Step 0. WTL installation
After you've downloaded zip file from
http://www.pocketpcdn.com/libraries/wtlce.zip you get a bunch of header files.
Follow these steps to install them.
- Go to the smartphone 2002 SDK root directory (on my computer it is
D:\Windows CE Tools\wce300\Smartphone 2002\). Create 'wtl' subdirectory there and
copy WTL header files to this directory.
- Run eVC. Run Tools->Options command and go to Directories tab. Choose
Platform: Smartphone 2002, CPUs: Win32 (WCE x86), Show directories for:
Include files. Add path to the directory with WTL files to the list. When you get
ARM-based smartphone device you will want to add WTL path for ARM CPU as well.
That's all. Now you can use WTL in your programs.
Note: for your convenience I included WTL files in the alarm project folder.
But I recommend you to install WTL properly before you start to use it in your own
projects.
Step 1. Project creation
I decided to create my project from scratch. So I chose 'A simple Windows CE application'
option in 'WCE Smartphone 2002 application' wizard. The project I got contained an empty
WinMain proc only. No resource file. No precompiled headers. So the first step was to add
these features to the project.
I created 2 resource files - one for eVC resource editor (alarm.rc) and one for manual
editing (alarm.rc2). The second file is used for menu bar resources since eVC editor
does not handle them properly (see this Q&A
for more details).
Step 2. Main window and message loop
Next I created a skeleton for my application: included all necessary headers, wrote
WinMain and created a class for application's main window.
stdafx.h file with all necessary #includes looks like this.
#include
#include
extern CAppModule _Module;
#include
#include
#include
#include
atlbase.h and atlwin.h files are from ATL library for Smartphones (distributed with the
SDK). Other files are from WTL. When I tried to compile them I got some errors. No
surprise since this WTL port was not supposed to be used with smartphones. Here is
a list of errors I corrected.
- atlapp.h(40). #include directive was commented out. There is no atlres.h file in ATL
for Smartphone.
- atlctrls.h(1492). I got rid of the default value for uFlags parameter
(LR_DEFAULTCOLOR | LR_DEFAULTSIZE). These flags are never used on Windows CE since it
always behaves as though they were set.
- atlmisc.h(2410). The compiler could not find ID_FILE_MRU_LAST and ID_FILE_MRU_FIRST
constants required for MRU (most recently used) documents list. I decided to remove support
for MRU altogether since it is not required for Smartphone applications (design guide for
smartphone does not recommend to use MRU lists anywhere). If you hate this decision of mine
just replace 'ID_FILE_MRU_LAST - ID_FILE_MRU_FIRST + 1' with 16.
That's all I needed to make WTL work for me. Next, I created a main window class and
WinMain:
class CMainWnd : public CWindowImpl
{
public:
BEGIN_MSG_MAP(CMainWnd)
END_MSG_MAP()
DECLARE_WND_CLASS(g_szUniqueString);
};
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int CmdShow)
{
// Check if one instance is already running.
const HANDLE hMutex = ::CreateMutex(NULL, FALSE, g_szUniqueString);
if(::GetLastError() == ERROR_ALREADY_EXISTS)
{
// This is not the first instance of the application.
// Activate the firsty instance and exit.
HWND hPrevMainWnd = ::FindWindow(g_szUniqueString, NULL);
::SetForegroundWindow(hPrevMainWnd);
return 0;
}
// Init common controls.
INITCOMMONCONTROLSEX comctrex;
comctrex.dwSize = sizeof(comctrex);
comctrex.dwICC = ICC_DATE_CLASSES | ICC_UPDOWN_CLASS;
InitCommonControlsEx(&comctrex);
// Init module.
_Module.Init(NULL, hInstance);
int result = 0;
// Create main window.
CMainWnd wnd;
RECT rc;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
if(wnd.Create(NULL, rc, _T("Alarm"), WS_VISIBLE) == NULL)
{
result = 1;
}
else
{
// Run message loop.
CMessageLoop loop;
result = loop.Run();
}
// Terminate.
_Module.Term();
return result;
}
I would like to accent a number of details in CMainWnd and WinMain implementation.
- CMainWnd is derived from CWindowImpl<CMainWnd> class. CWindowImpl is roughly equivalent
to MFC CWnd class.
- I use g_szUniqueString (it's a string containing GUID) as a class name for my main
window. It's done to be able to find the main window easily using FindWindow API. To
assign class name to WTL window DECLARE_WND_CLASS(g_szUniqueString) macro is used.
- Design guide does not recommend more than one instance of your application on smartphone.
That's why WinMain checks if another instance of Alarm is already running. I use named mutex
for this purpose (by the way I assign it g_szUniqueString, too). If another instance is
detected I find its main window (FindWindow), bring it to foreground (SetForegroundWindow)
and exit immediately.
- I call InitCommonControlsEx to register necessary control classes (date time picker and
up down control) for my application. MFC does this transparently but with WTL you have
to do this yourself! Please refer to this Q&A for
more information.
- Before I use any ATL/WTL features I have to init application's module. It's a global
structure similar to app state structures in MFC. Just to be a good citizen I also deinit
the module before WinMain returns.
- Main window is create using CMainWnd::Create method. This is accomplished very much like
it's done in MFC. Note that I use SystemParametersInfo function to determine available screen
size.
- In WTL message loop is encapsulated in CMessageLoop class. I use it to run usual
Get/Translate/Dispatch message pump.
Step 3. Data structures
Data structures in Alarm application are pretty simple. All properties of an alarm are
stored in CAlarm structure (defined in defs.h). Since the user can create any number of
alarms (see Design section) we need a variable-sized array to store them all. Since MFC
is unavailable I decided to use CSimpleArray class (one of ATL light-weight containers).
Another option was to use STL containers instead. Refer to this Q&A for more information on how to use STL in smartphone
applications.
Alarms array is atored in CMainWnd class (see m_alarms member).
Step 4. Basic application logic
Basic Alarm application logic is implemented in CMainWnd message handlers. Good news is
that these handlers could be added using some IDE support so I did no have to insert
them all manually. To add message handler to ATL/WTL window class go to ClassView
tab, right click the class and choose Add Windows Message Handler... command.
Below I'll briefly describe what the handlers do.
-
OnCreate. Here I create a menu bar for my main window, create child list view
and insert two columns to it. Also here the alarms are loaded from registry
(more on this later).
-
OnTimer. Since I use windows timers to implement my alarms this handler is called
every time an alarm expires. It calls CAlarm::DoModal method to notify the user about this.
-
OnSetFocus. This handler simply passes the focus to the child list view.
-
OnEdit, OnDelete, OnNew. This are handlers for menu commands.
Their implementation is pretty straightforward. They just modify m_alarms array and
update the list view.
-
OnGetDispInfo. Since my list view is virtual it does not store any data itself.
Instead it sends LVN_GETDISPINFO message to the parent to obtain the text to display.
OnGetDispInfo handles this message. It formats relevant information from m_alarms array
and passes it back to list view.
-
OnItemChanged. Whenever the selection in list view changes I update the menu
using UpdateMenu method. This method grays 'Edit' and 'Delete' menu items if nothing is
selected in the list view.
Step 5. Properties dialog
Properties dialog is implemented in a separate class CAlarmDlg derived from
CSimpleDialog<IDD_ALARM_DIALOG> class. CSimpleDialog is roughly equivalent to CDialog
class from MFC. ATL/WTL also provide some more advanced dialog classes (CDialogImpl,
CAxDialogImpl), but they would be overkill for Alarm application. Note
enum { IDD = IDD_ALARM_DIALOG }; definition on CAlarmDlg class. This line is not
necessary since IDD_ALARM_DIALOG is specified as CSimpleDialog template's parameter.
It's used to enable ClassView support for CAlarmDlg class.
CAlarmDlg is a really simple class. It has two handlers. OnInitDialog performs dialog
initialization. All "magic" with SHInitDialog and SHCreateMenuBar APIs is required to
create a standard full-screen dialog with OK/Cancel menu (smartphone design guide
recomends to make the dialogs this way). OnOK handler closes the dialog.
One thing I want to stress here is DDX mechnism used to exchange data between controls
and data members of CAlarmDlg class. This mechanism is provided by WTL and has much in
common with MFC DDX. To attach DDX support to CAlarmDlg class I made the following steps.
- Added CWinDataExchange class to the list of base classes for CAlarmDlg.
- Wrote a DDX map in CAlarmDlg class. Each entry in this map binds together a control ID
and member variable in CAlarmDlg class. So DDX map is similar to DoDataExchange function
in MFC.
- Added call of DoDataExchange(FALSE) to OnInitDialog in order to transfer values from
member variables to controls.
- Added call of DoDataExchange(TRUE) to OnOK in order to transfer values from
controls to member variables.
Step 6. Alarm notification code
Alarm notification code is located in CAlarm::DoAlarm method. It uses PlaySound function
to play wav file and MessageBox function to display alarm description.
Note: you must copy chimes.wav file to '\Windows\Start' menu folder on the
emulator to actually hear the sound.
Step 7. Persistence. Using CVORegistry class
In order to make alarms data persistent we need to store it in some persistent storage
(e. g. file or windows registry). I decided to use CVORegistry class to work with
registry. It's a handy class written by Virtual Office Systems. Other options include
Reg* API (not very convenient) and CRegKey class from ATL (much better but still does not
support CString class). CVORegistry was written for MFC applications and refused to work
with WTL. So I changed it to support WTL. Here is a list of changes I made to CVoRegistry
class.
- Replaced all TRACE marcos to ATLTRACE macros provided by ATL.
- Wrote my own AfxIsValidString function to replace the one from MFC. My implementation
looks like this (OK, I admit that I stole it from MFC sources. Why reinvent the wheel? :)
BOOL AfxIsValidString(LPCTSTR lpsz, int nLength = -1)
{
if (lpsz == NULL)
return FALSE;
if(IsBadReadPtr(lpsz, sizeof(TCHAR)))
return FALSE;
return ::IsBadReadPtr(lpsz + min((UINT)nLength, (UINT)_tcslen(lpsz)), sizeof(TCHAR)) == 0;
}
The rest part is simple. I store name and description of every alarm as string, mode
as DWORD and time as binary. CVORegistry has methods to read/write all these types of
values. LoadAlarms and SaveAlarms methods do the trick.
Discuss
Discuss this sample.
Here you can write your comments and read comments of other developers.
|