爲CListBox加上智能水平滾動條(轉)


作者:俞良軍

提交者:eastvc 發佈日期:2004-1-2 20:05:51
原文出處:http://www.ccw.com.cn

在MFC中,用列表框(CListBox)來顯示多個字符串是一種很方便的方法。但缺省的列表框水平滾動條不夠智能——這裏智能的含義是:在應該出現的時候出現,不應該出現的時候消失,而且應能自動調節自己的大小。本文通過實例說明了存在的問題和解決辦法。

一、問題演示

首先用Visual Studio應用嚮導創建工程CustomCListBox。這是一個基於對話框的應用,嚮導提供的所有可選參數均採用其缺省值。

在資源編輯器中將對主話框字體設爲宋體12,插入一個CListBox控制,設其ID爲IDC_LLISTTEST,大小爲125 X 84。 請確認列表框的垂直滾動條、水平滾動條有效,取消其排序風格。

啓動Class Wizard,選擇Member Variables選項卡,爲列表框加入對應的成員變量m_lListTest,在Category中選擇Control。

接下來在Workspace窗格中選擇ClassView,擴展CCustomCListBoxDlg類並雙擊OnInitDialog(),在編輯窗格中找到註釋行“TODO: Add extra initialization here”,在該行下面加入以下內容:

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("北國風光,千里冰封,萬里雪飄。"));
m_lListTest.AddString(_T("Eight"));
m_lListTest.AddString(_T("Nine"));
m_lListTest.AddString(_T("Ten"));

編譯並運行這個工程,可以發現列表框能夠正確顯示全部內容。

如果在上述m_lListText.AddString(_T"Ten"))後面加入一行:

m_lListTest.AddString(_T("Eleven"));

重新編譯並運行該工程,可以發現出現了一個垂直滾動條。垂直滾動條的出現使得列表框水平方向有效顯示寬度變小,第七行的內容被切割而不能完整顯示。但此時水平滾動條並沒有自動出現,第七行被切割部分就無法看到了。

如果我們刪除最後加入的語句,把第七行漢字加長到超出列表框顯示寬度爲止,也可以發現水平滾動條不會自動出現。被切割部分仍舊無法看到。

由此可知,CListBox的水平滾動條並不象垂直滾動條那樣“聰明”:垂直滾動條總是能夠在需要它的時候自動出現,並能夠自動調節自身大小,而水平滾動條不能。

二、解決問題

爲提高代碼的可重用性,可以創建CListBox的派生類,在派生類中實現“智能”水平滾動條。需要考慮的主要問題包括:跟蹤最大字符串寬度(應能適應不同場合下的字體變化),必要時計算垂直滾動條寬度,自動顯示和調節水平滾動條的大小。

選菜單 Insert/New Class,設新創建類的名字爲CDJListBox,其基類爲CListBox,其它選項採用缺省值。單擊OK,Visual Studio自動生成DJListBox.cpp和DJListBox.h兩個文件。

接下來將主對話框的列表框改爲CDJListBox類型,即在CLassView擴展CCustomListBoxDlg類並雙擊m_lListTest成員,在編輯窗格,修改

CListBox m_lListTest;

爲:

CDJListBox m_lListTest;

然後,在類聲明代碼之前,插入

#include "DJListBox.h"

此時如果重新編譯並運行,是無法看到任何實質性的改變的,因爲我們並沒有修改CDJListBox。所有對於CDJListBox的調用都直接傳遞給基類CListBox了。

跟蹤字符串最大寬度可以通過覆蓋CListBox::AddString()實現。打開DJListBox.h,緊接類的析構函數加入如下聲明:

int AddString( LPCTSTR lpszItem );

並在實現文件DJListBox.cpp加入該函數框架:

int CDJListBox::AddString(LPCTSTR lpszItem)
{
//此處加入字符串寬度跟蹤、水平滾動條顯示等代碼
}

字符串寬度跟蹤可以用整形成員變量m_nMaxWidth實現。在DjListBox.h的protected聲明區內,加入以下一行:

int m_nMaxWidth;

在DJListBox.cpp文件,找到CDJListBox的建構函數,爲這個最大寬度作初始化:

m_nMaxWidth = 0;

現在可以改動新加入的AddString()了。先應該調用基類AddString(),並用nRet記錄其返回值:

