您的位置:首页 > 产品设计 > UI/UE

MiniGUI源码分析--hellowworld(2):主窗口诞生的秘密

2011-12-12 18:30 288 查看
上一篇:MiniGUI源码分析--hellowworld(1)
:MiniGUIMain中有什么奥秘

上一篇讲到MiniGUI程序的启动过程。当MiniGUI完成了初始化之后,就可以创建一个主窗口。(主窗口是唯一可以作为根窗口的窗口对象。这可能是MiniGUI在当初设计时为了方便而设立的。但是个人认为,这实在是一个蹩脚的设计。应该将主窗口与控件的接口完全统一了,就像windows API那样。)

创建主窗口函数,是CreateMainWindow ,这是一个内联函数:

static inline HWND GUIAPI CreateMainWindow (PMAINWINCREATE pCreateInfo)
{
return CreateMainWindowEx (pCreateInfo, NULL, NULL, NULL, NULL);
}
而CreateMainWindowEx的定义如下:

MG_EXPORT HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo,
const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs,
const char* window_name, const char* layer_name);
该函数后面的参数都是和LF渲染器有关的,这一部分会在以后的章节专门叙述。最后一个参数可以忽略。

最重要的参数就是PMAINWINCREATE,它描述了一个窗口的主要信息,它的定义是:

/**
* Structure defines a main window.
*/
typedef struct _MAINWINCREATE
{
/** The style of the main window */
DWORD dwStyle;

/** The extended style of the main window */
DWORD dwExStyle;

/** The caption of the main window */
const char* spCaption;

/** The handle to the menu of the main window */
HMENU hMenu;

/** The handle to the cursor of the main window */
HCURSOR hCursor;

/** The handle to the icon of the main window */
HICON hIcon;

/** The hosting main window */
HWND  hHosting;

/** The window callback procedure */
int (*MainWindowProc)(HWND, int, WPARAM, LPARAM);

/** The position of the main window in the screen coordinates */
int lx, ty, rx, by;

/** The pixel value of background color of the main window */
int iBkColor;

/** The first private data associated with the main window */
DWORD dwAddData;

/** Reserved, do not use */
DWORD dwReserved;
}MAINWINCREATE;
typedef MAINWINCREATE* PMAINWINCREATE;
其中最重要的就是MainWindowProc了,这是窗口的过程回调。

该函数返回一个窗口句柄。

在MiniGUI中,窗口对象是用句柄来表示的,那么,这个句柄是什么呢?

看它的定义:(common.h)

typedef unsigned int HWND;
这只是一个幌子,它的本质,是一个MAINWIN的结构体对象指针,该结构体声明在src/include/internals.h中。

可以通过CreateMainWindowEx函数非常清楚的看到:

HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo,
const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs,
const char* window_name, const char* layer_name)
{
//
PMAINWIN pWin;//定义了窗口指针

if (pCreateInfo == NULL) {

return HWND_INVALID;
}

if (!(pWin = calloc(1, sizeof(MAINWIN)))) { //注意这里,分配了MAINWIN大小的空间

return HWND_INVALID;
}

#ifdef _MGRM_THREADS
.....

return (HWND)pWin;

err:
......

return HWND_INVALID;
}
我将大部分代码删除后,可以看到,HWND不过简单的把地址值转换为整数。

这个结构体的定义,如下:(内容很多,请注意注释部分)

