可编辑的列表控件

来源:互联网 发布:网络神书 编辑:程序博客网 时间:2024/05/16 11:16
在界面上成组地显示含有多个数据项的数据集,是列表控件的主要用途。如下图所示,Windows资源管理器中文件列表的显示就是列表控件的一个典型应用。

 

 

从数据显示的角度看,列表控件的功能已经比较强大了(支持大图标、小图标、列表、详细资料等多种显示方式;支持排序、查找、定位、增删等)。但美中不足的是,它不支持数据项的编辑功能。在很多的实际应用中,需要在显示数据的同时,允许用户“就地”对某些数据项进行修改。例如,在Windows资源管理器中,我们可以在浏览文件夹的同时修改其中任何一个文件的名字。这主要得益于Windows资源管理器中所使用的列表控件支持字段编辑功能。否则,简单的文件名修改也会变成一件很麻烦的事情。

 

 

因此,标准的列表控件只适合用于数据集的显示,而具有数据编辑功能的列表控件却可以在更广的范围里得到应用。本文重点介绍其实现过程。

1.基本原理

在列表控件上实现可编辑功能的原理非常简单,借助一个编辑框控件即可达到目的。具体步骤如下:①从 CListCtrl派生一个子类,并拦截某个意味着进入编辑状态的消息,获取需要编辑的数据项的相关信息。所拦截的消息通常选择鼠标消息(例如双击),这样更容易确定数据项在列表控件中的位置(行号、列号)及其所占的区域。②将一个编辑框控件移动到待编辑数据项所在的区域上,装入待编辑的数据并显示出来,供用户进行修改。③编辑结束后将修改后的数据返回给列表控件,让其在对应的子项上显示新的数据。

2.实现过程

1)在VC 6.0中,新建一个基于对话框的项目,名称:Exam02。
2)编辑对话框资源,删除IDOK 按钮和静态标签;保留IDCANCEL按钮,将其标题改为“退出”;添加一个列表控件,将其显示风格改为report。利用类向导为列表控件添加一个关联变量m_list(Type:CListCtrl)。在CExam02Dlg::OnInitDialog函数中添加如下代码:

 m_list.InsertColumn(0,_T("1"),LVCFMT_LEFT,100);
 m_list.InsertColumn(1,_T("2"),LVCFMT_LEFT,100);
 m_list.InsertColumn(2,_T("3"),LVCFMT_LEFT,100);
 m_list.InsertColumn(3,_T("4"),LVCFMT_LEFT,100);
 m_list.InsertItem(0,_T("123"));
 m_list.SetItemText(0,1,_T("c"));
 m_list.SetItemText(0,2,_T("d"));
 m_list.SetItemText(0,3,_T("e"));
 m_list.InsertItem(1,_T("456"));
 m_list.SetItemText(1,1,_T("f"));
 m_list.SetItemText(1,2,_T("g"));
 m_list.SetItemText(1,3,_T("h"));
 m_list.InsertItem(2,_T("789"));
 m_list.SetItemText(2,1,_T("i"));
 m_list.SetItemText(2,2,_T("j"));
 m_list.SetItemText(2,3,_T("k"));
 m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT ); 

如果在此时运行程序,则显示一个普通的列表控件,不具备编辑功能(如下图所示)。

 

 

3)添加一个类:CEditListCtrl,继承自CListCtrl。
注释掉 EditListCtrl.cpp文件中的 #include "Exam02.h"。该指令是类向导自动生成的,而CEditListCtrl类的实现并不依赖它。如不注掉它,将该类用于其他项目时,会无法编译。
在 Exam02Dlg.h的头部添加:#include "EditListCtrl.h";将CListCtrl m_list;语句替换成CEditListCtrl m_list;(该操作将列表控件资源与CEditListCtrl类关联起来,效果与椭圆形按钮实现过程的步骤4相同)。
此时程序的执行效果与步骤2是完全一样的。但控制列表控件行为的类已经换成CEditListCtrl了。接下来只需要对CEditListCtrl进行修改,就可以改变列表控件的行为了。
4)添加一个类:CItemEdit,继承自CEdit。注意,虽然这个类单独生成一样可以使用,但其主要作用就是为 CEditListCtrl类服务。考虑到使用的方便性,将其放在CEditListCtrl的类定义文件中更为合适。
具体方法如下:在生成新类 的对话框中,点击“Change”按钮(如左下图),在弹出的“Change Files”对话框中(如右下图所示),分别将头文件和实现文件指向editlistctrl.h和editlistctrl.cpp。
          

