用MFC控制客户区最小大小
内容 介绍 背景 的代码 演示程序 改进的限制和建议 结论 参考书目 历史 介绍 这难道不奇怪吗?有时候,一开始看起来很容易的问题却会给我们带来最大的麻烦。试图将客户端视图限制为最小大小就是其中一个问题。我尝试了很多方法才得到一个满意的解决办法。最终的解决方案还是不够完美,稍后我会解释原因。在这篇文章中,我将介绍一些关于这个问题的背景知识,并通过介绍我为解决这个问题所做的第一次尝试,来描述我在这条路上遇到的各种困难。然后我将展示最终解决方案的内部工作,并给出为什么它被设计成这样的见解。然后,我将继续展示演示程序,其目的是展示如何在自己的程序中使用这些代码,最后,我将讨论当前代码的局限性和可能的增强。 背景 我尝试的第一个尝试是在子视图类中直接处理WM_GETMINMAXINFO消息,它看起来像这样:复制Code
void CChildView::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { lpMMI->ptMinTrackSize.x = DEFAULTMINCLIENTSIZE; lpMMI->ptMinTrackSize.y = DEFAULTMINCLIENTSIZE; }
要是有那么简单就好了!它不工作,因为只有可调整的顶部大多数窗口接收WM_GETMINMAXINFO消息,而CChildView不是这样的一个窗口。这是解决这个问题的第一个困难。用MFC解决这个问题的第二个困难是,MFC将框架客户区分隔在不同的UI组件(视图、状态栏和工具栏)之间,而该区域的大部分管理是由MFC在内部完成的,实际上没有文档记录。我的第二次尝试试图解决这个难题,结果是这样的:藏起来。复制Code
CMainFrame::CMainFrame() { minX = DEFAULTMINCLIENTSIZE; minY = DEFAULTMINCLIENTSIZE; } void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { CView *pView = GetActiveView(); if( pView ) { RECT viewRect; pView->GetClientRect(&viewRect); if( viewRect.right < DEFAULTMINCLIENTSIZE ) { minX += (DEFAULTMINCLIENTSIZE - viewRect.right); } if( viewRect.bottom < DEFAULTMINCLIENTSIZE ) { minY += (DEFAULTMINCLIENTSIZE - viewRect.bottom); } } lpMMI->ptMinTrackSize.x = minX; lpMMI->ptMinTrackSize.y = minY; }
它给出了一个下降的结果,但不是很准确。例如,对于一个有工具栏和状态栏的应用程序,如果你想减少框架的垂直大小,它会比DEFAULTMINCLIENTSIZE略低一点,然后迅速恢复到指定的最小高度。另外,如果您想在达到最小尺寸后删除状态栏和工具栏,您会发现即使视图大小高于指定的最小尺寸,窗口也拒绝进一步最小化。在第二次尝试之后,我在网上搜索了一下,看看其他人对同样的问题有什么解决方案。我找到了一篇来自ovidiucucu的文章,他提出了以下代码来解决我遇到的问题:复制Code
void CChildFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { // the minimum client rectangle (in that is lying the view window) CRect rc(0, 0, 200, 150); // compute the required size of the frame window rectangle // based on the desired client-rectangle size CalcWindowRect(rc); lpMMI->ptMinTrackSize.x = rc.Width(); lpMMI->ptMinTrackSize.y = rc.Height(); }
这听起来不错,但也不管用。如前所述,框架客户端矩形在状态栏、工具栏和视图之间共享。如果你的框架客户区只有一个视图,它会工作,否则就不会。现在通过这些不同的尝试,我们应该开始清楚地认识到解决这个问题的唯一方法是将我们的手放在MFC内部,以便能够准确地跟踪每个客户区域组件的大小和位置。这正是我的代码所做的,我将在下一节中向您展示如何做到这一点。 的代码 这部分是可选的。如果您只对使用代码感兴趣,而不关心实现,那么您可以直接跳到下一节。你还在那里吗?太好了!好的程序员都很有好奇心。在描述类CMinMaxFrame需要完成的不同任务之前,我将用一个小的类图向您展示整个代码组织。 每当我使用MFC时,我都会强加给自己这个小的设计模式,在这个模式中,我将所有与MFC无关的东西解耦到一个名为CSomethingLogic的类中。这样,如果我将代码移植到不同的框架中,移植就会变得更容易。也就是说,设计很简单。CMinMaxFrame是从CFrameWnd派生的,使用的是CMinMaxLogic。为了使用代码,你必须从CMinMaxFrame派生你的主框架类。CMinMaxFrame责任: 跟踪状态栏的大小和可见性状态。 跟踪工具栏的大小、可见性状态及其位置(未停靠、停靠;如果停靠,它停靠在哪一边的框架)。 处理WM_GETMINMAXINFO消息和计算帧大小有请求的客户端视图大小基于从前面的点的信息。 现在您已经知道了代码必须做什么,接下来我将展示代码本身。代码包含了大量的注释,应该是不言自明的,但是我可能还会提供更多的见解。首先,下面是类声明:收缩,复制Code
/* * class CMinMaxLogic * * It is used with the class CMinMaxFrame. Its purpose is to isolate * everything that is not related to MFC to ease an eventual porting * to another framework (ie.: WTL). * * Note: This class assumes that the associated frame has a menu and the * following Window Styles: * * - WS_BORDER * - WS_CAPTION * - WS_THICKFRAME * * This condition should always be met since the MFC AppWizard * generated code is using WS_OVERLAPPEDWINDOW that includes all 3 styles * to create the frame window. */ class CMinMaxLogic { public: CMinMaxLogic(LONG x, LONG y); ~CMinMaxLogic(void); /********************************************************* * * Name : setClientMin * * Purpose : Compute the minimum frame size * from the provided minimum client * area size. It is called at construction * and can be recalled anytime * by the user. * * Parameters: * x (LONG) Minimum client horizontal size. * y (LONG) Minumum client vertical size. * * Return value : None. * ********************************************************/ void setClientMin(LONG x, LONG y ); /******************************************************** * * Name : OnGetMinMaxInfo * * Purpose : Set the minimum size * to the minimum frame size and make * adjustments based on the toolbar * and status bar visibility * state and their sizes. * * Parameters: * lpMMI (MINMAXINFO FAR*) MinMax info * structure pointer. * * Return value : None. * *******************************************************/ void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI); BOOL m_sbVisible; /* Status bar visibility */ LONG m_sbHeight; /* Status bar height */ BOOL m_tbVisible; /* Toolbar visibility */ int m_tbPos; /* Toolbar position (left, right, top, bottom) */ int m_tbSize; /* Toolbar size */ private: LONG m_cxMin; /* Minimum client size */ LONG m_cyMin; LONG m_fyMin; /* Minimum frame size that includes borders, the frame, the toolbar */ LONG m_fxMin; /* and the status bar to have a client area of m_cxMin*m_cyMin */ }; #define DEFAULTMINCLIENTSIZE 350 class CMinMaxFrame : public CFrameWnd { public: CMinMaxFrame( LONG minX = DEFAULTMINCLIENTSIZE, LONG minY = DEFAULTMINCLIENTSIZE ); /******************************************************** * * Name : setClientMin * * Purpose : Recompute the minimum frame size * from the newly provided minimum * client area size. It can be called * anytime by the user. * * Parameters: * x (LONG) Minimum client horizontal size. * y (LONG) Minumum client vertical size. * * Return value : None. * *******************************************************/ void setClientMin(LONG x, LONG y ) { m_MinMaxLogic.setClientMin(x,y); } /******************************************************** * * Name : setToolBar * * Purpose : Register the toolbar to monitor * for adjusting the minimum frame * size to respect the requested * the minimum client area size. * * Note : Currently only 1 toolbar * is supported but more could be * supported with the help of a toolbar list. * * Parameters: * pTB (CToolBar *) Toolbar to register. * * Return value : None. * *********************************************************/ void setToolBar( CToolBar *pTB ) { m_pTB = pTB; if( pTB ) { m_MinMaxLogic.m_tbPos = TBFLOAT; } else { m_MinMaxLogic.m_tbPos = TBNOTCREATED; } } /********************************************************** * * Name : setStatusBar * * Purpose : Register the status bar to monitor * for adjusting the minimum * frame size to respect the requested * the minimum client area * size. * * Parameters: * pST (CStatusBar *) Status bar to register. * * Return value : None. * *********************************************************/ void setStatusBar( CStatusBar *pST ) { // Compute the status bar height if( pST ) { m_MinMaxLogic.m_sbHeight = pST->CalcFixedLayout(TRUE,TRUE).cy; } else { m_MinMaxLogic.m_sbHeight = 0; } } // Overrides /********************************************************** * * Name : RecalcLayout * * Purpose : This function is called * by the MFC framework whenever a * toolbar status is changing * (is attached or detached to/from * the frame). It is used as * a hook to maintain this class * internal state concerning * the toolbar position and size. * It should not be called directly. * * Parameters: * bNotify (BOOL) Not used. * * Return value : None. * *********************************************************/ virtual void RecalcLayout(BOOL bNotify = TRUE); protected: afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI); afx_msg BOOL OnBarCheck(UINT nID); DECLARE_MESSAGE_MAP() private: CMinMaxLogic m_MinMaxLogic; CToolBar *m_pTB; // TB Functions void triggerGetMinMaxInfoMsg(void); int getTBSize(int pos); int findDockSide(void); };
第一个任务(跟踪状态栏大小和可见性状态)是最简单的任务,所以让我们先去掉这个。由于状态栏的垂直大小通常不会改变,所以所需要的就是通过调用CStatusBar::CalcFixedLayout()来存储它的值。这是在CMinMaxFrame::setStatusBar():Hide 收缩,复制Code
/********************************************************** * * Name : setStatusBar * * Purpose : Register the status bar * to monitor for adjusting the minimum * frame size to respect the requested * the minimum client area size. * * Parameters: * pST (CStatusBar *) Status bar to register. * * Return value : None. * *********************************************************/ void setStatusBar( CStatusBar *pST ) { // Compute the status bar height if( pST ) { m_MinMaxLogic.m_sbHeight = pST->CalcFixedLayout(TRUE,TRUE).cy; } else { m_MinMaxLogic.m_sbHeight = 0; } }
能见度年代泰特是被处理视图菜单项ID_VIEW_STATUS_BAR收购。这样做是在CMinMaxFrame: OnBarCheck():隐藏,收缩,复制Code
/* * CMinMaxFrame::OnBarCheck function * * Purpose : MFC defined message handler. It is called whenever a toolbar * or a status bar visibility state change. It is used to trigger * a WM_GETMINMAXINFO since the minimum frame size to maintain a * minimum client area size has changed. */ BOOL CMinMaxFrame::OnBarCheck(UINT nID) { BOOL res = CFrameWnd::OnBarCheck(nID); // TODO: Add your command handler code here if( nID == ID_VIEW_STATUS_BAR ) { m_MinMaxLogic.m_sbVisible = !m_MinMaxLogic.m_sbVisible; if( m_MinMaxLogic.m_sbVisible ) { triggerGetMinMaxInfoMsg(); } } else if( nID == ID_VIEW_TOOLBAR ) { m_MinMaxLogic.m_tbVisible = !m_MinMaxLogic.m_tbVisible; if( m_MinMaxLogic.m_tbVisible ) { triggerGetMinMaxInfoMsg(); } } return res; }
相同的功能也可以用于跟踪工具栏可见性状态。这里是一个假设。代码假设在启动时,酒吧是可见的,并不总是如此。有一天这方面应该改进。如果一个酒吧变得可见,框架必须重新计算最小大小,这就是triggerGetMinMaxInfoMsg()所做的:隐藏,复制Code
/* * CMinMaxFrame::triggerGetMinMaxInfoMsg function */ void CMinMaxFrame::triggerGetMinMaxInfoMsg() { /* * Trigger a WM_MINMAXINFO message by calling the function MoveWindow() * with the current frame size. The purpose of generating a call to the * WM_GETMINMAXINFO handler is to verify that the new client area size * still respect the minimum size. */ RECT wRect; GetWindowRect(&wRect); MoveWindow(&wRect); }
现在,最难的部分就是跟踪工具栏的位置和大小。即使它不是记录,CFrameWnd虚函数RecalcLayout()每次工具栏状态改变。CMinMaxFrame使用这方面的知识得到通知,当这一切发生的时候:隐藏,收缩,复制Code
/* * CMinMaxFrame::RecalcLayout function * * Purpose : This function is called by the MFC framework whenever a * toolbar status is changing (is attached or detached to/from * the frame). It is used as a hook to maintain this class * internal state concerning the toolbar position and size. * It should not be called directly. */ void CMinMaxFrame::RecalcLayout(BOOL bNotify) { CFrameWnd::RecalcLayout(bNotify); // TODO: Add your specialized code here and/or call the base class if( m_MinMaxLogic.m_tbPos != TBNOTCREATED ) { if( !m_pTB->IsFloating() ) { int newPos = findDockSide(); if( m_MinMaxLogic.m_tbPos != newPos ) { m_MinMaxLogic.m_tbPos = newPos; m_MinMaxLogic.m_tbSize = getTBSize(m_MinMaxLogic.m_tbPos); triggerGetMinMaxInfoMsg(); } } else { m_MinMaxLogic.m_tbPos = TBFLOAT; m_MinMaxLogic.m_tbSize = 0; } } } /* * CMinMaxFrame::findDockSide function * * Note: This function is using AFXPRIV. It might not be working anymore * with a future MFC version. */ #include "afxpriv.h" int CMinMaxFrame::findDockSide() { // dwDockBarMap static const DWORD dwDockBarMap[4] = { AFX_IDW_DOCKBAR_TOP, AFX_IDW_DOCKBAR_BOTTOM, AFX_IDW_DOCKBAR_LEFT, AFX_IDW_DOCKBAR_RIGHT }; int res = TBFLOAT; for( int i = 0; i < 4; i++ ) { CDockBar *pDock = (CDockBar *)GetControlBar(dwDockBarMap[i]); if( pDock != NULL ) { if( pDock->FindBar(m_pTB) != -1 ) { res = i; break; } } } return res; } /* * CMinMaxFrame::getTBSize function * * Purpose : Returns the horizontal or the vertical toolbar size based on the * toolbar position. */ int CMinMaxFrame::getTBSize(int pos) { int res; CSize cbSize = m_pTB->CalcFixedLayout(FALSE, (pos==TBTOP||pos==TBBOTTOM)?TRUE:FALSE); if( pos == TBTOP || pos == TBBOTTOM ) { res = cbSize.cy; } else { res = cbSize.cx; } return res; }
有一个潜在的问题在函数CMinMaxFrame:: findDockSize()。它是函数使用一个非记录函数和一个私有类。这个解决方案测试了MFC 6和MFC 7但没有保证,它将继续与MFC的未来版本。我还是找一个“官方”的方式来执行这个任务,但我不知道另一个方法。CFrameWnd包含框架的每一方的一个CDockBar对象,这些对象,MFC知道工具栏的位置。顺便说一句,您可能想知道我是如何的知识完成这个噱头。我从这本书MFC内部得到了这个信息。当然,你可以只深入MFC源代码和找到自己的一切,但是有一本书,这次事件凸显了MFC是如何工作的非常重要的东西是一个很大的节省时间的。你应该认真考虑有这本书。,这或许是一种祝福,当你试着用MFC实现的东西,似乎没有一个明显的方法。 演示程序 本节的目的是使用CMinMaxFrame类的秘诀。演示程序只是一个普通MFC向导生成的程序,使用CMinMaxFrame已被修改。以下是所需的步骤使用CMinMaxFrame: 编辑你的框架类头文件和CPP文件替换每个CFrameWnd CMinMaxFrame发生。 叫CMinMaxFrame:: setToolBar()和CMinMaxFrame:: setStatusBar在OnCreate()()处理程序一旦创建了酒吧。 指定你的客户视图的最小大小CMinMaxFrame构造函数,或者调用函数CMinMaxFrame: setClientMin()在任何你想要的。 这它!它就是这么简单。 局限性和改进的建议 即使我很满意最终的结果,这段代码还不完美。这是一个可以做的事情列表: 支持多个工具栏列表。 支持最大大小。 覆盖PreCreateWindow()函数来确保这三个强制性窗口风格标志是永远存在的。 使用MFC记录特性与当前与未来的MFC版本解决方案可能不再工作了。 当我写这篇文章,心里面有两个目标。首先,帮助我的程序员,遇到同样的问题。其次,我希望能收到来自你的反馈,如果你找到方法来改进代码。如果你这样做,我将会更新这篇文章与你的改进。 结论 这就是它!我希望你喜欢这篇文章,如果你做了,发现它有用,请花几秒钟排名。你可以这样做,在文章的底部。我也邀请你访问我的网站,以检查任何更新这篇文章。 参考书目 ovidiucucu,如何防止一个可调整大小的窗口小于……? 乔治牧羊人,温格,MFC内部,艾迪生韦斯利,1996年。 杰夫•Prosise与MFC编程窗口,第二版微软出版社,1999年。 查尔斯的作品、编程窗口,第五版,微软出版社,1999年。 历史 02-13-2006 原来的文章。 本文转载于:http://www.diyabc.com/frontweb/news7254.html