MFC的listctrl控件中水平添加按钮并刷新
这个项目中需要用MFC实现一个界面功能:listctrl中水平添加按钮。
MFC本身的listctrl控件只能显示简单的文本,简单的添加按钮也不是一两句代码能解决的问题,从这方面讲,MFC开发界面真是不得已而为之。
因为需要的按钮数目是不确定的,所以只能是动态创建,然后再根据listctrl控件的位置计算出按钮应该放置的位置,然后将按钮移动到指定坐标。
对MFC里面的类和关系,我并不熟悉,所以花了很长时间搜索,最终在下载的好几个版本的代码中找了一个基本可用的,修改开发。
一、 针对我们需要管理自己动态创建的按钮,所以我们自定义了一个CButton的子类。
1
2 class 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} ;
2 class 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} ;
1
CButtonEx::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}
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 >
5 using namespace std;
6
7 typedef map < int ,CButtonEx *> button_map;
8 // CListCtrlEx
9
10 class 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} ;
2
3 #include " ButtonEx.h "
4 #include < map >
5 using namespace std;
6
7 typedef map < int ,CButtonEx *> button_map;
8 // CListCtrlEx
9
10 class 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实现如下:
1
CListCtrlEx::CListCtrlEx()
2 {
3 m_uID = 0;
4
5 font.CreatePointFont(100,"宋体");
6}
7
8 CListCtrlEx:: ~ CListCtrlEx()
9 {
10 release();
11}
2 {
3 m_uID = 0;
4
5 font.CreatePointFont(100,"宋体");
6}
7
8 CListCtrlEx:: ~ CListCtrlEx()
9 {
10 release();
11}
然后是创建按钮的逻辑,注意我这里是在ListCtrl控件中水平添加按钮(同一行的每一列),而不是垂直(同一列的每一行):
1
void
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默认的一点点高。
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
1
void
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}
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控件添加按钮:
1
int
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);
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的水平滚动命令:
1
void
CListCtrlEx::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar
*
pScrollBar)
2 {
3 // TODO: 在此添加消息处理程序代码和/或调用默认值
4 CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
5 updateListCtrlButtonPos();
6 //Invalidate(FALSE);
7}
2 {
3 // TODO: 在此添加消息处理程序代码和/或调用默认值
4 CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
5 updateListCtrlButtonPos();
6 //Invalidate(FALSE);
7}
1
void
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}
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}
这里操作的过程是:取得控件水平滚动条的位置,然后将所有按钮的水平坐标左移响应的值。其实这里可以优化一下:判断只有那些按钮会被显示才处理,其他的并不需要处理,例如:
1
void
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}
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}
这样,按钮就能正确刷新了。
不过,还有一个小问题:在拖动滚动条时,我们发现界面有些闪烁。但是我还没找到合适的解决方法。欢迎大家给出可行的方案。