⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mfc.txt

📁 MFC工具条和状态栏 Windows控制窗口
💻 TXT
📖 第 1 页 / 共 4 页
字号:
CWnd* pLeftOver = CWnd::FromHandle(hWndLeftOver);

// allow extra space as specified by lpRectBorder

if (nFlags == reposExtra)

{

ASSERT(lpRectParam != NULL);

layout.rect.left += lpRectParam->left;

layout.rect.top += lpRectParam->top;

layout.rect.right -= lpRectParam->right;

layout.rect.bottom -= lpRectParam->bottom;

}

//基于layout.rect表示的客户尺寸计算出窗口尺寸

pLeftOver->CalcWindowRect(&layout.rect);

//导致函数::DeferWindowPos的调用

AfxRepositionWindow(&layout, hWndLeftOver, &layout.rect);

}


//给所有的窗口设置尺寸、位置(size and layout)

if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP))

TRACE0("Warning: DeferWindowPos failed - low system resources.\n");

}

RepositionBars用来改变客户窗口中控制条的尺寸大小或者位置,其中:

参数1和参数2定义了需要重新放置的子窗口ID的范围,一般是0到0xFFFF。

参数3指定了一个子窗口ID,它拥有客户窗口剩下的空间,一般是AFX_IDW_PANE_FIRST,表示视的窗口ID。

参数4指定了操作类型,缺省是CWnd::ReposDefault,表示执行窗口放置操作,参数5不会用到;若取值CWnd::ReposQuery,则表示尝试进行窗口放置(Layout) ,但最后不执行这个操作,只是把参数5初始化成客户区的尺寸大小;若取值CWnd::ReposExtra,则把参数5的值加到参数2表示的子窗口的客户区域,并执行窗口放置操作。

参数6表示传递给函数的可用窗口客户区的尺寸,如果空则使用窗口客户区尺寸。

如果执行layout操作的话,该函数的核心处理就是:

首先,调用::BeginDeferWindowPos初始化一个Windows内部的多窗口位置结构(Multiple-window - position structure)hDWP;

然后,让各个子窗口逐个调用::DeferWindowPos,更新hDWP。在调用::DeferWindowPos之前,要作一个确定子窗口大小的工作。这些工作通过给各个控制子窗口发送消息WM_SIZEPARENT来完成。

控制子窗口通过函数OnSizeParent响应WM_SIZEPARENT消息,先确定自己的尺寸,然后,如果需要进行窗口布置(WM_SIZEPARENT消息参数lParam包含了一个非空的HDWP结构(lpLayout->hDWP)),则OnSizeParent将调用AfxRepositionWindow函数计算本控制窗口的位置,结果保存到hDWP中。

在所有的控制窗口尺寸确定之后,剩下的留给窗口hWndLeftOver(如果存在的话)。确定了hWndLeftOver的大小之后,调用AfxRepositionWindow函数计算其位置,结果保存到hDWP中。

上面提到的函数AfxRepositionWindow间接调用了::DeferWindowPos。

最后,::EndDeferWindowPos,使用hDWP安排所有子窗口的位置和大小。


至于其他函数,如OnSizeparent、OnWindowPosChanging、CalcWindowRect,这里不作进一步的分析。


工具条、状态栏和边框窗口的接口 


应用程序在状态栏中显示信息 

MFC内部通过给边框窗口发送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING的方式在状态栏中显示信息。这两个消息在afxpriv.h里头定义。

WM_SETMESSAGESTRING消息表示在状态栏中显示和某个ID对应的字符串信息或者指定的字符串信息,消息参数wParam指定了字符串资源ID,消息参数lParam指定了字符串指针,两个消息参数只有一个有用。一般,一个命令ID对应了一个字符串ID,对应的字符串是命令ID的说明。

消息WM_POPMESSAGESTRING用来重新设置状态栏。

这两个消息对应的消息处理函数分别是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分别实现如下:


OnSetMessageString 

LRESULT CFrameWnd::OnSetMessageString(WPARAM wParam, LPARAM lParam)

