1. 程式人生 > 實用技巧 >堆疊Windows控制教程

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