您现在的位置:首页 >> 金山界面库 >> 金山界面库分析_解析bkwin,金山开源界面库,金山影视库,金山词霸库没有注册

金山界面库分析_解析bkwin,金山开源界面库,金山影视库,金山词霸库没有注册

时间:2012-10-01 来源: 泥巴往事网

金山界面库分析(6) 消息和事件的传递、分发、相应 既然没有真实的窗口,那么也就不... 程序转到了CBkWindow类的这块: BKWIN_BEGIN_MSG_MAP () MSG_WM_CREATE ( ...

通过 XML 创建界面---对象的动态创建以及属性的设置 为了界面可配置化和换肤,需要界面元素可以根据 XML 动态创建和设置属性。

在 BKLib 中,CBkObject 类就完成了这样的功能,主要负责类的创建和属性的设置。因为对 象都是从 XML 动态创建的,动态的创建是一个类最基本的属性,所以其他类都从 CBkObject 派生。

来看看这个类的四个方法:

BOOL IsClass(LPCSTR lpszName):判断是不是这个类的对象。纯虚方法,也就是从 CBkObject 继承来的类都要实现这个方法才行,同时,这个方法在不同的类上面会有不同的表现。所以 上层定义接口,下层提供实现。这个方法可以在运行时检测类实例的实际类型。

LPCSTR GetObjectClass():同上一个方法,用于获取类的名字。

BOOL Load(TiXmlElement* pXmlElem):从 XML 中获取属性并将属性设置到对象中,在基类中 仅仅是将一个 XML 元素的属性设置到对象中。当然,如果子类对象有更复杂的实现,比如 一个对象对应的 XML 元素还有子节点,就需要 Load 其子节点,这些都可以在子类中通过覆 盖父类方法来实现。

HRESULT SetAttribute(CStringA strAttribName, CStringA strValue, BOOL bLoading):

设置属性的方 法,CBkObject 是纯虚类,在 XML 中不会有对应的节点,自然也没有相应的属性,所以其实 现仅仅放回了一个 E_FAIL,没有其他操作。

接着我们就看到了在 bkobject.h 里面一大堆的宏定义:

宏定义一般是为了简洁,而这些宏的用途多为子类使用。

BKOBJ_DECLARE_CLASS_NAME:获取类名,判断是否是某个类的对象,还有 CheckAndNew, 用于动态创建 以下的宏定义主要用于属性的设置和映射(XML 节点属性和对象属性的对应) BKWIN_DECLARE_ATTRIBUTES_BEGIN BKWIN_DECLARE_ATTRIBUTES_END BKWIN_CHAIN_ATTRIBUTE BKWIN_CUSTOM_ATTRIBUTE BKWIN_INT_ATTRIBUTE BKWIN_UINT_ATTRIBUTE BKWIN_DWORD_ATTRIBUTE BKWIN_STRING_ATTRIBUTE BKWIN_TSTRING_ATTRIBUTE BKWIN_HEX_ATTRIBUTE BKWIN_COLOR_ATTRIBUTE BKWIN_FONT_ATTRIBUTE BKWIN_ENUM_ATTRIBUTE BKWIN_ENUM_VALUE BKWIN_ENUM_END BKWIN_STYLE_ATTRIBUTE BKWIN_SKIN_ATTRIBUTE 现在我们看一个例子, 继承自 CBkObject 的控件对象 CBkProgress 是如何完成从 XML 动态创建 的。

首先,在类的定义中包含宏 BKOBJ_DECLARE_CLASS_NAME(CBkProgress, "progress"),将宏展开 如下: public: static CBkProgress* CheckAndNew(LPCSTR lpszName) { if (strcmp(GetClassName(), lpszName) == 0) return new CBkProgress;

else return NULL;

} //通过传入名称创建对应的类,在解析 XML 中按照节点名字创建对应类的实例 static LPCSTR GetClassName() { return “progress”;

} virtual LPCSTR GetObjectClass() { return “progress”;

} //覆盖父类方法,返回类实例对应的 XML 节点名字 virtual BOOL IsClass(LPCSTR lpszName) { return strcmp(GetClassName(), lpszName) == 0;

} //覆盖父类方法,根据 XML 节点名字检查类实例是否是此节点 另外,包含设置节点属性的宏,如下 BKWIN_DECLARE_ATTRIBUTES_BEGIN() BKWIN_SKIN_ATTRIBUTE("bgskin", m_pSkinBg, TRUE) BKWIN_DWORD_ATTRIBUTE("min", m_dwMinValue, FALSE) BKWIN_UINT_ATTRIBUTE("showpercent", m_bShowPercent, FALSE) BKWIN_DECLARE_ATTRIBUTES_END() 将宏展开如下: public: virtual HRESULT SetAttribute( CStringA strAttribName, CStringA strValue, BOOL bLoading) //添加 SetAttribute 方法,在 Load 中循环调用设置属性 { HRESULT hRet = __super::SetAttribute( //首先设置父类定义的属性 strAttribName, strValue, bLoading );

