分享

扩展通用控件的功能

 学海无涯GL 2013-04-27

Windows操作系统提供了许多通用的控件,比如按钮、编辑框、滚动条、列表控件等。这些控件为Windows软件开发提供了标准的界面元素,大大降低了应用软件用户界面的开发难度。在MFC中,这些控件被封装成相应的控件类,可以很方便地为编程人员所使用。
    然而,实际的应用程序是各种各样的,通用控件往往无法满足应用程序的特定需求。比如椭圆形的按钮、可编辑的列表控件,等等。所以,编程人员在很多时候需要自己来编写需要的控件,其中最为常用的方式就是在通用控件的基础上扩展自己需要的功能。在VC中,这种方法叫作控件类的子类化技术。
    所谓的子类化技术,就是以标准的控件类为基类,派生出自己的控件类。这种方法的要点是用我们自己的消息处理函数替代基类中原有的、标准的消息处理函数,来实现个性化的功能。当然,我们只需要拦截我们关心的消息,其他的消息仍交给原来的消息函数进行处理。显然,这种做法比较经济,不易出错,是易于掌握和广泛应用的控件类定制方式。
    本文将通过两个例子来介绍其具体的实现过程,与遇到同样问题的朋友们分享。

一、回顾控件的基本知识

    在图形模式的Windows程序中,“窗口”是必不可少的组成元素。对于用户而言,窗口就是应用程序界面,是他们与程序进行交互的场所。对于程序员而言,窗口是一个对象。它不仅拥有展示给用户的界面资源,还有更重要的窗口函数。窗口函数负责接收来自用户、系统或其他应用程序的各种消息,并对它们进行相应的处理,从而实现程序的具体功能。换句话说,窗口的外观只是皮肉,而窗口函数才是窗口的行为中枢。
    与工具栏和状态栏一样,控件本身也是一种窗口,拥有自己的外观和窗口函数。但是这种窗口又比较特殊,它一般无法独立存在,必须依附于其他的窗口。所以,控件是一种“子窗口”,它所依附的窗口相应地叫作“父窗口”。当父窗口移动时,控件也随之移动;当父窗口销毁时,控件也自动被销毁。值得注意的是,如果控件本身的窗口函数没有对某个消息进行响应,则该消息会交给它的父窗口来处理。
    在MFC中,每种通用控件都被封装成相应的控件类,比如按钮控件被封装成CButton,列表控件被封装成CListCtrl等。它们的父窗口类通常是对话框类或视图类。因此,MFC程序中的控件编程其实就是在父窗口类中添加需要的控件类,并对关心的控件消息进行处理。
    我们可以在资源编辑器中为父窗口添加控件,这样在父窗口生成时,其上的控件也会自动生成。同时,我们也可以调用控件类的Creat函数来创建一个控件。其实呢,两种方法是完全等价的,最终都是要调用Windows API函数CreatWindow来完成实质性的创建工作。请注意,资源编辑器并不能产生实际的窗口或控件,它产生的是一个文本文件(.rc),其中的内容只是资源的描述信息(比如控件的标题、ID、位置、尺寸等等)。

 

二、椭圆形按钮实例

1)在VC 6.0中,新建一个基于对话框的项目,名称:Exam01。

2)编辑对话框资源,添加一个按钮,ID:IDC_BTN_TEST,标题:Test。如果在此时运行程序,则显示一个普通的按钮(如下图所示)。

 

 

3)添加一个类:CMyButton,继承自CButton。

4)通过类向导添加与IDC_BTN_TEST关联的控件变量:m_btn,类型:CMyButton。这一步是很重要的,它将预设的控件资源与新的控件类关联起来。别忘了在父窗口类中包含自定义控件类的头文件。

5)改写CMyButton类的虚拟成员函数DrawItem,代码如下:

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

CRect rect;

GetClientRect(&rect);

CDC dc;

dc.Attach(lpDrawItemStruct->hDC);