{

//最近一次被显示的消息字符串IDS(一个消息对应的字符串)

UINT nIDLast = m_nIDLastMessage;

m_nFlags &= ~WF_NOPOPMSG;


//得到状态栏

CWnd* pMessageBar = GetMessageBar();

if (pMessageBar != NULL)

{

LPCTSTR lpsz = NULL;

CString strMessage;


//设置状态栏文本

if (lParam != 0) //指向一个字符串

{

ASSERT(wParam == 0); // can't have both an ID and a string

lpsz = (LPCTSTR)lParam; // set an explicit string

}

else if (wParam != 0)//一个字符串资源IDS

{

//打印预览时映射SC_CLOSE成AFX_IDS_PREVIEW_CLOSE;

if (wParam == AFX_IDS_SCCLOSE && m_lpfnCloseProc != NULL)

wParam = AFX_IDS_PREVIEW_CLOSE;


//得到资源ID所标识的字符串

GetMessageString(wParam, strMessage);

lpsz = strMessage;

}

//在状态栏中显示文本

pMessageBar->SetWindowText(lpsz);


// 根据最近一次选择的消息更新状态条所属窗口的有关记录

CFrameWnd* pFrameWnd = pMessageBar->GetParentFrame();

if (pFrameWnd != NULL)

{

//记录最近一次显示的消息字符串

pFrameWnd->m_nIDLastMessage = (UINT)wParam;

//记录最近一次Tracking的命令ID和字符串IDS

pFrameWnd->m_nIDTracking = (UINT)wParam;

}

}


m_nIDLastMessage = (UINT)wParam; // new ID (or 0)

m_nIDTracking = (UINT)wParam; // so F1 on toolbar buttons work

return nIDLast;

}

OnSetMessageString函数直接或者从ID从字符串资源中得到字符串指针。如果是从ID得到字符串指针,则函数GetMessageString被调用。

和命令ID对应的字符串由两部分组成,前一部分用于在状态栏显示,后一部分用于Tooltip显示,分隔符号是“\n”。例如,字符串ID_APP_EXIT(对应“退出”菜单、按钮)是“Exit Application\nExit”,当鼠标落在“退出”按钮上时,状态栏显示“Exit Application”,Tooltip显示“Exit”。根据这种格式,GetMessageString分离出第一部分的文本信息。至于第二部分的用途将在讨论Tooltip的章节将用到。

得到了字符串之后,OnSetMessageString调用状态栏的SetWindowText函数。SetWindowText导致消息WM_SETTEXT消息发送给状态栏,状态栏的消息处理函数OnSetText被调用,实际上等于调用了SetPaneText(0, lpsz),即在状态栏的第0格中显示字符串lpsz的信息。对于工具栏来说,SetWindowText可以认为是SetPaneText(0, lpsz)的简化版本。

顺便指出,pMessageBar->GetParentFrame()返回主边框窗口,即使pMessageBar指向漂浮的工具条。关于泊位和漂浮,见后面13.2.5节的描述。

关于OnSetText,其实现如下:

LRESULT CStatusBar::OnSetText(WPARAM, LPARAM lParam)

{

ASSERT_VALID(this);

ASSERT(::IsWindow(m_hWnd));


int nIndex = CommandToIndex(0); //返回0

if (nIndex < 0)

return -1;


return SetPaneText(nIndex, (LPCTSTR)lParam) ? 0 : -1;

}


OnPopMessageString 

LRESULT CFrameWnd::OnPopMessageString(WPARAM wParam, 

LPARAM lParam)

{

//WF_NOPOPMSG表示边框窗口不处理WM_POPMESSAGESTRING

if (m_nFlags & WF_NOPOPMSG)

return 0;


//调用OnSetMessageString

return SendMessage(WM_SETMESSAGESTRING, wParam, lParam);

}

一般,在清除状态栏消息时,发送WM_POPMESSAGESTRING,通过消息参数wParam指定一个字符串资源,其ID 为AFX_IDS_IDLEMESSAGE,对应的字符串是“Ready”。


状态栏显示菜单项的提示信息 

状态栏的一个重要作用是显示菜单命令或者工具条按钮的提示信息。本节讨论如何显示菜单命令的提示信息,关于工具条按钮在这方面的讨论见后面13.2.4.4章节。

显示菜单命令的提示信息,就是每当一个菜单项被选中之后,在状态栏显示该菜单的功能、用法等信息。这些信息以字符串资源的形式保存,字符串ID对应于菜单项的命令ID。

所以,必须处理菜单选择消息WM_MENUSELECT。CFrameWnd实现了消息处理函数OnMenuSelect,其实现如下:

void CFrameWnd::OnMenuSelect(UINT nItemID, 

UINT nFlags, HMENU /*hSysMenu*/)