5)实现列表控件对鼠标双击事件的响应——编辑框的显示功能
在CEditListCtrl类中添加如下一个私有成员变量:
 CItemEdit m_edit;//编辑框空间类对象
在其构造函数中添加:

m_edit.m_hWnd = NULL;
添加一个私有成员函数ShowEdit,用于在待编辑区域显示一个编辑框。函数声明如下:
  void ShowEdit(BOOL bShow,int nItem,int nIndex,CRect rc = CRect(0,0,0,0));
下面为该函数的实现代码:
void CEditListCtrl::ShowEdit(BOOL bShow, int nItem, int nIndex, CRect rc)
{
 // 如果编辑框对象尚未创建
    if(m_edit.m_hWnd == NULL)
 {
  //创建一个编辑框(大小为零)
  m_edit.Create(ES_AUTOHSCROLL|WS_CHILD|ES_LEFT
|ES_WANTRETURN|WS_BORDER,CRect(0,0,0,0),this,IDC_EDIT);
  m_edit.ShowWindow(SW_HIDE);// 隐藏

  //使用默认字体
  CFont tpFont;
  tpFont.CreateStockObject(DEFAULT_GUI_FONT);
  m_edit.SetFont(&tpFont);
  tpFont.DeleteObject();
 }

 //如果bShow为true,显示编辑框
 if(bShow == TRUE)
 {
  CString strItem = CListCtrl::GetItemText(nItem,nIndex);//获取列表控件中数据项的内容
  m_edit.MoveWindow(rc);// 移动到子项所在区域
  m_edit.ShowWindow(SW_SHOW);//显示控件
  m_edit.SetWindowText(strItem);// 显示数据
  ::SetFocus(m_edit.GetSafeHwnd());//设置焦点
  ::SendMessage(m_edit.GetSafeHwnd(), EM_SETSEL, 0, -1);//使数据处于选择状态
 }
 else
  m_edit.ShowWindow(SW_HIDE);
}
添加鼠标双击事件的响应函数,填写代码如下:
void CEditListCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
 // TODO: Add your message handler code here and/or call default
     CRect rcCtrl;        //数据项所在区域
  LVHITTESTINFO lvhti; //用于列表控件子项鼠标点击测试的数据结构
     lvhti.pt = point;  //输入鼠标位置
  int nItem = CListCtrl::SubItemHitTest(&lvhti);//调用基类的子项测试函数,返回行号
  if(nItem == -1)   //如果鼠标在控件外双击,不做任何处理
     return;
  int nSubItem = lvhti.iSubItem;//获得列号
  CListCtrl::GetSubItemRect(nItem,nSubItem,LVIR_LABEL,rcCtrl);
//获得子项所在区域,存入rcCtrl
    ShowEdit(TRUE,nItem,nSubItem,rcCtrl); //调用自定义函数,显示编辑框

 CListCtrl::OnLButtonDblClk(nFlags, point);//调用基类鼠标鼠标双击事件的响应函数
}

 

    

编译后执行,双击列表控件上某个子项,该处就会显示出一个编辑框,其中显示的数据与对应位置上的数据项相同(如左上图所示)。而且,该数据已经被全部选中(高亮显示),用户可以对其进行更改,只是新的数据无法在列表控件上显示。

 


它还有一个小问题:当在列表控件的其他点击时,被双击过的编辑框不消失。下一步来解决这个问题。
6)在 CEditListCtrl类中添加NM_CLICK消息的响应函数,隐藏编辑框的显示。
代码如下:
void CEditListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
 // TODO: Add your control notification handler code here
  if(m_edit.m_hWnd != NULL)
  {
   DWORD dwStyle = m_edit.GetStyle();
   if((dwStyle&WS_VISIBLE) == WS_VISIBLE)
   {
    m_edit.ShowWindow(SW_HIDE);
   }
  
 *pResult = 0;
}