if (SUCCEEDED(hRet)) return hRet;

if ("bgskin"

== strAttribName) { m_pSkinBg = BkSkin::GetSkin(strValue);

hRet = TRUE ? S_OK : S_FALSE;

} else if ("min"

== strAttribName) { m_dwMinValue = (DWORD)::StrToIntA(strValue);

hRet = FALSE ? S_OK : S_FALSE;

} else if ("showpercent"

== strAttribName) { m_bShowPercent = (UINT)::StrToIntA(strValue);

hRet = FALSE ? S_OK : S_FALSE;

} else return E_FAIL;

return hRet;

} 现在我们来看看 CheckAndNew 和 SetAttribute 这两个方法是如何被调用的,看调用栈:

//属性名称是 strAttribName //属性的值是 strValue //是否全部重绘 1.在实窗口的 Create 方法中(DoModal 中调用 Create)调用实窗口的 Load 和 SetXml 方法装载 XML,在 SetXML 方法中查找 XML 中存在的"header"、"body"、"footer"节点调用各自的 Load 方法并设置相应属性。这三个节点开始就是虚窗口了,调用其 Load 方法就进入了虚窗口的 创建体系。

2.在这三者的调用中会调用 CBkPanel::Load 方法 virtual BOOL Load(TiXmlElement* pTiXmlElem) { if (!CBkWindow::Load(pTiXmlElem)) return FALSE;

//调用父类load方法,主要进行自身属性设置 return LoadChilds(pTiXmlElem->FirstChildElement());

} //load子节点 在 CBkPanel::LoadChilds 方法中,顺序调用每个子节点的创建方法并调用 Load 方法。

3. 在 CBkPanel::LoadChilds 方法中进行了如下调用: CBkWindow *pNewChildWindow = _CreateBkWindowByName(pXmlChild->Value());

在_CreateBkWindowByName 函数根据从 XML 解析出的节点名称调用 pNewWindow = CBkDialog::CheckAndNew(lpszName);

// CBkDialog 为需要动态创建类的名称 创建出对应节点对象。安装我们展开的 CheckAndNew 方法,如果节点名称相同,创建类对象 并返回,否则返回空。

至此,按照 XML 节点名称动态创建类对象的过程就完成了。 统一的资源管理 为了对界面资源进行管理,同时也为了方便替换,需要对使用的资源进行统一的管理。

在 BKLib 中,资源管理主要由以下几种:

BkBmpPool:HBITMAP 资源池,用于统一管理 HBITMAP,单例。

BkFontPool:FONT 资源池,用于统一管理 FONT,单例。

BkPngPool:PNG 图片资源池,用于统一管理 PNG 图片,单例,使用 GDI+。

BkString:CString 资源池,用于统一管理 String,单例,从 XML 中获取。

BkColor:HLS&RGB 颜色处理工具类 CBkImage:图像处理类 BkResManager:资源处理器,单例,用于获取资源 BkSkin:Skin 资源池,单例,统一管理 Skin (CBkSkinBase),从 XML 中获取 BkStyle:Style 资源池,单例,统一管理 Style,从 XML 中获取 在窗口创建之前需要加载相应的资源 BkString::Load(IDR_BK_STRING_DEF);

// 加载字符串 BkFontPool::SetDefaultFont(BkString::Get(IDS_APP_FONT), -12);

// 设置字体 BkSkin::LoadSkins(IDR_BK_SKIN_DEF);

// 加载皮肤 BkStyle::LoadStyles(IDR_BK_STYLE_DEF);

// 加载风格 //其中输入参数为 XML 文件的名称 在程序关闭后应释放对应资源。

使用时只要按照 ID 在资源池中查找对应资源即可,如 if ("bgskin"

== strAttribName) { m_pSkinBg = BkSkin::GetSkin(strValue);

hRet = TRUE ? S_OK : S_FALSE;

} //属性名称是 strAttribName //属性的值是 strValue //是否全部重绘 根据 ID(strValue)就可在 BkSkin 中获取相应的 Skin。 真实窗口的封装以及实窗口到虚窗口的转化 所谓的 DUI 库, windowless 都是在一个窗口体系内虚拟出来虚窗口概念, 并且通过接管界面 布局、 消息传递和分发以及界面绘制来完成更优秀的界面效果。

