使用mfc CWnd 自绘实现一个类似于QQ好友的一个控件

来源:互联网 发布:gbk转utf java 编辑:程序博客网 时间:2024/05/02 04:51

使用mfc CWnd 自绘实现一个类似于QQ好友的一个控件

使用CWnd自绘控件其实挺简单的,可以从CWnd派生一个自己命名的类,

响应鼠标移动,鼠标按下事件,在这些响应的事件中,使用双缓存的方式重绘控件的DC;

界面就可以根据用户的操作做出对应的修改。基本原理就是如此。点击打开链接

以QQ的好友管理控件为例,源代码下载地址:http://download.csdn.net/download/weiweiloong/4991943


需要绘制的信息保存在一个std::list中,包含要绘制节点的,图片,坐标值,文字信息等;

用户每次操作后,修改list中缓存的节点值,然后根据这些节点值重新计算修改节点信息值。接着通知界面重绘。

具体请参考源码:

#pragma once

#include <map>
#include <list>
#include <string>
#include <atlimage.h>

using namespace std;
// CCatalogCtrl
/*********************** 好友分组管理 *********************/

// 成员节点
typedef struct _MemberNode
{
    int         iIndex;
    int         iParentIndex;      // 所属目录编号
    string      strName;
    string      strDeclaration;    // 好友个性宣言

    // 好友信息窗口
    // 好友聊天窗口
    bool        bOnline;           // 好友是否在线
    bool        bSelect;           // 节点被选中(高亮)
    bool        bPress;            // 节点被按下(是否放大显示)
    CImage      imgMemberPic;      // 成员头像

    int         iItemHeight;
    int         iItemWidth;
    int         iUserState;        // 用户状态
    CPoint      iItemPos;          // 元素位置
    CPoint      iImgPos;           // 头像位置
    CPoint      iNamePos;          // 用户名位置
    CPoint      iDeclarationPos;   // 个性宣言位置

}MemberNode;

// 分组节点
typedef struct _GroupNode
{
    int         iIndex;
    int         iMemNum;        // 好友人数
    int         iOnlineNum;     // 好友在线人数
    string      strGroupName;   // 分组名称
    bool        bGroupState;    // 分组状态(展开/合并)
    CPoint      iItemPos;       // 元素位置
    CPoint      iImgPos;        // 图片位置
    CPoint      iInfoPos;       // 信息位置

    list<MemberNode*>  lstMember;  // 小组成员
    
    ~_GroupNode()
    {
        // 清空成员链表                      
        list<MemberNode*>::iterator iPos, iEnd;
        iEnd = lstMember.end();

        for( iPos = lstMember.begin(); iPos != iEnd; iPos++)
        {
            delete(*iPos);
        }
        lstMember.clear();
    }

}GroupNode;

class CScrollHelper;

//  好友列表控件
class CCatalogCtrl : public CWnd
{
    DECLARE_DYNAMIC(CCatalogCtrl)

public:
    CCatalogCtrl( CWnd *parentWnd);
    virtual ~CCatalogCtrl();

protected:
    DECLARE_MESSAGE_MAP()

private:

    CPen        m_PenItemFrame;       // 节点框架
    CBrush      m_BrushItemRect;      // 节点被选中填充画刷
    CBrush      m_BrushItemMove;      // 鼠标移动过程元素的颜色
    CDC         m_TempDC;             // 记录要绘图的部分DC内容1
    CFont       m_TextFont;           // 桌面文字字体
    CImage      m_imgGroupOpen;       // 分组展开图标
    CImage      m_imgGroupClose;      // 分组合并图标
    int         m_iChildHeight;       // 分组高度
    int         m_iChildWidth;        // 分组宽度

    int         m_iCtrlHeight;        // 控件高度
    int         m_iCtrlWidth;         // 控件宽度
    CPoint      m_CtrlPos;            // 控件绘制相对位置
    int         m_iAllItemsNum;       // 控件所有节点个数

private:

    list<GroupNode*>   m_lstGroup;       // 分组信息
    CScrollHelper      *m_pScrollHelper; // 滚动条类

private:

    int  ClearGroupLst();                       // 清空分组链表
    int  ClearMemberLst();                      // 清空成员链表

    int  CurrPosAttribute( CPoint pos);         // 当前点击位置是分组、节点、空白
    int  InitCtrl();                            // 初始化当前控件
    int  ProcessDrawInfo( CDC &TempDC);         // 初始化绘制信息
    void DrawScrollInfo( CDC &TempDC);          // 绘制滚动条改变后的窗口视图
    int  ReCaculateItemsPos( CDC &TempDC);      // 某个节点元素改变后,重新调整所有节点位置
public:

