logo资料库

VS2010 MFC 读写Excel 可运行.doc

第1页 / 共9页
第2页 / 共9页
第3页 / 共9页
第4页 / 共9页
第5页 / 共9页
第6页 / 共9页
第7页 / 共9页
第8页 / 共9页
资料共9页,剩余部分请下载后查看
在界面上成组地显示含有多个数据项的数据集,是列表控件的主要用途。如下图所示,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。(VS2010 步骤: 右键”Exam02”- “Add” – “Class” – “MFC Class”, 不用管直接确定会弹出界面的) 注释掉 EditListCtrl.cpp 文件中的 #include "Exam02.h"。该指令是类向导自动生成的,而 CEditListCtrl 类的实现并不依 赖它。如不注掉它,将该类用于其他项目时,会无法编译。 (这个地方不要注释,否则有一个控件 IDC_EDIT 找不到) 在 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。 (这个地方不要这样做,直接建一个新的类 CItemEdit) 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) { //创建一个编辑框(大小为零) (这个地方,我直接新建一个 edit 然后设置为不可见了”Visible”为 False) 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());//设置焦点 DWORD dt = nItem << 16 | nIndex; m_edit.SetCtrlData(dt); //这个地方一定要加,否则后期找不到item 和 subitem ::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); (不要通过 class wizard 添加, 一下为添加步骤)
将其声明语句移动到 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);语句之前添加
分享到:
收藏