如何优雅的写UI——(5)选项卡功能实现
先在我们的选项卡可以说能用了,每个标签页都能点进去,但是这还远远没到能用的地步,比如说你把窗口最大化后。
立马就露出马脚了,所以这篇我们要先讲讲tabctrl的最基本的功能实现
改变选项卡大小
上图的原因就是主窗口在改变的大小的时候没有通知选项卡让他跟着主窗口一起变,所以我们现在通知选项卡一下
添加ON_WM_SIZE消息,实现函数
void CtabView::OnSize(UINT nType, int cx, int cy) { //CFormView::OnSize(nType, cx, cy); if (m_tab && m_tab.GetSafeHwnd()) { m_tab.SetWindowPos (NULL, -1, -1, cx, cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER); } }
在主窗体改变大小的时候,会时时通知选项卡,主窗体我已经变成多大了(cx,cy),你赶快跟上。
改变控件位置
选项卡现在已经可以跟着主窗体一起变了,但是发现没有,里面的控件的位置却没有变,如果里面的控件位置都没变,那我们最大化还有什么意义呢?所以我们还得通知子窗体里面的控件“子窗体大小已经改变了,你控件也赶快挪位置跟上。”
既然是子窗体里的控件,那肯定是要在子窗体去实现,但是一个子窗口会有很多控件,我们一个一个去取句柄然后再通知他们会不会很麻烦,当然麻烦,但是我们有简单方法。
GetWindow函数会给我们返回子窗口里所有控件ID,这样我们就不用一个一个找了。所以我们只需要一个循环就能改变控件位置和大小
HWND GetWindow(HWND hWnd,UNIT nCmd)。
参数说明:
hWnd:窗口句柄。这个函数要返回的窗口句柄是依据nCmd参数值相对于hWnd参数的关系。
nCmd:说明指定窗口与要获得句柄的窗口之间的关系。该参数值可以是下列之一:
GW_CHILD(&H5):如果指定窗口是父窗口,则获得的是在Tab序顶端的子窗口的句柄,否则为NULL。函数仅检查指定父窗口的子窗口,不检查继承窗口。
GW_ENABLEDPOPUP(&H6):(WindowsNT 5.0)返回的句柄标识了属于指定窗口的处于使能状态弹出式窗口(检索使用第一个由GW_HWNDNEXT 查找到的满足前述条件的窗口);
如果无使能窗口,则获得的句柄与指定窗口相同。
GW_HWNDFIRST(&H0):返回的句柄标识了在Z序最高端的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在Z序最高端的最高端窗口;
如果指定窗口是顶层窗口,则该句柄标识了在z序最高端的顶层窗口:如果指定窗口是子窗口,则句柄标识了在Z序最高端的同属窗口。
GW_HWNDLAST(&H1):返回的句柄标识了在z序最低端的相同类型的窗口。如果指定窗口是最高端窗口,则该柄标识了在z序最低端的最高端窗口:
如果指定窗口是顶层窗口,则该句柄标识了在z序最低端的顶层窗口;如果指定窗口是子窗口,则句柄标识了在Z序最低端的同属窗口。
GW_HWNDNEXT(&H2):返回的句柄标识了在Z序中指定窗口下的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口下的最高端窗口:
如果指定窗口是顶层窗口,则该句柄标识了在指定窗口下的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口下的同属窗口。
GW HWNDPREV(&H3):返回的句柄标识了在Z序中指定窗口上的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口上的最高端窗口;
如果指定窗口是顶层窗口,则该句柄标识了在指定窗口上的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口上的同属窗口。
GW_OWNER(&H4):返回的句柄标识了指定窗口的所有者窗口(如果存在)。GW_OWNER与GW_CHILD不是相对的参数,没有父窗口的含义,如果想得到父窗口请使用GetParent()。
例如:例如有时对话框的控件的GW_OWNER,是不存在的。
返回值:如果函数成功,返回值为窗口句柄;如果与指定窗口有特定关系的窗口不存在,则返回值为NULL。
若想获得更多错误信息,请调用GetLastError函数。
POINT m_old;
创建一个chang函数并在OnSize中调用
void CDialog1::ChangeSize(/*CWnd * pWnd, int cx, int cy*/) { float fsp[2]; POINT Newp; //获取现在对话框的大小 CRect recta; GetClientRect(&recta); //取客户区大小 Newp.x=recta.right-recta.left; Newp.y=recta.bottom-recta.top; fsp[0]=(float)Newp.x/m_old.x; fsp[1]=(float)Newp.y/m_old.y; CRect Rect; int woc; CPoint OldTLPoint,TLPoint; //左上角 CPoint OldBRPoint,BRPoint; //右下角 HWND hwndChild=::GetWindow(m_hWnd,GW_CHILD); //列出所有控件 while(hwndChild) { woc=::GetDlgCtrlID(hwndChild);//取得ID GetDlgItem(woc)->GetWindowRect(Rect); ScreenToClient(Rect); OldTLPoint = Rect.TopLeft(); TLPoint.x = long(OldTLPoint.x*fsp[0]); TLPoint.y = long(OldTLPoint.y*fsp[1]); OldBRPoint = Rect.BottomRight(); BRPoint.x = long(OldBRPoint.x *fsp[0]); BRPoint.y = long(OldBRPoint.y *fsp[1]); Rect.SetRect(TLPoint,BRPoint); GetDlgItem(woc)->MoveWindow(Rect,TRUE); hwndChild=::GetWindow(hwndChild, GW_HWNDNEXT); } m_old=Newp; }
现在感觉好多了,中间的文本框字体需要另外的代码去改变它
实现关闭标签页功能
最后有没有发现,标签页上的按钮是没有反应的,这是我们没有实现这个按钮
添加一个MFC类CMFCTabCtrlEx,继承与CMFCTabCtrl
给CMFCTabCtrlEx类添加ON_WM_LBUTTONDOWN()事件,因为是动态创建选项卡,所以这个事件只能自己手写。
//.h class CMFCTabCtrlEx : public CMFCTabCtrl { DECLARE_DYNAMIC(CMFCTabCtrlEx) public: CMFCTabCtrlEx(); virtual ~CMFCTabCtrlEx(); protected: DECLARE_MESSAGE_MAP() public: afx_msg void OnLButtonDown(UINT nFlags, CPoint point); };
//.cpp
// MFCTabCtrlEx.cpp : 实现文件
//
#include "stdafx.h"
#include "MFCTabCtrlEx.h"
// CMFCTabCtrlEx
IMPLEMENT_DYNAMIC(CMFCTabCtrlEx, CMFCTabCtrl)
CMFCTabCtrlEx::CMFCTabCtrlEx()
{
}
CMFCTabCtrlEx::~CMFCTabCtrlEx()
{
}
BEGIN_MESSAGE_MAP(CMFCTabCtrlEx, CMFCTabCtrl)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
void CMFCTabCtrlEx::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CMFCTabCtrl::OnLButtonDown(nFlags, point); // 获取Tab的数量 int n = GetTabsNum(); if (n == 0) return; CRect rc; // 获取整个Tab区域 CalcRectEdit(rc); GetTabsRect(rc); // 获取当前选项卡窗口的指针 CWnd *pTabWnd = GetActiveWnd(); pTabWnd->GetClientRect(rc); int sel; // 获取当前选择的Tab sel = GetActiveTab(); // 获取单个Tab区域 GetTabRect(sel, rc); // 获取单个Tab的最大宽度 int nWidth = GetTabMaxWidth(); // 获取关闭按钮的区域 CRect rcCloseBtn; rcCloseBtn = GetTabCloseButton(); if (rcCloseBtn.PtInRect(point)) { if(MessageBox(_T("确认关闭该标签页?"), _T("关闭提示"), MB_YESNO) == IDYES) { // 关闭Tab页关联窗口 pTabWnd->SendMessage(WM_CLOSE); // 删除Tab RemoveTab(sel); } } }
将主窗口选项卡基类替换为CMFCTabCtrlEx就完成了。