[ATL/WTL]_[初级]_[在ListView指定的单元格显示ToolTip]

场景

  1. WTLCListViewCtrl是常用的显示表格数据的类. 有时候我们需要在表格的某个单元格显示一个按钮,并且鼠标移动上去时按钮有状态变化;或者显示这个按钮的一个ToolTip提示。如何做?

说明

  1. 要在表格的指定Cell或者某个鼠标位置发生消息响应。最后选的方案就是子类化CListViewCtrl,并响应WM_MOUSEMOVE消息,之后就是在这个消息里通过得到的坐标点计算得出nItem行和nSubItem列. 再来处理现实显示单元格内的按钮或ToolTip问题。但是这种方案需要在CListViewCtrl子类里实现。

  2. 除了改动子类外,可以查阅ListView的消息和通知看有没有相关的可以在父窗口捕抓的消息。有一个通知LVN_HOTTRACK就可以在父窗口捕抓,并且它提供了参数NMLISTVIEW,这个类型的iItem的值有时候会是-1,而iSubItem也有可能得到0,所以这个两个成员变量不需要。可以通过CListViewCtrl.SubItemHitTest方法来获取指定坐标所属的单元格,它需要提供一个LVHITTESTINFO的参数,获取成功后会在iItemiSubItem里得到正确的单元格位置。

    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;
    
  3. 关于CListViewCtrl响应ToolTip,它提供了一个SetToolTips方法,我们只需要创建一个CToolTipCtrl并初始化即可使用. 当不需要显示ToolTip时,可以通过SetToolTips(NULL)来关闭, 可以通过这个特性来开关ToolTip.

    tooltip_.Create(listview_,NULL,NULL,TTS_NOPREFIX);
    tooltip_.AddTool(listview_,L"");
    listview_.SetToolTips(tooltip_);
    
  4. 最后就是在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

  1. 代码
    #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

  1. 代码
    // 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);
    
    }
    
    

图示

[ATL/WTL]_[初级]_[在ListView指定的单元格显示ToolTip]

项目下载

参考

extended-list-view-styles

lvm-sethovertime

上一篇:javascript-使用IE插件浏览器帮助器对象(BHO)访问iframe中的正文(至少一些数据)


下一篇:C++基于ATL工程编写ActiveX控件步骤