WTL體系結構(3) (轉)

amyz發表於2007-11-16
WTL體系結構(3) (轉)[@more@]

WTL體系結構

檢視

檢視視窗看起來顯得很簡單:

class CMyView : public CWindowImpl
{
public:
  DECLARE_WND_CLASS(NULL)
  BOOL PreTranslateMessage(MSG* pMsg)
  {
    pMsg;
  return FALSE;
  }
  BEGIN_MSG_MAP(CMyView)
  MESSAGE_HANDLER(WM_PAINT, OnPaint)
  END_MSG_MAP()
  LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&)
  {
  CPaintDC dc(m_hWnd);
  //TODO: Add your drawing code here
  return 0;
  }
};

上面是一個SDI的檢視類. 多執行緒SDI和MDI的檢視類在本質上也跟這個一樣,但他們沒有PreTranslateMessage()方法. SDI程式就是使用這個,趕在類處理訊息之前把訊息抓住. PreTranslateMessage()在SDI的框架類中的實現是,直接將訊息轉發給檢視類.

這裡顯示的檢視實際上沒有做什麼工作.你應該自己在OnPaint()函式中加入畫出文件內容的程式碼.如果需要支援輸入,如滑鼠的點選和鍵盤的按鍵,你應該加入相應訊息處理函式到類和對映中. 可以看到這個視窗是從CWindowImpl<>繼承下來的,如果你想讓它基於一個的話,就應該從定義在AtlCtrls.h中某個WTL類繼承.

如果想在基於CWindowImpl<>的類里加上捲軸,那麼你應該把基類換成CScrollWindowImpl<>,同時把訊息鏈給它:

class CMyView : public CScrollWindowImpl
{
public:
  typedef CScrollWindowImpl parent;
  BEGIN_MSG_MAP(CMyView)
  CHAIN_MSG_MAP(parent)
  END_MSG_MAP()
  void DoPaint(CDCHandle dc)
  {
  }
};

基類保證視窗具備捲軸,並提供捲軸訊息的預設處理.檢視類不再有WM_PAINT的處理函式,因為它已被CScrollWindowImpl<>處理.根據捲軸的位置,CScrollWindowImpl<>畫出檢視相對應的部分. 取而代之的是,在你的類裡實現DoPaint(),在這裡你需要畫出整個檢視.如果你想指定滾動的範圍,大小或起點,你需要加上處理WM_CREATE訊息的函式,把這些初始化程式碼放到裡邊.

正如我先前所提到的,框架視窗會改變檢視視窗的大小,以使它客戶區未被狀態條和工具條覆蓋的部分為檢視所填充. 在大多數情況下,這樣就夠了.但是當你想要一個具有 Explorer樣子的程式時,該怎麼辦呢? Windows Explorer的視窗包含了一個tree view 和一個list view,還有兩者之間的分割條. WTL的解決方案很簡單:使用splitter視窗!

為此你需要改變一下框架視窗,讓它建立splitter視窗的一個例項作為它的檢視. 例如, 在你的框架類裡有如下的資料成員:

CSplitterWindow m_view;
CTreeViewCtrl m_tree;
CListViewCtrl m_list;

你可以在OnCreate()建立一個splitter視窗:

// get the frame client rect, so that we set the splitter initial size
// and we can get the splitter bar in the centre
RECT rect;
GetClientRect(&rect);
m_hWndClient = m_view.Create(m_hWnd, rect,
  NULL, WS_CHILD | WS_VISIBLE);
m_tree.Create(m_view, rcDefault, NULL,
  WS_CHILD | WS_VISIBLE | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESAT,
  WS_EX_CLIENTEDGE);
m_list.Create(m_view, rcDefault,
  NULL, WS_CHILD | WS_VISIBLE | LVS_REPORT, WS_EX_CLIENTEDGE);
m_view.SetSplitterPanes(m_tree, m_list);
m_view.SetSplitterPos();

Splitter視窗如同一個檢視,將框架視窗作為它的父視窗. 在這段程式碼裡,我將框架視窗客戶區的實際大小傳給了splitter視窗. 我也可以在這裡使用 rcDefault,因為一旦框架視窗建立完成,框架視窗就會轉發WM_SIZE訊息給splitter. 這樣splitter可以馬上改變自身的大小來填充框架. 然而,當我準備使用不帶引數的SetSplitterPos(),把分割條設定於視窗中線時,出現了問題.Splitter視窗使用它的大小來決定中線的位置,由於rcDefault告訴視窗它的大小是0(因此中線的位置也是0),從而意味著分割條將出現在z最左邊,將左視窗隱藏了起來.

建立了splitter視窗後,你需要建立那些你想要分割的視窗了.它們將作為splitter視窗的子視窗被建立.最後你將這些子視窗透過SetSplitterPanes()加到splitter視窗中去,並確定分割條的位置所在.

UI Update

選單項可以被設定為有效或無效,可以帶check記號或著像radio按鈕一樣,在一組選單項中同時有且只有一個能被check.此外,選單項還可以帶圖示和文字. 所有的這些狀態都可以在執行時根據程式中的某個值進行改變.工具條在某種程度上可以看做是選單的易見形態,因為它們的按鈕可以個別地,或者作為一組的一部分被置成有效或無效,推入推出. UI update機制允許你指定哪些UI元件(UI element)的狀態可以在執行時改變. WTL使用如下的UI update對映來實現這一功能:


BEGIN_UPDATE_UI_MAP(CMainFrame)
  UPDATE_ELEMENT(ID_FILE_SAVERESULTS, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
  UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
  UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()

這個例子指出三個選單項在執行時有一個狀態需要顯示,其中的一個, ID_FILE_SAVERESULTS,還有一個工具條按鈕跟它相關聯. WTL透過建立一個陣列來儲存這些資訊.為此你需要完成兩方面的工作:

首先是UI元件的狀態. 如果是選單項, 你可以使用UIEnable()使能該選單項, UISetCheck()設定check記號, UISetText()改變選單的文字.如果是工具條按鈕,那麼你使用UIEnable()使能該按鈕, UISetCheck()或者UISetRadio()決定按鈕是推入還是推出.下邊的程式碼根據是否有文字被選中,來使能Cut選單項和工具條按鈕:

BOOL bed = GetSelected();
UIEnable(ID_EDIT_CUT, bSelected);

你可以把這樣的程式碼放入相應處理函式中(如一個選單項的狀態依賴於另一個選單項的動作,將它放入後者的處理函式中),或者放入OnIdle()方法,透過檢查某個類變數來決定元件的狀態.

其次是確定各個UI元件是否都被了,為此你需要CUpdateUI<>的某個方法將UI元件加入到列表中.主選單已被自動加入,但是其他的任何選單和所有的工具條必須分別透過呼叫UIAddMenuBar()UIAddToolBar()手動加入.

其他還有一堆事情要注意. 首先,設定了工具條的狀態後,使用UIUpdateToolBar()以使工具條狀態更新. 對於選單,你不需如此,因為子選單是動態生成的.UIUpdateMenuBar()這個方法也存在,但是它的作用是把選單恢復到初始狀態,如果你改變過某些項的文字,呼叫UIUpdateMenuBar()的結果可能不是你所期望的(因為選單項的文字會變成老的).

儘管還有一個方法UISetRadio(),但是還沒有一個把幾個選單項或者工具條按鈕當做radio按鈕組(也就是說,有一個而且只有一個被選中)的機制.如果你希望得到這樣效果,你必須自己編碼,不過它並不難.

(未完待續)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982758/,如需轉載,請註明出處,否則將追究法律責任。

相關文章