MFC中如何实现CListCtrl单元格可编辑

实现效果

最近接到一个任务,要让ClistCtrl像Excel一样可以编辑,经过一些摸索,最终实现效果如下。
MFC中如何实现CListCtrl单元格可编辑_第1张图片

基本思路

当鼠标单击CListCtrl对象时,控件发出NM_CLICK消息,在这个消息的响应函数中,能够获取用户单击了哪个单元格;然后,动态创建一个CEdit控件,使其和单元格大小、字体、位置、文字完全相同;最后,当用户对编辑框操作完,焦点转向其他控件时,提取CEdit中文字内容,并更新到CListCtrl当中去。
为了集成上述功能,从CListCtrl派生一个自定义类CListCtrlEdit,具体实现代码如下(代码中另外实现了自动弹出提示框功能)。

代码实现

//自定义列表控件头文件
//

#pragma once

class CListCtrlEdit	: public CListCtrl
{
public:
	CListCtrlEdit();
	BOOL EnableTips(BOOL bEnableTips);

protected:
	virtual BOOL PreTranslateMessage(MSG* pMsg);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnEnEdtKillFocus();
	DECLARE_MESSAGE_MAP()

private:
	CEdit m_wndEdt;
	int m_iCurRow;		//鼠标单击的行
	int m_iCurColunm;	//鼠标单击的列
	int m_iHoverRow;	//鼠标悬停的行
	int m_iHoverColumn;	//鼠标悬停的列
	CToolTipCtrl m_wndToolTip;	//提示框控件
	BOOL m_bEnableTips;			//鼠标移动到某个单元格时,是否给出提示
};

以下为自定义类的实现文件

#include "pch.h"
#include "CListCtrlEdit.h"

#define _ID_DYN_CREATE_EDIT_ 10000

CListCtrlEdit::CListCtrlEdit()
	:m_iCurColunm(-1)
	,m_iCurRow(-1)
	,m_bEnableTips(FALSE)
	,m_iHoverColumn(-1)
	,m_iHoverRow(-1)
{}

BOOL CListCtrlEdit::EnableTips(BOOL bEnableTips)
{
	EnableToolTips(TRUE);
	//创建tooltip控件
	m_bEnableTips = m_wndToolTip.Create(this, TTS_ALWAYSTIP);

	if (m_bEnableTips)
	{
		m_wndToolTip.Activate(TRUE);
		m_wndToolTip.SetDelayTime(TTDT_INITIAL, 500);
	}
	return m_bEnableTips;
}

BEGIN_MESSAGE_MAP(CListCtrlEdit, CListCtrl)
	ON_NOTIFY_REFLECT(NM_CLICK, &CListCtrlEdit::OnNMClick)
	ON_EN_KILLFOCUS(_ID_DYN_CREATE_EDIT_,&CListCtrlEdit::OnEnEdtKillFocus)
	ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

void CListCtrlEdit::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码

	//进行单击检测,这个结构已经被扩展为能够适应子项的单击检测。
	LVHITTESTINFO cHitTest;	
	cHitTest.pt = pNMItemActivate->ptAction;
	if (-1 != SubItemHitTest(&cHitTest))	//检测给定坐标位于哪个单元格上
	{
		if (cHitTest.flags & LVHT_ONITEMLABEL)
		{
			m_iCurRow = cHitTest.iItem;
			m_iCurColunm = cHitTest.iSubItem;

			//获取单元格信息
			CRect rect;	//单元格的尺寸
			GetSubItemRect(m_iCurRow, m_iCurColunm, LVIR_LABEL, rect);	//注意,这个坐标是相对于ListCtrl左上角的
			CString sValue = GetItemText(m_iCurRow, m_iCurColunm);	//获取当前单元格的内容

			//动态创建编辑框
			if (!m_wndEdt.m_hWnd)
			{
				m_wndEdt.Create(WS_CHILD | ES_CENTER | ES_AUTOHSCROLL | ES_WANTRETURN | ES_MULTILINE, rect, this, _ID_DYN_CREATE_EDIT_);
				m_wndEdt.SetFont(this->GetFont(), FALSE);		//设置编辑框的字体
			}

			m_wndEdt.SetWindowText(sValue);
			m_wndEdt.MoveWindow(rect);
			m_wndEdt.ShowWindow(SW_SHOW);
			int iLength = sValue.GetLength();
			m_wndEdt.SetSel(0, iLength, FALSE);	//设置光标选中所有文字
			m_wndEdt.SetFocus();
		}
	}

	*pResult = 0;
}

void CListCtrlEdit::OnEnEdtKillFocus()
{
	m_wndEdt.ShowWindow(SW_HIDE);
	if (m_iCurColunm < 0 || m_iCurRow < 0)
		return;

	CString sEdt;
	m_wndEdt.GetWindowText(sEdt);
	SetItemText(m_iCurRow, m_iCurColunm, sEdt);	//更新List Control的单元格显示
}

BOOL CListCtrlEdit::PreTranslateMessage(MSG* pMsg)
{
	//为了让tool tip控件不错过重要消息,比如鼠标单击,必须将该消息转发给你的tool tip控件。最好的办法是在
	//PreTranslateMessage函数中调用CToolTipCtrl::RelayEvent进行转发。
	//以下为MSDN中原文:
	//In order for the tool tip control to be notified of important messages, such as WM_LBUTTONXXX messages, 
	//you must relay the messages to your tool tip control. The best method for this relay is to make a call 
	//to CToolTipCtrl::RelayEvent, in the PreTranslateMessage function of the owner window. 
	//The following example illustrates one possible method (assuming the tool tip control is called m_ToolTip): 
	if (m_bEnableTips)
	{
		if (pMsg->message == WM_LBUTTONDOWN ||
			pMsg->message == WM_LBUTTONUP ||
			pMsg->message == WM_MOUSEMOVE)
		{
			m_wndToolTip.RelayEvent(pMsg);
		}
	}

	return CListCtrl::PreTranslateMessage(pMsg);
}

void CListCtrlEdit::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if (m_bEnableTips)
	{
		//判断当前鼠标所在的位置
		CString str;
		LVHITTESTINFO cHit;
		cHit.pt = point;
		SubItemHitTest(&cHit);
		
		if (cHit.iItem != m_iHoverRow || cHit.iSubItem != m_iHoverColumn)
		{
			//保存当前鼠标所在的行、列
			m_iHoverRow = cHit.iItem;
			m_iHoverColumn = cHit.iSubItem;

			if (m_iHoverRow != -1 && m_iHoverColumn != -1)
			{
				str = GetItemText(m_iHoverRow, m_iHoverColumn);
				m_wndToolTip.AddTool(this, str);
				m_wndToolTip.Pop();		//Removes a displayed tool tip window from the view. 
			}
			else
			{
				m_wndToolTip.AddTool(this, _T("请单击某个单元格进行编辑"));
				m_wndToolTip.Pop();
			}
		}
	}

	CListCtrl::OnMouseMove(nFlags, point);
}

在主对话框资源编辑器中添加一个CListCtrl控件,并在控件属性表中将其改为Report类型,最后在对话框头文件中将该控件类型修改为

public:
	CListCtrlEdit m_wndLstEdit;

之后,在主对话框的OnInitDialog函数中完成对列表控件的初始化。

BOOL CEditableListCtrlDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	CRect rect;
	m_wndLstEdit.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE);
	m_wndLstEdit.GetClientRect(rect);
	m_wndLstEdit.InsertColumn(0, _T("项目"), LVCFMT_RIGHT, rect.Width() / 2, 0);
	m_wndLstEdit.InsertColumn(1, _T("值"), LVCFMT_CENTER, rect.Width() / 2, 1);

	//这个地方特别注意,CListCtrl中第1列无法直接设置对齐样式,需要用以下方法
	LVCOLUMN lvc;
	lvc.mask = LVCF_FMT;
	m_wndLstEdit.GetColumn(0, &lvc);
	lvc.fmt &= ~LVCFMT_JUSTIFYMASK;
	lvc.fmt |= LVCFMT_CENTER;
	m_wndLstEdit.SetColumn(0, &lvc);
	m_wndLstEdit.InsertItem(0, _T("河北"));
	m_wndLstEdit.SetItemText(0, 1, _T("8"));
	m_wndLstEdit.InsertItem(1, _T("山东"));
	m_wndLstEdit.SetItemText(1, 1, _T("10"));
	m_wndLstEdit.InsertItem(2, _T("河南"));
	m_wndLstEdit.SetItemText(2, 1, _T(""));

	m_wndLstEdit.EnableTips(TRUE);

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

Ok, that is all!

你可能感兴趣的:(MFC,mfc)