Introduction
This article continues to chronicle my quest to get a Dialog Based WinCE application that I can use objects with. This article uses the sample from my earlier project, a Win32 "Hello World" app with a wrapper object, as the starting point.
Before beginning I copied the "Hello World" app and ran one of the copies through the Visual Studio Source Code Renamer by Niek Albers.
Exchanging a Window for a Dialog Box
The creation of a dialog box was demonstrated in the "Hello World" example. CreateDialogParam
is called and the final parameter gets set to the this
pointer. The rest of the parameters are the same as a standard CreateDialog
call. This seemed a logical way to proceed.
A new dialog box was created in the resource editor and the call to CreateWindow
was replaced with the call to CreateDialogParam
. Now several things must change because of the differences between windows and dialogs.
The Message Pump
This is the message pump from the WinMain
procedure of the "Hello World" example:
while ( ( status = GetMessage( &msg, NULL, 0, 0 ) ) != 0 )
{
if ( status == -1 )
return -1;
if ( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
Now that a dialog is created, the call to TranslateAccelerator
can be removed. For the dialog messages to get processed, a call to IsDialogMessage
must be made. If the message was not for the dialog, then a call to TranslateMessage
and DispatchMessage
must be made to process it. The message pump now looks like this:
while ( ( status = GetMessage( &msg, NULL, 0, 0 ) ) != 0 )
{
if ( status == -1 )
return -1;
if ( !IsDialogMessage( DialogWindow.hWnd, &msg ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
Methodically working my way from the top to the bottom...
The Class Is Gone
Since this app uses CreatDialogParam
, there is no need to create a class. The API calls for creating dialogs create their own class, so the method MyRegisterClass
has been removed. The ability to name the window class has implications in the following sections.
Avoiding Multiple Instances
The next problem was in InitInstance
. To avoid multiple instances, the app wizzard generated code that used FindWindow
to search for the class name set up by the previous instance.
hWnd = FindWindow( szWindowClass, szTitle );
if ( hWnd )
{
SetForegroundWindow ((HWND) (((DWORD)hWnd) | 0x01));
return 0;
}
FindWindow
would return a handle to the window of the previous instance to bring it to the top. This is extreamly important in Windows CE because the easiest way to switch back to a running program is to tap on the link that starts the program.
Unfortuneatly, the class creation has been removed from control of the app. The easy solution would be to use FindWindow
to search for the window title. This would impose the limitation of not ever changing the title of the dialog as is sometimes done to indicate the file open or the program state. This left me searching for another solution.
I was directed to Joseph Newcomer's article on Avoiding Multiple Instances. His article points out the many problems with the FindWindow
method and suggests some alternatives. At his use of SendMessageTimeout
I had to pause. This API call doesn't exist in CE, so I had to do a little inventing to make it work.
CreateMutex
is used as he suggests. The operating system then insures that only one instance of the code can create a Mutex with the same name. If the creation fails, then another instance is already running and race conditions associated with the creation of 2 instances of the app are avoided. With the knowledge that the Mutex creation failed, the app must exit.
This leaves the problem of how to get the first instance to the top of the Z-order. I chose to broadcast a registered message to all top level windows. Since the registered message is guaranteed to be unique, only the first instance will act on it. When it sees the message, it must bring itself to the top of the Z-order.
Register a message at startup.
const UINT UWM_ARE_YOU_ME = ::RegisterWindowMessage(
_T("UWM_ARE_YOU_ME-{A17001C2-2614-4406-82CE-0691BB605948}") );
Try to create the mutex to check for a running app.
HANDLE hMutexOneInstance = ::CreateMutex( NULL, FALSE,
_T("LtWtDlg-{A17001C2-2614-4406-82CE-0691BB605948}") );
AlreadyRunning = ( ::GetLastError() == ERROR_ALREADY_EXISTS ||
::GetLastError() == ERROR_ACCESS_DENIED );
if ( AlreadyRunning )
{
BOOL bresult = PostMessage( HWND_BROADCAST, UWM_ARE_YOU_ME, 0, 0 );
return FALSE; }
Now test the message in the WndProc
and if it is yours, bring yourself to the top. This works on my Pocket PC, but not on the x86 emulator supplied with EVC++ 3.0. The emulator limits the instances, but will not bring the first instance to the top of the Z-order.
At this point I contacted Joseph Newcomer to get his opinion. This is his response, "This is another reasonable solution; particularly on CE, where there are probably not a lot of windows that would have to reject the message, the WND_BROADCAST would be quite low overhead."
Command Bars and Task Bars and Menus... Oh My!
When you read the documentation for the API calls for creating the bars at the top and the bottom you'll feel like you're in Oz. For this discussion, the Bar with the Start button on it is the Start Menu. The other bar will may contain a menu and it will be called the Menu Bar. I'm doing this because the API documentation refers to the Command Bar, the Task Bar, The Menu Bar, etc... Worse yet, in the call to SHInitDialog
the Menu Bar refers to the Start Menu. In SHCreateMenuBar
, Menu Bar refers to the bar with the menu in it. In the call to SHFullScreen
the Task Bar refers to the Start Menu. The app wizzard creates a function called CreateRpCommandBar
that calls CreateMenuBar
to create a Menu Bar and then removes it with the CommandBar_Destroy
API call. And so on...
All of this inconsistancy can be traced back to the evolution of the Windows CE operating system. For example, version 2 had the Start Bar at the bottom and the Menu Bar at the top. Version 3 reversed them and Menu Bars became Command Bars because of the new functionality.
Given that there are 2 of these bars, all, some or none may be desireable in the dialog box app. In the constructor for CDialogWindow
is the variable dialog_view
. It can take on the values 1 to 4 for showing each possible combination of Start Bar and Menu Bar.
switch ( dialog_view )
{
case 1:
SHCreateMenuBar( &mbi );
hwndCB = mbi.hwndMB;
shidi.dwFlags |= SHIDIF_SIZEDLGFULLSCREEN;
SHInitDialog( &shidi );
SetForegroundWindow( hDlg );
SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON );
break;
case 2:
shidi.dwFlags |= SHIDIF_SIZEDLGFULLSCREEN;
SHInitDialog( &shidi );
GetWindowRect( hDlg, &rc );
rc.bottom += MENU_HEIGHT;
MoveWindow( hDlg, rc.left, rc.top, rc.right, rc.bottom, TRUE );
SetForegroundWindow( hDlg );
SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_HIDESIPBUTTON );
break;
case 3:
SHCreateMenuBar( &mbi );
hwndCB = mbi.hwndMB;
shidi.dwFlags |= SHIDIF_FULLSCREENNOMENUBAR;
SHInitDialog( &shidi );
GetWindowRect( hDlg, &rc );
rc.top -= MENU_HEIGHT;
MoveWindow( hDlg, rc.left, rc.top, rc.right, rc.bottom, TRUE );
SetForegroundWindow( hDlg );
SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
break;
case 4:
shidi.dwFlags |= SHIDIF_FULLSCREENNOMENUBAR;
SHInitDialog( &shidi );
GetWindowRect( hDlg, &rc );
rc.top -= MENU_HEIGHT;
MoveWindow( hDlg, rc.left, rc.top, rc.right, rc.bottom, TRUE );
SetForegroundWindow( hDlg );
SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
break;
default:
*pbProcessed = bWasProcessed;
lResult = FALSE;
return lResult;
}
To summarize this, if you need a menu then call SHCreateMenuBar
. The next step is to gain control over the screen areas needed by calling SHInitDialog
. After getting control, resize the dialog box, if needed, to cover the area that you just got control of. Bring the dialog to the front with SetForegroundWindow
because if the window isn't in front then the final call will have no effect. Finally, hide those screen elements that are not needed by calling SHFullScreen
. It hides the Start Bar by moving it to the bottom of the Z-order.
The final thought in dealing with how to show a dialog deals with redrawing the page. The WM_ACTIVATE
messgae was added to the dialog procedure so that the screen elements set by SHFullScreen
are reestablished when the dialog app main window becomes active again.
case WM_ACTIVATE:
switch ( dialog_view )
{
case 1:
SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON );
break;
case 2:
SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_HIDESIPBUTTON );
break;
case 3:
SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_SHOWSIPBUTTON );
break;
case 4:
SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
break;
default:
*pbProcessed = bWasProcessed;
lResult = FALSE;
return lResult;
}
break;
Loading Icons
Another thing that was lost when giving up the creation of the class, was the ability to specify the icons to the class structure. Now they must be loaded when processing the WM_INITDIALOG message. At first they may not be missed at all. There is almost no place where the loaded icon shows. There are Task Manager programs that do show the loaded icons. WISbar is a good example.
In this example, the dialog app has the icon with the red number 2 on it.
Here is how to load them:
HICON hIcon = LoadIcon( hInst, MAKEINTRESOURCE( IDI_LtWtDlg ) );
if ( hIcon )
{
SendMessage( hDlg, WM_SETICON, WPARAM( ICON_SMALL ), LPARAM( hIcon ) );
SendMessage( hDlg, WM_SETICON, WPARAM( ICON_BIG ), LPARAM( hIcon ) );
}
Wrapping Things UP
The screen shot of this app shows something a little unexpected. In the lower right hand corner the SIP (Soft Input Panel) controls still show. No command bar had been created, so why did the SIP control show up on the display? The secret is to look at the controls on the dialog. If any controls were created with the Tab Stop attribute, then CE will automatically show the SIP. Once the controls on my dialog box had the Tab Stop attribute removed, then it behaved as expected.
That about does it. This should be a suitable start for a dialog box app if you have a single dialog. Multiple dialogs present a little different problem. Destroying the main application window without exiting is a bad idea. In this case, a regular window should be created then the window can create the dialogs as necessary. If there is any interest, maybe that is where I'll go next.
References
1. Steve Hanov, A Lightweight Windows Wrapper, C/C++ Users Journal, Aug 2000 pg.26
2. Conduits Website, http://www.conduits.com/ce/dev/, various Win CE samples.
3. Reliable Software Website, http://www.relisoft.com/win32/index.htm, various Win 32 tutorials.
4. Joseph Newcomer Website, http://www.pgh.net/~newcomer/mvp_tips.htm, various tips on Win 32 programming.