    bool InsertNewGroup( int iIndex, string strName);               // 插入一个分组
    bool ModifyGroupName( int iIndex, string strName);               // 修改一个分组名称
    bool DelCurrGroup( int iIndex);                                   // 删除一个分组

    bool InsertNewItem( int iGroupIndex, MemberNode *pMemInfo);       // 插入一个好友
    bool DelCurrItem( int iMumIndex);                               // 删除一个好友
    bool MoveItem( int iItemIndex, int iSrcGroup, int iDeskGroup); // 将一个好友从一个分组移动到另外一个分组

private:

    afx_msg void OnTimer(UINT nIDEvent);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnPaint();
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
    afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message);
public:
    afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
};

// CatalogCtrl.cpp : 实现文件
//

#include "stdafx.h"
#include "CCatalogDlg.h"
#include "CatalogCtrl.h"
#include ".\catalogctrl.h"
#include "ScrollHelper.h"

#define  ITEM_HEIGHT           25    // 默认状态元素高度

/******************************   局部相对位置    *****************************/
#define  PIC_GROUP_POS_X       4     // 分组图像位置x
#define  PIC_GROUP_POS_Y       9     // 分组图像位置y
#define  PIC_MEMBER_POS_X      0     // 分组成员图像位置 x
#define  PIC_MEMBER_POS_Y      0     // 分组成员图像位置 y
#define  INFO_MEMBER_POS_X     25    // 分组成员名称位置 x
#define  INFO_MEMBER_POS_Y     0     // 分组成员名称位置 y
#define  DECLAR_MEM_POS_X      0     // 分组成员个性宣言位置x
#define  DECLAR_MEM_POS_Y      0     // 分组成员个性宣言位置y
// CCatalogCtrl

IMPLEMENT_DYNAMIC(CCatalogCtrl, CWnd)
CCatalogCtrl::CCatalogCtrl( CWnd *parentWnd)
{
    m_TextFont.CreateFont(14,                  // nHeight
                    0,                         // nWidth
                    0,                         // nEscapement
                    0,                         // nOrientation
                    FW_NORMAL,                 // nWeight
                    FALSE,                     // bItalic
                    FALSE,                     // bUnderline
                    0,                         // cStrikeOut
                    ANSI_CHARSET,              // nCharSet
                    OUT_DEFAULT_PRECIS,        // nOutPrecision
                    CLIP_DEFAULT_PRECIS,       // nClipPrecision
                    DEFAULT_QUALITY,           // nQuality
                    DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
                    _T("Arial"));
    // 边框画笔
    m_PenItemFrame.CreatePen( PS_SOLID, 1, RGB( 250, 0, 200));

    m_imgGroupOpen.Load(  theApp.GetPath() + "GroupOpen.PNG");
    m_imgGroupClose.Load( theApp.GetPath() + "GroupClose.PNG");

    m_CtrlPos.x = 0;
    m_CtrlPos.y = 0;
    m_iCtrlHeight = 350;         // 控件高度
    m_iCtrlWidth  = 300;         // 控件宽度

    m_iAllItemsNum = 0;
    // 给当前窗口创建滚动条对象
    m_pScrollHelper = new CScrollHelper;
    m_pScrollHelper ->AttachWnd(this);
    // 创建带滚动条的窗口
    Create( NULL, _T("CatalogCtrl"), WS_CHILD | WS_VISIBLE |WS_HSCROLL | WS_VSCROLL,
        CRect(0, 0, 0, 0), parentWnd, 0, NULL);


    InitCtrl();
}

CCatalogCtrl::~CCatalogCtrl()
{
    delete m_pScrollHelper;
    ClearGroupLst();
}


BEGIN_MESSAGE_MAP(CCatalogCtrl, CWnd)
    ON_WM_TIMER()
    ON_WM_SIZE()
    ON_WM_RBUTTONDOWN()
    ON_WM_PAINT()
    ON_WM_CREATE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_MOUSEMOVE()
    ON_WM_VSCROLL()
    ON_WM_MOUSEACTIVATE()
    ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()



// CCatalogCtrl 消息处理程序

void CCatalogCtrl::OnTimer(UINT nIDEvent)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CWnd::OnTimer(nIDEvent);
}

void CCatalogCtrl::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);

    // TODO: 在此处添加消息处理程序代码
}

void CCatalogCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CWnd::OnRButtonDown(nFlags, point);
}

void CCatalogCtrl::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    // TODO: 在此处添加消息处理程序代码
    CBitmap graph;
    CDC TempDC;    
    CRect rcClient;
    GetClientRect(&rcClient);
    TempDC.CreateCompatibleDC(&dc);
    graph.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
    CBitmap *pOldBitmap = (CBitmap*)TempDC.SelectObject(&graph);

    // 绘制滚动条移动后的界面
    DrawScrollInfo( TempDC);

    dc.BitBlt( 0, 0, rcClient.right, rcClient.bottom, &TempDC, 0, 0, SRCCOPY);

    TempDC.SelectObject(pOldBitmap);
    TempDC.DeleteDC();
}

int CCatalogCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  在此添加您专用的创建代码

    return 0;
}

void CCatalogCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    
    list<GroupNode*>::iterator iPos, iEnd;
    iEnd = m_lstGroup.end();

    GroupNode  *pGroup  = NULL;
    for( iPos = m_lstGroup.begin(); iPos != iEnd; iPos++)
    {
        pGroup = (*iPos);
        // 判断点击位置在哪个节点
        if((point.x >= pGroup ->iItemPos.x)&&(point.x <= (pGroup ->iItemPos.x + m_iChildWidth))
            &&(point.y >= pGroup ->iItemPos.y) &&(point.y <= (pGroup ->iItemPos.y + m_iChildHeight)))
        {
            pGroup ->bGroupState = !(pGroup ->bGroupState);
            break;
        }
        // 遍历分组下的成员链表    
        list<MemberNode*>::iterator ipos, iend;
        iend = pGroup ->lstMember.end();
        MemberNode *pMember = NULL;
        for( ipos = pGroup ->lstMember.begin(); ipos != iend; ipos++)
        {
            pMember = ( *ipos);
            if((point.x >= pMember ->iItemPos.x)&&(point.x <= (pMember ->iItemPos.x + m_iChildWidth))
                &&(point.y >= pMember ->iItemPos.y) &&(point.y <= (pMember ->iItemPos.y + m_iChildHeight)))
            {
                pMember ->iItemHeight = ITEM_HEIGHT*2;
                pMember ->bPress      = true;
            }
            else
            {
                pMember ->iItemHeight = ITEM_HEIGHT;
                pMember ->bPress      = false;
            }
        
        }
    }

    Invalidate();

    CWnd::OnLButtonDown(nFlags, point);
}

void CCatalogCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CWnd::OnLButtonDblClk(nFlags, point);
}

void CCatalogCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    
    list<GroupNode*>::iterator iPos, iEnd;
    iEnd = m_lstGroup.end();

    GroupNode  *pGroup  = NULL;
    for( iPos = m_lstGroup.begin(); iPos != iEnd; iPos++)
    {
        pGroup = (*iPos);
        // 遍历分组下的成员链表    
        list<MemberNode*>::iterator ipos, iend;
        iend = pGroup ->lstMember.end();
        MemberNode *pMember = NULL;
        for( ipos = pGroup ->lstMember.begin(); ipos != iend; ipos++)
        {
            pMember = ( *ipos);
            if((point.x >= pMember ->iItemPos.x)&&(point.x <= (pMember ->iItemPos.x + m_iChildWidth))
                &&(point.y >= pMember ->iItemPos.y) &&(point.y <= (pMember ->iItemPos.y + pMember ->iItemHeight)))
            {
                pMember ->bSelect      = true;
            }
            else
            {
                pMember ->bSelect      = false;
            }
        
        }
    }

    Invalidate();

    CWnd::OnMouseMove(nFlags, point);
}

// 滚动条垂直方向滑动
void CCatalogCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    m_pScrollHelper ->OnVScroll(nSBCode, nPos, pScrollBar);

    Invalidate();
}

int CCatalogCtrl::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
{
    int status = CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message);

    // We handle this message so that when user clicks once in the
    // window, it will be given the focus, and this will allow
    // mousewheel messages to be directed to this window.
    SetFocus();

    return status;
}

BOOL CCatalogCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    BOOL wasScrolled = m_pScrollHelper->OnMouseWheel(nFlags, zDelta, pt);
    return wasScrolled;
}
/********************************* private:*********************************/

// 清空分组链表
int  CCatalogCtrl::ClearGroupLst()
{
    list<GroupNode*>::iterator iPos, iEnd;
    iEnd = m_lstGroup.end();

    for( iPos = m_lstGroup.begin(); iPos != iEnd; iPos++)
    {
        delete(*iPos);
    }
    m_lstGroup.clear();

    return 1;
}


// 当前点击位置是分组、节点、空白
int  CCatalogCtrl::CurrPosAttribute( CPoint pos)
{
    return 1;
}

// 绘制滚动条改变后的窗口视图
void CCatalogCtrl::DrawScrollInfo( CDC &TempDC)
{
    int x = 0;
    int y = 0;

    // Offset starting position due to scrolling.
    int iCurrX = m_pScrollHelper ->GetScrollPos().cx;
    int iCurrY = m_pScrollHelper ->GetScrollPos().cy;
    x -= iCurrX;
    y -= iCurrY;

    // 元素起始坐标       
    m_CtrlPos.x = x;         
    m_CtrlPos.y = y;

    // 重新计算每个元素的位置
    ReCaculateItemsPos( TempDC);

}


// 某个节点元素改变后,重新调整所有节点位置
int CCatalogCtrl::ReCaculateItemsPos( CDC &TempDC)           
{
    ProcessDrawInfo( TempDC);

    return 0;
}

// 初始化当前控件
int  CCatalogCtrl::InitCtrl()
{

    m_iChildHeight = ITEM_HEIGHT;

    GroupNode *pGroupItem      = new GroupNode();
    pGroupItem ->bGroupState   = false;

    pGroupItem ->iItemPos.x    = 0;
    pGroupItem ->iItemPos.y    = 0;

    pGroupItem ->iImgPos.x     = 0;
    pGroupItem ->iImgPos.y     = 0;

    pGroupItem ->iIndex        = 1;
    pGroupItem ->iMemNum       = 0;
    pGroupItem ->iOnlineNum    = 0;
    pGroupItem ->strGroupName  = "我的好友";

    m_iAllItemsNum ++;
    m_lstGroup.push_back( pGroupItem);

    return 1;
}

