WinCE Win32 对话框应用程序的窗口包装器





5.00/5 (6投票s)
本文继续记录我致力于构建可用的基于对话框的 WinCE 应用程序的历程。
引言
本文继续记录我为获得一个基于对话框的 WinCE 应用程序并能使用对象而进行的探索。本文以我早期项目中的示例(一个带有包装器对象的 Win32 "Hello World" 应用程序)作为起点。
在开始之前,我复制了 "Hello World" 应用程序,并通过 Niek Albers 的 Visual Studio 源代码重命名器 对其中一个副本进行了处理。
用对话框替换窗口
"Hello World" 示例演示了对话框的创建。调用了 CreateDialogParam
,最后一个参数被设置为 this
指针。其余参数与标准的 CreateDialog
调用相同。这似乎是一种合理的做法。
在资源编辑器中创建了一个新的对话框,并将 CreateWindow
的调用替换为 CreateDialogParam
的调用。现在,由于窗口和对话框之间的差异,必须改变几件事情。
消息泵
这是 "Hello World" 示例的 WinMain
过程中的消息泵
while ( ( status = GetMessage( &msg, NULL, 0, 0 ) ) != 0 ) { if ( status == -1 ) return -1; if ( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } }
现在创建了对话框,可以移除对 TranslateAccelerator
的调用。为了处理对话框消息,必须调用 IsDialogMessage
。如果消息不是用于对话框的,那么必须调用 TranslateMessage
和 DispatchMessage
来处理它。消息泵现在看起来是这样的
while ( ( status = GetMessage( &msg, NULL, 0, 0 ) ) != 0 ) { if ( status == -1 ) return -1; if ( !IsDialogMessage( DialogWindow.hWnd, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } }
有条不紊地从上到下进行...
类不见了
由于此应用程序使用 CreatDialogParam
,因此无需创建类。用于创建对话框的 API 调用会创建自己的类,因此 MyRegisterClass
方法已被移除。命名窗口类的能力对以下部分有影响。
避免多实例
下一个问题出现在 InitInstance
中。为了避免多实例,应用程序向导生成的代码使用了 FindWindow
来搜索由前一个实例设置的类名。
hWnd = FindWindow( szWindowClass, szTitle ); if ( hWnd ) { SetForegroundWindow ((HWND) (((DWORD)hWnd) | 0x01)); return 0; }
FindWindow
将返回前一个实例窗口的句柄,以将其置于最前面。这在 Windows CE 中极其重要,因为切换回正在运行的程序最简单的方法是点击启动程序的链接。
不幸的是,类的创建已从应用程序的控制中移除。简单的解决方案是使用 FindWindow
搜索窗口标题。这将强制限制,即永远不能更改对话框的标题,因为有时会这样做以指示打开的文件或程序状态。这让我寻找另一种解决方案。
我被引导到 Joseph Newcomer 关于避免多实例的文章。他的文章指出了 FindWindow
方法的许多问题,并提出了一些替代方案。在他使用 SendMessageTimeout
时,我不得不停顿。这个 API 调用在 CE 中不存在,所以我不得不做一些创造性的工作来使其工作。
如他所建议,使用 CreateMutex
。操作系统随后确保只有代码的一个实例可以创建具有相同名称的互斥体。如果创建失败,则另一个实例已在运行,从而避免了与创建两个应用程序实例相关的竞态条件。知道互斥体创建失败后,应用程序必须退出。
这就留下了如何将第一个实例置于 Z 顺序顶端的问题。我选择向所有顶级窗口广播注册消息。由于注册消息保证是唯一的,因此只有第一个实例会对其进行操作。当它看到消息时,它必须将自己置于 Z 顺序的顶端。
在启动时注册一条消息。
const UINT UWM_ARE_YOU_ME = ::RegisterWindowMessage( _T("UWM_ARE_YOU_ME-{A17001C2-2614-4406-82CE-0691BB605948}") );
尝试创建互斥体以检查正在运行的应用程序。
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 }
现在在 WndProc
中测试消息,如果它是你的消息,就将自己置于最前面。这在我的 Pocket PC 上有效,但在 EVC++ 3.0 附带的 x86 模拟器上无效。模拟器限制了实例,但不会将第一个实例置于 Z 顺序的最前面。
此时我联系了 Joseph Newcomer 征求他的意见。他回复说:“这是另一个合理的解决方案;尤其是在 CE 上,那里的窗口数量可能不多,无需拒绝消息,WND_BROADCAST 的开销会非常低。”
命令栏、任务栏和菜单……天啊!
当你阅读用于创建顶部和底部栏的 API 调用的文档时,你会觉得自己身处奥兹国。对于这次讨论,带有“开始”按钮的栏是“开始菜单”。另一个可能包含菜单的栏将被称为“菜单栏”。我这样做是因为 API 文档提到了命令栏、任务栏、菜单栏等等……更糟糕的是,在调用 SHInitDialog
时,“菜单栏”指的是“开始菜单”。在 SHCreateMenuBar
中,“菜单栏”指的是包含菜单的栏。在调用 SHFullScreen
时,“任务栏”指的是“开始菜单”。应用程序向导创建了一个名为 CreateRpCommandBar
的函数,该函数调用 CreateMenuBar
创建一个菜单栏,然后使用 CommandBar_Destroy
API 调用将其移除。依此类推……
所有这些不一致都可以追溯到 Windows CE 操作系统的演变。例如,版本 2 的“开始栏”在底部,“菜单栏”在顶部。版本 3 将它们反转,并且“菜单栏”由于新功能而变成了“命令栏”。
鉴于有这两个条形,在对话框应用程序中,全部、部分或 none 都可能是合意的。在 CDialogWindow
的构造函数中,有一个变量 dialog_view
。它可以取值 1 到 4,以显示“开始栏”和“菜单栏”的每种可能组合。
// 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; }
总结一下,如果你需要菜单,则调用 SHCreateMenuBar
。下一步是通过调用 SHInitDialog
来获得所需屏幕区域的控制权。获得控制权后,如果需要,调整对话框大小,以覆盖刚刚获得控制权的区域。使用 SetForegroundWindow
将对话框带到前面,因为如果窗口不在前面,则最后的调用将无效。最后,通过调用 SHFullScreen
隐藏不需要的屏幕元素。它通过将“开始栏”移到 Z 轴底部来隐藏它。
关于如何显示对话框的最后一点思考涉及页面重绘。WM_ACTIVATE
消息被添加到对话框过程,以便当对话框应用程序主窗口再次激活时,由 SHFullScreen
设置的屏幕元素能够重新建立。
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;
加载图标
放弃创建类时丢失的另一件事是指定图标到类结构的能力。现在,它们必须在处理 WM_INITDIALOG 消息时加载。最初,它们可能根本不会被错过。几乎没有地方会显示加载的图标。有一些任务管理器程序确实会显示加载的图标。WISbar 是一个很好的例子。
在此示例中,对话框应用程序的图标上有一个红色的数字 2。
以下是加载它们的方法
// 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 ) ); }
总结
这个应用程序的屏幕截图显示了一些意想不到的东西。在右下角,SIP(软输入面板)控件仍然显示。没有创建命令栏,那么为什么 SIP 控件会显示在屏幕上呢?秘密在于查看对话框上的控件。如果任何控件是使用 Tab Stop 属性创建的,那么 CE 将自动显示 SIP。一旦我的对话框上的控件移除了 Tab Stop 属性,它就按预期行事了。
差不多就是这样了。如果你只有一个对话框,这应该是一个合适的对话框应用程序的起点。多个对话框会带来一些不同的问题。在不退出的情况下销毁主应用程序窗口是一个坏主意。在这种情况下,应该创建一个常规窗口,然后该窗口可以根据需要创建对话框。如果有任何兴趣,也许这就是我下一步的方向。
参考文献
1. Steve Hanov,《一个轻量级 Windows 包装器》,C/C++ Users Journal,2000年8月,第26页
2. Conduits 网站,http://www.conduits.com/ce/dev/,各种 Win CE 示例。
3. Reliable Software 网站,http://www.relisoft.com/win32/index.htm,各种 Win 32 教程。
4. Joseph Newcomer 网站,http://www.pgh.net/~newcomer/mvp_tips.htm,各种 Win 32 编程技巧。