typedef struct _MAINWIN
{
/*
* These fields are similiar with CONTROL struct.
*/
unsigned char DataType;     // 数据类型,表示是否是一个窗口(主窗口或者控件窗口),对于该结构和CONTROL结构,都必须是TYPE_HWND
unsigned char WinType;      // 判断是否是主窗口,对于该结构,必须是TYPE_MAINWIN
unsigned short Flags;       // special runtime flags, such EraseBkGnd flags

int left, top;      // the position and size of main window.
int right, bottom;

int cl, ct;         // the position and size of client area.
int cr, cb;

DWORD dwStyle;      // the styles of main window.
DWORD dwExStyle;    // the extended styles of main window.

int iBkColor;       // the background color.
HMENU hMenu;        // handle of menu.
HACCEL hAccel;      // handle of accelerator table.
HCURSOR hCursor;    // handle of cursor.
HICON hIcon;        // handle of icon.
HMENU hSysMenu;     // handle of system menu.
PLOGFONT pLogFont;  // pointer to logical font.

char* spCaption;    // the caption of main window.
int   id;           // the identifier of main window.

LFSCROLLBARINFO vscroll;
// the vertical scroll bar information.
LFSCROLLBARINFO hscroll;
// the horizital scroll bar information.

/** the window renderer */
WINDOW_ELEMENT_RENDERER* we_rdr;

HDC   privCDC;      // the private client DC.
INVRGN InvRgn;      // 无效区域,在处理MSG_PAINT消息时很重要
PGCRINFO pGCRInfo;  // pointer to global clip region info struct.

// the Z order node.
int idx_znode;

PCARETINFO pCaretInfo;
// pointer to system caret info struct.

DWORD dwAddData;    // the additional data.
DWORD dwAddData2;   // the second addtional data.

int (*MainWindowProc)(HWND, int, WPARAM, LPARAM);
// 这是主窗口的主要函数

struct _MAINWIN* pMainWin;
// the main window that contains this window.
// for main window, always be itself.

HWND hParent;       // the parent of this window.
// for main window, always be HWND_DESKTOP.

/*
* Child windows.
*/
HWND hFirstChild;    // the handle of first child window.
HWND hActiveChild;  // the currently active child window.
HWND hOldUnderPointer;  // the old child window under pointer.
HWND hPrimitive;    // the premitive child of mouse event.

NOTIFPROC NotifProc;    // the notification callback procedure.

/*
* window element data.
*/
struct _wnd_element_data* wed;

/*
* Main Window hosting.
* The following members are only implemented for main window.
*/
struct _MAINWIN* pHosting;
// the hosting main window.
struct _MAINWIN* pFirstHosted;
// the first hosted main window.
struct _MAINWIN* pNextHosted;
// the next hosted main window.

PMSGQUEUE pMessages;
// the message queue.

GCRINFO GCRInfo;
// the global clip region info struct.
// put here to avoid invoking malloc function.

#ifdef _MGRM_THREADS
pthread_t th;        // the thread which creates this main window.
#endif
//the controls as main
HWND hFirstChildAsMainWin;
HDC secondaryDC;
ON_UPDATE_SECONDARYDC update_secdc; // the callback of secondary window dc RECT update_rc;
} MAINWIN;
主窗口包括的内容非常多,但是可以区分成几部分来看,

头部分, DataType是所有句柄共有的,表示它具体是哪个对象,在MiniGUI里面,支持的句柄有TYPE_HWND TYPE_HMENU TYPE_HACCEL TYPE_HCURSOR TYPE_HICON TYPE_HDC TYPE_WINTODEL。

其中,TYPE_HWND和TYPE_WINTODEL是两个可选值。当调用了DestroyMainWindow后,DataType就会被设置为TYPE_WINTODEL。这个时候,MAINWIN对象会和其他窗口脱离关系,但是内存并不删除。因为这个时候,该指针仍然可能被消息循环所使用,为了避免出现野指针,它只是被废弃,却没有被删除。只有调用了MainWindowThreadCleanup后,该内存才会被删除。

我们可以通过IsWindow函数,实际上是通过宏MG_IS_WINDOW来判断的:

#define MG_IS_WINDOW(hWnd)              \
(hWnd &&                    \
hWnd != HWND_INVALID &&    \
((PMAINWIN)hWnd)->DataType == TYPE_HWND)


WinType是为了区分主窗口和控件窗口而定义的。该值可以取TYPE_MAINWIN TYPE_CONTROL TYPE_ROOTWIN。主窗口必须是TYPE_MAINWIN
可以通过IsMainWindow来判断,实际上是通过宏MS_IS_MAIN_WINDOW来判断的:

#define MG_IS_MAIN_WINDOW(hWnd)         \
(MG_IS_WINDOW(hWnd) && ((PMAINWIN)hWnd)->WinType == TYPE_MAINWIN)


位置信息,包括left, top, right, bottom, 是窗口相对于屏幕的位置。cl,ct,cr,cb就是client left, client top, client right和client bottom,表示客户区的范围。这个范围,是相对于(left,top)
从dwStyle到MainWindowProc部分的结构都是属于窗口的特殊的属性的。dwAddData和dwAddData2是关联的附加数据,可以通过SetWindowAddtional/GetWindowAdditional以及SetWindowAdditonal2/GetWindowAdditional2来访问的。一般情况下,控件窗口会使用dwAddData2以保持控件的私有数据,只保留dwAddData给外部程序使用。不过,对于主窗口,就没有这个限制了。
pMainWin指向主窗口指针。这是相对于控件窗口来说的。它与hParent不同之处在于,hParent可以是一个主窗口也可以使控件窗口,总之,hParent是直接的,但是pMainWin必须是窗口树的根。 可以通过GetMainWindowHandle来获取
hosting窗口实际上是主窗口内部的一个树形关系,使用GetFirstHosted和GetNextHosted可以遍历所有主窗口。所有在一个hosting树上得主窗口,必须是通过一个线程内部的(只对线程版是这样),因为这些窗口,都共用一个消息队列。
有关sendaryDC的结构体。这些结构体是使用特效时用的。它可以把所有窗口内容绘制到sendaryDC中,而不是屏幕上。