{

CFrameWnd* pFrameWnd = GetTopLevelFrame();

ASSERT_VALID(pFrameWnd);


//跟踪被选中的菜单项

if (nFlags == 0xFFFF)

{

//取消菜单操作

m_nFlags &= ~WF_NOPOPMSG;

if (!pFrameWnd->m_bHelpMode)

m_nIDTracking = AFX_IDS_IDLEMESSAGE;

else

m_nIDTracking = AFX_IDS_HELPMODEMESSAGE;

//在状态栏显示

SendMessage(WM_SETMESSAGESTRING, (WPARAM)m_nIDTracking);

ASSERT(m_nIDTracking == m_nIDLastMessage);


// update right away

CWnd* pWnd = GetMessageBar();

if (pWnd != NULL)

pWnd->UpdateWindow();

}

else

{

//选中分隔栏、Popup子菜单或者没有选中一个菜单项

if (nItemID == 0 || nFlags & (MF_SEPARATOR|MF_POPUP))

{

// nothing should be displayed

m_nIDTracking = 0;

}

else if (nItemID >= 0xF000 && nItemID < 0xF1F0) // max of 31 SC_s

{

//系统菜单的菜单项被选中

m_nIDTracking = ID_COMMAND_FROM_SC(nItemID);

ASSERT(m_nIDTracking >= AFX_IDS_SCFIRST &&

m_nIDTracking < AFX_IDS_SCFIRST + 31);

}

else if (nItemID >= AFX_IDM_FIRST_MDICHILD)

{

//如果选中的菜单项表示一个MDI子窗口

m_nIDTracking = AFX_IDS_MDICHILD;

}

else

{

//选中了一个菜单项

m_nIDTracking = nItemID;

}

pFrameWnd->m_nFlags |= WF_NOPOPMSG;

}


// when running in-place, it is necessary to cause a message to

// be pumped through the queue.

if (m_nIDTracking != m_nIDLastMessage && GetParent() != NULL)

PostMessage(WM_KICKIDLE);

}

OnMenuSelect的作用在于跟踪当前选中的菜单项,把菜单项对应的ID保存在CFrameWnd的成员变量m_nIDTracking中。

如果菜单项没有选中,或者选中的是一个子菜单,则设置nIDTracking为0。

如果选中的是系统菜单,则把系统菜单ID转换成一个对应的命令ID;保存该值到nIDTracking中。

如果选中的菜单是MDI子窗口创建时添加的(用来表示MDI子窗口),则转换菜单ID为AFX_IDS_MDICHILD,所有对应MDI子窗口的菜单项都使用AFX_IDS_MDICHILD,保存该值到nIDTracking中。

其他情况,就是选中菜单项的ID,把它保存到nIDTracking中。


跟踪被选择的菜单项并保存其ID在m_nIDTracking中,OnEnterIdle将用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE的处理函数,CFrameWnd的实现如下。

void CFrameWnd::OnEnterIdle(UINT nWhy, CWnd* pWho)

{

CWnd::OnEnterIdle(nWhy, pWho);


//若不是因为菜单选择进入该函数

//或者当前跟踪到的菜单项ID是最近一次处理的,则返回

if (nWhy != MSGF_MENU || m_nIDTracking == m_nIDLastMessage)

return;


//将发送消息WM_SETMESSAGETEXT

//在状态栏显示m_nIDTracking对应的字符串

SetMessageText(m_nIDTracking);

ASSERT(m_nIDTracking == m_nIDLastMessage);

}

当一个对话框或者菜单被显示的时候,Windows发送WM_ENTERIDLE消息。消息参数wParam取值为MSGF_DIALOGBOX或者MSGF_MENU。前者表示显示对话框时发送该消息,这时消息参数lParam表示对话框的句柄;后者表示显示菜单时发送该消息,这时消息参数lParam表示菜单的句柄。

经过消息映射,wParam的值传递给OnEnterIdle的参数nWhy,参数lParam的值传给参数who。如果参数1取值为MSGF_MENU,并且OnEnterIdle最近一次在菜单显示被调用时的菜单ID不同于这一次,则调用SetMessageText在状态栏显示对应ID命令的字符串,并且记录当前菜单ID到变量m_nIDTracking中(见消息处理函数OnSetMessageText)。

这样,在菜单选择期间,用户选择的菜单项ID被OnMenuSelect记录,在消息WM_ENTERIDLE处理时在状态栏显示ID命令的提示。


控制条的消息分发处理 

工具条(包括对话框工具条)是一个子窗口,它们可以响应各种消息。如果按标准的Windows消息和命令消息的分发途径,一些消息不能送到拥有工具条的边框窗口,因为这些消息都将被工具条(对话框工具条)处理掉。所以,CControlBar覆盖了虚拟函数PreTranslateMessage和WindowProc以便实现特定的消息分发路径。


WindowProc 

CControlBar 的WindowProc实现了如下的消息分发路径:

用户对控制条的输入消息或者分发给CControlBar及其派生类处理,或者送给拥有控制条的边框窗口处理,或者送给Windows控制“窗口类”的窗口过程处理。

WindowProc的实现如下:

LRESULT CControlBar::WindowProc(UINT nMsg, 

WPARAM wParam, LPARAM lParam)

{

ASSERT_VALID(this);


LRESULT lResult;

switch (nMsg)

{

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -