堆叠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

posted @ 2020-08-09 11:32  Dincat  阅读(314)  评论(0编辑  收藏  举报