// HitTestEx
- Determine the row index and column index for a point
// Returns
- the row index or -1 if point is not over a row
// point
- point to be tested.
// col
- to hold the column index
int CMyListCtrl::HitTestEx(CPoint &point, int *col) const
{
int
colnum = 0;
int
row = HitTest( point, NULL );
if
( col ) *col = 0;
// Make sure that the ListView is in LVS_REPORT
if
( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
return
row;
// Get the top and bottom row visible
row = GetTopIndex();
int
bottom = row + GetCountPerPage();
if
( bottom > GetItemCount() )
bottom = GetItemCount();
// Get the number of columns
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int
nColumnCount = pHeader->GetItemCount();
// Loop through the visible rows
for
( ;row <= bottom;row++)
{
// Get bounding rect of item and check whether point falls in it.
CRect rect;
GetItemRect( row, &rect, LVIR_BOUNDS );
if
( rect.PtInRect(point) )
{
// Now find the column
for
( colnum = 0; colnum < nColumnCount; colnum++ )
{
int
colwidth = GetColumnWidth(colnum);
if
( point.x >= rect.left
&& point.x <= (rect.left + colwidth ) )
{
if
( col ) *col = colnum;
return
row;
}
rect.left += colwidth;
}
}
}
return
-1;
}
CListCtrl 類成員
CListCtrl::HitTest
int HitTest(LVHITTESTINFO* pHitTestInfo) const
int HitTest(CPoint pt,UINT* pFlags=NULL) const
返回值:
返回參數pHitTestInfo
指定位置的項的索引,否則爲-1
。
參數: pHitTestInfo
含有要進行擊中測試的位置以及接受擊中測試有關結果信息的LVHITTESTINFO
結構的地址。
pt
被測試的指針。
pFlags
指向接受測試結果信息的整數的指針。請參閱聯機文檔“
平臺SDK”
中有關LVHITTESTINFO
結構的flags
成員的註解。
說明:
如果有,則決定哪一個列表視圖項在指定的位置上。
可以通過使用結構中flags
成員的LVHT_ABOVE,
LVHT_BELOW,LVHT_TOLEFT
以及LVHT_TORIGHT
的值來決定是否滾動列表視圖控件的內容。上述兩種標誌可以自由組合,例如,假設其位於客戶區域的左上角。
可以通過測試結構中flags
成員的LVHT_ONITEM
值來決定是否給定的位置位於列表視圖項的上方。該數值通過結構flags
成員中的 LVHT_ONITEMICON
,LVHT_ONITEMLABEL,LVHT_ONITEMSTATEICON
的值的位或運算而獲取。
HitTest :得到當前鼠標位置的Item
其實關鍵是要有ScreenToClient 這個函數的使用,我先前沒有用這個函數,HitTest 老是返回-1, 搞得我都頭大了。不過這個不能用於SubItem, 那應該要用SubItemHitTest
LVHITTESTINFO ht ;
GetCursorPos(&(ht.pt)) ;
m_friendList.ScreenToClient(&ht.pt) ;
m_friendList.HitTest(&ht) ;
if(ht.iItem == -1) // 檢查是否有item 選中
return ;
SubItemHitTest :
void CTest6Dlg::OnClickList1(NMHDR* pNMHDR,
LRESULT* pResult)
{
/****************************************/
/*
確定單擊的listctrl
的行列號
方法1 */
/****************************************/
/*
DWORD dwPos = GetMessagePos();
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
m_list.ScreenToClient(&point);
LVHITTESTINFO lvinfo;
lvinfo.pt = point;
lvinfo.flags = LVHT_ABOVE;
int nItem = m_list.SubItemHitTest(&lvinfo);
if(nItem != -1)
{
CString strtemp;
strtemp.Format( "
單擊的是第%d
行第%d
列 ", lvinfo.iItem, lvinfo.iSubItem);
}
*pResult = 0;
}
雙擊時鼠標所在的位置。下面我們就再來爲
CScheduleView
添加一個處理
NM_DBLCLK
通知消息的處理函數
OnDblclk()
,並輸入下面的實現代碼:
void CScheduleView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult) {
if( ((LPNMLISTVIEW) pNMHDR)->iItem != -1 )
GetDocument()->OnEdittask();
*pResult = 0;
}
這個函數比較有意思,它的第一個參數本來是一個指向
NMHDR
結構的指針,然而在函數體中我們卻將之強制轉換成指向一個
NMLISTVIEW
結構的指針,爲什麼呢?原因是
4.71
版及更高版本的
List
控件(
IE 4.0
之後的
List
控件都滿足版本要求)在發送通知消息
NM_DBLCLK
的時,實際上傳遞的是
一個
NMLISTVIEW
結構的指針,但標準的
NM_DBLCLK
消息傳遞的卻是
NMHDR
結構的指針,
ClassWizard
在生成函數框架時是按照標
準消息格式來處理的。
NMLISTVIEW
結構除了在開頭部分包含了一個
NMHDR
結構外,它還提供了被雙擊項目的相關信息,如果它的
iItem
成員等於
-1
,則表示用戶不是對
着一個項目雙擊的,如果是這樣的話,我們也就不必調用
OnEdittask()
,從而避免了出現不必要的操作提示信息。實際上,
OnLButtonDblClk()
也可以實現
OnDblclk()
的這個功能,但需要將相關代碼改爲:
if( GetListCtrl().GetSelectedCount() == 1 )
GetDocument()->OnEdittask();
現在這兩個函數的功能就基本一致了,朋友們可以任意保留其中一個函數,而把另一個函數刪掉,當然,今後在編寫其它程序時
OnLButtonDblClk()
和
OnDblclk()
就不一定能做到等價了。另外,大家不妨進一步研究一下
WM_LBUTTONDBLCLK
和
NM_DBLCLK
之間的關係,解釋爲什麼用戶的一次雙
擊能引發這兩種消息,它們的發生順序又如何等等。
接下來我們再次利用
ClassWizard
爲
CScheduleView
添加一個處理
WM_RBUTTONDOWN
消息的函數
OnRButtonDown()
,準備在其中彈出一個關聯菜單。
編輯和使用彈出式菜單
關聯菜單實質上是一種彈出式菜單,通常情況下,彈出式菜單相當於主菜單的一個子菜單項。我們打開資源編輯器,添加一個新的菜單資源,修改其
ID
爲
IDR_TASKMENU
,然後在第一個頂級菜單項下面添加三個命令,它們與程序主菜單的
"
安排
"
子菜單下面的命令完全一樣,包括
ID
、
Caption
和
Prompt
。編輯完畢後,以
Popup
方式來查看
IDR_TASKMENU
,其效果應如圖
18-1
所示。
現在我們來爲
OnRButtonDown()
編寫實現代碼:
void CScheduleView::OnRButtonDown(UINT nFlags, CPoint point) {
CMenu tmpMenu;
CMenu* pSubMenu;
tmpMenu.LoadMenu(IDR_TASKMENU); //
裝載菜單資源
pSubMenu=tmpMenu.GetSubMenu(0); //
獲得子菜單的指針
ClientToScreen(&point); //
將客戶區的座標位置轉換成屏幕座標位置
pSubMenu->TrackPopupMenu( TPM_RIGHTBUTTON|TPM_LEFTALIGN|TPM_TOPALIGN,
point.x, point.y, this,0 ); //
顯示彈出式菜單
tmpMenu.DestroyMenu(); //
釋放菜單資源
}
在上面的代碼中,
LoadMenu()
用於裝載菜單資源
IDR_TASKMENU
,由於我們使用了局部變量來存放不屬於任何一個窗口的菜單對象,所以在退
出
OnRButtonDown()
函數之前必須釋放相應的菜單資源,否則多次調用該函數之後,程序將會佔掉不少的系統資源。
ClientToScreen()
的作用是將客戶區的座標位置轉換成屏幕座標位置,因爲
WM_RBUTTONDOWN
消息中的鼠標位置參數是相對於客戶區
的,而
TrackPopupMenu()
函數的參數必須是基於屏幕座標系的,如果不經過一次轉換,菜單彈出的位置就不知道會偏到哪裏去了。
現在我們來運行一下
Schedule
,在客戶區內單擊鼠標右鍵,就會彈出如圖
18-2
所示的關聯菜單,選擇命令之後它就會調用相應的處理函數。
到本講爲止,
Schedule
的編程工作也就要告一段落了,
Schedule
只是心鈴用來講解
VC
編程的一個實例,它的功能很單一,但仍有一定的實用價
值。已經用慣了各種優秀軟件的朋友肯定不會滿足於現在的
Schedule
,因爲它現在只是一隻醜小鴨而已,要把它變成一隻天鵝還需要我們更多的辛
勤勞動,心鈴在下面列出了一些可能的改進,希望已經喜愛上
VC
編程的朋友們自已動手來進一步修飾
Schedule
。
1
.刪除
Schedule
中一些不必要的菜單命令和工具欄上的按鈕;
2
.根據用戶選中事件條目的情況自動改變某些菜單命令的有效狀態,例如沒有選中任何條目時,
"
編輯條目
"
應處於不能選擇的無效狀態,它所對應的工具按鈕也應無效;
3
.讓
Schedule
能夠象網絡螞蟻那樣在最小化時隱藏自己的窗口,在任務欄的通知區域內放置一個圖標,並設計相關的菜單;
4
.修改
Schedule
的用戶界面,設計更好的圖標和位圖;
5
.修改添加和編輯事件條目的對話框,讓用戶能更直觀地設置時間;
6
.設計一個啓動畫面,把某些選項保存到註冊表之中;
7
.爲
Schedule
增添打印功能;
… …
等等
CListCtrl 幾點經驗
每個List
控件都有一個CHeaderCtrl
。且它的ID
是0
。
CHeaderCtrl* pHeader =(CHeaderCtrl*)m_listCtrl.GetDlgItem(0);
即使List
控件非report
模式,Header
控件也存在,只是此時它的尺寸爲0
。
可利用以下代碼使得控件的第一列自適應大小:
m_listctrl.SetColumnWidth( 0, LVSCW_AUTOSIZE );
List
控件中的圖標初始化時可如下設置:
m_listCtrl.InsertItem( LVIF_TEXT | LVIF_IMAGE, nRow, sItemText, 0, 0, nImage
, NULL);
在運行中需動態改變可調用SetItem()
函數.
m_listCtrl.SetItem( 0, 0, LVIF_IMAGE, NULL, nImage, 0, 0, 0 );
刪去圖標可將nImage
設置爲-1
。
以下代碼可將列表框的第一列限定大小:
BOOL CMyListCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
HD_NOTIFY *pHDN = (HD_NOTIFY*)lParam;
if((pHDN->hdr.code == HDN_BEGINTRACKW || pHDN->hdr.code == HDN_BEGIN
TRACKA)
&& pHDN->iItem == 0) // Prevent only first (col#
0) from resizing
{
*pResult = TRUE; // disable tracking
return TRUE; // Processed message
}
return CListCtrl::OnNotify(wParam, lParam, pResult);
}
選定cell
CListCtrl::OnClick(...)
{
int column;
CRect m_rect;
//the function below is provided in CListCtrl inPlace editing
int index = GetRowColumnIndex(point, &column);
if(index == -1)return;
int offset = 0;
for(int i = 0; i < column; i++)
offset += GetColumnWidth(i);
//Get the rectangle of the label and the icon
GetItemRect(index, &m_rect, LVIR_BOUNDS);
m_rect.left += offset + 4;
//Get the columnWidth of the selected column
m_rect.right = m_rect.left + GetColumnWidth(column);
Update(index);
CClientDC dc(this); //this is the pointer of the current view
dc.DrawFocusRect(m_rect);
}
Select NonFirst cell:
void CMyListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
CListCtrl::OnLButtonDown(nFlags, point);
int index;
point.x = 2;
if( ( index = HitTest( point, NULL )) != -1 )
{
SetItemState( index, LVIS_SELECTED | LVIS_FOCUSED ,
LVIS_SELECTED | LVIS_FOCUSED);
}
}
HitTestEx
的代碼如下:
判斷點擊的是哪一列:
// HitTestEx - Determine the row index and column index for a point
// Returns - the row index or -1 if point is not over a row
// point - point to be tested.
// col - to hold the column index
int CMyListCtrl::HitTestEx(CPoint &point, int *col) const
{
int colnum = 0;
int row = HitTest( point, NULL );
if( col ) *col = 0;
// Make sure that the ListView is in LVS_REPORT
if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
return row;
// Get the top and bottom row visible
row = GetTopIndex();
int bottom = row + GetCountPerPage();
if( bottom > GetItemCount() )
bottom = GetItemCount();
// Get the number of columns
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
// Loop through the visible rows
for( ;row <=bottom;row++)
{
// Get bounding rect of item and check whether point falls in it.
CRect rect;
GetItemRect( row, &rect, LVIR_BOUNDS );
if( rect.PtInRect(point) )
{
// Now find the column
for( colnum = 0; colnum < nColumnCount; colnum++ )
{
int colwidth = GetColumnWidth(colnum);
if( point.x >= rect.left
&& point.x <= (rect.left + colwidth ) )
{
if( col ) *col = colnum;
return row;
}
rect.left += colwidth;
}
}
}
return -1;
}
選中一定範圍內的Item
// SelItemRange - Selects/Deselect a range of items
// Returns - The number of new items selected
// bSelect - TRUE to select, FALSE to deselect
// nFirstItem - index of first item to select
// nLastItem - index of last item to select
int CMyListCtrl::SelItemRange(BOOL bSelect, int nFirstItem, int nLastItem)
{
// make sure nFirstItem and nLastItem are valid
if( nFirstItem >= GetItemCount() || nLastItem >= GetItemCount() )
return 0;
int nItemsSelected = 0;
int nFlags = bSelect ? 0 : LVNI_SELECTED;
int nItem = nFirstItem - 1;
while( (nItem = GetNextItem( nItem, nFlags )) >=0
&& nItem <= nLastItem )
{
nItemsSelected++;
SetItemState(nItem, bSelect ? LVIS_SELECTED : 0, LVIS_SELECT
ED );
}
return nItemsSelected;
}
SetHeaderBitmap:
static int _gnCols = 5;
static int _gnColSize[] =
{
18,21,22,18,380
};
static CString _gcsColLabel[] =
{
_T("x"),
_T("x"),
_T("x"),
_T("x"),
_T("Description:")
};
void CRightView::BuildColumns()
{
// Insert the columns into the list control
LV_COLUMN lvCol;
lvCol.mask = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
for (int i = 0; i < _gnCols; ++i)
{
lvCol.iSubItem = i;
lvCol.pszText = (char*)(LPCTSTR)_gcsColLabel;
lvCol.cx = _gnColSize;
lvCol.fmt = LVCFMT_LEFT;
m_ListCtrl->InsertColumn(i, &lvCol);
}
for (int x = 0; x < _gnCols-1; x++)
SetHeaderBitmap(x, nHeaderBmps[x], HDF_STRING);
}
void CRightView::SetHeaderBitmap(int nCol, int nBitmap, DWORD dwRemove)
{
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
HD_ITEM hdi;
hdi.mask = HDI_FORMAT;
pHeader->GetItem (nCol, &hdi);
hdi.mask = HDI_BITMAP | HDI_FORMAT;
hdi.fmt |= HDF_BITMAP;
hdi.hbm = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
MAKEINTRESOURCE(nBitmap),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
if (dwRemove)
hdi.fmt &= ~dwRemove;
pHeader->SetItem (nCol, &hdi);
}