Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Window Wrapper for a WinCE Win32 dialog app

0.00/5 (No votes)
11 Jul 2002 1  
This article continues to chronicle my quest to get a Dialog Based WinCE application that I can use

Sample Image

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 )
   {
      // PostMessage will avoid hanging waiting for a response
      BOOL bresult = PostMessage( HWND_BROADCAST, UWM_ARE_YOU_ME, 0, 0 );
      return FALSE;	 //create mutex failed so exit the app
   }

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.

   // the method of creating the dialog depends on if you want the
   // top and bottom bars to display.
   switch ( dialog_view )
   {
      case 1:
         // CASE 1:  normal - Start Bar and Menu Bar

         // add a simple menu
         SHCreateMenuBar( &mbi );
         hwndCB = mbi.hwndMB;

         // expand the dialog to full screen
         shidi.dwFlags |= SHIDIF_SIZEDLGFULLSCREEN;
         SHInitDialog( &shidi );

         SetForegroundWindow( hDlg );
         SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON );
         break;
      case 2:
         // CASE 2:  Start Bar and NO Menu Bar

         // expand the dialog to full screen
         shidi.dwFlags |= SHIDIF_SIZEDLGFULLSCREEN;
         SHInitDialog( &shidi );

         // expand the size of the dialog to cover the bottom bar
         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:
         // CASE 3:  NO Start Bar and Menu Bar

         // add a simple menu
         SHCreateMenuBar( &mbi );
         hwndCB = mbi.hwndMB;

         // expand the dialog to full screen give dialog control over
         // the Menu Bar area.
         shidi.dwFlags |= SHIDIF_FULLSCREENNOMENUBAR;
         SHInitDialog( &shidi );

         // expand the size of the dialog to cover the top bar
         GetWindowRect( hDlg, &rc );
         rc.top -= MENU_HEIGHT;
         MoveWindow( hDlg, rc.left, rc.top, rc.right, rc.bottom, TRUE );

         SetForegroundWindow( hDlg );
         // push Menu Bar (alias Task Bar) to the bottom of the z-order
         SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
         break;
      case 4:
         // CASE 4:  NO Start Bar and NO Menu Bar

         // expand the dialog to full screen give dialog control over
         // the Menu Bar area.
         shidi.dwFlags |= SHIDIF_FULLSCREENNOMENUBAR;
         SHInitDialog( &shidi );

         // expand the size of the dialog to cover the top bar
         GetWindowRect( hDlg, &rc );
         rc.top -= MENU_HEIGHT;
         MoveWindow( hDlg, rc.left, rc.top, rc.right, rc.bottom, TRUE );

         SetForegroundWindow( hDlg );
         // push Menu Bar (alias Task Bar) to the bottom of the z-order
         SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
         break;
      default:
         // the message was not processed
         // indicate if the base class handled it
         *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:
            // CASE 1:  normal - Start Bar (top) and Menu Bar (bottom)
            SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON );
            break;
         case 2:
            // CASE 2:  Start Bar and NO Menu Bar
            SHFullScreen( hDlg, SHFS_SHOWTASKBAR | SHFS_HIDESIPBUTTON );
            break;
         case 3:
            // CASE 3:  NO Start Bar and Menu Bar
            SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_SHOWSIPBUTTON );
            break;
         case 4:
            // CASE 4:  NO Start Bar and NO Menu Bar
            SHFullScreen( hDlg, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON );
            break;
         default:
            // the message was not processed
            // indicate if the base class handled it
            *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.

Start Bar

In this example, the dialog app has the icon with the red number 2 on it.

Here is how to load them:

   // Load the Icons for the application
   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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here