场景
- 在做
Win32/WTL
开发时,CListViewCtrl
控件是常用的表格控件。有时候自绘listview
时,需要在单元格上绘制小图标,并且小图标能响应鼠标点击的操作。 那么如何实现判断是否点击了小图标呢?
说明
-
要响应点击单元格上的小图标,那么需要判断点击的坐标
POINT
是否在小图标的区域CRect
里。 坐标很容易得到,点击消息都会带坐标值。 小图标区域需要获取单元格所在区域,之后在通过相对位置获取小图标的区域。也就是只要获取点击的单元格区域就可以获取小图标区域。所以下边来看看如何获取点击的单元格所在区域。 -
listview
要响应点击操作,可以使用传统的WM_LBUTTONUP
消息,也可以使用NM_CLICK
通知。 -
如果是响应
WM_LBUTTONUP
消息,可以使用CListViewCtrl.SubItemHitTest()
方法,它通过鼠标点击坐标来计算所在的行和列数值nItem
和info.iSubItem
[3]。LVHITTESTINFO info = {0}; info.pt = lpnmlv->ptAction; auto nItem = listview_.SubItemHitTest(&info);
-
如果是响应
NM_CLICK
通知,它的lParam
就可以强制转换为LPNMITEMACTIVATE
指针类型[2]。这个类型里可以使用它的成员iItem
行,iSubItem
列和ptAction
点击坐标。 通过行和列值可以通过listview
方法GetSubItemRect
获取所在单元格的区域[1]。 注意:这个方法只能获取第一列以上的区域,传递第0
列获取的是整行的区域,所以要获取第0
列的区域,还需要使用方法GetColumnWidth(0)
来进行换算。
listview_.GetSubItemRect(p->iItem,p->iSubItem,LVIR_BOUNDS,&rect); // 第1列以上
if(!p->iSubItem){
// 第0列计算
auto width = listview_.GetColumnWidth(0);
rect.right = rect.left+width;
}
例子
View.h
// View.h : interface of the CView class
//
/
#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()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
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)
{
auto p = (LPNMITEMACTIVATE) pnmh;
int row = p->iItem;
if(row == -1)
return 0;
CRect rect;
// The one-based index of the subitem.
// LVM_GETSUBITEMRECT message (Commctrl.h) - Win32 apps | Microsoft Learn https://learn.microsoft.com/en-us/windows/win32/controls/lvm-getsubitemrect
listview_.GetSubItemRect(p->iItem,p->iSubItem,LVIR_BOUNDS,&rect);
if(!p->iSubItem){
auto width = listview_.GetColumnWidth(0);
rect.right = rect.left+width;
}
auto header = listview_.GetHeader();
int nColumnCount = header.GetItemCount();
wstringstream wss;
static wchar_t buf[MAX_PATH];
memset(buf,0,sizeof(buf));
listview_.GetItemText(row,p->iSubItem,buf,MAX_PATH);
wss << buf << L": Rect:[" <<
rect.left << L"," <<
rect.top << L"," <<
rect.Width() << L"," <<
rect.Height() << L"] Position:[" <<
p->ptAction.x << L"," << p->ptAction.y << L"]";
wstring result = wss.str();
MessageBox(result.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 = {0};
info.pt = lpnmlv->ptAction;
auto nItem = listview_.SubItemHitTest(&info);
if(nItem == -1)
return 0;
static int kShowTooltipColumn = 2;
static wchar_t buf[MAX_PATH] = {0};
if(info.iSubItem == kShowTooltipColumn){
listview_.GetItemText(nItem,info.iSubItem,buf,MAX_PATH);
tooltip_.UpdateTipText(buf,listview_);
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);
}
项目资源
https://download.csdn.net/download/infoworld/87881180
图示
参考
-
LVM_GETSUBITEMRECT message (Commctrl.h)
-
NM_CLICK (list view) notification code (Commctrl.h)
-
LVM_SUBITEMHITTEST message (Commctrl.h)