那么,在看CreateMainWindowEx函数的实现,内容很多,但是也很有规律,请看注释:

HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo,
const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs,
const char* window_name, const char* layer_name)
{
//
PMAINWIN pWin;

if (pCreateInfo == NULL) {

return HWND_INVALID;
}

if (!(pWin = calloc(1, sizeof(MAINWIN)))) {//分配结构体内存

return HWND_INVALID;
}

#ifdef _MGRM_THREADS //这是重要部分,用于找到消息队列
if (pCreateInfo->hHosting == HWND_DESKTOP || pCreateInfo->hHosting == 0) {
/*
** Create thread infomation and message queue for this new main window.
*/
if ((pWin->pMessages = GetMsgQueueThisThread ()) == NULL) { //试图获取本线程关联的消息队列结构体
if (!(pWin->pMessages = mg_InitMsgQueueThisThread ()) ) { //试图去创建一个向消息队列结构体
free (pWin);

return HWND_INVALID;
}
pWin->pMessages->pRootMainWin = pWin;
}
else {
/* Already have a top level main window, in case of user have set
a wrong hosting window */
pWin->pHosting = pWin->pMessages->pRootMainWin;
}
}
else {
pWin->pMessages = GetMsgQueueThisThread (); //直接获取,这种情况下,是可以肯定消息队列已经存在
if (pWin->pMessages != kernel_GetMsgQueue (pCreateInfo->hHosting) || //该函数的调用者必须和hosting的消息队列所在线程一致。这很重要
pWin->pMessages == NULL) {
free (pWin);

return HWND_INVALID;
}
}

if (pWin->pHosting == NULL)
pWin->pHosting = gui_GetMainWindowPtrOfControl (pCreateInfo->hHosting);
/* leave the pHosting is NULL for the first window of this thread. */
#else
pWin->pHosting = gui_GetMainWindowPtrOfControl (pCreateInfo->hHosting);
if (pWin->pHosting == NULL)
pWin->pHosting = __mg_dsk_win;

pWin->pMessages = __mg_dsk_msg_queue;
#endif

pWin->pMainWin      = pWin; //以下部分在初始化结构体成员,可以忽略
pWin->hParent       = 0;
pWin->pFirstHosted  = NULL;
pWin->pNextHosted   = NULL;
pWin->DataType      = TYPE_HWND;
pWin->WinType       = TYPE_MAINWIN;

#ifdef _MGRM_THREADS
pWin->th            = pthread_self();
#endif

pWin->hFirstChild   = 0;
pWin->hActiveChild  = 0;
pWin->hOldUnderPointer = 0;
pWin->hPrimitive    = 0;

pWin->NotifProc     = NULL;

pWin->dwStyle       = pCreateInfo->dwStyle;
pWin->dwExStyle     = pCreateInfo->dwExStyle;

#ifdef _MGHAVE_MENU
pWin->hMenu         = pCreateInfo->hMenu;
#else
pWin->hMenu         = 0;
#endif
pWin->hCursor       = pCreateInfo->hCursor;
pWin->hIcon         = pCreateInfo->hIcon;

#ifdef _MGHAVE_MENU
if ((pWin->dwStyle & WS_CAPTION) && (pWin->dwStyle & WS_SYSMENU))
pWin->hSysMenu= CreateSystemMenu ((HWND)pWin, pWin->dwStyle);
else
#endif
pWin->hSysMenu = 0;

pWin->spCaption    = FixStrAlloc (strlen (pCreateInfo->spCaption));
if (pCreateInfo->spCaption [0])
strcpy (pWin->spCaption, pCreateInfo->spCaption);

pWin->MainWindowProc = pCreateInfo->MainWindowProc;
pWin->iBkColor    = pCreateInfo->iBkColor;

pWin->pCaretInfo = NULL;

pWin->dwAddData   = pCreateInfo->dwAddData;
pWin->dwAddData2  = 0;
pWin->secondaryDC = 0;

/* Scroll bar */ //下面是初始化滚动条相关的内容
if (pWin->dwStyle & WS_VSCROLL) {
pWin->vscroll.minPos = 0;
pWin->vscroll.maxPos = 100;
pWin->vscroll.curPos = 0;
pWin->vscroll.pageStep = 101;
pWin->vscroll.barStart = 0;
pWin->vscroll.barLen = 10;
pWin->vscroll.status = SBS_NORMAL;
}
else
pWin->vscroll.status = SBS_HIDE | SBS_DISABLED;

if (pWin->dwStyle & WS_HSCROLL) {
pWin->hscroll.minPos = 0;
pWin->hscroll.maxPos = 100;
pWin->hscroll.curPos = 0;
pWin->hscroll.pageStep = 101;
pWin->hscroll.barStart = 0;
pWin->hscroll.barLen = 10;
pWin->hscroll.status = SBS_NORMAL;
}
else
pWin->hscroll.status = SBS_HIDE | SBS_DISABLED;

/** perfer to use parent renderer */ //初始化渲染器相关的内容,这时可以忽略这一部分
if (pWin->dwExStyle & WS_EX_USEPARENTRDR) {
if (((PMAINWIN)pCreateInfo->hHosting)->we_rdr) {
pWin->we_rdr = ((PMAINWIN)pCreateInfo->hHosting)->we_rdr;
++pWin->we_rdr->refcount;
}
else {
return HWND_INVALID;
}
}
else {
/** set window renderer */
set_window_renderer (pWin, werdr_name);
}

/** set window element data */
while (we_attrs && we_attrs->we_attr_id != -1) {
// append_window_element_data (pWin,
//       we_attrs->we_attr_id, we_attrs->we_attr);
DWORD _old;
set_window_element_data ((HWND)pWin,
we_attrs->we_attr_id, we_attrs->we_attr, &_old);
++we_attrs;
}

/** prefer to parent font */
if (pWin->dwExStyle & WS_EX_USEPARENTFONT)
pWin->pLogFont = __mg_dsk_win->pLogFont;
else {
pWin->pLogFont = GetSystemFont (SYSLOGFONT_WCHAR_DEF);
}

if (SendMessage ((HWND)pWin, MSG_NCCREATE, 0, (LPARAM)pCreateInfo))
goto err;

/** reset menu size */
ResetMenuSize ((HWND)pWin);

#ifdef __TARGET_FMSOFT__
pCreateInfo->lx += __mg_mainwin_offset_x;
pCreateInfo->rx += __mg_mainwin_offset_x;
pCreateInfo->ty += __mg_mainwin_offset_y;
pCreateInfo->by += __mg_mainwin_offset_y;
#endif

SendMessage ((HWND)pWin, MSG_SIZECHANGING, //开始发生一些消息,让窗口进行一些工作
(WPARAM)&pCreateInfo->lx, (LPARAM)&pWin->left);
SendMessage ((HWND)pWin, MSG_CHANGESIZE, (WPARAM)&pWin->left, 0);

pWin->pGCRInfo = &pWin->GCRInfo;

if (SendMessage (HWND_DESKTOP, MSG_ADDNEWMAINWIN, (WPARAM) pWin, 0) < 0)//这个很重要:把主窗口发送给Desktop窗口托管,进行管理。
goto err;

/*
* We should add the new main window in system and then
* SendMessage MSG_CREATE for application to create
* child windows.
*/
if (SendMessage ((HWND)pWin, MSG_CREATE, 0, (LPARAM)pCreateInfo)) {//发送MSG_CREATE消息
SendMessage(HWND_DESKTOP, MSG_REMOVEMAINWIN, (WPARAM)pWin, 0);
goto err;
}

return (HWND)pWin;

err:
#ifdef _MGRM_THREADS
if (pWin->pMessages && pWin->pHosting == NULL) {
mg_FreeMsgQueueThisThread ();
}
#endif

if (pWin->secondaryDC) DeleteSecondaryDC ((HWND)pWin);
free (pWin);

return HWND_INVALID;
}


其实该函数的核心,做了以下几件事:

创建结构体
获取并填充消息队列
初始化其他信息
把自己交给Desktop主窗口托管,让其管理主窗口之际的关系
发送一个MSG_CREATE消息,告知应用程序窗口已经创建成功

Desktop对主窗口的管理,是相当复杂的,但是目前来说,不需要了解那么深。只要知道Desktop为窗口提供什么功能就可以了。

接下来,我们将剖析消息循环的奥秘,并详细讲解MSG_PAINT消息时怎么产生的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: