这个项目中需要用MFC实现一个界面功能:listctrl中水平添加按钮。
MFC本身的listctrl控件只能显示简单的文本,简单的添加按钮也不是一两句代码能解决的问题,从这方面讲,MFC开发界面真是不得已而为之。
因为需要的按钮数目是不确定的,所以只能是动态创建,然后再根据listctrl控件的位置计算出按钮应该放置的位置,然后将按钮移动到指定坐标。
对MFC里面的类和关系,我并不熟悉,所以花了很长时间搜索,最终在下载的好几个版本的代码中找了一个基本可用的,修改开发。
一、 针对我们需要管理自己动态创建的按钮,所以我们自定义了一个CButton的子类。
1
2class CButtonEx : public CButton
3{
4 DECLARE_DYNAMIC(CButtonEx)
5
6public:
7 CButtonEx();
8 CButtonEx( int nItem, int nSubItem, CRect rect, HWND hParent,void * pData );
9 virtual ~CButtonEx();
10
11protected:
12 DECLARE_MESSAGE_MAP()
13public:
14 afx_msg void OnBnClicked(); //点击响应函数
15 int m_inItem; //所属listctrl的行
16 int m_inSubItem; //所属listctrl的列
17 CRect m_rect; //按钮所在的位置
18 HWND m_hParent; //按钮的父窗口
19 BOOL bEnable;
20 void * m_pData; //按钮带的用户自定义数据
21};
1CButtonEx::CButtonEx( int nItem, int nSubItem, CRect rect, HWND hParent,void * pData )
2{
3 m_inItem = nItem;
4 m_inSubItem = nSubItem;
5 m_rect = rect;
6 m_hParent = hParent;
7 bEnable = TRUE;
8 m_pData = pData;
9} 按钮点击的响应逻辑在OnBnClicked函数中。之所以加入m_pData成员变量,是便于存放用户自定义数据,这样就可以在OnBnClicked函数中根据自定义变量做出相应的处理。
二、自定义listctrl子类
1#pragma once
2
3#include "ButtonEx.h"
4#include <map>
5using namespace std;
6
7typedef map<int,CButtonEx*> button_map;
8// CListCtrlEx
9
10class CListCtrlEx : public CListCtrl
11{
12 DECLARE_DYNAMIC(CListCtrlEx)
13
14public:
15 CListCtrlEx();
16 virtual ~CListCtrlEx();
17
18protected:
19 DECLARE_MESSAGE_MAP()
20
21public:
22 //动态创建Button
23 void createItemButton( int nItem, int nSubItem, HWND hMain,LPCTSTR lpszCaption ,void * pData);
24 //释放创建的Button
25 void release();
26 void deleteItemEx( int nItem );
27 button_map m_mButton;
28
29public:
30 UINT m_uID;
31 CFont font ; //按钮上面的字体
32 void updateListCtrlButtonPos(); //更新按钮的位置
33 //void enableButton( BOOL bFlag, int iItem );
34 //重载水平滚动条滚动函数
35 afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
36}; CListCtrlEx实现如下:
1CListCtrlEx::CListCtrlEx()
2{
3 m_uID = 0;
4
5 font.CreatePointFont(100,"宋体");
6}
7
8CListCtrlEx::~CListCtrlEx()
9{
10 release();
11} 然后是创建按钮的逻辑,注意我这里是在ListCtrl控件中水平添加按钮(同一行的每一列),而不是垂直(同一列的每一行):
1void CListCtrlEx::createItemButton( int nItem, int nSubItem, HWND hMain,LPCTSTR lpszCaption ,void * pData)
2{
3 CRect rect;
4 /**//*if( !EnsureVisible(nItem, TRUE))
5 return ;*/
6
7 GetSubItemRect(nItem, nSubItem, LVIR_BOUNDS, rect);
8 rect.bottom = rect.top + 150;
9 //rect.right = rect.left + 150;
10
11 DWORD dwStyle = WS_CHILD | WS_VISIBLE | BS_MULTILINE;
12 CButtonEx *pButton = new CButtonEx(nItem,nSubItem,rect,hMain,pData);
13 m_uID++;
14
15 pButton->Create(lpszCaption,dwStyle, rect, this, m_uID);
16 //CDC* pDC = pButton->GetDC();
17 //pDC->SetTextColor(RGB(255,0,0));
18 pButton->SetFont(&font);
19
20 // m_mButton.insert( make_pair( nItem, pButton ) ); //纵向添加用
21 m_mButton.insert( make_pair( nSubItem, pButton ) ); //单行横向添加用
22
23 return;
24}
25 上面的代码中,我将按钮的高都设为了150,而不是listctrl默认的一点点高。
1void CListCtrlEx::release()
2{
3 button_map::iterator iter = m_mButton.begin();
4 while ( iter != m_mButton.end() )
5 {
6 delete iter->second;
7 iter->second = NULL;
8 iter++;
9 }
10 m_mButton.clear();
11} 当完成以上代码以后,就可以在对话框中添加listctrl控件的成员变量了:CListCtrlEx m_lsPath;
然后在OnInitDialog函数中给listctrl控件添加按钮:
1int i = 0;
2 m_lsPath.InsertColumn(i,_T(""),LVCFMT_LEFT,150);
3
4 nRow = m_lsPath.InsertItem(0, "tim");
5
6 TCHAR caption[1000] = {0};//标题
7 ImageCfg * pImageCfg = new ImageCfg;//自定义数据
8 m_lsPath.createItemButton(nRow,i++,m_lsPath,caption,pImageCfg); 这样看起来一切很好,但是运行时发现,当按钮较多需要水平滚动条时,拖动水平滚动条并不能正确的显示按钮!
所以我们还需要处理CListCtrlEx的水平滚动命令:
1void CListCtrlEx::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
2{
3 // TODO: 在此添加消息处理程序代码和/或调用默认值
4 CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
5 updateListCtrlButtonPos();
6 //Invalidate(FALSE);
7}
1void CListCtrlEx::updateListCtrlButtonPos()
2{
3 button_map::iterator iter = m_mButton.begin();
4 button_map::iterator itrEnd = m_mButton.end();
5 //调整横向的
6 int posx = GetScrollPos(SB_HORZ);//取得水平滚动条的位置
7 for (;iter != itrEnd;++iter)
8 {
9 CRect rect;
10 rect = iter->second->m_rect;
11 rect.left -= posx;
12 rect.right -= posx;
13 iter->second->ShowWindow( SW_HIDE );
14
15 iter->second->MoveWindow( &rect );
16 iter->second->ShowWindow( SW_SHOW );
17 /**//*if( iLine < iTopIndex )
18 {
19 iterUp->second->ShowWindow( SW_HIDE );
20 }*/
21 }
22 return;
23} 这里操作的过程是:取得控件水平滚动条的位置,然后将所有按钮的水平坐标左移响应的值。其实这里可以优化一下:判断只有那些按钮会被显示才处理,其他的并不需要处理,例如:
1void CListCtrlEx::updateListCtrlButtonPos()
2{
3 button_map::iterator iter = m_mButton.begin();
4 button_map::iterator itrEnd = m_mButton.end();
5
6 CRect rect;
7 GetClientRect(rect);
8 LONG width = rect.right;
9 //调整横向的
10 int posx = GetScrollPos(SB_HORZ);//取得水平滚动条的位置
11 for (;iter != itrEnd;++iter)
12 {
13 iter->second->ShowWindow( SW_HIDE );
14
15 rect = iter->second->m_rect;
16 rect.left -= posx;
17 rect.right -= posx;
18 if (rect.right > 0)
19 {
20 if (rect.left > width)
21 {
22 //其他的都超出了显示范围
23 break;
24 }
25 iter->second->MoveWindow( &rect );
26 iter->second->ShowWindow( SW_SHOW );
27 }
28
29 /**//*if( iLine < iTopIndex )
30 {
31 iterUp->second->ShowWindow( SW_HIDE );
32 }*/
33 }
34 return;
35} 这样,按钮就能正确刷新了。
不过,还有一个小问题:在拖动滚动条时,我们发现界面有些闪烁。但是我还没找到合适的解决方法。欢迎大家给出可行的方案。