HomeQuestions and AnswersArticlesSamplesLibrariesForumsNewsLinks

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.

  1. 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.
  2. 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 <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlwin.h> #include <atlctrls.h> #include <atlmisc.h> #include <atlddx.h>

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<CMainWnd> { 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.

  © 2002 Smartphone Developer Network | Want to be an author? | Contact Us