dc.Ellipse(0,0,rect.Width(),rect.Height());

}

这个函数很简单,只是在按钮自己的客户区中绘制一个椭圆。重新编译后运行,咦,椭圆形按钮并未出现,它依旧显示和上图一摸一样的画面。咋回事呢?在默认情况下,我们自定义的DrawItem函数是不会被调用的,只有控件具有BS_OWNERDRAW风格时才行。

6)在资源编辑器中,打开Test控件的属性页,勾选“Owner Draw”。

呵呵,如下图所示,椭圆形的按钮出现了。

 

 

事实上,这个属性也可以通过代码来控制。在资源编辑器中去掉Test按钮的Owner Draw属性,改写CMyButton类的另一个虚拟成员函数PreSubclassWindow,在其中添加如下语句:

 ModifyStyle(0,BS_OWNERDRAW);   

则同样显示椭圆形按钮。如果注掉它,则会显示默认的按钮。

 

现在,椭圆形按钮的基本框架已经搭建好了,接下来的任务是完善DrawItem函数,使椭圆按钮的外观更好看,并能对鼠标点击作出响应。其实,DrawItem函数的代码编写与窗口类的OnDraw函数非常相似。

7)在CMyBotton类中添加一个布尔型的成员变量IsPressed用来描述鼠标是否被按下。

8)在CMyBotton类的构造函数中,将IsPressed的初值赋为false。

9)添加WM_LBUTTONDOWN和WM_LBUTTONUP的响应函数,分别为IsPressed赋值,代码如下:

 

void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)

{

         // TODO: Add your message handler code here and/or call default

         IsPressed=true;      

         CButton::OnLButtonDown(nFlags, point);

}

 

void CMyButton::OnLButtonUp(UINT nFlags, CPoint point)

{

         // TODO: Add your message handler code here and/or call default

         IsPressed=false;    

         CButton::OnLButtonUp(nFlags, point);

}

 

10)改写DrawItem函数,使其在鼠标按下和弹起后显示不一样的外观:

 

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

         CRect rect;

         GetClientRect(&rect);

         CDC dc;

         dc.Attach(lpDrawItemStruct->hDC);

 

         dc.SetBkMode(TRANSPARENT);

 

         CBrush brush(RGB(192,224,255));

         CBrush* pOldBrush;

         pOldBrush=dc.SelectObject(&brush);

        

         CPen* pOldPen;

 

         if(IsPressed)

         {

                   CPen pen2(PS_DOT,2,RGB(0,0,0));

                   CPen pen4(PS_DOT,1,RGB(0,0,0));

                   pOldPen=dc.SelectObject(&pen2);

                   dc.Ellipse(0,0,rect.Width(),rect.Height());

                   dc.SelectObject(pOldPen);

 

                   pOldPen=dc.SelectObject(&pen4);

                   dc.Ellipse(4,4,rect.Width()-4,rect.Height()-4);

                   dc.SelectObject(pOldPen);

         }

         else

         {

                   CPen pen3(PS_SOLID,2,RGB(192,224,225));

                   pOldPen=dc.SelectObject(&pen3);

                   dc.Ellipse(0,0,rect.Width(),rect.Height());

                   dc.SelectObject(pOldPen);

 

         }

 

         dc.SelectObject(pOldBrush);

 

         //绘制标题

         CString str;

         GetWindowText(str);

         dc.SetTextColor(RGB(0,0,0));

         dc.DrawText(str,CRect(0,0,rect.right,rect.bottom),

                   DT_CENTER|DT_VCENTER|DT_SINGLELINE);

 

}

 

程序的执行效果如下图所示:

 

三、可编辑的列表控件

在界面上成组地显示含有多个数据项的数据集,是列表控件的主要用途。如下图所示,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类的客户区中,动态创建可编辑列表控件的实例。

 

 

注:本文参考了网友Captainliyun的文章及代码,特示感谢。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多