场景
-
WTL
里CListViewCtrl
是常用的显示表格数据的类. 有时候我们需要在表格的某个单元格显示一个按钮,并且鼠标移动上去时按钮有状态变化;或者显示这个按钮的一个ToolTip
提示。如何做?
说明
-
要在表格的指定
Cell
或者某个鼠标位置发生消息响应。最后选的方案就是子类化CListViewCtrl
,并响应WM_MOUSEMOVE
消息,之后就是在这个消息里通过得到的坐标点计算得出nItem
行和nSubItem
列. 再来处理现实显示单元格内的按钮或ToolTip
问题。但是这种方案需要在CListViewCtrl
子类里实现。 -
除了改动子类外,可以查阅ListView的消息和通知看有没有相关的可以在父窗口捕抓的消息。有一个通知LVN_HOTTRACK就可以在父窗口捕抓,并且它提供了参数
NMLISTVIEW
,这个类型的iItem
的值有时候会是-1
,而iSubItem
也有可能得到0
,所以这个两个成员变量不需要。可以通过CListViewCtrl.SubItemHitTest
方法来获取指定坐标所属的单元格,它需要提供一个LVHITTESTINFO
的参数,获取成功后会在iItem
和iSubItem
里得到正确的单元格位置。typedef struct tagNMLISTVIEW { NMHDR hdr; int iItem; int iSubItem; UINT uNewState; UINT uOldState; UINT uChanged; POINT ptAction; LPARAM lParam; } NMLISTVIEW, *LPNMLISTVIEW; typedef struct tagLVHITTESTINFO { POINT pt; UINT flags; int iItem; #if (_WIN32_IE >= 0x0300) int iSubItem; // this is was NOT in win95. valid only for LVM_SUBITEMHITTEST #endif #if _WIN32_WINNT >= 0x0600 int iGroup; // readonly. index of group. only valid for owner data. // supports single item in multiple groups. #endif } LVHITTESTINFO, *LPLVHITTESTINFO;
-
关于
CListViewCtrl
响应ToolTip
,它提供了一个SetToolTips
方法,我们只需要创建一个CToolTipCtrl
并初始化即可使用. 当不需要显示ToolTip
时,可以通过SetToolTips(NULL)
来关闭, 可以通过这个特性来开关ToolTip
.tooltip_.Create(listview_,NULL,NULL,TTS_NOPREFIX); tooltip_.AddTool(listview_,L""); listview_.SetToolTips(tooltip_);
-
最后就是在
LVN_HOTTRACK
处理函数进行判断条件并显示ToolTip
.static int kShowTooltipColumn = 2; if(info.iSubItem == kShowTooltipColumn){ BSTR buf = NULL; listview_.GetItemText(nItem,info.iSubItem,buf); tooltip_.UpdateTipText(buf,listview_); ::SysFreeString(buf); if(!listview_.GetToolTips()) listview_.SetToolTips(tooltip_); }else{ if(listview_.GetToolTips()) listview_.SetToolTips(NULL); }
例子
view.h
- 代码
#pragma once #include <utility> #include <string> enum { kMyStaticId = WM_USER+1, kMyListViewId, kMyCheckListViewId, kMySortListViewId, kMyButtonId }; class CView : public CWindowImpl<CView> { public: DECLARE_WND_CLASS(NULL) BOOL PreTranslateMessage(MSG* pMsg); BEGIN_MSG_MAP_EX(CView) MSG_WM_CREATE(OnCreate) MESSAGE_HANDLER(WM_PAINT, OnPaint) NOTIFY_HANDLER(kMyListViewId,NM_CLICK,OnNMClickListResult) NOTIFY_HANDLER(kMyListViewId,LVN_HOTTRACK,OnListItemHotTrack) REFLECT_NOTIFICATIONS() END_MSG_MAP() int OnCreate(LPCREATESTRUCT lpCreateStruct); LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); void UpdateLayout(); LRESULT OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled); void AddMockData(CListViewCtrl& listview); void OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl); LRESULT OnListItemHotTrack(int idCtrl,LPNMHDR pnmh,BOOL &bHandled); private: std::wstring GetControlText(HWND hwnd,wchar_t* buf = NULL); CListViewCtrl listview_; CFont font_normal_; CFont font_bold_; CBrushHandle brush_white_; CBrushHandle brush_hollow_; CBrush brush_red_; CToolTipCtrl tooltip_; TCHAR strToolTipText_[MAX_PATH]; public: };
View.cpp
- 代码
// View.cpp : implementation of the CView class // / #include "stdafx.h" #include "resource.h" #include <utility> #include <sstream> #include <assert.h> #include "View.h" #include <CommCtrl.h> #include <string> #include <regex> using namespace std; BOOL CView::PreTranslateMessage(MSG* pMsg) { return FALSE; } LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { CPaintDC dc(m_hWnd); CMemoryDC mdc(dc,dc.m_ps.rcPaint); CRect rect_client; GetClientRect(&rect_client); mdc.FillSolidRect(rect_client,RGB(255,255,255)); //TODO: Add your drawing code here return 0; } static HFONT GetFont(int pixel,bool bold,const wchar_t* font_name) { LOGFONT lf; memset(&lf, 0, sizeof(LOGFONT)); // zero out structure lf.lfHeight = pixel; // request a 8-pixel-height font if(bold) { lf.lfWeight = FW_BOLD; } lstrcpy(lf.lfFaceName, font_name); // request a face name "Arial" HFONT font = ::CreateFontIndirect(&lf); return font; } std::wstring CView::GetControlText(HWND hwnd,wchar_t* buf) { auto length = ::GetWindowTextLength(hwnd); bool bufNull = false; if(!buf){ buf = new wchar_t[length+1](); bufNull = true; } ::GetWindowText(hwnd,buf,length+1); std::wstring str(buf); if(bufNull) delete []buf; return str; } static std::wstring GetProductBinDir() { static wchar_t szbuf[MAX_PATH]; GetModuleFileName(NULL,szbuf,MAX_PATH); PathRemoveFileSpec(szbuf); int length = lstrlen(szbuf); szbuf[length] = L'\\'; szbuf[length+1] = 0; return std::wstring(szbuf); } LRESULT CView::OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled) { NMLISTVIEW* p = (NMLISTVIEW*) pnmh; int row = p->iItem; if(row == -1 || (row % 2)) return 0; auto header = listview_.GetHeader(); int nColumnCount = header.GetItemCount(); int minLength = 8; wstringstream wss; for(int i =0; i< nColumnCount; ++i){ BSTR buf = NULL; listview_.GetItemText(row,i,buf); wss << buf << L" : "; ::SysFreeString(buf); } wstring str = wss.str(); MessageBox(str.c_str()); return 0; } void CView::AddMockData(CListViewCtrl& listview) { listview.InsertColumn(0,L"No.",LVCFMT_LEFT,40); listview.InsertColumn(1,L"Name",LVCFMT_LEFT,200); listview.InsertColumn(2,L"Website",LVCFMT_LEFT,200); listview.InsertColumn(3,L"Level",LVCFMT_LEFT,200); wchar_t buf[MAX_PATH] = {0}; LVCOLUMN co; memset(&co,0,sizeof(co)); co.mask = LVCF_TEXT; co.pszText = buf; co.cchTextMax = MAX_PATH; listview.GetColumn(1,&co); std::wstring c0Text(buf); listview.GetColumn(2,&co); std::wstring c1Text(buf); listview.GetColumn(3,&co); std::wstring c2Text(buf); for(int i = 0; i<10;++i){ wsprintf(buf,L"%d",i+1); listview.AddItem(i,0,buf); wsprintf(buf,(c0Text+L"-%d").c_str(),i); listview.AddItem(i,1,buf); wsprintf(buf,(c1Text+L"-%d").c_str(),i); listview.AddItem(i,2,buf); wsprintf(buf,(c2Text+L"-%d").c_str(),i); listview.AddItem(i,3,buf); } } LRESULT CView::OnListItemHotTrack(int idCtrl,LPNMHDR pnmh,BOOL &bHandled) { bHandled = TRUE; LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW) pnmh; // 获取坐标所在的Item(行)和SubItem(列). LVHITTESTINFO info; memset(&info,0,sizeof(info)); info.pt = lpnmlv->ptAction; auto nItem = listview_.SubItemHitTest(&info); if(nItem == -1) return 0; static int kShowTooltipColumn = 2; if(info.iSubItem == kShowTooltipColumn){ BSTR buf = NULL; listview_.GetItemText(nItem,info.iSubItem,buf); tooltip_.UpdateTipText(buf,listview_); ::SysFreeString(buf); if(!listview_.GetToolTips()) listview_.SetToolTips(tooltip_); }else{ if(listview_.GetToolTips()) listview_.SetToolTips(NULL); } return 0; } int CView::OnCreate(LPCREATESTRUCT lpCreateStruct) { font_normal_ = ::GetFont(16,false,L"Arial"); font_bold_ = ::GetFont(16,true,L"Arial"); // 1.创建CListViewCtrl listview_.Create(m_hWnd,0,NULL,WS_CHILD | WS_TABSTOP |WS_VISIBLE |LVS_ALIGNLEFT|LVS_REPORT|LVS_SHOWSELALWAYS|WS_BORDER,0,kMyListViewId); // 2.如果需要监听在listview鼠标移动消息,创建时传入LVS_EX_TRACKSELECT扩展样式。 listview_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_DOUBLEBUFFER|LVS_EX_TRACKSELECT); listview_.SetFont(font_normal_); auto header = listview_.GetHeader(); header.SetFont(font_bold_); listview_.SetBkColor(RGB(255,255,255)); AddMockData(listview_); tooltip_.Create(listview_,NULL,NULL,TTS_NOPREFIX); tooltip_.AddTool(listview_,L""); listview_.SetToolTips(tooltip_); listview_.SetHoverTime(10); brush_hollow_ = AtlGetStockBrush(HOLLOW_BRUSH); brush_white_ = AtlGetStockBrush(WHITE_BRUSH); brush_red_.CreateSolidBrush(RGB(255,0,0)); UpdateLayout(); return 0; } void CView::OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) { } void CView::UpdateLayout() { CRect rect; GetClientRect(&rect); CClientDC dc(m_hWnd); dc.SelectFont(font_normal_); CSize size_control(700,300); CRect rect_control = CRect(CPoint(20,20),size_control); listview_.MoveWindow(rect_control); }