(本文發表於VCKBase,可從如下鏈接獲取源代碼:http://www.vckbase.com/code/downcode.asp?id=3110歡迎討論)
很多MFC程序都用到了屬性表和屬性頁來實現選項設置的界面,但是MFC本身提供的屬性表頁功能有限,一些新軟件都實現了自己定義的屬性頁。MFC原始的屬性頁是通過CTabCtrl進行切換控制的,這裏給出了一種現在較爲常見的用CListCtrl進行頁面切換的屬性頁的方法,並且對對列表控件進行了重繪。 CMyPropertySheet是一個從CPropertySheet派生而來的類,因此仍然可以使用MFC CPropertySheet的諸多特性,具體使用方法稍後我會詳細說明。
該屬性表的實現效果如下:
一、使用
CMyPropertySheet的使用方法與MFC的CPropertySheet類似,首先要在程序中創建兩個屬性頁,也就是兩個CPropertyPage的派生對象。然後將MyPropertySheet.cpp 和 MyPropertySheet.h添加至工程,在程序的視圖類頭文件中(假定是個SDI程序)將CMyPropertySheet的頭文件包含進來
#include “MyPropertySheet.h”
在資源視圖裏設置一個新的菜單項“選項”(放在哪兒隨你) 用ClassWizard添加響應函數,在該函數裏添加如下代碼創建一個屬性表對象myPS
CMyPropertySheet myPS;
然後向屬性表添加兩個屬性頁。
myPS.AddPage(&m_page1); myPS.AddPage(&m_page2);
接下來要添加屬性頁的圖標,該圖標會在對應列表項以及屬性頁的標題上顯示,注意這裏添加的順序要與屬性頁的添加順序保持一致。
myPS.AddIcon(IDI_GLOBAL);
myPS.AddIcon(IDI_ADDITION);
最後創建並顯示該屬性頁。
myPS.DoModal();
剩下的工作就跟一般屬性表完全一樣了。
CMyPropertySheet類提供如下自定義函數,可以對屬性表的外觀進行設置。
SetSepratorColor,SetCaptionColor與SetSelectedColor都接受一個類型爲COLORREF的參數,分別用以設置列表分隔線,屬性頁標題以及列表選擇項背景的顏色。
SetListFont設置列表的字體。
讀者也可以根據自己的需要對其進行擴充。
二、實現邏輯
MFC原來的屬性頁是由TabCtrl控制的,而且屬性頁的大小已經與屬性表按比例設置好,因此,要實現如圖一所示的屬性頁,我們有如下幾步工作需要做:
1. 對屬性頁原來的TabCtrl進行隱藏。
2. 調整屬性表的大小,將屬性頁移至屬性表右側,以容納列表控件。
3. 獲得TabCtrl的矩形,根據該矩形畫屬性頁標題。
4. 初始化列表控件內容,調整列表項高度。
5. 響應列表的NM_CLICK事件,根據得到的點擊項ID進行屬性頁的切換。
其中調整尺寸的工作必須在OnInitDialog函數中進行。
BOOL CMyPropertySheet::OnInitDialog() { BOOL bResult = CPropertySheet::OnInitDialog(); //計算屬性頁的矩形,擴大屬性表並將屬性頁其移至右側 CRect rect, rectPage, rectTab; GetPage(0)->GetWindowRect(&rectPage); GetWindowRect(&rect); rect.right += 150; int nWidth = rectPage.Width(); rectPage.right = rect.right - 20; rectPage.left = rect.right - nWidth; ScreenToClient(&rectPage); m_rectPage = rectPage; MoveWindow(&rect); GetPage(0)->MoveWindow(&rectPage); //隱藏屬性頁原來的TabControl CTabCtrl *pTab = GetTabControl() ; pTab->GetWindowRect(&rectTab); ScreenToClient(&rectTab); if(!pTab->ShowWindow(SW_HIDE)) return FALSE; //創建列表控件並用一個CImageList對象與之關聯 if(!m_wndList.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER , CRect(10 ,rectTab.top,150,rectPage.bottom ),this,0xFFFF)) return FALSE; m_wndList.SetExtendedStyle(LVS_EX_FULLROWSELECT); m_wndList.SetImageList(&m_imgList, LVSIL_SMALL); InitList(); //設置行高度 CFont font; font.CreatePointFont(240,_T("宋體")); m_wndList.SetFont(&font); CString strCaption; GetPage(0)->GetWindowText(strCaption); _tcscpy(m_szCaption, strCaption.GetBuffer(strCaption.GetLength())); return bResult; }
屬性頁的切換:
void CMyPropertySheet::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult) { LPNMITEMACTIVATE lpItem = reinterpret_cast(pNMHDR); m_nSelectedItem = lpItem->iItem ; if (lpItem->iItem >= 0 && lpItem->iItem < m_wndList.GetItemCount()) { m_nSelectedItem = lpItem->iItem; CString strCaption = m_wndList.GetItemText(lpItem->iItem,0); _tcscpy(m_szCaption, strCaption); SetActivePage(m_nSelectedItem); Invalidate(); GetPage(m_nSelectedItem)->MoveWindow(&m_rectPage); m_wndList.SetFocus(); } }
三、總結
關於列表控件自繪的問題在這裏不做詳細討論,可以參考源代碼裏面OnNMCustomDraw的部分以及MSDN上的相關資料。對屬性頁的修改還有很多種方法和很多種方式,比如還可以用樹型控件進行控制,這裏提供的方法也可以做一般意義上的推廣 ,並不難實現其它方式的控制。程序在Visual C++ 2005 下編譯通過。
四、源碼
MyPropertySheet.h
#ifndef MYPROPERTYSHEET_H
#define MYPROPERTYSHEET_H
#pragma once
#pragma warning(disable : 4996)
// CMyPropertySheet
class CMyPropertySheet : public CPropertySheet
...{
DECLARE_DYNAMIC(CMyPropertySheet)
private:
void InitList(void);
void DrawCaption(CDC * pDC, const COLORREF clrCaption);
void DrawGradientLine(CDC* pDC, COLORREF clrLine, POINT ptStart, POINT ptEnd);
public:
CMyPropertySheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
CMyPropertySheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
virtual ~CMyPropertySheet();
protected:
CImageList m_imgList;
CListCtrl m_wndList;
int m_nSelectedItem;
//列表的字體,大小不能超過列表項的高度
CFont m_ftList;
COLORREF m_clrTextBkSele ;
COLORREF m_clrSeprator;
COLORREF m_clrCaption;
COLORREF m_clrSelected;
LPTSTR m_szCaption;
CRect m_rectPage;
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnNMCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
int AddIcon(HICON icon);
//設置屬性頁標題的初始顏色
void SetCaptionColor(const COLORREF clrCaption);
//設置列表控件分隔線的初始顏色
void SetSepratorColor(const COLORREF clrSeprator);
//設置列表控件某項被選擇時的背景色
void SetSelectedColor(const COLORREF clrSelected);
//設置列表控件字體
void SetListFont(CFont * pFont);
};
#endif
MyPropertySheet.cpp
//
#include "stdafx.h"
#include "MyPropertySheet.h"
IMPLEMENT_DYNAMIC(CMyPropertySheet, CPropertySheet)
CMyPropertySheet::CMyPropertySheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage), m_nSelectedItem(0), m_clrTextBkSele(RGB(0,132,255)),m_clrSeprator(RGB(0,132,255)),
m_clrCaption(RGB(92,132,255))
...{
m_szCaption = new TCHAR[128];
//默認16*16,32位色圖標
m_imgList.Create(16,16,ILC_COLOR32, 0, 20);
m_ftList.CreatePointFont(90,_T("宋體"));
}
CMyPropertySheet::CMyPropertySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage), m_nSelectedItem(0), m_clrTextBkSele(RGB(0,132,255)),m_clrSeprator(RGB(0,132,255)),
m_clrCaption(RGB(92,132,255))
...{
m_szCaption = new TCHAR[128];
m_imgList.Create(16,16,ILC_COLOR32, 0, 20);
m_ftList.CreatePointFont(90,_T("宋體"));
}
CMyPropertySheet::~CMyPropertySheet()
...{
delete [] m_szCaption;
}
BEGIN_MESSAGE_MAP(CMyPropertySheet, CPropertySheet)
ON_WM_PAINT()
ON_NOTIFY(NM_CLICK, 0xFFFF, OnNMClick)
ON_NOTIFY(NM_CUSTOMDRAW,0xFFFF, OnNMCustomDraw)
END_MESSAGE_MAP()
// CMyPropertySheet 消息處理程序
BOOL CMyPropertySheet::OnInitDialog()
...{
BOOL bResult = CPropertySheet::OnInitDialog();
//計算屬性頁的矩形,擴大屬性表並將屬性頁其移至右側
CRect rect, rectPage, rectTab;
GetPage(0)->GetWindowRect(&rectPage);
GetWindowRect(&rect);
rect.right += 150;
int nWidth = rectPage.Width();
rectPage.right = rect.right - 20;
rectPage.left = rect.right - nWidth;
ScreenToClient(&rectPage);
m_rectPage = rectPage;
MoveWindow(&rect);
GetPage(0)->MoveWindow(&rectPage);
//隱藏屬性頁原來的TabControl
CTabCtrl *pTab = GetTabControl() ;
pTab->GetWindowRect(&rectTab);
ScreenToClient(&rectTab);
if(!pTab->ShowWindow(SW_HIDE))
return FALSE;
//創建列表控件並用一個CImageList對象與之關聯
if(!m_wndList.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER , CRect(10 ,rectTab.top,150,rectPage.bottom ),this,0xFFFF))
return FALSE;
m_wndList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
m_wndList.SetImageList(&m_imgList, LVSIL_SMALL);
InitList();
//這一步是爲了擴大行高度
CFont font;
font.CreatePointFont(240,_T("宋體"));
m_wndList.SetFont(&font);
CString strCaption;
GetPage(0)->GetWindowText(strCaption);
_tcscpy(m_szCaption, strCaption.GetBuffer(strCaption.GetLength()));
return bResult;
}
void CMyPropertySheet::OnPaint()
...{
CPaintDC dc(this); // device context for painting
CRect rectList,rectPage;
m_wndList.GetWindowRect(&rectList);
GetPage(0)->GetWindowRect(&rectPage);
ScreenToClient(&rectPage);
ScreenToClient(&rectList);
rectList.left = rectList.left -1;
rectList.right = rectList.right + 1;
rectList.top = rectList.top - 1;
rectList.bottom = rectList.bottom + 1;
rectPage.left -= 1;
rectPage.right += 1;
rectPage.top -= 1;
rectPage.bottom += 1;
CBrush brush(RGB(141,141,141));
dc.FrameRect(&rectList,&brush);
dc.FrameRect(&rectPage, &brush);
DrawCaption(&dc, m_clrCaption);
}
void CMyPropertySheet::DrawCaption(CDC * pDC, const COLORREF clrCaption)
...{
CDC dcBuf;
dcBuf.CreateCompatibleDC(pDC);
CBitmap bmp;
CRect rectCap, rectList, rectPage,rectSheet;
m_wndList.GetWindowRect(&rectList);
ScreenToClient(&rectList);
GetPage(0)->GetWindowRect(&rectPage);
ScreenToClient(&rectPage);
rectCap = rectPage;
rectCap.top = rectList.top -1;
rectCap.left -= 1;
rectCap.right += 1;
rectCap.bottom =rectPage.top -1 ;
GetClientRect(&rectSheet);
rectCap.bottom +=1;
bmp.CreateCompatibleBitmap(pDC, rectCap.right , rectSheet.Height());
dcBuf.SelectObject(bmp);
//起始顏色
int clrBBase = clrCaption>>16 & 0x000000FF;
int clrGBase = clrCaption>>8 & 0x000000FF;
int clrRBase = clrCaption & 0x000000FF;
//過渡中顏色
int clrRCurr = clrRBase;
int clrGCurr = clrGBase;
int clrBCurr = clrBBase;
//色彩增量
const double nRClrInc = (double)(255 - clrRBase) / (double)rectCap.Width() ;
const double nGClrInc = (double)(255 - clrGBase) / (double)rectCap.Width() ;
const double nBClrInc = (double)(255 - clrBBase) / (double)rectCap.Width() ;
//畫漸進色標題
CRect drawRect = rectCap;
for (int nLeft = rectCap.left, nRight = rectCap.left + 1 ; nLeft < rectCap.right; nLeft ++, nRight ++)
...{
drawRect.left = nLeft;
drawRect.right = nRight;
dcBuf.FillSolidRect(&drawRect, RGB(clrRCurr,clrGCurr,clrBCurr));
clrRCurr = (int)((nLeft - rectCap.left) * nRClrInc + clrRBase);
clrGCurr = (int)((nLeft - rectCap.left) * nGClrInc + clrGBase);
clrBCurr = (int)((nLeft - rectCap.left) * nBClrInc + clrBBase);
}
dcBuf.SetBkMode(TRANSPARENT);
CFont font;
font.CreatePointFont(110,_T("宋體"),pDC);
dcBuf.SelectObject(&font);
dcBuf.SetTextColor(RGB(0,0,0));
dcBuf.TextOut(rectCap.left + 26, rectCap.top +5,m_szCaption, (int)_tcslen(m_szCaption));
dcBuf.SetTextColor(RGB(255,255,255));
dcBuf.TextOut(rectCap.left + 25, rectCap.top + 4, m_szCaption, (int)_tcslen(m_szCaption));
::DrawIconEx(dcBuf,rectCap.left + 4, rectCap.top + 3, m_imgList.ExtractIcon(m_nSelectedItem),16, 16, NULL,NULL, DI_NORMAL);
pDC->BitBlt(rectCap.left,rectCap.top,rectCap.Width()+rectCap.Width(),rectCap.Height(),&dcBuf,rectCap.left,rectCap.top,SRCCOPY);
}
void CMyPropertySheet::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
...{
LPNMITEMACTIVATE lpItem = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
m_nSelectedItem = lpItem->iItem ;
if (lpItem->iItem >= 0 && lpItem->iItem < m_wndList.GetItemCount())
...{
m_nSelectedItem = lpItem->iItem;
CString strCaption = m_wndList.GetItemText(lpItem->iItem,0);
_tcscpy(m_szCaption, strCaption);
SetActivePage(m_nSelectedItem);
Invalidate();
GetPage(m_nSelectedItem)->MoveWindow(&m_rectPage);
m_wndList.SetFocus();
}
}
void CMyPropertySheet::InitList(void)
...{
LVITEM lvi;
::ZeroMemory(&lvi, sizeof(lvi));
CHeaderCtrl *pHeader = m_wndList.GetHeaderCtrl();
pHeader->ShowWindow(SW_HIDE);
CRect rectList;
m_wndList.GetWindowRect(&rectList);
ScreenToClient(&rectList);
//報表頭不會顯示,但是是必需的
m_wndList.InsertColumn(0,_T("設置"), LVCFMT_CENTER, rectList.Width(), 0);
CString strCaption;
CTabCtrl *pTab = GetTabControl();
TCITEM tci;
::ZeroMemory(&tci,sizeof(tci));
tci.mask = TCIF_TEXT;
tci.cchTextMax = 256;
TCHAR szBuf[256] = ...{0};
tci.pszText = szBuf;
for (int idxPge = 0; idxPge < GetPageCount(); idxPge ++)
...{
if(pTab->GetItem(idxPge, &tci))
...{
lvi.iItem = idxPge;
lvi.iSubItem = 0;
lvi.iImage = idxPge;
lvi.mask = LVIF_TEXT | LVIF_IMAGE;
lvi.pszText = tci.pszText ;
m_wndList.InsertItem(&lvi);
}
}
}
void CMyPropertySheet::OnNMCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
...{
LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
CRect rectRow, rectList;
m_wndList.GetWindowRect(&rectList);
ScreenToClient(&rectList);
m_wndList.GetItemRect(0, &rectRow, LVIR_BOUNDS);
int iItemHeight = rectRow.Height();
int iItemTop = rectRow.top ;
::SelectObject(pLVCD->nmcd.hdc, m_ftList);
switch (pLVCD->nmcd.dwDrawStage)
...{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW;
break;
case (CDDS_ITEMPREPAINT | CDDS_SUBITEM):
...{
int iCol = pLVCD->iSubItem ;
int iRow = (int)pLVCD->nmcd.dwItemSpec;
CRect rectItem(pLVCD->nmcd.rc), rectIcon;
//計算每個子項的矩形
rectItem.top = iItemTop + iRow * iItemHeight;
rectItem.bottom = rectItem.top + iItemHeight;
rectItem.left +=3;
rectItem.right -=3;
if (iRow == 0)
rectItem.top +=3;
CDC *pDC = CDC::FromHandle(pLVCD->nmcd.hdc);
LOGFONT lf;
::ZeroMemory(&lf, sizeof(lf));
pDC->GetCurrentFont()->GetLogFont(&lf);
//獲得第圖標所在的矩形
m_wndList.GetSubItemRect(iRow,0, LVIR_ICON, rectIcon);
const COLORREF clrBlack = RGB(0,0,0);
const COLORREF clrWhite = RGB(255,255,255);
if ((pLVCD->nmcd.uItemState & (CDIS_FOCUS | CDIS_SELECTED)) == (CDIS_FOCUS | CDIS_SELECTED))
...{
pDC->FillSolidRect(&rectItem, m_clrTextBkSele);
pDC->SetTextColor(clrWhite);
pDC->TextOut(rectItem.left + rectIcon.Width() + 8, (iRow == 0?(rectItem.top - 3):rectItem.top) + (iItemHeight - abs(lf.lfHeight))/2, m_wndList.GetItemText(iRow, iCol), (int)_tcslen(m_wndList.GetItemText(iRow, iCol)));
::DrawIconEx(*pDC,rectIcon.left, rectIcon.top + (iItemHeight - 16) / 2,m_imgList.ExtractIcon(iRow),16,16,NULL,NULL,DI_NORMAL);
pDC->SetTextColor(clrBlack);
DrawGradientLine(pDC,m_clrSeprator,CPoint(rectItem.left, rectItem.bottom-1), CPoint(rectItem.right, rectItem.bottom-1));
}
else
...{
pDC->FillSolidRect(&rectItem, clrWhite);
pDC->TextOut(rectItem.left + rectIcon.Width() + 8, (iRow == 0?(rectItem.top - 3):rectItem.top) + (iItemHeight - abs(lf.lfHeight))/2, m_wndList.GetItemText(iRow, iCol), (int)_tcslen(m_wndList.GetItemText(iRow, iCol)));
::DrawIconEx(*pDC,rectIcon.left, rectIcon.top + (iItemHeight - 16) / 2, m_imgList.ExtractIcon(iRow),16,16,NULL,NULL,DI_NORMAL);
DrawGradientLine(pDC,m_clrSeprator,CPoint(rectItem.left, rectItem.bottom-1), CPoint(rectItem.right, rectItem.bottom-1));
}
*pResult = CDRF_SKIPDEFAULT;
break;
}
default:
*pResult = CDRF_SKIPDEFAULT;
break;
}
}
void CMyPropertySheet::DrawGradientLine(CDC* pDC, COLORREF clrLine, POINT ptStart, POINT ptEnd)
...{
//畫漸近線,從clrLine的顏色變化至白色
int clrBBase = clrLine>>16 & 0x000000FF;
int clrGBase = clrLine>>8 & 0x000000FF;
int clrRBase = clrLine & 0x000000FF;
int clrBCurr = 255;
int clrGCurr = 255;
int clrRCurr = 255;
double dRInc = (double)(255 - clrRBase) / (double)(abs(ptEnd.x - ptStart.x));
double dGInc = (double)(255 - clrGBase) / (double)(abs(ptEnd.x - ptStart.x));
double dBInc = (double)(255 - clrBBase) / (double)(abs(ptEnd.x - ptStart.x));
POINT ptCurr = ptStart;
for (;ptCurr.x < ptEnd.x;ptCurr.x ++)
...{
pDC->SetPixel(ptCurr.x, ptCurr.y -1,RGB(clrRCurr,clrGCurr,clrBCurr));
pDC->SetPixel(ptCurr, RGB(clrRCurr,clrGCurr,clrBCurr));
clrRCurr = clrRBase + (int)((ptCurr.x - ptStart.x) * dRInc);
clrGCurr = clrGBase + (int)((ptCurr.x - ptStart.x) * dGInc);
clrBCurr = clrBBase + (int)((ptCurr.x - ptStart.x) * dBInc);
}
}
int CMyPropertySheet::AddIcon(HICON icon)
...{
return m_imgList.Add(icon);
}
void CMyPropertySheet::SetCaptionColor(const COLORREF clrCaption)
...{
m_clrCaption = clrCaption;
}
void CMyPropertySheet::SetSepratorColor(const COLORREF clrSeprator)
...{
m_clrSeprator = clrSeprator;
}
void CMyPropertySheet::SetListFont(CFont * pFont)
...{
m_ftList.Attach(pFont->GetSafeHandle());
}
void CMyPropertySheet::SetSelectedColor(const COLORREF clrSelected)
...{
m_clrTextBkSele = clrSelected;
}