7)实现编辑完成后列表控件上的数据显示
用户所作的编辑是在编辑框控件中进行的,若想让编辑后的结果在列表控件上正确显示,则需要向列表控件传递新的数据。在两个控件间传递数据的方法非常简单,调用源控件的GetWindowText函数将数据放在一个字符串类对象中,再调用目标控件的SetItemText函数将字串中内容显示出来即可。但是,我们还要考虑下面两个问题:数据何时传递,是否更新。用户在编辑框中进行数据编辑时,一般是以按下回车键或者鼠标点击到其他地方(编辑框失去焦点)作为编辑完成的确认,显然此时是传递数据的合适时机;如果在编辑过程中,用户不想对原来的数据作出更改,则会按下ESCAPE键以示放弃,也意味着要退出编辑状态,但不应对数据作出更新。上述三种事件的发生,列表控件是无法自动感知的,因此我们需要给它发送一个消息,它才能及时处理数据更新的问题。另外,还需要让列表控件知道它要更新哪个子项的数据。
在 EditListCtrl.h的头部添加一个自定义消息:
#define WM_USER_EDIT_END  WM_USER+1001
为CEditListCtrl类添 加一个成员函数:
LRESULT OnEditEnd(WPARAM wParam,LPARAM lParam = FALSE);
 将其声明语句移动到 DECLARE_MESSAGE_MAP()宏之前,添加afx_msg前缀。
 afx_msg LRESULT OnEditEnd(WPARAM wParam,LPARAM lParam = FALSE);
 在CEditListCtrl类的END_MESSAGE_MAP()宏之前插入:
 ON_MESSAGE(WM_USER_EDIT_END,OnEditEnd)
【上述操作是添加自定义消息响应函数的一般方法。MFC无法自动建立自定义消息映射,所以需要手工完成。自定义消息不能与系统消息相冲突,一般选用WM_USER之后的某个数值。自定义消息的处理函数本质上也是类的成员函数,因此可以采用添加成员函数的方法生成。将其声明语句移到 DECLARE_MESSAGE_MAP()宏之前并添加afx_msg前缀只是为了增加程序的可读性,不这样做也不会影响程的功能。关键一步是 OnMessage宏的插入,它将自定义消息及其响应函数添加到消息列表之中。如果不执行该操作,则自定义消息不会被MFC的消息网络接收和处理。另外该宏必须放在BEGIN_MESSAGE_MAP宏和END_MESSAGE_MAP宏之间,否则编译无法通过。】

在CItemEdit类中添加两个私有变量:
 BOOL m_bExchange;//是否进行数据交换
 DWORD m_dwData;//待编辑区域行列号信息
添加两个公共成员函数:
 DWORD GetCtrlData();
 void SetCtrlData(DWORD dwData);

实现代码如下:
void CItemEdit::SetCtrlData(DWORD dwData)
{
  m_dwData=dwData;
}

DWORD CItemEdit::GetCtrlData()
{
  return m_dwData;
}

添加两个消息处理函数OnSetFocus和 OnKillFocus:
void CItemEdit::OnSetFocus(CWnd* pOldWnd)
{
 CEdit::OnSetFocus(pOldWnd);
 // TODO: Add your message handler code here
    m_bExchange = TRUE; 
}

void CItemEdit::OnKillFocus(CWnd* pNewWnd)
{
 CEdit::OnKillFocus(pNewWnd);
 // TODO: Add your message handler code here
 CWnd* pParent = this->GetParent();
 ::PostMessage(pParent->GetSafeHwnd(),WM_USER_EDIT_END,m_bExchange,0);
}

填写OnEditEnd函数的代码如下:
LRESULT CEditListCtrl::OnEditEnd(WPARAM wParam, LPARAM lParam)
{
 if(wParam == TRUE)
 {
  CString strText(_T(""));
  m_edit.GetWindowText(strText);
  DWORD dwData = m_edit.GetCtrlData();
  int nItem= dwData>>16;
   int nIndex = dwData&0x0000ffff;
   CListCtrl::SetItemText(nItem,nIndex,strText);
  }
  else
    
  }

    if(lParam == FALSE)
       m_edit.ShowWindow(SW_HIDE);
  return 0;
}
编译后执行,鼠标双击某个字段,键入新的文本后,点击列表框的其他地方,新的数据在列表控件中正确显示了(如下图所示)。

 

 