不过这些的根基却又都要落 到真实的窗口上, 所以在界面库中需要对真实窗口进行封装, 并将真实窗口纳入到我们创建 的控件体系当中,并在这个过程中完成 windows 消息的传递,鼠标键盘事件的分发处理, 实窗口上的虚窗口的布局排版和绘制操作。

首先我们看看 BKLib 中的实窗口体系: BKLib 中并没有自己对 windows 窗口进行封装,而是使用了 WTL 的 CWindowImpl 类,这个 类对于 windows 窗口,以及消息分发、窗口属性设置等进行了封装。对于 windows 消息分 发中有一个重要的地方就是注册的窗口过程是按照窗口句柄进行处理, 但是在我们的程序中 是窗口类的一个成员方法, 如何将窗口句柄和 C++类实例之间进行映射就成了一个重要的话 题,WTL 中主要使用 trunk 技术,而 MFC 则使用链表进行查找,具体的细节大家可以在网 上查询,这里就不赘述了。

这里我们看一下上面类图中其他几个类的作用:

CBkDialog:无窗口控件,它是我们虚窗口体系的一部分 CBkViewImpl:这个算是一个接口类,不过它提供了我们需要包含虚窗口体系的实窗口所必须 具备的一些方法(这句话真绕) CBkDialogViewImpl:它是一个实窗口了,因为他从封装了实窗口的 CWindowImpl 类继承而 来,同时他也具有包含虚窗口所必须实现的方法(从 CBkViewImpl 继承而来),同时他还聚合 了我们虚窗口体系的一部分,也就是 CBkDialog,但是我们实际使用的类并不是它。

CBkDialogView:这个类在我们的程序中会创建一个真正的窗口的,从 CBkDialogViewImpl 继 承而来,具有了所有的能力,算是这里的中坚力量了。

CBkDialogImpl:它也是一个实窗口,我们自定义窗口就是从此继承创建,同时它还聚合了上 面的包含容器窗口(CBkDialogView),所以在 BKLib 中创建的窗口是有两层窗口的,上面的用 于承载控件体系, 下面的则是我们需要自定义的窗口。

为什么要创建两个窗口我们之后再研 究,这里就先不解释了。

了解了上面的窗口体系后,我们就来看看在一个窗口创建过程中,各种消息、排版、绘制是 如何从我们封装的实窗口想我们的虚窗口——控件体系上转化的吧。

创建过程: 布局过程(WM_SIZE 消息处理) 绘制过程(WM_PAINT 消息处理) 在实窗口到虚窗口的转化过程中,主要在于 CBkDialogViewImpl 类中包含三个成员变量:

m_bkHeader,m_bkBody,m_bkFooter,这三者的类型均是 CBkDialog,也就是我们控件体系的 一部分。BkLib 中将一个实窗口划分为三个虚窗口:head、body、foot,对于界面的创建、 布局、 绘制等操作也由这三者传递到虚窗口体系中, 并通过递归调用来对所有成员进行处理。 如何创建一个模态对话框 我们创建的窗口类从 CBkDialogImpl 继承而来,这个窗口就是一个模态的窗口,我们需要调 用其 DoModal 方法,但是在界面库里面是如何实现的一个模态的对话框呢。

核心就在这个类中的_ModalMessageLoop 方法,我们来研究一下。 void _ModalMessageLoop() { BOOL bRet;

MSG msg;

for(;;) { if (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (WM_QUIT == msg.message) break;

} if (m_bExitModalLoop || NULL == m_hWnd || !::IsWindow(m_hWnd)) break;

bRet = ::GetMessage(&msg, NULL, 0, 0);

if (bRet == -1) { continue;

} else if (!bRet) { ATLTRACE(L"Why Receive WM_QUIT here?\r\n");

break;

} ::TranslateMessage(&msg);

::DispatchMessage(&msg);

} } // WM_QUIT, exit message loop // error, don't process 在这个函数里面建立了一个消息处理循环。

首先进行 WM_QUIT 消息的检测,并且采用 PM_NOREMOVE 的方式,如果获得了这个消 息,那么退出消息循环,在之后的 DoModal 函数中模态窗口就被销毁了。 然后检测 bExitModalLoop 标志,如果这个标志位为 TRUE 的话那么也退出处理。我们来 看一下退出模态的函数: void EndDialog(UINT uRetCode) { m_uRetCode = uRetCode;

m_bExitModalLoop = TRUE;

// DestroyWindow里面直接Send了WM_DESTROY,所以不会跑到DoModal的消息循环里, 所以有了下面那行代码 // DestroyWindow();

// 这句非常重要,可以让DoModal消息循环再跑一次,防止卡死在GetMessage,泪奔 ::PostThreadMessage(::GetCurrentThreadId(), WM_NULL, 0, 0);

} 可见就是设置 m_bExitModalLoop 这个标志位,使窗口退出。

