程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> 為CListBox加上智能水平滾動條

為CListBox加上智能水平滾動條

編輯:C#基礎知識

在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);
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved