公共控件 ListView 内置了编辑框功能,但是有个致命的缺陷:只能编辑第一列,第二列后都无法编辑。
为了能编辑任意单元格,所以我们需要扩展它。
编辑框基本用法
控件窗口添加 LVS_EDITLABELS 样式
1 | DWORD dwStyle = _list.GetStyle(); |
然后就可以调用其成员方法 EditLabel 了。
相应的,取消编辑框使用 CancelEditLabel 方法。
ListView 内部处理过程
当用户调用 EditLabel 方法时,本质就是向控件发送一个 LVM_EDITLABEL 消息,接着内部就创建一个 Edit 控件。(向控件发送 LVM_GETEDITCONTROL 消息可以获得控件句柄)
在 EditLabel 执行过程中,会向其自身发送一个 LVN_BEGINLABELEDIT 消息,预示着编辑框即将显示。
当编辑框失去焦点或编辑完成时,ListView 会收到 LVN_ENDLABELEDIT 消息,表示编辑工作结束。
解决方案
网上有两种解决方案,一个是子类化Edit
控件,就像这两篇文章中介绍的方法
Editing listview subitems using LVM_GETEDITCONTROL
Simplified Subitem Editing
测试发现有些弊端,比如在 WinCE 系统上没有 ON_WM_WINDOWPOSCHANGING 消息,得在其他地方改变 Edit 控件的位置。还有一个绘制问题,ListView 内部实现会使第一列内容区无效(表现为空白,可能是为了提高绘图效率),待编辑框结束后才会显示。
第二种方法就是自己创建一个 Edit 控件,这样就没有第一种方法的一些问题,实现更简单。
网上看到的方案都是扩展 ListView 控件,内部在单击或双击时显示 Edit 控件,完全不理会 MFC 提供的 EditLabel 等方法。
我稍微变化一下,自己处理几个相关的消息,这样的好处是兼容了 MFC 的接口,对外是透明的。
根据前面提到的知识点,源头是从 LVN_BEGINLABELEDIT 消息开始的,所以我们需要”劫持”这个消息
1 | BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl) |
这里要做的就是将 Edit 移动到合适的位置,并填充文本内容
1 | BOOL CListCtrlEx::OnBeginLabelEdit(LPNMHDR pNMHDR, LRESULT* pResult) |
接着要考虑编辑框按下回车或ESC键时,不要让父窗口关闭了(基于对话框的程序)
1 | BOOL CEditEx::PreTranslateMessage(MSG* pMsg) |
CListCtrlEx
还要监视编辑框的退出
1 | BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl) |
最后在 LVN_ENDLABELEDIT 处理过程中将编辑后的内容写回对应的单元格
1 | BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl) |