之后就是 GetMessage,TranslateMessage 和 DispatchMessage 了,就完成了常见的消息 处理。当我们创建窗口并运行在 ModalMessageLoop 函数当中时就形成了模态窗口的效果, 也就是接管了消息的分发和处理,其他的窗口就被模住了。 无窗口模式---逻辑树结构的建立 对于无窗口的模式,各种控件之间的关系需要我们自己来维护,因为我们要进行消息传递, 排版布局, 创建等操作时都需要沿着各种包含关系来逐个调用, 这样对于窗口中的控件就形 成了一个逻辑上的树形结构。我们看一下 CBkPanel 类,这个类的名字就表示它是一个有包 含功能的类, 可以有自己的子节点。

这个类有一个成员 CAtlList<CBkWindow *>

m_lstWndChild, 也就是每个 CBkPanel 类都有一个链表,在这个链表中存储了它的子节点控件。

那么这个链表是何时被填充的呢,我们看一下 LoadChilds 方法,就是在这个函数中进行的填 充操作,从前面我们知道,这个函数是在用 xml 初始化界面是调用的。其中对于当前节点 下的所有 xml 子节点进行下面的处理: CBkWindow *pNewChildWindow = _CreateBkWindowByName(pXmlChild->Value());

//创建控件 if (!pNewChildWindow) continue;

//创建失败,不进行后续设置 //设置父节点HBKWND //设置容器窗口的HWND //Load这个节点的子节点 //将自己加入到父节点的链表中 pNewChildWindow->SetParent(m_hBkWnd);

pNewChildWindow->Load(pXmlChild);

m_lstWndChild.AddTail(pNewChildWindow); pNewChildWindow->SetContainer(m_hWndContainer); 在 CBkPanel 的 OnDestroy 函数中进行子节点的销毁操作 POSITION pos = m_lstWndChild.GetHeadPosition();

while (pos != NULL) { CBkWindow *pBkWndChild = m_lstWndChild.GetNext(pos);

pBkWndChild->BkSendMessage(WM_DESTROY); delete pBkWndChild;

} m_lstWndChild.RemoveAll(); 之后进行消息传递,绘制,排版操作都可以使用这个树来进行处理,循环调用所有的节点。

如在 OnPaint 函数中: POSITION pos = m_lstWndChild.GetHeadPosition();

BOOL bDisabled = IsDisabled(), bIsChildDisabled = FALSE;

while (pos != NULL) { //……此处略去 pBkWndChild->BkSendMessage(WM_PAINT, (WPARAM)(HDC)dc);

//……此处略去 } 可见也是循环向每个子节点发送 WM_PAINT 消息。

同时,在界面库中,还使用了 BkWnds 来记录所有的控件,以便可以便捷地进行获取操作:

CBkWindow 类有 HBKWND 类型的成员变量 m_hBkWnd,这是一个虚拟的窗口句柄。

BkWnds 是一个控件池,创建的控件会在这里注册,并通过一个 HBKWND 类型变量标示,以 后就可以通过控件的 m_hBkWnd 来获取这个控件。 消息和事件的传递、分发、相应 既然没有真实的窗口,那么也就不能使用 windows 的根据句柄来分发消息的方式了,我们 需要创建自己的消息和事件分发体系。主要应该包括这几个方面:

1. 接收真实窗口的消息,并将其转化虚窗口体系中的处理 2. 虚窗口体系内有一套独立的消息分发机制,可以讲系统消息发至该接收的控件 3. 虚窗口需要能够抛出事件的能力,因为虚窗口之间也需要有相互的通知和相应的能力, 那么就需要虚窗口可以对于指定窗口抛出事件 4. 对于 3 中所抛出的事件,可以传递到指定的控件,并且控件内部应该有一套指定额相应 体系 在 CBkDialogViewImpl 中使用 WTL 的消息分发方法用于系统消息的分发: BEGIN_MSG_MAP_EX(CBkDialogViewImpl) MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnToolTipEvent) MSG_WM_SIZE(OnSize) MSG_WM_PRINT(OnPrint) MSG_WM_PAINT(OnPaint) //……从略 NOTIFY_CODE_HANDLER_EX(BKINM_INVALIDATERECT, OnBKINMInvalidateRect) REFLECT_NOTIFY_CODE(NM_CUSTOMDRAW) MESSAGE_HANDLER_EX(WM_NOTIFY, OnChildNotify) MESSAGE_HANDLER_EX(WM_COMMAND, OnChildNotify) MESSAGE_HANDLER_EX(WM_VSCROLL, OnChildNotify) MESSAGE_HANDLER_EX(WM_HSCROLL, OnChildNotify) REFLECT_NOTIFICATIONS_EX() END_MSG_MAP() 下面的是对于 WM_SIZE 消息的分发过程: 在 CBkWindow 类中创建了用于发送通知的函数: // Send a message to BkWindow LRESULT BkSendMessage(UINT Msg, WPARAM wParam = 0, LPARAM lParam = 0) { LRESULT lResult = 0;

