很多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
// MyPropertySheet.cpp : 实现文件
// MyPropertySheet.cpp : 实现文件 // #include "stdafx.h" #include "MyPropertySheet.h" // CMyPropertySheet static BOOL bMoved[4] = {FALSE,FALSE,FALSE,FALSE}; 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; }