堆叠Windows控制教程
介绍 从头开始开发自定义控件通常是不必要的,因为标准工具集非常全面,如果还不够,子类化或所有者自己绘制的风格就可以完成这项工作。这一点很重要,不容忽视。当从头开始开发自定义控件时,结果很有可能会低于标准。 也就是说,有一些控件完全丢失了,如果我们想在应用程序中部署它们,除了凭空构建它们之外,没有其他解决方案。其中一个例子就是Spybot或Outlook使用的“堆叠窗口控件”(或者随便叫什么名字)。因为它不在标准控件中,而且它是一个有趣的练习,所以本教程解释了如何一步一步地开发这种类型的控件。 本教程的目标读者是新手程序员,在开始之前,我想建议您不要阅读本文,而是尝试自己开发控件。虽然它可能看起来令人生畏,或者您可能不知道从哪里开始,但它并不像您可能认为的那么难。试一试,看看你能走多远,然后回来看看我要说什么。提示:这一切都是关于窗口的大小调整和重新定位,没有别的。 要完成什么 目标是一个“堆叠窗口控件”。就是这样。它将尽可能地通用,并将说明如何组装此类控件。 热心的读者可能想知道,我在编写演示项目的同时也编写了本教程。下面的说明、解释和代码相当于上面屏幕截图(准确地说是左边的那个)中堆叠窗口控件的开发。 继续编码。 循序渐进的过程 项目启动 设置很简单。创建一个新的基于对话框的项目,并将警告级别设置为4(项目设置,C/ c++选项卡)。四级将确保我们注意到任何可疑的信息,这样我们就可以决定如何处理“信息警告,在大多数情况下,可以安全地忽略”(来自文档)。 让我们开始处理控件。创建一个新的名为CStackedWndCtrl的MFC类,它使用CStatic作为基类。 在资源编辑器中,添加一个ID为IDC_SWC的图片控件。保留默认的框架为类型,黑色为颜色。 使用MFC ClassWizard,向IDC_SWC添加一个名为m_StackedWndCtrl的成员变量,确保选择Control作为类别,CStackedWndCtrl作为变量类型。 在单击OK时,一个消息框警告我们确保在对话框代码中包含了类CStackedWndCtrl的头文件。如果你还没有做过,现在就做。 数据结构 任何类型的控件的主干都是一个数据结构,用于保存将要显示的信息。 会显示什么呢?该控件由窗格组成,每个窗格包含两个窗口,一个rubric窗口和一个content窗口。下图说明了这个概念。 该控件的机制要求一次只显示一个窗格的内容窗口。单击窗格的红字窗口将触发其相关内容窗口的显示,还将隐藏当前显示的窗格的内容窗口。 因此,该数据结构将包含一对指向CWnd对象的指针和一个布尔标志,用于指示是显示还是隐藏窗格的内容窗口。不需要其他东西。隐藏,复制Code
#include <afxtempl.h> class CStackedWndCtrl : public CStatic { .... .... // Attributes protected: typedef struct { CWnd* m_pwndRubric; CWnd* m_pwndContent; BOOL m_bOpen; } TDS_PANE, *PTDS_PANE; CArray<PTDS_PANE, PTDS_PANE> m_arrPanes; .... .... }
数组是存储、检索和使用这些结构的一种方便而充分的方法。请记住,为了使用数组模板,我们需要包括适当的标题。 下一个任务是编写一个公共方法,该方法允许我们向控件添加窗格。什么也没有做。我们复制作为参数传递的窗口对象的指针,并将新窗格设置为所显示的窗格。隐藏,收缩,复制Code
int CStackedWndCtrl::AddPane( CWnd* pwndRubric, CWnd* pwndContent ) { // Hide whatever pane's content window is currently shown // We will always show the content window of the last pane added for( int i = 0; i < m_arrPanes.GetSize(); i++ ) if( m_arrPanes[ i ]->m_bOpen ) m_arrPanes[ i ]->m_bOpen = FALSE; // Create a new pane structure PTDS_PANE pPane = new TDS_PANE; if( pPane == NULL ) { AfxMessageBox( "Failed to add a new pane to" " the stack.\n\nOut of memory." ); return -1; } // Copy the pointers to the rubric and content windows // Also, set this pane as open pPane->m_pwndRubric = pwndRubric; pPane->m_pwndContent = pwndContent; pPane->m_bOpen = TRUE; // Add the new pane to the end of the stack int iIndex = m_arrPanes.Add( pPane ); // Rearrange the stack RearrangeStack(); // Return the index of the new pane return iIndex; }
在我们考虑安排和显示窗格之前(如果您想测试代码,只需注释掉对RearrangeStack方法的调用),确保该结构在退出时被正确删除是非常重要的,以防止内存泄漏。我们在析构函数中执行这个任务,如下所示:复制Code
CStackedWndCtrl::~CStackedWndCtrl() { for( int i = 0; i < m_arrPanes.GetSize(); i++ ) { // Delete the rubric window m_arrPanes[ i ]->m_pwndRubric->DestroyWindow(); delete m_arrPanes[ i ]->m_pwndRubric; // Delete the content window m_arrPanes[ i ]->m_pwndContent->DestroyWindow(); delete m_arrPanes[ i ]->m_pwndContent; // Delete structure delete m_arrPanes[ i ]; } m_arrPanes.RemoveAll(); }
简单的东西。我们循环遍历窗格数组,销毁每个窗口,然后删除每个窗口对象,然后删除每个窗格对象,最后删除数组中的所有指针。 这个功能足以使CStackedWndCtrl类能够完成它的工作。我们可以添加窗格,当控件被销毁时,这些窗格将被正确地处理。 视觉魔术 恐怕一点也不。安排和显示控件的算法非常简单。 我们循环遍历窗格,通过一个预定的度量来抵消帧的顶部,m_iRubricHeight,它已经在演示中设置了一个默认值(可以自由地进行实验)。当我们点击的窗格是开放的,我们使用的数量标题左显示窗口,计算此窗格的内容窗口的尺寸。查看代码。隐藏,收缩,复制Code
void CStackedWndCtrl::RearrangeStack() { CRect rFrame; GetClientRect( &rFrame ); for( int i = 0; i < m_arrPanes.GetSize(); i++ ) { // Rubric windows are always visible m_arrPanes[ i ]->m_pwndRubric->SetWindowPos( NULL, 0, rFrame.top, rFrame.Width(), m_iRubricHeight, SWP_NOZORDER | SWP_SHOWWINDOW ); // Only the content window of the flagged pane is shown // All others are hidden if they aren't already if( m_arrPanes[ i ]->m_bOpen ) { // From the bottom of the frame, take off as many rubric // window's heights as there are left to display int iContentWndBottom = rFrame.bottom - ( ( m_arrPanes.GetSize() - i ) * m_iRubricHeight ); m_arrPanes[ i ]->m_pwndContent->SetWindowPos( NULL, 0, rFrame.top + m_iRubricHeight, rFrame.Width(), iContentWndBottom - rFrame.top, SWP_NOZORDER | SWP_SHOWWINDOW ); // The next rubric window will be placed right below // this pane's content window rFrame.top = iContentWndBottom; } else m_arrPanes[ i ]->m_pwndContent->ShowWindow( SW_HIDE ); // The top of the frame is offset by the height of a rubric window rFrame.top += m_iRubricHeight; } }
负责安排和显示控制。 现在让我们添加一个调用PreSubclassWindow摆脱周围的黑色框架控制。虽然它是有用的在使用资源编辑器时,它是不必要的,难看的,当应用程序运行。隐藏,复制Code
void CStackedWndCtrl::PreSubclassWindow() { // Remove the black frame and clip children to reduce flickering ModifyStyle( SS_BLACKFRAME, WS_CLIPCHILDREN ); CStatic::PreSubclassWindow(); }
我们也借此机会增加WS_CLIPCHILDREN国旗减少闪烁的调整控制时,这提醒了我…… …它总是一个好主意,以确保控制能够在必要时调整自己。在这种情况下,功能很容易实现。火Classwizard,弹出添加消息处理程序,RearrangeStack打电话。隐藏,复制Code
void CStackedWndCtrl::OnSize(UINT nType, int cx, int cy) { CStatic::OnSize(nType, cx, cy); RearrangeStack(); }
我们几乎已经完成了。如果你添加一些测试窗格、编译和运行;栈控制将显示所有窗口标题windows最后窗格的内容。 当然,控制不能响应用户点击标题窗口。我们还没有为它编写的代码。是我们的下一个和最后一个任务列表。 唯一的要求的窗口标题 只要我们控制而言,标题和内容窗口可以是任何类型的窗口。字面上。对话框、静态控件、列表框控件、树控件,日历控件,编辑/ richedit控件,通用的窗户,甚至自定义控件。如果我们能得到一个CWnd指针指向它,类CStackedWndCtrl将按预期工作。唯一的限制是常识,而不是一个技术问题。举个例子,一个组合框可以设置为标题或内容窗口,但其适当性相当可疑。 然而,有一个要求,它适用于标题窗口。点击时,必须告知其母(CStackedWndCtrl对象),这样窗口可以显示相关内容。我们将完成这个通过发送一条消息。 为简单起见,我将使用按钮标题窗口。毕竟,他们是最明智的选择。我们将得到一个从CButton类,并添加这个专门的功能。 那么,创建一个名为CTelltaleButton来自CButton类。添加以下消息定义它的头,一个消息处理程序= BN_CLICKED(反映消息)。隐藏,复制Code
// In TelltaleButton.h #define WM_RUBRIC_WND_CLICKED_ON ( WM_APP + 04100 ) // In TelltaleButton.cpp void CTelltaleButton::OnClicked() { GetParent()->SendMessage( WM_BUTTON_CLICKED, (WPARAM)this->m_hWnd ); }
将发送一个消息,其中包含标题窗口,按钮,自己处理。根据这些信息,母公司控制能够找出哪些标题窗口点击。 现在,我们在CStackedWndCtrl处理消息通过手动添加消息映射的方法如下:隐藏,收缩,复制Code
// In StackedWndCtrl.h #define WM_RUBRIC_WND_CLICKED_ON ( WM_APP + 04100 ) ... ... // Generated message map functions protected: //{{AFX_MSG(CStackedWndCtrl) afx_msg void OnSize(UINT nType, int cx, int cy); //}}AFX_MSG afx_msg LRESULT OnRubricWndClicked(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() // In StackedWndCtrl.cpp ... ... BEGIN_MESSAGE_MAP(CStackedWndCtrl, CStatic) //{{AFX_MSG_MAP(CStackedWndCtrl) ON_WM_SIZE() //}}AFX_MSG_MAP ON_MESSAGE(WM_RUBRIC_WND_CLICKED_ON, OnRubricWndClicked) END_MESSAGE_MAP() ... ... LRESULT CStackedWndCtrl::OnRubricWndClicked(WPARAM wParam, LPARAM /*lParam*/) { HWND hwndRubric = (HWND)wParam; BOOL bRearrange = FALSE; for( int i = 0; i < m_arrPanes.GetSize(); i++ ) if( m_arrPanes[ i ]->m_pwndRubric->m_hWnd == hwndRubric ) { // Rearrange the control only if a rubric window // other than the one belonging to the pane that // is currently open is clicked on if( m_arrPanes[ i ]->m_bOpen == FALSE ) { m_arrPanes[ i ]->m_bOpen = TRUE; bRearrange = TRUE; } } else m_arrPanes[ i ]->m_bOpen = FALSE; if( bRearrange ) RearrangeStack(); // In case the rubric window that has sent the message wants to know // if the control has been rearranged, return the flag return bRearrange; }
所有这一切都归结到遍历窗格,以便找到被点击的标题窗口。如果是不同的,属于当前打开面板,重新排列控制。 一些华而不实 因为CStackedWndCtrl非常灵活,可以用于其标题和内容窗口,很容易爵士乐。为了说明如何做到这一点,我已经包含在演示项目一个“普通”的控制和使用委员会的大卫。Calabro阴影按钮和Everaldo科埃略的图标。如您所见,通过检查代码演示,而不是一行代码在CStackedWndCtrl需要修改。,因为它应该。 我们的短旅程结束,我的朋友;我走这条路,你走。我希望我展示了你的风景,种子你的想象力,并且我们的安静的交易将会有利于你。 反馈 我的意图是提供一个教程,是编码显然,尽可能简单的理解和遵循。我相信有更好的解决方案,我在这里实现的功能。任何改进的建议,简化,或更好的解释代码是受欢迎的。 致谢 演示项目,我使用一个旧版本的CResizableDialog Paolo梅西纳时我已经喜欢写文章的代码项目。谢谢保罗。 另一个意大利的工作委员会的大卫。Calabro CButtonST,被用于演示项目。谢谢大卫。。 我用Everaldo科埃略的一些图标在演示项目。你可以找到更多他的作品。Everaldo表示感谢。 我还用丹造型的视觉检漏仪来检查内存的恶作剧。一个非常方便的工具,我推荐给所有人。谢谢丹。 最后,我想表达我的感激之情,每个人都分享,或可以自由地分享知识。一次又一次,我看到的人类同胞写文章,教程,帮助陌生人的论坛,和我谦卑,出于他们的慷慨。很高兴能够回馈。 本文转载于:http://www.diyabc.com/frontweb/news4969.html