if ( Msg <

WM_USER &&

Msg != WM_DESTROY &&

Msg != WM_CLOSE ) { TestMainThread();

} SetMsgHandled(FALSE);

ProcessWindowMessage(NULL, Msg, wParam, lParam, lResult);

return lResult;

} 这个函数很像 windows 的消息发送函数 SendMessage, LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM IParam) 需要向那个对象发送消息,那么首先需要获取这个对象,然后调用这个对象的 BkSendMessage 函数,用消息类型和附属参数填充,就可以完成事件通知的过程了。

在这个函数中调用了 ProcessWindowMessage 方法,但是我们并没有看见这个函数的实现, 我们来找一下哈~~ 我们点击调用栈里面的 ProcessWindowMessage 方法,程序转到了 CBkWindow 类的这块: BKWIN_BEGIN_MSG_MAP() MSG_WM_CREATE(OnCreate) MSG_WM_PAINT(OnPaint) MSG_WM_DESTROY(OnDestroy) MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged) MSG_WM_NCCALCSIZE(OnNcCalcSize) MSG_WM_SHOWWINDOW(OnShowWindow) BKWIN_END_MSG_MAP_BASE() 难道就是这串宏实现了实现了通知的相应机制,我们来分析一下这个几个宏的实现。 // BkWindow Message Map Define // Use WTL Message Map Ex (include atlcrack.h) #define BKWIN_BEGIN_MSG_MAP() protected: virtual BOOL ProcessWindowMessage( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT&

lResult) { #define BKWIN_END_MSG_MAP() if (!IsMsgHandled()) return __super::ProcessWindowMessage( hWnd, uMsg, wParam, lParam, lResult);

return TRUE;

} #define BKWIN_END_MSG_MAP_BASE() return TRUE;

} \ \ \ \ \ \ \ \ \ \ \ \ \ \ 上面的是自定义的,而其他则是使用的 WTL 的宏,如: // int OnCreate(LPCREATESTRUCT lpCreateStruct) #define MSG_WM_CREATE(func) \ if (uMsg == WM_CREATE) \ { \ SetMsgHandled(TRUE);

\ lResult = (LRESULT)func((LPCREATESTRUCT)lParam);

\ if(IsMsgHandled()) \ return TRUE;

\ } 我们将宏展开看一下就是:

protected: virtual BOOL ProcessWindowMessage( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT&

lResult) { if (uMsg == WM_CREATE) { SetMsgHandled(TRUE);

lResult = (LRESULT) OnCreate ((LPCREATESTRUCT)lParam);

if(IsMsgHandled()) return TRUE;

} //......此处略去其他分发函数 return TRUE; } 可见就是给每个虚窗口类增加了一个函数 ProcessWindowMessage,而这个函数的作用就是 根据发送通知的不同来选择处理的函数,也就是 BkSendMessage?ProcessWindowMessage? 根据消息的不同选择不同的处理函数, 并且这个函数和消息我们可以自己进行搭配的, 可能 是为了简便使用了系统消息宏,其实我们也可以自己进行定义的。比如定义一个消息为 BKM_CREATE,那么定义一个宏: #define MSG_BKM_CREATE(func) \ if (uMsg == BKM_CREATE) \ { \ SetMsgHandled(TRUE);

\ lResult = (LRESULT)func((LPCREATESTRUCT)lParam);

\ if(IsMsgHandled()) \ return TRUE;

\ } 就可以自己来定义通知的类型了。

对 于 CBkWindow , 因 为 它 是 所 有 虚 窗 口 的 基 类 , 所 以 消 息 处 理 到 此 为 止 , ProcessWindowMessage 函数最后什么都没做,使用了 BKWIN_END_MSG_MAP_BASE 宏。