但是,当我们在编辑框中输入回车或Esc键时,整个对话框却退出了。为什么呢,还没有对这两个键盘消息进行拦截。
改 写CItemEdit类的虚拟成员函数:PreTranslateMessage,添加如下代码:

BOOL CItemEdit::PreTranslateMessage(MSG* pMsg)
{
 // TODO: Add your specialized code here and/or call the base class
 if(pMsg->message == WM_KEYDOWN)
 {
  if(pMsg->wParam == VK_RETURN)
  {
   CWnd* pParent = this->GetParent();
   m_bExchange = TRUE;
   ::PostMessage(pParent->GetSafeHwnd(),WM_USER_EDIT_END,m_bExchange,0);
   return true;
  }
  else if(pMsg->wParam == VK_ESCAPE)
  {
           CWnd* pParent = this->GetParent();
        m_bExchange = FALSE;
   ::PostMessage(pParent->GetSafeHwnd(),WM_USER_EDIT_END,m_bExchange,0);
   return true;
  }
 }
 return CEdit::PreTranslateMessage(pMsg);
}
现在,当编辑框处于编辑状态时,回车键和Esc键可以被正常响应了(如下图所示)。

 

 

至此,可编辑列表控件的基本功能就已经实现了。但是当用户需要对列表中的多个数据作出修改时,依次双击需要修改的字段就太麻烦了。能否像Eexel表格一样,通过按下某些控制键来实现行列之间的快速跳转呢?

3.强化功能

根据习惯,这里采用Tab键跳转到下一字段(如果到行尾,则跳到下一行的第一个字段);Shift+Tab键跳转到上一字段(如过到行头则跳到上一行的行尾);Ctrl+Tab键跳转到下一行的同一字段(到最后一行则跳回第一行)。实现原理则是捕获按键消息。具体实现过程如下:
1) 在CEditListCtrl类中,改写虚拟成员函数PreTranslateMessage,添加如下代码:

BOOL CEditListCtrl::PreTranslateMessage(MSG* pMsg)
{
 // TODO: Add your specialized code here and/or call the base class
 if(pMsg->message == WM_KEYDOWN)
 {
  //拦截Tab键
  if(pMsg->wParam == VK_TAB && m_edit.m_hWnd!= NULL)
  {
   //检测编辑框是否处于显示状态
    DWORD dwStyle = m_edit.GetStyle();
   if((dwStyle&WS_VISIBLE) == WS_VISIBLE)
   {
        OnEditEnd(TRUE,TRUE);//更新前一个子项的数据
     CRect rcCtrl;  
     int nItem;
     int nSub;
                Key_Shift(nItem,nSub);//调用Key_Shift更改行号及列号
     //获得跳转后子项区域
                 CListCtrl::GetSubItemRect(nItem,nSub,LVIR_LABEL,rcCtrl);
     //进入编辑状态
     CPoint pt(rcCtrl.left+1,rcCtrl.top+1);
     OnLButtonDblClk(0,pt);

     //控制行被选中状态
     POSITION pos = CListCtrl::GetFirstSelectedItemPosition();
     if (pos == NULL)
     { }
     else
     {
      while (pos)
      {
       int ntpItem = CListCtrl::GetNextSelectedItem(pos);
       CListCtrl::SetItemState(ntpItem,0,LVIS_SELECTED);
      }
     }
     CListCtrl::SetItemState(nItem,  LVIS_SELECTED,  LVIS_SELECTED);
     return TRUE;
   }
  }
  }
 return CListCtrl::PreTranslateMessage(pMsg);
}

2)添加一个私有成员函数Key_Shift,添加代码如下:

