::視窗控制項 清單方塊 List Boxes篇::
清單方塊屬於視窗控制元件之一,主要的功能是提供一個視窗,在其上顯示項目供使用者選擇。這些顯示的項目能以是文字或是bitmaps格式的圖片(也可二者同時)的形式呈現。如果清單方塊的視窗無法同時顯示所有項目,還可以提供捲軸讓使用者依需要上下捲動以顯示更多的清單項目。
就像學校的考試為了鑑別考生的學習程度而在選擇題作答方式上有單選與複選的差別,應用程式對於使用者在項目選擇的方式與過程上同樣必須反映單選與複選的差異,因此Win32 API函式庫提供二種清單方塊的選擇風格。
單選當然就是使用者在同一時間內只能選擇一個項目,而複選則沒有限制,作業系統還提供其他外觀以及操作上不同的風格用來適應各種不同的需求,以下是清單方塊可以選擇的不同風格: LBS_DISABLENOSCROLL LBS_EXTENDEDSEL LBS_HASSTRINGS LBS_MULTICOLUMN LBS_MULTIPLESEL LBS_NODATA LBS_NOINTEGRALHEIGHT LBS_NOREDRAW LBS_NOSEL LBS_NOTIFY LBS_OWNERDRAWFIXED LBS_OWNERDRAWVARIABLE LBS_SORT LBS_STANDARD LBS_USETABSTOPS LBS_WANTKEYBOARDINPUT 在應用程式中使用清單方塊,主要的問題來自二個部分:一是如何讓使用者確認所選取的項目,二是讓應用程式可以接收到使用者所選取的項目(這也是高階輸入設備的目的)。
通常清單方塊中會有許多不同的選項,不論選項的數量多寡,選項都會處在二種狀態-選取或未選取之一,就如同答案卷會要求以黑色2B鉛筆塗滿方格一般,清單方塊同樣必須以使用者視覺以及操作的角度來提供辨識清單項目在二種狀態變換時的顯示方式,以典型的清單方塊為例:被選取的項目會在視覺上會產生變化(通常是背景顏色改變),使用者可以透過這些變化來區隔其他未獲選取的項目,這些處理的細節都已經由作業系統在清單方塊類別內定義好了,除非在建立清單方塊時選擇OWNERDRAW的風格,否則程式設計師不需另行處理這個問題。
不同於外觀的部分是由清單方塊已經預先處理好,對程式設計師而言,如何處理使用者操作的問題是比較複雜而難處理的問題。
我們可以假設清單方塊就像一張採購的清單,在展示給使用者選擇之前,首先要如何在清單上新增項目供其選擇?
還記得視窗的訊息架構嗎?清單方塊也是視窗,所以也具有自己的訊息佇列,當我們需要清單方塊執行特定的工作時,只需要發送特定的訊息給清單方塊的視窗訊息佇列,其餘的部分清單方塊會自行處理,以下是清單方塊的訊息列表: LB_ADDFILE LB_ADDSTRING LB_DELETESTRING LB_DIR LB_FINDSTRING LB_FINDSTRINGEXACT LB_GETANCHORINDEX LB_GETCARETINDEX LB_GETCOUNT LB_GETCURSEL LB_GETHORIZONTALEXTENT LB_GETITEMDATA LB_GETITEMHEIGHT LB_GETITEMRECT LB_GETLOCALE LB_GETSEL LB_GETSELCOUNT LB_GETSELITEMS LB_GETTEXT LB_GETTEXTLEN LB_GETTOPINDEX LB_INITSTORAGE LB_INSERTSTRING LB_ITEMFROMPOINT LB_RESETCONTENT LB_SELECTSTRING LB_SELITEMRANGE LB_SELITEMRANGEEX LB_SETANCHORINDEX LB_SETCARETINDEX LB_SETCOLUMNWIDTH LB_SETCOUNT LB_SETCURSEL LB_SETHORIZONTALEXTENT LB_SETITEMDATA LB_SETITEMHEIGHT LB_SETLOCALE LB_SETSEL LB_SETTABSTOPS LB_SETTOPINDEX LBN_DBLCLK LBN_ERRSPACE LBN_KILLFOCUS LBN_SELCANCEL LBN_SELCHANGE LBN_SETFOCUS 根據清單方塊的訊息列表,我們可以知道發送LB_ADDSTRING訊息並且提供足夠的參數,會讓清單方塊增加一個字串形式的項目到清單方塊中,現在問題只剩如何將訊息發送給清單方塊的視窗訊息佇列。
利用SendMessage()函式可以發送特定的訊息給指定的視窗訊息佇列,以下是SendMessage()函式的原型: LRESULT SendMessage(
HWND hWnd,// 接收訊息的視窗代碼
UINT Msg,// 發送的訊息
WPARAM wParam,// 訊息的第一個參數
LPARAM lParam // 訊息的第二個參數
);
當應用程式呼叫SendMessage()函式發送訊息時,作業系統首先檢查hWnd參數,找到參數所對應的視窗接著將Msg訊息以及wParam,lParam放到該視窗的訊息佇列中,接下來就由視窗程序處理該執行的工作。
使用者在清單方塊上選擇某些項目或是取消選擇項目等等動作都會觸發清單方塊發送訊息,通知主視窗發生操作者或是清單方塊狀態改變的特定事件,這些訊息都會以"通報訊息" Notification Messages的方式,發送給主視窗的視窗程序。
所謂的"通報訊息"也就是將特定的訊息(已經預先定義好)附加在WM_COMMAND訊息中的wParam參數的高字組,用來進一步區分產生WM_COMMAND訊息的控制項以及觸發的事件。
清單方塊有以下的通報訊息: LBN_DBLCLK 使用者雙擊清單方塊內的項目 LBN_ERRSPACE 清單方塊沒有足夠的記憶體空間 LBN_KILLFOCUS 使用者將輸入焦點轉移出清單方塊 LBN_SELCANCEL 使用者取消選取某個清單方塊的項目 LBN_SELCHANGE 使用者改變所選取的項目 LBN_SETFOCUS 使用者將輸入焦點轉移至清單方塊 現在可以來看看範例程式,如何建立一個清單方塊,然後填入幾個項目,供使用者選擇。 //-------------------start windows_list.cpp---------------
#include < windows.h >
#include < Winuser.h >
const char* szAppName = "MyWndClass";
const intnList_ID = 1; //清單方塊編號
const intnBtn_ID = 2; //關閉視窗按鈕編號
charszText[] = "視窗控制元件-清單方塊";
int nIndex; //清單方塊內項目的索引值
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nTopXY(UINT, UINT);
//程式進入點
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
int wnd_W;
int wnd_H;
wnd_W=400;
wnd_H=300;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "視窗類別登記失敗!", "發生錯誤!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
szAppName,
"視窗控制元件-清單方塊篇",
WS_OVERLAPPEDWINDOW,
nTopXY(wnd_W, GetSystemMetrics(SM_CXSCREEN)),
nTopXY(wnd_H, GetSystemMetrics(SM_CYSCREEN)),
wnd_W,wnd_H,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "視窗建立失敗!", "發生錯誤!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND hList;
HWND hBtn;
HDChMyDC;
PAINTSTRUCTps;
intnBX,nBY; //按鈕的座標
intnBW,nBH; //按鈕的寬高
intnLX,nLY; //清單方塊的座標
intnLW,nLH; //清單方塊的寬高
//初始化清單方塊的位置及大小
nLX = 10;
nLY = 10;
nLW = 100;
nLH = 80;
//初始化按鈕的位置及大小
nBX = 10;
nBY = (10+nLH+10);
nBW = 80;
nBH = 30;
//清單項目
char *pItem1="台北";
char *pItem2="台中";
char *pItem3="台南";
char *pItem4="高雄";
//字元陣列
char szBuffer[30];
switch(msg)
{
case WM_CREATE:
//建立清單方塊
hList = CreateWindowEx(
WS_EX_CLIENTEDGE,
TEXT("LISTBOX"),
"清單方塊",
WS_CHILD | LBS_STANDARD,
nLX,
nLY,
nLW,nLH,
hwnd,(HMENU) nList_ID,
((LPCREATESTRUCT) lParam)->hInstance, NULL);
//填四個清單項目
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem1);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem2);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem3);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem4);
if(hList == NULL) {
MessageBox(NULL, "建立清單方塊失敗!", "發生錯誤!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
//建立關閉視窗按鈕
hBtn = CreateWindowEx(
WS_EX_CLIENTEDGE,
TEXT("BUTTON"),
"關閉視窗",
WS_CHILD | BS_DEFPUSHBUTTON ,
nBX,
nBY,
nBW,nBH,
hwnd,
(HMENU) nBtn_ID,
((LPCREATESTRUCT) lParam)->hInstance,
NULL);
if(hBtn == NULL) {
MessageBox(NULL, "按鈕建立失敗!", "發生錯誤!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
//顯示視窗控制元件
ShowWindow(hList, SW_SHOWNORMAL);
ShowWindow(hBtn, SW_SHOWNORMAL);
break;
case WM_COMMAND:
//過濾觸發WM_COMMAND訊息的控制項
switch(LOWORD(wParam)){
case nList_ID:
//過濾使用者選擇清單項目
if ((HIWORD(wParam)) ==LBN_SELCHANGE){
hList = GetDlgItem(hwnd, nList_ID);
nIndex=SendMessage(hList, LB_GETCURSEL, 0, 0);
SendMessage(hList,LB_GETTEXT,nIndex,(LPARAM) szBuffer);
wsprintf(szText,"選取的是第%d項:%s",nIndex,szBuffer);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case nBtn_ID:
SendMessage(hwnd,WM_CLOSE,0,0);
break;
}
break;
case WM_PAINT:
hMyDC=BeginPaint(hwnd, &ps);
TextOut(hMyDC,100,100,szText,lstrlen(szText));
EndPaint(hwnd, &ps);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
int nTopXY(UINT wnd_XY, UINT wnd_Dim)
{
int resl;
resl=((wnd_Dim/2)-(wnd_XY/2));
return resl;
}
//-----------------end---------------------
//清單項目
char *pItem1="台北";
char *pItem2="台中";
char *pItem3="台南";
char *pItem4="高雄";
因為LB_ADDSTRING訊息的參數需要指向儲存清單項目的字串指標,所以這裡宣告了四個項目的指標,在稍後新建完清單方塊時可以使用。
//字元陣列
char szBuffer[30];
為了取回使用者所選取的清單項目,先宣告一個字元陣列稍後在視窗程序中可以使用。
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem1);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem2);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem3);
SendMessage(hList,LB_ADDSTRING,0,(LPARAM) pItem4);
呼叫SendMessage()函式並且加入四個清單項目 case WM_COMMAND:
//過濾觸發WM_COMMAND訊息的控制項
switch(LOWORD(wParam)){
case nList_ID:
//過濾使用者選擇清單項目
if ((HIWORD(wParam)) ==LBN_SELCHANGE){
hList = GetDlgItem(hwnd, nList_ID);
nIndex=SendMessage(hList, LB_GETCURSEL, 0, 0);
SendMessage(hList,LB_GETTEXT,nIndex,(LPARAM) szBuffer);
wsprintf(szText,"選取的是第%d項:%s",nIndex,szBuffer);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case nBtn_ID:
SendMessage(hwnd,WM_CLOSE,0,0);
break;
}
break;
清單方塊的訊息處理比較迂迴曲折,必須透過幾個不同的步驟才能完成,首先是位在在視窗程序中的WM_COMMAND區塊以LOWORD(wParam)設置條件,過濾觸發WM_COMMAND訊息的視窗控制元件,再以HIWORD(wParam)條件解析存放於WM_COMMAND訊息中的視窗控制項"通報訊息"。
if ((HIWORD(wParam)) ==LBN_SELCHANGE)
使用者在清單方塊上的不同操作都可能觸發WM_COMMAND訊息,所以必需再檢查視窗控制項的"通報訊息",以確定使用者是因為選擇了某個清單選項而觸發WM_COMMAND訊息。
hList = GetDlgItem(hwnd, nList_ID);
nIndex=SendMessage(hList, LB_GETCURSEL, 0, 0);
SendMessage(hList,LB_GETTEXT,nIndex,(LPARAM) szBuffer);
GetDlgItem()函式可以利用視窗控制元件的編號取回元件的視窗代碼,當獲得視窗代碼之後就能以SendMessage()函式發送訊息,要求元件完成特定的工作,以下是GetDlgItem()函式的原型: HWND GetDlgItem(
HWND hDlg,// 視窗控制元件所屬的主視窗代碼
int nIDDlgItem // 元件編號
);
在取得視窗代碼之後,我們可以呼叫SendMessage()函式將LB_GETCURSEL訊息傳送給清單方塊,就能獲得使用者選取的清單項目索引(0基索引,第一個項目的索引值為0),以下為LB_GETCURSEL訊息的原型。
LB_GETCURSEL
wParam = 0; // 未使用; 必須為0
lParam = 0; // 未使用; 必須為0
呼叫SendMessage()函式將LB_GETTEXT訊息發送給清單方塊,並將儲存清單項目的字元陣列以及剛獲得的索引值一併以參數的形式傳送給清單方塊。
當清單方塊接收到LB_GETTEXT訊息之後,就會將索引值所指定的清單項目(也就是使用者選取的項目)填寫在字元陣列中,並且顯示視窗上,以下為LB_GETTEXT訊息的原型: LB_GETTEXT
wParam = (WPARAM) index;// 要取回文字的項目索引值
lParam = (LPARAM) (LPCTSTR) lpszBuffer; // 字元陣列的指標
case nBtn_ID:
SendMessage(hwnd,WM_CLOSE,0,0);
break;
這個練習重新定義了按鈕的行為,當使用者點擊後會發送WM_CLOSE訊息給主視窗,進而結束視窗應用程式。
"讓物件自己處理本身的問題"
視窗控制元件本身就具備處理訊息以及外觀變化上大部分的工作,程式設計師只要把焦點放在與使用者有關的細節上,這種物件概念的設計方式讓繁瑣的視覺處理變得比較單純,程式在偵錯上或功能的改進上也可以比較容易歸納出問題發生的位置,當元件引用的數量增加,訊息開始熱鬧時,這種設計方式就更顯得簡單而明確。
by Jack 2004/1/14