在 CBkWindow 子类中,我们将 BKWIN_END_MSG_MAP_BASE 替换为 BKWIN_END_MSG_MAP,那 么宏展开了就是: protected: virtual BOOL ProcessWindowMessage( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT&

lResult) { if (uMsg == WM_CREATE) { SetMsgHandled(TRUE);

lResult = (LRESULT)func((LPCREATESTRUCT)lParam);

if(IsMsgHandled()) return TRUE;

} //......此处略去其他分发函数 if (!IsMsgHandled()) return __super::ProcessWindowMessage( hWnd, uMsg, wParam, lParam, lResult);

return TRUE;

} __super::ProcessWindowMessage 完整的链式消息传递啊~~,对于子类处理之后的消息还行 传递给父类进行处理就可以这么搞了~~~ 如何进行排版 前面我们分析过对于 WM_SIZE 消息的处理过程,CBkDialogViewImpl 窗口类在处理 WM_SIZE 消息时, 调用了自身的_RepositionItems 方法, 在这个方法内对窗口上的虚拟控件进行处理, 现在我们来详细看一下控件体系内是如何进行排版操作的。

在_RepositionItems 方法中定义了 4 个 CRect 变量, 就是 rcClient, rcHeader, rcFooter 和 rcBody, 其中 rcHeader, rcFooter 和 rcBody 分别对应 m_bkHeader,m_bkFooter 和 m_bkBody 的位置, rcClient 是 rcHeader,m_bkFooter 和 m_bkBody 之和。

在控件中哪些变量表示控件的位置信息呢:

在 CBkWindow 中, 用 m_rcWindow 这个 CRect 类型的变量来表示每个控件的基本位置信息(控 件的长宽以及左上角相对于实窗口的 pos),用 m_dlgpos 这个 BKDLG_POSITION 类型的变量 来表示 从 Xml 获取的控件位置信息(相对于父控件的距离)。

CBkWindow 类中的 BKDLG_POSITION 类型包含了控件上下左右的边界位置, 而且可以表示控 件边界相对于父控件边界是左对齐,右对齐还是居中对齐,以及具体的偏移量。

而在 CBkWindow 类中也设置了一系列的枚举值: enum { // Specify by "width"

attribute SizeX_Mask SizeX_Specify SizeX_FitContent SizeX_FitParent SizeY_Mask SizeY_Specify SizeY_FitContent SizeY_FitParent Position_Mask Position_Relative Position_Float control (Vert-Align) Align_Mask VAlign_Top VAlign_Middle VAlign_Bottom Align_Left Align_Center Align_Right };

= 0xF000UL, = 0x0000UL, // valign = top = 0x1000UL, // valign = middle = 0x2000UL, // valign = bottom = 0x0000UL, // align = left = 0x4000UL, // align = center = 0x8000UL, // align = right = 0x0007UL, = 0x0001UL, // width >

0 = 0x0002UL, // width <= 0 = 0x0004UL, // width = "full"

default = 0x0070UL, = 0x0010UL, // height >

0 = 0x0020UL, // height <= 0 default = 0x0040UL, // height = "full"

default = 0x0300UL, = 0x0100UL, // float = 0 default = 0x0200UL, // float = 1 // Specify by "height"

attribute // Specify by "float"

attribute // Specify by "valign"

and "align"

attribute, only using at float = 1 or panel 用来表示控件是按照内容填充,按照父控件填充,还是按照固定的值进行填充,也可以定义 对齐的方式,这些描述可以比上面的 BKDLG_POSITION 类型更加丰富,对于在 xml 中描述控 件位置也更加灵活。

控件中用于处理位置信息的方法有哪些呢: CBkWindow:

OnWindowPosChanged:处理 WM_WINDOWPOSCHANGED 消息,发送 WM_NCCALCSIZE 消息 获取自身长宽,通过传入的 Pos 和前面取得的长宽设置 m_rcWindow 变量 OnNcCalcSize:处理 WM_NCCALCSIZE 消息,根据父控件的长宽信息以 Xml 文件中自身位置 信息的描述来计算自身对于父控件的位置信息。

CBkPanel:

OnWindowPosChanged :处理 WM_WINDOWPOSCHANGED 消息,调用 CBkWindow 类的 OnWindowPosChanged 方法,调用_ComposingPanel 方法。

CBkDialog:

OnWindowPosChanged :处理 WM_WINDOWPOSCHANGED 消息,调用 CBkWindow 类的 OnWindowPosChanged 方 法 , 调 用 _RepositionChilds 方 法 ( 依 次 调 用 每 个 子 节 点 的 RepositionChild 方法)。

