最近在学习MiniGui,深入研究发现minigui的窗口虽然跟window差不多,但是还是有些不一样的东西,下面这篇文章写的不错。特别转过来。
图形编程中,窗口是一个重要的概念,窗口其实是一个矩形框,应用程序可以使用其从而达到输出结果和接受用户输入的效果。窗口系统(Window System)界于操作系统层次之上,它是一个软件系统,负责把显示屏幕分隔为不同的部分来帮助用户管理和控制不同的显示环境,它提供基于窗口的工作模式。在Linux上面,X就是一个典型的窗口系统吧。
在MiniGui中有三种窗口类型:主窗口,对话框和控件窗口。主窗口作为应用程序的主界面或开始界面。子窗口通常是控件窗口,也可以是自定义窗口类,这里的控件窗口说白了就是一些窗口上面的控件,比如按钮,编辑框等。对话框其实就是主窗口,只不过一般为了完成特殊用途,所以在此加以区分。
下面我们一起来看看这三种窗口类型的创建吧。首先看CreateMainWindow函数,它创建一个主窗口:由于代码比较长,这里就不全部贴出了,主要是说说关键的部分。
CreateMainWindow函数通过接受PMAINWINCREATE类型的参数而创建一个窗口,并返回其句柄。关于PMAINWINCREATE结构的具体成员变量,大家可以去查看源码。下面主要对函数内部做个简单介绍。
1.
声明一个PMAINWIN类型,并分配空间,该变量用来存放创建的主窗口的信息
2.
说下面的代码之前,先说说托管(Hosting)窗口和被托管(Hosted)窗口吧。我们知道MiniGui内部实现了消息机制,即当有键盘输入事件发生时,就往消息队列中发送键盘消息,而一般是主窗口会不停的从消息队列中取出消息来处理,或者自己响应,或者忽略,或者派发给其他的窗口。那么这里就有一个问题,消息队列是每个主窗口都有一个呢,还是所有的主窗口都使用同一个消息队列?在MiniGui中有个特殊的主窗口HWND_DESKTOP,它是所有窗口的父窗口,直观的说就是整个桌面的窗口。当一个主窗口在创建的时候,可以指定新建一个消息队列,也可以使用别的主窗口的消息队列,如果是后者,假设主窗口A在创建时指定使用主窗口B的消息队列,那么A就被称为被托管窗口,而B则被称为托管窗口。所以很明显CreateInfo.hHosting就是用来指明托管窗口的。来看下面的代码,这里对MiniGUI的两种运行模式进行了区分,1-15行是MiniGUI-Threads模式,在这种模式下,如果托管窗口为HWND_DESKTOP,则新建一个消息队列(2-12行);如果托管窗口不为HWND_DESKTOP,则返回hHosting所在的主窗口的消息队列。16行是非MiniGUI-Threads模式下,仅仅表明新的主窗口使用HWND_DESKTOP的消息队列,这里其实忽略了pHosting参数。
1: #ifndef _LITE_VERSION
2: if (pCreateInfo->hHosting == HWND_DESKTOP) {
3: // Create message queue for this new main window.
4: if( !(pWin->pMessages = malloc(sizeof(MSGQUEUE))) ) {
5: free(pWin);
6: return HWND_INVALID;
7: }
8:
9: // Init message queue.
10: if (!InitMsgQueue(pWin->pMessages, 0))
11: goto err;
12: }
13: else
14: pWin->pMessages = GetMsgQueue (pCreateInfo->hHosting);
15: #else
16: pWin->pMessages = &__mg_dsk_msgs;
17: #endif
|
3.
下面的几行是对pWin进行初始化的操作,第1行赋值消息处理回调函数。第9行,初始化pZorderNode成员
1: pWin->MainWindowProc = pCreateInfo->MainWindowProc;
2: pWin->iBkColor = pCreateInfo->iBkColor;
3:
4: pWin->pCaretInfo = NULL;
5:
6: pWin->dwAddData = pCreateInfo->dwAddData;
7: pWin->dwAddData2 = 0;
8:
9: if ( !( pWin->pZOrderNode = malloc (sizeof(ZORDERNODE))) )
10: goto err;
|
4.
初始化结束之后,就开始发送消息通知自身来真正的绘制窗口了。1-4行发送本窗口的MSG_SIZECHANGING和MSG_CHANGESIZE消息,会调用本窗口消息回调函数中的相应处理部分。第6行是发送MSG_ADDNEWMAINWIN消息给HWND_DESKTOP窗口,HWND_DESKTOP窗口主要负责初始化Clip区和Invalid区,并且把当前窗口添加到sg_MainWinZOrder链表里,这个链表记录的是所有窗口的叠加顺序,在显示和隐藏窗口的时候,叠加顺序很重要,它会决定屏幕上哪些窗口会受影响而需要重绘。第9行发送MSG_CREATE消息给窗口,窗口接受到此消息一般进行子窗口的初始化和创建,如果创建失败了,则通知HWND_DESKTOP窗口销毁该主窗口。
1: SendMessage ((HWND)pWin, MSG_SIZECHANGING,
2: (WPARAM)&pCreateInfo->lx, (LPARAM)&pWin->left);
3: SendMessage ((HWND)pWin, MSG_CHANGESIZE,
4: (WPARAM)&pWin->left, 0);
5:
6: SendMessage (HWND_DESKTOP, MSG_ADDNEWMAINWIN,
7: (WPARAM) pWin, (LPARAM) pWin->pZOrderNode);
8:
9: if (SendMessage ((HWND)pWin, MSG_CREATE, 0, (LPARAM)pCreateInfo)) {
10: SendMessage(HWND_DESKTOP,
11: MSG_REMOVEMAINWIN, (WPARAM)pWin, 0);
12: goto err;
13: }
|
接下来我们看对话框的创建过程,对话框分为模态和非模态对话框。非模态对话框的创建过程和主窗口的创建过程差不多,其中也调用了CreateMainWindow函数,之后还调用了CreateWindowEx创建对话框上的控件。模态对话框就是显示之后,用户不能再切换到其他主窗口进行工作的对话框,而只能在关闭之后,才能使用其他的主窗口,通过DialogBoxIndirectParam创建,一开始的步骤与非模态对话框类似,以下的代码是其不同的部分:第7行,hOwner是待创建对话框的托管主窗口,这里其实是把它disable掉了。第11行是处理MSG_INITDIALOG消息。第18-21行,是消息处理的循环机制,这里可以看到这就是为什么模态对话框一定要等到关闭之后,才可以使用其它的主窗口,这里还需要注意一点,由于是从对话框的托管主窗口是HWND_DESKTOP窗口,因此他们共用一个消息队列,此时,对话框可能接受到发送给托管主窗口的消息,而由于在第7行中已经将托管主窗口的dwStyle设置为WS_DISABLE了,因此在这些消息处理流程里面可以做相应的处理(例如当窗口被设置为WS_DISABLE时,忽略该消息)。25-28行,当窗口关闭时,进行的收尾工作。第31行enable托管主窗口。第23行判断了当前对话框是否是激活窗口,如果是的话,当它关闭时,它的托管主窗口应该被激活(34 -35L )。
1: hDlg = CreateMainWindow (&CreateInfo);
2: if (hDlg == HWND_INVALID)
3: return -1;
4:
5: SetWindowAdditionalData2 (hDlg, (DWORD)(&retCode));
6:
7: if (hOwner)
8: EnableWindow (hOwner, FALSE);
9:
10: hFocus = GetNextDlgTabItem (hDlg, (HWND)0, FALSE);
11: if (SendMessage (hDlg, MSG_INITDIALOG, hFocus, lParam)) {
12: if (hFocus)
13: SetFocus (hFocus);
14: }
15:
16: ShowWindow (hDlg, SW_SHOWNORMAL);
17:
18: while( GetMessage (&Msg, hDlg) ) {
19: TranslateMessage (&Msg);
20: DispatchMessage (&Msg);
21: }
22:
23: isActive = (GetActiveWindow() == hDlg);
24:
25: dlgDestroyAllControls (hDlg);
26: DestroyMainWindow (hDlg);
27: ThrowAwayMessages (hDlg);
28: MainWindowThreadCleanup (hDlg);
29:
30: if (hOwner) {
31: EnableWindow (hOwner, TRUE);
32: if(isActive)
33: {
34: ShowWindow (hOwner, SW_SHOWNORMAL);
35: SetActiveWindow (hOwner);
36: }
37: }
38:
39: return retCode;
|
最后说一下子窗口(即控件)的创建过程。在MiniGUI中通过调用CreateWindow函数(CreateWindow其实是CreateWindowEx函数的宏)可以建立某个控件。控件的创建需要一个PCONTROL结构变量,下面这段代码中的第1行获取控件的主窗口。第4行通过向HWND_DESKTOP发送MSG_GETCTRLCLASSINFO,接受到消息之后会调用GetControlClassInfo函数根据传入的spClassName来获取控件的class info。控件的class info包括控件名称,默认的风格和扩展风格,消息回调函数等。后续的代码设置控件的属性。
1: if (!(pMainWin = GetMainWindowPtrOfControl (hParentWnd)))
2: return HWND_INVALID;
3:
4: cci = (PCTRLCLASSINFO)SendMessage (HWND_DESKTOP,
5: MSG_GETCTRLCLASSINFO, 0, (LPARAM)spClassName);
6: if (!cci) return HWND_INVALID;
7:
8: pNewCtrl = calloc (1, sizeof (CONTROL));
9:
10: if (!pNewCtrl) return HWND_INVALID;
11:
12: pNewCtrl->DataType = TYPE_HWND;
13: pNewCtrl->WinType = TYPE_CONTROL;
14:
15: pNewCtrl->left = x;
16: pNewCtrl->top = y;
17: pNewCtrl->right = x + w;
18: pNewCtrl->bottom = y + h;
……
|
设置完控件的属性之后,向HWND_DESKTOP发送MSG_NEWCTRLINSTANCE消息,HWND_DESKTOP接受到之后,会调用dskOnNewCtrlInstance函数创建控件,将它添加到其父窗口的children链表中( 1L )。第16-18行判断是否可见,可见的话,就更新窗口显示它。
1: SendMessage (HWND_DESKTOP, MSG_NEWCTRLINSTANCE,
2: (WPARAM)hParentWnd, (LPARAM)pNewCtrl);
3:
4: if (SendMessage ((HWND)pNewCtrl, MSG_CREATE,
5: (WPARAM)hParentWnd, (LPARAM)dwAddData)) {
6: SendMessage (HWND_DESKTOP,
7: MSG_REMOVECTRLINSTANCE,
8: (WPARAM)hParentWnd, (LPARAM)pNewCtrl);
9: goto error;
10:}
11: SendMessage ((HWND)pNewCtrl, MSG_SIZECHANGING,
12: (WPARAM)&rcExpect, (LPARAM)&pNewCtrl->left);
13: SendMessage ((HWND)pNewCtrl, MSG_CHANGESIZE,
14: (WPARAM)(&pNewCtrl->left), 0);
15:
16: if (pNewCtrl->pParent->dwStyle & WS_VISIBLE && pNewCtrl->dwStyle &
17: WS_VISIBLE)
18: UpdateWindow ((HWND)pNewCtrl, TRUE);
|