int nRet = CListBox::AddString(lpszItem);

接下來調用GetScrollInfo()以獲得垂直滾動條的相關信息。這些信息是通過一個SCROLLINFO結構傳遞的,下面是對該結構初始化並調用GetScrollInfo()的代碼:

SCROLLINFO scrollInfo;
memset(&scrollInfo, 0, sizeof(SCROLLINFO));
scrollInfo.cbSize = sizeof(SCROLLINFO);
scrollInfo.fMask = SIF_ALL;
GetScrollInfo(SB_VERT, &scrollInfo, SIF_ALL);

在調試器內觀察SCROLLINFO,可以發現要獲得nMax和nPage的正確數值,列表框至少應含有一個字符串。SCROLLINFO的成員nPage保存了列表框“每頁”能夠顯示的項目數,nMax是列表框內項目總數。當nMax大於或等於nPage,就出現了垂直滾動條。我們需要知道垂直滾動條的寬度以正確計算列表框的有效顯示寬度。這裏使用一個初始值爲0的整數nScrollWidth表示,並在垂直滾動條顯示時將它賦值:

int nScrollWidth = 0;
if(GetCount() > 1 && ((int)scrollInfo.nMax
> = (int)scrollInfo.nPage))
{
nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
}

接下來聲明一個SIZE變量sSize,並實例化對話框的CClientDC:

SIZE sSize;
CClientDC myDC(this);

對話框所採用的字體,有可能是缺省字體,也有可能是有目的的選擇。在對話框編輯器中右擊對話框,並選擇Properties可以查看當前值。雖然MyDC是從列表框取得的,但列表框字體信息並未包含在MyDC中。也就是說,對話框創建時所用字體並沒有“選入”CClientDC。要從GetTextExtentPoint32()獲得真正的字符串大小,應該先調用GetFont()獲得列表框的字體信息,然後將此字體選入MyDC,代碼爲:

CFont* pListBoxFont = GetFont();
if(pListBoxFont != NULL)
{
CFont* pOldFont =
myDC.SelectObject(pListBoxFont);

現在可以調用GetTextExtendPoint32()函數來獲得字符串的寬度了。字符串的寬度由sSize結構的cx成員返回,將該值和已有最大寬度相比較:

GetTextExtentPoint32(myDC.m_hDC,
lpszItem, strlen(lpszItem), &sSize);
m_nMaxWidth = max(m_nMaxWidth, (int)sSize.cx);

剩下的重要工作之一,就是設置水平滾動條的大小了。這可以通過調用SetHorizontalExtent()完成。如果傳遞給它的整形參數比列表框本身寬度小,則水平滾動條被隱藏。

這裏有一個容易被忽略的地方。如果仔細觀察CListBox,可以發現文本左邊有一欄小小的空白,它的大小爲3 。這部分寬度應該加到文本寬度上。如果希望在文本右邊也同樣空出一欄,則可以在文本寬度上再加3。

SetHorizontalExtent(m_nMaxWidth + 3);

在結束之前,我們需要爲MyDC選入原有字體。原有字體保存在pOldFont中:

myDC.SelectObject(pOldFont); }

return nRet;

編譯並執行新的代碼,可以看到水平滾動條終於能夠自動顯示了。

三、其它問題

在實際應用中,凡是改變列表框內容的函數都可能影響水平滾動條的顯示要求,因而也必須加以定製。但其基本過程——計算文本寬度並按指定大小顯示滾動條等,和上述討論過程是相似的。

CListBox類能夠改變列表內容的方法除AddString()外,還有DeleteString(),InsertString(),ResetContent()。其中InsertString()用於在指定位置插入字符串,在本文討論的主題內它和AddString()是一樣的。

DeleteString()刪除一個字符串,在派生類中其參考代碼如下:

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) // 顯示水平滾動條
{
ShowScrollBar(SB_HORZ, TRUE);
SetHorizontalExtent(m_nMaxWidth);
}
else
{
ShowScrollBar(SB_HORZ, FALSE);
}
return nRet;
}

ResetContent()用於清除列表框的全部內容。在派生類中其參考代碼如下:

void CDJListBox::ResetContent()
{
CListBox::ResetContent();

m_nMaxWidth = 0;
SetHorizontalExtent(0);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章