void CEditListCtrl::Key_Shift(int &nItem, int &nSub)
{
  //列表总行数
  int nItemCount = CListCtrl::GetItemCount();
  //当前编辑框所在位置
  DWORD dwData = m_edit.GetCtrlData();
  nItem= dwData>>16;
  nSub = dwData&0x0000ffff;
  //获取标题控件指针
  CHeaderCtrl* pHeader = CListCtrl::GetHeaderCtrl();
  if(pHeader == NULL)
   return;
 
  // 检测SHIFT键的状态,最高位为1-触发;0-未触发
  short sRet = GetKeyState(VK_SHIFT);
  int nSubcCount = pHeader->GetItemCount();//总列数
  sRet = sRet >>15;
 
  if(sRet == 0)//未触发
  {
   nSub += 1;//列号递增
   if(nSub >= nSubcCount)//到行尾
   {
    if(nItem == nItemCount-1)//到表尾,跳回表头
    {
     nItem = 0;
     nSub  = 0;
    }else //未到表尾,跳到下一行行首
    {
     nSub = 0;
     nItem += 1;
    }
   }
   
   if(nItem >= nItemCount)
    nItem = nItemCount-1;
  }
  else//触发
  {
   nSub -= 1;//列号递减
   if(nSub < 0)//到行首,跳到上一行行尾
   {
    nSub = nSubcCount -1;
    nItem --;
   }
   if(nItem < 0)//到表首,跳到表尾
    nItem = nItemCount-1;
    }
}

这样就实现了Tab及Shift键的跳转功能,同理易于实现Ctrl+Tab的功能。

3)添加一个私有成员函数Key_Ctrl,添加代码如下:
BOOL CEditListCtrl::Key_Ctrl(int &nItem, int &nSub)
{
    short sRet = GetKeyState(VK_CONTROL);
  DWORD dwData = m_edit.GetCtrlData();
  nItem= dwData>>16;
  nSub = dwData&0x0000ffff;
 
  sRet = sRet >>15;
  int nItemCount = CListCtrl::GetItemCount();
  if(sRet != 0)
  {
   nItem = nItem >=nItemCount-1? 0:nItem+=1;
   return TRUE;
  }
  return FALSE;
}

同时在CEditListCtrl::PreTranslateMessage函数的 Key_Shift(nItem,nSub);语句之前添加

     if(FALSE == Key_Ctrl(nItem,nSub))
 即可。

4.该类的使用

生成的可编辑列表控件类CEditListCtrl,其使用方法与CListCtrl类基本相同。由于相关的代码都包含在EditListCtrl.h和EditListCtrl.cpp文件之中,只需要将这两个文件拷贝到引用它的工程目录并加入到工程,然后在使用它的类中包含EditListCtrl.h即可使用。对于对话框或FormView等允许在资源编辑器中添加列表控件的窗口类来说,首先需要按常规将列表控件资源与CListCtrl变量进行关联,然后将该变量声明语句中的CListCtrl改成CEditListCtrl即可。对于一般的窗口类(例如 View类),则需要声明一个CEditListCtrl类型的成员变量,再利用其Create成员函数动态创建。下图为在View类的客户区中,动态创建可编辑列表控件的实例。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 练瑜伽出汗瑜伽垫手滑怎么办 cf多出来的神器怎么办 脖子痛得低不了怎么办 六岁的孩孑不吃.饭怎么办 大腿肌肉练废了怎么办 吃鸡里的信誉分太低怎么办she 血压高老是晕怎么办27 我腰疼的厉害怎么办 奥克斯空调外机上霜风扇不转怎么办 燃脂膏辣辣的怎么办 减肥期间暴食了怎么办 健身减脂后腹部皮松怎么办 吃减肥药上火怎么办呢 魔域怀旧版新区进不去怎么办 dnf十周年礼盒打开了怎么办 房地产项目完成后企业员工怎么办啊 韩服lol延迟太高怎么办 LOL等级奖励卡掉怎么办 魔域手机号换了怎么办 买的qq号找回了怎么办 买dnf账号被找回怎么办 微博账号已锁定怎么办 抖音账号封手机怎么办 手机号码绑定被别人占用了怎么办 DNF账号给找回了怎么办 转转上被骗了200怎么办 7彩账号被锁定怎么办 猪不吃食没精神怎么办 cf手游签到没给怎么办 cf说停止运行了怎么办 cf端游永久禁赛怎么办 cf端游爆破怕死怎么办 王者荣耀累计扣分12分怎么办 去医院看病没带身份证怎么办 ps4星战2鬼服怎么办 冒险岛英雄五转怎么办 6儿童视力低常怎么办 腰干活累的酸痛怎么办 狗狗体力很差怎么办啊 脉差总是五十多怎么办 吃过敏药嗜睡乏力怎么办