所以对控件发送 WM_WINDOWPOSCHANGED 消息时需要传入 WINDOWPOS 结构体,其中 x 和 y 为控件 的位置,而 cx 和 cy 为父窗口的长宽,控件根据 cx 和 cy 以及 xml 中的描述来计算自身的长宽。 现在我们来看一下控件位置调整的过程: CBkWindow 类负责自身的计算工作,所有控件的自身计算工作都是由调用父类 CBkWindow 的 OnWindowPosChanged 方法来进行的, 在这个方法中通过 xml 描述和传入的父控件长宽计 算自身大小,同时根据传入的 pos 设置控件的 m_rcWindow。CBkDialog 类计算自身,并计算 其子节点的大小,设置子节点的 pos 并向其发送 WM_WM_WINDOWPOSCHANGED 消息,令其计算 自身。 如何进行绘制 首先我们来看一下 CBkDialogViewImpl 类的绘制过程:

CBkDialogViewImpl 含有一个 CBkImage 类成员 m_imgMem 用于双缓冲绘制,在 WM_SIZE 消 息处理函数中进行创建: m_imgMem.CreateBitmap(rcClient.Width(), rcClient.Height(), RGB(0, 0, 0)); CBkDialogViewImpl 含有一个 CRgn 类成员 m_rgnInvalidate 用于脏区域描述, 在_InvalidateRect 方法内创建(绘制之前): m_rgnInvalidate.CreateRectRgnIndirect(rcInvalidate); 我们来看看 CBkDialogViewImpl 类的 OnPrint 方法,在处理 WM_PAINT 消息时会调用这个函 数,首先获取一个兼容 DC,并将双缓冲的图像渲染兼容 DC: CDC dcMem;

CDCHandle dcMemHandle;

HDC hDCDesktop = ::GetDC(NULL);

dcMem.CreateCompatibleDC(hDCDesktop);

::ReleaseDC(NULL, hDCDesktop);

HBITMAP hbmpOld = dcMem.SelectBitmap(m_imgMem); 然后设置兼容 DC 的脏区域: dcMem.SelectClipRgn(m_rgnInvalidate); 之后在这个 DC 上面进行绘制操作, if (m_bHasHeader) m_bkHeader.RedrawRegion(dcMemHandle, m_rgnInvalidate);

if (m_bHasBody) m_bkBody.RedrawRegion(dcMemHandle, m_rgnInvalidate);

if (m_bHasFooter) m_bkFooter.RedrawRegion(dcMemHandle, m_rgnInvalidate); 最后将这个双缓冲图像整体绘制到窗口 DC 上面: m_imgMem.Draw(dc, 0, 0); CBkDialogViewImpl 的方法_InvalidateRect 用于绘制指定区域,合并现有脏区域,重绘指定区 域。_InvalidateControl 方法用于重绘指定控件,即获取此控件位置并重绘此位置 可见现在绘制转入了控件层的 RedrawRegion 方法。

控件中用于处理绘制的方法有哪些呢:

CBkWindow:

RedrawRegion(只有控件在重绘区域内时才进行):调用 DrawBkgnd,发送 WM_PAINT 消息 DrawBkgnd:在兼容 DC 中绘制背景,如果 Skin 存在,用 Skin 绘制,否则用 Style 中颜色绘 制。

OnPaint:只有绘制文字的过程,一般不被调用 CBkPanel:

RedrawRegion:调用父类 CBkWindow 的 RedrawRegion 方法,重绘自身背景,循环调用每个 子节点的 RedrawRegion 方法。

OnPaint:原来有处理,现在被 return 掉了。

在 CBkPanel 类的 RedrawRegion 方法中调用了 BeforePaint 和 AfterPaint 两个方法, BeforePaint 用来设置 BkMode、BkColor、Font 和 TextColor 并保存原始信息,AfterPaint 用于将这些环境 重置回原值。 渲染层的封装和隔离:BkSkin 控件的绘制工作基本都封装到了 Skin 里面, 如果控件对应的 Skin 存在的话, 那么会按照 Skin 的描述进行绘制,在 CBkWindow 的 DrawBkgnd 方法中,使用 Skin 进行了绘制: CBkSkinBase* pSkin = BkSkin::GetSkin(m_style.m_strSkinName);

if (pSkin) { pSkin->Draw(dc, m_rcWindow, m_dwState);

} 看一下 Skin 的继承关系: 对于 CBkSkinBase 类,只需要绘制的区域,绘制的状态以及绘制的 DC 就可以。CBkSkinBase 是一个虚基类,从 CBkSkinBase 继承的子类需要实现 Draw 方法,在这个方法中实现具体的 绘制操作。

同时 CBkSkinBase 类也提供了一下这些工具方法,用来辅助绘制操作:

