专题1: 如何使CListCtrl完全可编辑?
1. 背景 : 我们知道如果CListCtrl是报表样式,那么CListCtrl所提供的编辑功能只局限于第一列.也就是说只有第一列可编辑.这样显然无法满足一般数据库的要求.我们想要每个子项都能编辑.
2. 思路 : CEdit是一个很好的可控制编辑控件.如何把CEdit和我们的CListCtrl联系起来?一种很好的想法是------一般我们如果想编辑某一项,那么就应该去双击.双击以后就让CEdit在那里显示,当然要把大小调整和子项表格一样.如果CEdit失去了焦点,表示修改完毕,那么立即更改子项的数据,同时让CEdit隐藏.因为每次只能编辑一项,所以只需要一个CEdit就够了.
3. 方法:
(1) 首先从CListCtrl派生一个类,其他已经有的变量或者函数设置我已经介绍,如果不清楚的读者,可以去参考”基础篇”.
(2) 有一点可以肯定,我们必须响应双击事件:
void Cmylist::OnLButtonDblClk(UINT nFlags, CPoint point)
{
int index;//行号
int colnum;//列号
GetWindowRect(r);//稍后说明
GetParent()->ScreenToClient(r);//稍后说明
if((index=HitTestEx(point,&colnum))!=-1)
EditSubItem(index,colnum);
CListCtrl::OnLButtonDblClk(nFlags, point);
}
其中HitTestEx是用来求出双击点所在的行列号,如果行号不为-1,那么就调用函数EditSubItem. 这个函数会根据行列号求出该子项具体坐标,方便CEdit调整位置.
(3) 如何求出行列号?行号是很好求出来的 ,但是列号就不是很简单了,必须详细判断.
int Cmylist::HitTestEx(CPoint &point, int *pcolumn)
{
int columnNum=0;
//获取页面内首行索引号,不一定是0,要考虑滚动条的情况
int row=GetTopIndex();
// GetCountPerPage()获取在页面内行的总数
int bottom=row+this->GetCountPerPage();
// 防止超出范围
if(bottom>this->GetItemCount())
bottom=GetItemCount();
//获取列的总数
int ncolumncount=this->GetHeaderCtrl()->GetItemCount();
//可以肯定双击点肯定在页面内,因此从页面首行索引号开始判断
for(;row<=bottom;++row)
{
CRect rect;
//求出行的rect
GetItemRect(row,&rect,LVIR_BOUNDS);
//点是否在行的矩形内
if(rect.PtInRect(point))
//如果点在行的矩形内,求出点在哪一列
for(columnNum=0;columnNum<ncolumncount;columnNum++)
{
//求出列的宽度
int colwidth=this->GetColumnWidth(columnNum);
if(point.x>=rect.left&&point.x<=(rect.left+colwidth))
{
*pcolumn=columnNum;
return row;
}
rect.left+=colwidth;
}
}
return -1;
}
当然上面那种方法有点复杂,是完全从头开始判断.其实我们可以先利用CListCtrl提供的函数求出行号,再求列号,这样稍微简单点
int Cmylist::HitTestEx(CPoint &point, int *pcolumn)
{
int columnNum=0;
int row=HitTest(point);//求出行号
int ncolumncount=this->GetHeaderCtrl()->GetItemCount();
LVHITTESTINFO Info;
Info.pt=point;
this->SubItemHitTest(&Info);
*pcolumn=Info.iSubItem;
if(*pcolumn>=0&&*pcolumn<ncolumncount)
return row;
else
return -1;
/* int ncolumncount=this->GetHeaderCtrl()->GetItemCount();
CRect rect;
GetItemRect(row,&rect,LVIR_BOUNDS);
if(rect.PtInRect(point))
for(columnNum=0;columnNum<ncolumncount;columnNum++)
{
int colwidth=this->GetColumnWidth(columnNum);
if(point.x>=rect.left&&point.x<=(rect.left+colwidth))
{
*pcolumn=columnNum;
return row;
}
rect.left+=colwidth;
}*/
}
(4) 求出具体CEdit移动坐标
int Cmylist::Item_X(int row, int column,CRect& rect_X)
{
int offset=0;
for(int i=0;i<column;i++)
offset+=GetColumnWidth(i);
CRect rect;
GetItemRect(row,rect,LVIR_BOUNDS);
//注意水平滚动条的影响,如果已经移动了水平滚动条,可能left为0,或者超出总大小
if(offset+rect.left<0||offset+rect.left>client_rect.right)
{
CSize size;
//offset肯定为正,如果出现了rect.left为负
if(offset+rect.left>0)
size.cx=- (offset+rect.left);
else
size.cx=offset+rect.left;
size.cy=0;//垂直不用管
//如果某一列的一半在滚动条左边,一半在右边,就再次调整滚动条的位置.
Scroll(size);
rect.left - =size.cx;
}
rect.left+=offset+2;
rect.right=rect.left+GetColumnWidth(column)-2;
//bottom和top不用管
rect_X=rect;
return rect.right;
}
(5) 移动CEdit
void Cmylist::EditSubItem(int Item, int Column)
{
CRect rect;
//求出行列所在rect
this->Item_X(Item,Column,rect);
EditCellShow(rect,Item,Column,r);
}
void Cmylist::EditCellShow(CRect rect, int Item, int Column,CRect r)
{
//还记得r吗?在开始的双击函数OnLButtonDblClk中,它是CListCtrl在父窗口中的位置
rect.left+=r.left;
rect.top+=r.top+2;
rect.right+=r.left;
rect.bottom+=r.top+2;
//pedit是CEdit对象的指针,提供接口,只要在程序中让pedit指向一个对象即可
pedit->MoveWindow(rect,TRUE);
pedit->ShowWindow(TRUE);
pedit->SetFocus();
}
^_^!这样就完成了.效果还可以.当然你还要去响应CEdit失去焦点和得到焦点的事件.这个就不是我的任务了,因为每个人的要求不一样啊!
看看我的效果!