赞
踩
最终效果如图:
功能:
支持插入,删除,设置标签项
详细说明:
一:使用MFC应用程序向导,创建一个工程名为“tb”的基于对话框程序,用于演示通过创建自定义子窗口来开发一个标签控件的过程
二:在类视图中添加CWnd派生类,由空白窗口开发出一个标签控件,如果1-1所示
三:在建立好的自定义类头文件中(VcTab.h)中,添加一些成员变量和函数
#include <Afxtempl.h>
class CVcTab : public CWnd
{
// Construction
public:
struct SItem
{
CString szText; //每项文字
int nWidth; //每项宽度
int nImage; //每项图像索引
};
struct SLader //标签梯形的四个点
{
POINT m_TopLeft;
POINT m_BottomLeft;
POINT m_BottomRight;
POINT m_TopRight;
};
enum{POINT_CNT=4}; //标签形状点的个数
CArray<SItem,SItem> m_arr; //标签项的数据集合
CArray<SLader,SLader> m_lader; //标签项的梯形坐标
int m_nCurSel,m_nTrack; //选择状态和跟踪状态的项索引
CFont m_font; //文字字体
CFont m_fontSel; //选中标签字体
static const CPen m_pen; //选择状态的边框
static const COLORREF m_clNormal,m_clSel,m_clTrack;//三态标签项的背景色
static const COLORREF m_clText; //所有标签文本的颜色
POINT m_pt[POINT_CNT]; //选择的标签的梯形点
CRect m_rtSel; //选择的标签的矩形(以上边两点为边的大矩形)
CString m_szSel; //选择的标签的文字
public:
int GetTextWidth(CString szText); //根据文字和字体计算每项宽度
void GetFreeLab(CRect& rect); //获取Tab控件的的不包含标签项的剩余控件
int GetCurSel()const
{
return m_nCurSel;
}
//获取标签总数
int GetItemCount()const
{
return m_arr.GetSize();
}
//获取标签文本
CString GetItemText(int nItem)const
{
ASSERT(nItem<m_arr.GetSize());
return m_arr[nItem].szText;
}
//设置标签文本
void SetItemText(int nItem,CString szText)
{
ASSERT(nItem<m_arr.GetSize()&&nItem>=0);
m_arr[nItem].szText=szText;
}
//删除标签项
void DeleteItem(int nItem)
{
ASSERT(nItem < m_arr.GetSize()&&nItem>=0);
m_arr.RemoveAt(nItem);
}
//获取标签项的梯形点坐标
void GetLaderPoint(int nItem,SLader& slader)
{
ASSERT(nItem<m_lader.GetSize()&&nItem>=0);
slader=m_lader[nItem];
}
BOOL InsertItem(int nItem,LPCTSTR szItem);
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
void Register();
.............
}
四:在源文件中,编写注册和创建两个成员函数的代码
#define VC_TAB _T("__VC_TAB__") //自定义窗口类名
void CVcTab::Register()
{
WNDCLASS wc={0};
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.lpfnWndProc=::DefWindowProc;
wc.hInstance=AfxGetInstanceHandle();
wc.hIcon=NULL;
wc.hCursor=AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wc.hbrBackground=::GetSysColorBrush(COLOR_BTNSHADOW);
wc.lpszClassName=VC_TAB;
wc.lpszMenuName=NULL;
AfxRegisterClass(&wc);
}
BOOL CVcTab::Create(DWORD dwStyle, const RECT &rect, CWnd *pParentWnd, UINT nID)
{
static BOOL b=TRUE;
if(b)
{
//只执行一次
b=FALSE;
Register();
}
CFont* pFont=pParentWnd->GetFont();
if(!pFont)
{
HFONT hFont=(HFONT)::GetStockObject(DEFAULT_GUI_FONT);
pFont=CFont::FromHandle(hFont);
}
//从父窗口复制字体,如果获取失败则从系统字体复制,
//设置选中标签的字体
LOGFONT lf;
pFont->GetLogFont(&lf);
m_font.CreateFontIndirect(&lf);
lf.lfWeight=700;
m_fontSel.CreateFontIndirect(&lf);
return CWnd::Create(VC_TAB,NULL,dwStyle,rect,pParentWnd,nID);
}
五:在源文件中继续编写插入标签项的函数
int CVcTab::GetTextWidth(CString szText)
{
CClientDC dc(this);
dc.SelectObject(&m_font);
CSize size=dc.GetTextExtent(szText);
return size.cx;
}
BOOL CVcTab::InsertItem(int nItem, LPCTSTR szItem)
{
SItem item;
item.szText=szItem;
item.nImage=-1;//保留
item.nWidth=GetTextWidth(szItem)+15;
m_arr.InsertAt(nItem,item);
return TRUE;
}
六:在类视图中添加一些窗口显示与鼠标操作相关的消息映射函数
七:修改以上建立的所有消息映射函数的代码
#define DISTWIDTH 10 //标签梯形的上边和下边差的1/2
#define IDC_NEWLAB 2010 //修改标签项文本时创建的编辑框控件ID
const COLORREF CVcTab::m_clNormal
=GetSysColor(COLOR_BTNFACE),
CVcTab::m_clSel=GetSysColor(COLOR_WINDOW),
CVcTab::m_clTrack=GetSysColor(COLOR_GRADIENTINACTIVECAPTION),
CVcTab::m_clText=RGB(0,0,0);
const CPen CVcTab::m_pen(0,1,RGB(0,0,0)); //标签边框画笔
CVcTab::CVcTab()
{
m_nTrack=-1;
m_nCurSel=0;
}
BOOL CVcTab::OnEraseBkgnd(CDC* pDC)
{
//去除背景绘图可以减少闪烁
return TRUE;
}
void CVcTab::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
dc.SelectObject(m_font);
dc.SetBkMode(TRANSPARENT);
dc.SelectObject(m_pen);
dc.SetTextColor(m_clText);
CString szText;
CRect rect,rc;
GetClientRect(rc);
rect=rc;
rect.right=rect.left+DISTWIDTH;
int i=-1,nCount=GetItemCount(),nSel=GetCurSel();
while(++i<nCount)
{
rect.left=rect.right-DISTWIDTH;
rect.right=rect.right+m_arr[i].nWidth+DISTWIDTH;
//获取标签项梯形的四个点坐标
POINT pt[4]={
{rect.left,rect.top},
{rect.left+DISTWIDTH,rect.bottom},
{rect.right-DISTWIDTH,rect.bottom},
{rect.right,rect.top}
};
//保存标签项图形的四个点坐标
SLader slader;
slader.m_TopLeft=pt[0];
slader.m_BottomLeft=pt[1];
slader.m_BottomRight=pt[2];
slader.m_TopRight=pt[3];
m_lader.InsertAt(i,slader);
szText=GetItemText(i);
if(nSel!=i)
{
//跟踪态(加量)和正常态
CBrush br(i==m_nTrack?m_clTrack:m_clNormal);
CBrush* pOldBrush=dc.SelectObject(&br);
dc.Polygon(pt,sizeof(pt)/sizeof(pt[0]));
dc.DrawText(szText,szText.GetLength(),rect,
DT_SINGLELINE|DT_VCENTER|DT_CENTER);
dc.SelectObject(pOldBrush);
br.DeleteObject();
}
else
{
//选择态
CBrush br(m_clSel);
CBrush* pOldBr=dc.SelectObject(&br);
CFont* pOldFont=dc.SelectObject(&m_fontSel);
dc.Polygon(pt,sizeof(pt)/sizeof(pt[0]));
dc.DrawText(szText,szText.GetLength(),rect,
DT_SINGLELINE|DT_VCENTER|DT_CENTER);
dc.SelectObject(pOldFont);
dc.SelectObject(pOldBr);
for(int j=0;j<POINT_CNT;j++)
m_pt[j]=pt[j];
m_rtSel=rect;
m_szSel=szText;
}
rect.top=rc.top;
rect.bottom=rc.bottom;
}
//用正常色填充剩余控件部分的控件
rect.left=rect.right;
rect.right=rc.right;
dc.FillSolidRect(rect,RGB(200,200,200));
//重新绘制选中标签项,使梯形覆盖左右的梯形
CBrush br(m_clSel);
CBrush* pOldBr=(CBrush*)dc.SelectObject(&br);
CFont* pOldFont=(CFont*)dc.SelectObject(&m_fontSel);
dc.Polygon(m_pt,sizeof(m_pt)/sizeof(m_pt[0]));
dc.DrawText(m_szSel,m_szSel.GetLength(),m_rtSel,
DT_SINGLELINE|DT_VCENTER|DT_CENTER);
dc.SelectObject(pOldFont);
dc.SelectObject(pOldBr);
}
void CVcTab::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CWnd::OnMouseMove(nFlags, point);
CRect rect;
GetClientRect(rect);
if(!rect.PtInRect(point))
{
ReleaseCapture();
return;
}
if(GetCapture()-this)
SetCapture();
int i=-1,nCount=GetItemCount();
rect.right=rect.left+DISTWIDTH;
while(++i<nCount)
{
//循环测试每个标签项的空间
rect.left=rect.right-DISTWIDTH;
rect.right+=m_arr[i].nWidth+DISTWIDTH;
if(rect.PtInRect(point))
{
//若光标在某个标签区域内移动,则设置为加量标签
if(m_nTrack!=i)
{
m_nTrack=i;
Invalidate(FALSE);
}
return;
}
}
}
void CVcTab::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CRect rect;
GetClientRect(rect);
rect.right=rect.left+DISTWIDTH;
int i=-1,nCount=GetItemCount();
BOOL flag=TRUE; //如果点击位置不再任何标签项内,=TRUE;
while(++i<nCount)
{
//循环测试每个标签项的空间
rect.left=rect.right-DISTWIDTH;
rect.right+=m_arr[i].nWidth+DISTWIDTH;
if(rect.PtInRect(point))
{
flag=FALSE;
//若单击某个标签区域内,则设置为选择标签
m_nCurSel=i;
Invalidate(FALSE);
//模拟发送CTabCtrl的TCN_SELCHANGE反射型消息
NMHDR hdr={m_hWnd,GetDlgCtrlID(),TCN_SELCHANGE};
GetParent()->SendMessage(WM_NOTIFY,(WPARAM)m_hWnd,(LPARAM)&hdr);
return;
}
}
//项父窗口发送消息
GetClientRect(&rect);
if(flag&&rect.PtInRect(point))
{
GetParent()->SendMessage(WM_LBUTTONDOWN,(WPARAM)nFlags,(LPARAM)MAKELONG((WORD)point.x,(WORD)point.y));
}
CWnd::OnLButtonDown(nFlags, point);
}
八:在主对话框的源文件(tbDlg.h)中,添加一些成员变量和函数
public:
enum{IDC_TAB=1234}; //控件ID
CVcTab m_tab; //控件类对象
public:
//用于作为TCN_SELCHANGE的消息反射函数
void OnSelChangeTab(NMHDR* pNMHDR,LRESULT* pResult);
九:在主对话框的源文件中,编写TCN_SELCHANGE的消息反射函数
void CTbDlg::OnSelChangeTab(NMHDR *pNMHDR, LRESULT *pResult)
{
int nSel=m_tab.GetCurSel();
CString str;
str.Format("第%d页",nSel+1);
str+=m_tab.GetItemText(nSel);
SetWindowText(str);
*pResult=0;
}
十:添加消息映射代码,建立控件消息和函数直接的关联
BEGIN_MESSAGE_MAP(CTbDlg, CDialog)
ON_NOTIFY(TCN_SELCHANGE,IDC_TAB,OnSelChangeTab)
//{{AFX_MSG_MAP(CTbDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_KEYDOWN()
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
十一:在资源文件中加入一个新的对话框IDD_DIALGO1,去掉新建对话框的所有默认控件,为新建对话框添加一个新类CEditDlg,在新对话框类的头文件中加入一些变量
class CEditDlg : public CDialog
{
// Construction
public:
CString m_str; //自定义标签控件标签项文本
CRect m_rtDlg; //自定义标签窗口大小
BOOL m_bType; //弹出该对话框是添加标签项还是修改标签项
........
}
十二:修改CEditDlg对话框的初始化函数
BOOL CEditDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//移动弹出模态对话框的位置,使其在要修改的标签项的表面
this->MoveWindow(m_rtDlg);
return TRUE;
}
十三:在新建的对话中添加一个按钮和EditCtrl控件,修改按钮的ID为IDOK,双击按钮建立OnOk()按钮消息相应函数后,删除按钮控件,修改对话框界面如下
十四:修改IDD_DIALOG1的按钮消息相应函数,使的当按下回车键后,关闭对话框的时候将对话框内的文本传回父窗口
CEditDlg::CEditDlg(CWnd* pParent /*=NULL*/)
: CDialog(CEditDlg::IDD, pParent)
{
m_bType=0;
//{{AFX_DATA_INIT(CEditDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
void CEditDlg::OnOK()
{
// TODO: Add extra validation here
CTbDlg* pParent=(CTbDlg*)GetParent();
CString str;
this->GetDlgItemText(IDC_EDIT,str);
if(str.IsEmpty())
{
CDialog::OnOK();
return;
}
switch(m_bType)
{
case 0://修改标签项
{
int nItem=pParent->m_tab.GetCurSel();
pParent->m_tab.SetItemText(nItem,str);
pParent->m_tab.Invalidate(FALSE);
CDialog::OnOK();
return;
}
case 1://添加标签项
{
int nCount=pParent->m_tab.GetItemCount();
pParent->m_tab.InsertItem(nCount,str);
pParent->m_tab.Invalidate(FALSE);
CDialog::OnOK();
return;
}
default:
{
CDialog::OnOK();
return;
}
}
}
相关提示:父窗口与子窗口之间的数据传递可参考以下链接
enter description here
十五:在主对话中添加一些与鼠标和键盘消息相关的消息相应函数
十六:修改主对话的消息相应函数,按下回车键的时候,系统会自动调用主对话框的OnOk()函数
void CTbDlg::OnOK()
{
// TODO: Add extra validation here
CEditDlg dlg;
CRect rc=m_tab.m_rtSel;
ClientToScreen(&rc);
dlg.m_bType=0;
dlg.m_rtDlg=rc;
dlg.DoModal();
// CDialog::OnOK();
}
void CTbDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
// AfxMessageBox("Test");
CVcTab* pWnd=(CVcTab*)GetDlgItem(IDC_TAB);
CRect rect,rc;
pWnd->GetClientRect(&rect);
if(rect.PtInRect(point))
{
CEditDlg dlg;
dlg.m_bType=1;
m_tab.GetFreeLab(rc);
m_tab.GetClientRect(&rect);
if(rc.left>=rect.right)
return;
ClientToScreen(&rc);
dlg.m_rtDlg=rc;
dlg.DoModal();
}
CDialog::OnLButtonDown(nFlags, point);
}
十七:修改主对话框的初始化函数,调用CVcTab::Create函数创建自定义标签控件
BOOL CTbDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_tab.Create(WS_VISIBLE,CRect(0,0,500,30),this,IDC_TAB);
m_tab.InsertItem(0,_T("Build"));
m_tab.InsertItem(1,_T("Debug"));
m_tab.InsertItem(2,_T("Find in Files1"));
m_tab.InsertItem(3,_T("Find in Files2"));
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
以上只是工程的部分代码,完整代码请点以下链接下载
=enter description here
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。