HorzExtendDraw FrameDraw GradientFillRectV GradientFillRectH GradientFillRectV GradientFillRectH 我们来看个 CBkSkinButton 类,当然主要看 Draw 方法: virtual void Draw(CDCHandle dc, CRect rcDraw, DWORD dwState) { CPen penFrame;

CRect rcBg = rcDraw;

dc.FillSolidRect(rcDraw, m_crBg);

rcBg.DeflateRect(2, 2);

if (BkWndState_Disable == (BkWndState_Disable &

dwState)) 不进行绘制 { } else GradientFillRectV( dc, rcBg, IIF_STATE3(dwState, m_crBgUpNormal, m_crBgUpHover, m_crBgUpPush), IIF_STATE3(dwState, m_crBgDownNormal, m_crBgDownHover, m_crBgDownPush));

penFrame.CreatePen( PS_SOLID, 1, m_crBorder );

HPEN hpenOld = dc.SelectPen(penFrame);

HBRUSH hbshOld = NULL, hbshNull = (HBRUSH)::GetStockObject(NULL_BRUSH);

//空画 刷 hbshOld = dc.SelectBrush(hbshNull);

dc.Rectangle(rcDraw);

dc.SelectBrush(hbshOld);

dc.SelectPen(hpenOld);

} //绘制矩形边框 //创建画笔,用于边框绘制 //如果Disable状态, //填充背景颜色 在上面的绘制中使用了控件的状态,控件状态定义如下: // State Define enum { BkWndState_Normal BkWndState_Hover BkWndState_PushDown BkWndState_Check BkWndState_Invisible BkWndState_Disable = 0x00000000UL, = 0x00000001UL, = 0x00000002UL, = 0x00000004UL, = 0x00000008UL, = 0x00000010UL, }; 这个 IIF_STATE3 是个宏定义,对于指定的状态,返回相应的背景,具体如下: #define IIF_STATE2(the_state, normal_value, hover_value) \ (((the_state) &

BkWndState_Hover) ? (hover_value) : (normal_value)) #define IIF_STATE3(the_state, normal_value, hover_value, pushdown_value) \ (((the_state) &

BkWndState_PushDown) ? (pushdown_value) : IIF_STATE2(the_state, normal_value, hover_value)) #define IIF_STATE4(the_state, normal_value, hover_value, pushdown_value, disable_value) \ (((the_state) &

BkWndState_Disable) ? (disable_value) : IIF_STATE3(the_state, normal_value, hover_value, pushdown_value)) 鼠标消息、窗口状态的管理 首先来看 CBkDialogViewImpl 类的鼠标消息处理方法 OnMouseLeave 方法:

如果现在处于非跟踪的状态,那么调用_TrackMouseEvent 并设置 m_bTrackFlag; 之后获取现在鼠标所悬停的控件,通过调用 header,body 和 footer 的 BkGetHWNDFromPoint 方法来判断,因为这三者覆盖整个客户区域 如果当前悬停控件和记录的 hover 控件不一致,那么进行更新操作 OnMouseLeave 方法:

设置不跟踪状态 如果 hover 控件存在,那么重置状态 如果 pushdown 控件存在,那么重置状态 OnLButtonDown 方法:

查看是否是点击标题栏,如果是,调用最大化,最小化和恢复处理 在非标题栏点击情况下,设置 capture 状态 OnLButtonUp 方法 ReleaseCapture,设置控件状态 OnLButtonDblClk 方法 在双击标题栏情况下最大化或者恢复窗口 在类 CBkWindow 中,使用 m_dwState 变量来标示控件的状态,主要由以下几种状态: // State Define enum { BkWndState_Normal BkWndState_Hover BkWndState_PushDown BkWndState_Check BkWndState_Invisible BkWndState_Disable };

= 0x00000000UL, = 0x00000001UL, = 0x00000002UL, = 0x00000004UL, = 0x00000008UL, = 0x00000010UL, //正常 //悬停 //按下 //选中 //不可见 //失活 主要使用 ModifyState 方法来改变控件的状态。

所以在界面库中需要对真实窗口进行封装, 并将真实窗口纳入到我们创建 的控件体系当... 前面我们分析过对于 WM_SIZE 消息的处理过程,CBkDialogViewImpl 窗口类在处理 WM...

Hi,我正在使用 #百度网盘# ,给大家分享“金山界面库分析_解析bkwin.docx”文件,快来看看吧~ http://url.cn/FPgAZO 李舒加油,坚强地挺过去.宝宝们加油! 【 】她是三胞胎...

BkWin 金山界面库 界面创建流程剖析 1. 读取string 2. 读取字体 3. 读取皮肤 4. 读取style(风格) 5. 对话框DoModal() 6. 按照dlg_main.xml(假设这是对话框xml定义的文件名称)...

 
  • 泥巴往事网(www.nbwtv.com) © 2014 版权所有 All Rights Reserved.