为CListBox加上智能水平滚动条
生活随笔
收集整理的這篇文章主要介紹了
为CListBox加上智能水平滚动条
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
在MFC中,用列表框(CListBox)來顯示多個(gè)字符串是一種很方便的方法。但缺省的列表框水平滾動(dòng)條不夠智能——這里智能的含義是:在應(yīng)該出現(xiàn)的時(shí)候出現(xiàn),不應(yīng)該出現(xiàn)的時(shí)候消失,而且應(yīng)能自動(dòng)調(diào)節(jié)自己的大小。本文通過實(shí)例說明了存在的問題和解決辦法。
一、問題演示
首先用Visual Studio應(yīng)用向?qū)?chuàng)建工程CustomCListBox。這是一個(gè)基于對(duì)話框的應(yīng)用,向?qū)峁┑乃锌蛇x參數(shù)均采用其缺省值。
在資源編輯器中將對(duì)主話框字體設(shè)為宋體12,插入一個(gè)CListBox控制,設(shè)其ID為IDC_LLISTTEST,大小為125 X 84。 請(qǐng)確認(rèn)列表框的垂直滾動(dòng)條、水平滾動(dòng)條有效,取消其排序風(fēng)格。
啟動(dòng)Class Wizard,選擇Member Variables選項(xiàng)卡,為列表框加入對(duì)應(yīng)的成員變量m_lListTest,在Category中選擇Control。
接下來在Workspace窗格中選擇ClassView,擴(kuò)展CCustomCListBoxDlg類并雙擊OnInitDialog(),在編輯窗格中找到注釋行“TODO: Add extra initialization here”,在該行下面加入以下內(nèi)容:
m_lListTest.AddString(_T("One"));
m_lListTest.AddString(_T("Two"));
m_lListTest.AddString(_T("Three"));
m_lListTest.AddString(_T("Four"));
m_lListTest.AddString(_T("Five"));
m_lListTest.AddString(_T("Six"));
m_lListTest.AddString(_T("北國風(fēng)光,千里冰封,萬里雪飄。"));
m_lListTest.AddString(_T("Eight"));
m_lListTest.AddString(_T("Nine"));
m_lListTest.AddString(_T("Ten"));
編譯并運(yùn)行這個(gè)工程,可以發(fā)現(xiàn)列表框能夠正確顯示全部內(nèi)容。
如果在上述m_lListText.AddString(_T"Ten"))后面加入一行:
m_lListTest.AddString(_T("Eleven"));
重新編譯并運(yùn)行該工程,可以發(fā)現(xiàn)出現(xiàn)了一個(gè)垂直滾動(dòng)條。垂直滾動(dòng)條的出現(xiàn)使得列表框水平方向有效顯示寬度變小,第七行的內(nèi)容被切割而不能完整顯示。但此時(shí)水平滾動(dòng)條并沒有自動(dòng)出現(xiàn),第七行被切割部分就無法看到了。
如果我們刪除最后加入的語句,把第七行漢字加長到超出列表框顯示寬度為止,也可以發(fā)現(xiàn)水平滾動(dòng)條不會(huì)自動(dòng)出現(xiàn)。被切割部分仍舊無法看到。
由此可知,CListBox的水平滾動(dòng)條并不象垂直滾動(dòng)條那樣“聰明”:垂直滾動(dòng)條總是能夠在需要它的時(shí)候自動(dòng)出現(xiàn),并能夠自動(dòng)調(diào)節(jié)自身大小,而水平滾動(dòng)條不能。
二、解決問題
為提高代碼的可重用性,可以創(chuàng)建CListBox的派生類,在派生類中實(shí)現(xiàn)“智能”水平滾動(dòng)條。需要考慮的主要問題包括:跟蹤最大字符串寬度(應(yīng)能適應(yīng)不同場合下的字體變化),必要時(shí)計(jì)算垂直滾動(dòng)條寬度,自動(dòng)顯示和調(diào)節(jié)水平滾動(dòng)條的大小。
選菜單 Insert/New Class,設(shè)新創(chuàng)建類的名字為CDJListBox,其基類為CListBox,其它選項(xiàng)采用缺省值。單擊OK,Visual Studio自動(dòng)生成DJListBox.cpp和DJListBox.h兩個(gè)文件。
接下來將主對(duì)話框的列表框改為CDJListBox類型,即在CLassView擴(kuò)展CCustomListBoxDlg類并雙擊m_lListTest成員,在編輯窗格,修改
CListBox m_lListTest;
為:
CDJListBox m_lListTest;
然后,在類聲明代碼之前,插入
#include "DJListBox.h"
此時(shí)如果重新編譯并運(yùn)行,是無法看到任何實(shí)質(zhì)性的改變的,因?yàn)槲覀儾]有修改CDJListBox。所有對(duì)于CDJListBox的調(diào)用都直接傳遞給基類CListBox了。
跟蹤字符串最大寬度可以通過覆蓋CListBox::AddString()實(shí)現(xiàn)。打開DJListBox.h,緊接類的析構(gòu)函數(shù)加入如下聲明:
int AddString( LPCTSTR lpszItem );
并在實(shí)現(xiàn)文件DJListBox.cpp加入該函數(shù)框架:
int CDJListBox::AddString(LPCTSTR lpszItem)
{
//此處加入字符串寬度跟蹤、水平滾動(dòng)條顯示等代碼
}
字符串寬度跟蹤可以用整形成員變量m_nMaxWidth實(shí)現(xiàn)。在DjListBox.h的protected聲明區(qū)內(nèi),加入以下一行:
int m_nMaxWidth;
在DJListBox.cpp文件,找到CDJListBox的建構(gòu)函數(shù),為這個(gè)最大寬度作初始化:
m_nMaxWidth = 0;
現(xiàn)在可以改動(dòng)新加入的AddString()了。先應(yīng)該調(diào)用基類AddString(),并用nRet記錄其返回值:
int nRet = CListBox::AddString(lpszItem);
接下來調(diào)用GetScrollInfo()以獲得垂直滾動(dòng)條的相關(guān)信息。這些信息是通過一個(gè)SCROLLINFO結(jié)構(gòu)傳遞的,下面是對(duì)該結(jié)構(gòu)初始化并調(diào)用GetScrollInfo()的代碼:
SCROLLINFO scrollInfo;
memset(&scrollInfo, 0, sizeof(SCROLLINFO));
scrollInfo.cbSize = sizeof(SCROLLINFO);
scrollInfo.fMask = SIF_ALL;
GetScrollInfo(SB_VERT, &scrollInfo, SIF_ALL);
在調(diào)試器內(nèi)觀察SCROLLINFO,可以發(fā)現(xiàn)要獲得nMax和nPage的正確數(shù)值,列表框至少應(yīng)含有一個(gè)字符串。SCROLLINFO的成員nPage保存了列表框“每頁”能夠顯示的項(xiàng)目數(shù),nMax是列表框內(nèi)項(xiàng)目總數(shù)。當(dāng)nMax大于或等于nPage,就出現(xiàn)了垂直滾動(dòng)條。我們需要知道垂直滾動(dòng)條的寬度以正確計(jì)算列表框的有效顯示寬度。這里使用一個(gè)初始值為0的整數(shù)nScrollWidth表示,并在垂直滾動(dòng)條顯示時(shí)將它賦值:
int nScrollWidth = 0;
if(GetCount() > 1 && ((int)scrollInfo.nMax
> = (int)scrollInfo.nPage))
{
nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
}
接下來聲明一個(gè)SIZE變量sSize,并實(shí)例化對(duì)話框的CClientDC:
SIZE sSize;
CClientDC myDC(this);
對(duì)話框所采用的字體,有可能是缺省字體,也有可能是有目的的選擇。在對(duì)話框編輯器中右擊對(duì)話框,并選擇Properties可以查看當(dāng)前值。雖然MyDC是從列表框取得的,但列表框字體信息并未包含在MyDC中。也就是說,對(duì)話框創(chuàng)建時(shí)所用字體并沒有“選入”CClientDC。要從GetTextExtentPoint32()獲得真正的字符串大小,應(yīng)該先調(diào)用GetFont()獲得列表框的字體信息,然后將此字體選入MyDC,代碼為:
CFont* pListBoxFont = GetFont();
if(pListBoxFont != NULL)
{
CFont* pOldFont =
myDC.SelectObject(pListBoxFont);
現(xiàn)在可以調(diào)用GetTextExtendPoint32()函數(shù)來獲得字符串的寬度了。字符串的寬度由sSize結(jié)構(gòu)的cx成員返回,將該值和已有最大寬度相比較:
GetTextExtentPoint32(myDC.m_hDC,
lpszItem, strlen(lpszItem), &sSize);
m_nMaxWidth = max(m_nMaxWidth, (int)sSize.cx);
剩下的重要工作之一,就是設(shè)置水平滾動(dòng)條的大小了。這可以通過調(diào)用SetHorizontalExtent()完成。如果傳遞給它的整形參數(shù)比列表框本身寬度小,則水平滾動(dòng)條被隱藏。
這里有一個(gè)容易被忽略的地方。如果仔細(xì)觀察CListBox,可以發(fā)現(xiàn)文本左邊有一欄小小的空白,它的大小為3 。這部分寬度應(yīng)該加到文本寬度上。如果希望在文本右邊也同樣空出一欄,則可以在文本寬度上再加3。
SetHorizontalExtent(m_nMaxWidth + 3);
在結(jié)束之前,我們需要為MyDC選入原有字體。原有字體保存在pOldFont中:
myDC.SelectObject(pOldFont); }
return nRet;
編譯并執(zhí)行新的代碼,可以看到水平滾動(dòng)條終于能夠自動(dòng)顯示了。
三、其它問題
在實(shí)際應(yīng)用中,凡是改變列表框內(nèi)容的函數(shù)都可能影響水平滾動(dòng)條的顯示要求,因而也必須加以定制。但其基本過程——計(jì)算文本寬度并按指定大小顯示滾動(dòng)條等,和上述討論過程是相似的。
CListBox類能夠改變列表內(nèi)容的方法除AddString()外,還有DeleteString(),InsertString(),ResetContent()。其中InsertString()用于在指定位置插入字符串,在本文討論的主題內(nèi)它和AddString()是一樣的。
DeleteString()刪除一個(gè)字符串,在派生類中其參考代碼如下:
int CDJListBox::DeleteString(UINT nIndex)
{
RECT lRect;
GetWindowRect(&lRect);
int nRet = CListBox::DeleteString(nIndex);
int nBoxWidth = lRect.right - lRect.left;
m_nMaxWidth = nBoxWidth;
SIZE sSize;
CClientDC myDC(this);
int i;
char szEntry[257];
for (i = 0; i nBoxWidth) // 顯示水平滾動(dòng)條
{
ShowScrollBar(SB_HORZ, TRUE);
SetHorizontalExtent(m_nMaxWidth);
}
else
{
ShowScrollBar(SB_HORZ, FALSE);
}
return nRet;
}
ResetContent()用于清除列表框的全部內(nèi)容。在派生類中其參考代碼如下:
void CDJListBox::ResetContent()
{
CListBox::ResetContent();
m_nMaxWidth = 0;
SetHorizontalExtent(0);
}
一、問題演示
首先用Visual Studio應(yīng)用向?qū)?chuàng)建工程CustomCListBox。這是一個(gè)基于對(duì)話框的應(yīng)用,向?qū)峁┑乃锌蛇x參數(shù)均采用其缺省值。
在資源編輯器中將對(duì)主話框字體設(shè)為宋體12,插入一個(gè)CListBox控制,設(shè)其ID為IDC_LLISTTEST,大小為125 X 84。 請(qǐng)確認(rèn)列表框的垂直滾動(dòng)條、水平滾動(dòng)條有效,取消其排序風(fēng)格。
啟動(dòng)Class Wizard,選擇Member Variables選項(xiàng)卡,為列表框加入對(duì)應(yīng)的成員變量m_lListTest,在Category中選擇Control。
接下來在Workspace窗格中選擇ClassView,擴(kuò)展CCustomCListBoxDlg類并雙擊OnInitDialog(),在編輯窗格中找到注釋行“TODO: Add extra initialization here”,在該行下面加入以下內(nèi)容:
m_lListTest.AddString(_T("One"));
m_lListTest.AddString(_T("Two"));
m_lListTest.AddString(_T("Three"));
m_lListTest.AddString(_T("Four"));
m_lListTest.AddString(_T("Five"));
m_lListTest.AddString(_T("Six"));
m_lListTest.AddString(_T("北國風(fēng)光,千里冰封,萬里雪飄。"));
m_lListTest.AddString(_T("Eight"));
m_lListTest.AddString(_T("Nine"));
m_lListTest.AddString(_T("Ten"));
編譯并運(yùn)行這個(gè)工程,可以發(fā)現(xiàn)列表框能夠正確顯示全部內(nèi)容。
如果在上述m_lListText.AddString(_T"Ten"))后面加入一行:
m_lListTest.AddString(_T("Eleven"));
重新編譯并運(yùn)行該工程,可以發(fā)現(xiàn)出現(xiàn)了一個(gè)垂直滾動(dòng)條。垂直滾動(dòng)條的出現(xiàn)使得列表框水平方向有效顯示寬度變小,第七行的內(nèi)容被切割而不能完整顯示。但此時(shí)水平滾動(dòng)條并沒有自動(dòng)出現(xiàn),第七行被切割部分就無法看到了。
如果我們刪除最后加入的語句,把第七行漢字加長到超出列表框顯示寬度為止,也可以發(fā)現(xiàn)水平滾動(dòng)條不會(huì)自動(dòng)出現(xiàn)。被切割部分仍舊無法看到。
由此可知,CListBox的水平滾動(dòng)條并不象垂直滾動(dòng)條那樣“聰明”:垂直滾動(dòng)條總是能夠在需要它的時(shí)候自動(dòng)出現(xiàn),并能夠自動(dòng)調(diào)節(jié)自身大小,而水平滾動(dòng)條不能。
二、解決問題
為提高代碼的可重用性,可以創(chuàng)建CListBox的派生類,在派生類中實(shí)現(xiàn)“智能”水平滾動(dòng)條。需要考慮的主要問題包括:跟蹤最大字符串寬度(應(yīng)能適應(yīng)不同場合下的字體變化),必要時(shí)計(jì)算垂直滾動(dòng)條寬度,自動(dòng)顯示和調(diào)節(jié)水平滾動(dòng)條的大小。
選菜單 Insert/New Class,設(shè)新創(chuàng)建類的名字為CDJListBox,其基類為CListBox,其它選項(xiàng)采用缺省值。單擊OK,Visual Studio自動(dòng)生成DJListBox.cpp和DJListBox.h兩個(gè)文件。
接下來將主對(duì)話框的列表框改為CDJListBox類型,即在CLassView擴(kuò)展CCustomListBoxDlg類并雙擊m_lListTest成員,在編輯窗格,修改
CListBox m_lListTest;
為:
CDJListBox m_lListTest;
然后,在類聲明代碼之前,插入
#include "DJListBox.h"
此時(shí)如果重新編譯并運(yùn)行,是無法看到任何實(shí)質(zhì)性的改變的,因?yàn)槲覀儾]有修改CDJListBox。所有對(duì)于CDJListBox的調(diào)用都直接傳遞給基類CListBox了。
跟蹤字符串最大寬度可以通過覆蓋CListBox::AddString()實(shí)現(xiàn)。打開DJListBox.h,緊接類的析構(gòu)函數(shù)加入如下聲明:
int AddString( LPCTSTR lpszItem );
并在實(shí)現(xiàn)文件DJListBox.cpp加入該函數(shù)框架:
int CDJListBox::AddString(LPCTSTR lpszItem)
{
//此處加入字符串寬度跟蹤、水平滾動(dòng)條顯示等代碼
}
字符串寬度跟蹤可以用整形成員變量m_nMaxWidth實(shí)現(xiàn)。在DjListBox.h的protected聲明區(qū)內(nèi),加入以下一行:
int m_nMaxWidth;
在DJListBox.cpp文件,找到CDJListBox的建構(gòu)函數(shù),為這個(gè)最大寬度作初始化:
m_nMaxWidth = 0;
現(xiàn)在可以改動(dòng)新加入的AddString()了。先應(yīng)該調(diào)用基類AddString(),并用nRet記錄其返回值:
int nRet = CListBox::AddString(lpszItem);
接下來調(diào)用GetScrollInfo()以獲得垂直滾動(dòng)條的相關(guān)信息。這些信息是通過一個(gè)SCROLLINFO結(jié)構(gòu)傳遞的,下面是對(duì)該結(jié)構(gòu)初始化并調(diào)用GetScrollInfo()的代碼:
SCROLLINFO scrollInfo;
memset(&scrollInfo, 0, sizeof(SCROLLINFO));
scrollInfo.cbSize = sizeof(SCROLLINFO);
scrollInfo.fMask = SIF_ALL;
GetScrollInfo(SB_VERT, &scrollInfo, SIF_ALL);
在調(diào)試器內(nèi)觀察SCROLLINFO,可以發(fā)現(xiàn)要獲得nMax和nPage的正確數(shù)值,列表框至少應(yīng)含有一個(gè)字符串。SCROLLINFO的成員nPage保存了列表框“每頁”能夠顯示的項(xiàng)目數(shù),nMax是列表框內(nèi)項(xiàng)目總數(shù)。當(dāng)nMax大于或等于nPage,就出現(xiàn)了垂直滾動(dòng)條。我們需要知道垂直滾動(dòng)條的寬度以正確計(jì)算列表框的有效顯示寬度。這里使用一個(gè)初始值為0的整數(shù)nScrollWidth表示,并在垂直滾動(dòng)條顯示時(shí)將它賦值:
int nScrollWidth = 0;
if(GetCount() > 1 && ((int)scrollInfo.nMax
> = (int)scrollInfo.nPage))
{
nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
}
接下來聲明一個(gè)SIZE變量sSize,并實(shí)例化對(duì)話框的CClientDC:
SIZE sSize;
CClientDC myDC(this);
對(duì)話框所采用的字體,有可能是缺省字體,也有可能是有目的的選擇。在對(duì)話框編輯器中右擊對(duì)話框,并選擇Properties可以查看當(dāng)前值。雖然MyDC是從列表框取得的,但列表框字體信息并未包含在MyDC中。也就是說,對(duì)話框創(chuàng)建時(shí)所用字體并沒有“選入”CClientDC。要從GetTextExtentPoint32()獲得真正的字符串大小,應(yīng)該先調(diào)用GetFont()獲得列表框的字體信息,然后將此字體選入MyDC,代碼為:
CFont* pListBoxFont = GetFont();
if(pListBoxFont != NULL)
{
CFont* pOldFont =
myDC.SelectObject(pListBoxFont);
現(xiàn)在可以調(diào)用GetTextExtendPoint32()函數(shù)來獲得字符串的寬度了。字符串的寬度由sSize結(jié)構(gòu)的cx成員返回,將該值和已有最大寬度相比較:
GetTextExtentPoint32(myDC.m_hDC,
lpszItem, strlen(lpszItem), &sSize);
m_nMaxWidth = max(m_nMaxWidth, (int)sSize.cx);
剩下的重要工作之一,就是設(shè)置水平滾動(dòng)條的大小了。這可以通過調(diào)用SetHorizontalExtent()完成。如果傳遞給它的整形參數(shù)比列表框本身寬度小,則水平滾動(dòng)條被隱藏。
這里有一個(gè)容易被忽略的地方。如果仔細(xì)觀察CListBox,可以發(fā)現(xiàn)文本左邊有一欄小小的空白,它的大小為3 。這部分寬度應(yīng)該加到文本寬度上。如果希望在文本右邊也同樣空出一欄,則可以在文本寬度上再加3。
SetHorizontalExtent(m_nMaxWidth + 3);
在結(jié)束之前,我們需要為MyDC選入原有字體。原有字體保存在pOldFont中:
myDC.SelectObject(pOldFont); }
return nRet;
編譯并執(zhí)行新的代碼,可以看到水平滾動(dòng)條終于能夠自動(dòng)顯示了。
三、其它問題
在實(shí)際應(yīng)用中,凡是改變列表框內(nèi)容的函數(shù)都可能影響水平滾動(dòng)條的顯示要求,因而也必須加以定制。但其基本過程——計(jì)算文本寬度并按指定大小顯示滾動(dòng)條等,和上述討論過程是相似的。
CListBox類能夠改變列表內(nèi)容的方法除AddString()外,還有DeleteString(),InsertString(),ResetContent()。其中InsertString()用于在指定位置插入字符串,在本文討論的主題內(nèi)它和AddString()是一樣的。
DeleteString()刪除一個(gè)字符串,在派生類中其參考代碼如下:
int CDJListBox::DeleteString(UINT nIndex)
{
RECT lRect;
GetWindowRect(&lRect);
int nRet = CListBox::DeleteString(nIndex);
int nBoxWidth = lRect.right - lRect.left;
m_nMaxWidth = nBoxWidth;
SIZE sSize;
CClientDC myDC(this);
int i;
char szEntry[257];
for (i = 0; i nBoxWidth) // 顯示水平滾動(dòng)條
{
ShowScrollBar(SB_HORZ, TRUE);
SetHorizontalExtent(m_nMaxWidth);
}
else
{
ShowScrollBar(SB_HORZ, FALSE);
}
return nRet;
}
ResetContent()用于清除列表框的全部內(nèi)容。在派生類中其參考代碼如下:
void CDJListBox::ResetContent()
{
CListBox::ResetContent();
m_nMaxWidth = 0;
SetHorizontalExtent(0);
}
總結(jié)
以上是生活随笔為你收集整理的为CListBox加上智能水平滚动条的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云炬Android开发教程0 jdk下载
- 下一篇: 线程的创建方法