// 初始化绘制信息
int  CCatalogCtrl::ProcessDrawInfo( CDC &TempDC)
{

    CRect rcClient;
    this ->GetClientRect(rcClient);
    m_iChildWidth  = rcClient.Width();    
    //TempDC.RoundRect( 0, 0, rcClient.Width(), rcClient.Height(), 0, 0);

    CPen  *pOldPen = TempDC.SelectObject( &m_PenItemFrame);
    CFont *pOldFont   = TempDC.SelectObject( &m_TextFont);
    list<GroupNode*>::iterator iPos, iEnd;
    iEnd = m_lstGroup.end();
    
    CDC imgDC;
    imgDC.CreateCompatibleDC(&TempDC);

    GroupNode *pGroup = NULL;

    for( iPos = m_lstGroup.begin(); iPos != iEnd; iPos++)
    {
        pGroup = (*iPos);
        pGroup ->iItemPos.x = m_CtrlPos.x;
        pGroup ->iItemPos.y = m_CtrlPos.y;
        if( pGroup ->bGroupState == false)
        {    
            imgDC.SelectObject( m_imgGroupClose);
            TRACE("关闭图形\r\n");
        }
        else
        {
            imgDC.SelectObject( m_imgGroupOpen);
            TRACE("展开图形\r\n");
        }
        TempDC.RoundRect( 0, m_CtrlPos.y, m_iChildWidth, m_CtrlPos.y + m_iChildHeight, 0, 0);
        TempDC.StretchBlt( 4, m_CtrlPos.y + 9, 13, 13, &imgDC,0, 0, 13, 13, SRCCOPY);
        TempDC.SetBkMode( TRANSPARENT);

        CString strGroupInfo = (*iPos) ->strGroupName.c_str();
        strGroupInfo  += "(";
        CString strNum;
        strNum.Format("%d", (*iPos) ->iMemNum);
        strGroupInfo  += strNum;
        strGroupInfo  += ",";
        strNum.Format("%d", (*iPos) ->iOnlineNum);
        strGroupInfo  += strNum;
        strGroupInfo  += ")";

        TempDC.TextOut( 18, m_CtrlPos.y + 8, strGroupInfo);
        // 下一个元素绘制位置
        m_CtrlPos.y += m_iChildHeight;
        // 绘制组成员
        if( pGroup ->bGroupState == true)
        {
            list<MemberNode*>::iterator ipos, iend;
            iend = pGroup ->lstMember.end();
            MemberNode *pMember = NULL;
            int iItemRow = 1;
            for( ipos = pGroup ->lstMember.begin(); ipos != iend; ipos++)
            {   
                pMember = ( *ipos);
                int iHeight = pMember ->iItemHeight - 6;
                TempDC.RoundRect( 0, m_CtrlPos.y, m_iChildWidth, m_CtrlPos.y + pMember ->iItemHeight, 0, 0);
                if( pMember ->bSelect == true)
                {
                    CBrush brush( RGB( 100, 150, 150));
                    TempDC.FillRect( CRect( 1, m_CtrlPos.y + 1, m_iChildWidth - 1, m_CtrlPos.y + pMember ->iItemHeight - 1), &brush);
                }
                imgDC.SelectObject( pMember ->imgMemberPic);

                int iPicWidth = pMember ->imgMemberPic.GetWidth();
                int iPicHeight= pMember ->imgMemberPic.GetHeight();
                TempDC.SetStretchBltMode( HALFTONE);
                TempDC.StretchBlt( 3, m_CtrlPos.y + 3, iHeight, iHeight, &imgDC,0, 0, iPicWidth, iPicHeight, SRCCOPY);
                
                CString strMumInfo = pMember ->strName.c_str();
                if( pMember ->bPress == true)
                {
                    TempDC.TextOut( iHeight + 6, m_CtrlPos.y + 2, strMumInfo);
                    strMumInfo  = "(";
                    strMumInfo += pMember ->strDeclaration.c_str();
                    strMumInfo += ")";
                    TempDC.TextOut( iHeight + 6, m_CtrlPos.y + 16, strMumInfo);
                }
                else
                {
                    strMumInfo += "(";
                    strMumInfo += pMember ->strDeclaration.c_str();
                    strMumInfo += ")";
                    TempDC.TextOut( iHeight + 6, m_CtrlPos.y + 2, strMumInfo);
                }
                pMember ->iItemPos.x = 0;
                pMember ->iItemPos.y = m_CtrlPos.y;
                m_CtrlPos.y += pMember ->iItemHeight;
                iItemRow++;
            }
        }

    }

    TempDC.SelectObject( pOldPen);
    TempDC.SelectObject( pOldFont);


    return 1;
}
/********************************* public:*********************************/
// 插入一个分组
bool CCatalogCtrl::InsertNewGroup( int iIndex, string strName)
{
    m_iChildHeight = ITEM_HEIGHT;

    GroupNode *pGroupItem      = new GroupNode();
    pGroupItem ->bGroupState   = false;

    pGroupItem ->iItemPos.x    = 0;
    pGroupItem ->iItemPos.y    = 0;

    pGroupItem ->iImgPos.x     = 0;
    pGroupItem ->iImgPos.y     = 0;

    pGroupItem ->iIndex        = (int)m_lstGroup.size() + 1;
    pGroupItem ->iMemNum       = 0;
    pGroupItem ->iOnlineNum    = 0;
    pGroupItem ->strGroupName  = strName;

    m_lstGroup.push_back( pGroupItem);

    Invalidate();

    m_iAllItemsNum++;

    return false;
}

// 修改一个分组名称               
bool CCatalogCtrl::ModifyGroupName( int iIndex, string strName)
{
    return false;
}

// 删除一个分组               
bool CCatalogCtrl::DelCurrGroup( int iIndex)
{
    m_iAllItemsNum++;
    return false;
}

// 插入一个好友
bool CCatalogCtrl::InsertNewItem( int iGroupIndex, MemberNode *pMemInfo)
{

    list<GroupNode*>::iterator iPos, iEnd;
    iEnd = m_lstGroup.end();    

    GroupNode *pGroup = NULL;
    for( iPos = m_lstGroup.begin(); iPos != iEnd; iPos++)
    {
        pGroup = (*iPos);
        if( iGroupIndex == pGroup ->iIndex)
        {
            pGroup ->iMemNum++;
            pGroup ->lstMember.push_back( pMemInfo);
            m_iAllItemsNum ++;
            break;
        }
    }
    Invalidate();
    
    
    if( m_iAllItemsNum > 14)
    {
        m_iCtrlHeight += ITEM_HEIGHT;
        m_pScrollHelper ->SetDisplaySize( 0, m_iCtrlHeight);
    }

    return false;
}

// 删除一个好友       
bool CCatalogCtrl::DelCurrItem( int iMumIndex)
{
    return false;
}

// 将一个好友从一个分组移动到另外一个分组
bool CCatalogCtrl::MoveItem( int iItemIndex, int iSrcGroup, int iDeskGroup)
{
    return false;
}