MFC模仿vc6.0梯形标签控件

最终效果如图:
这里写图片描述

功能:
支持插入,删除,设置标签项

详细说明:

一:使用MFC应用程序向导,创建一个工程名为“tb”的基于对话框程序,用于演示通过创建自定义子窗口来开发一个标签控件的过程

二:在类视图中添加CWnd派生类,由空白窗口开发出一个标签控件,如果1-1所示
图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

十五:在主对话中添加一些与鼠标和键盘消息相关的消息相应函数
图1-3

十六:修改主对话的消息相应函数,按下回车键的时候,系统会自动调用主对话框的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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值