02实现了基本的Txt操作功能,现在还想加一个拖拽功能,即把一个Txt文本拖拽进来,然后自动打开该文本。(参考Txt拖拽,不理解的话就试一下,就是把另一个Txt拖到一个已打开的Txt中)
(★★★★这里请千万注意,我们使用的RichEdit是视图继承的CRichEditView,而不是一个RichEdit控件,所以你会发现很多资料介绍的不适用,区别还是有的★★★★)
知道需求了,那就去查资料呗,嘿,还真有这种功能,WM_DROPFILES消息------>>>>OnDropFiles函数;
这不就简单了,直接解决问题,开写代码,先写一个MessageBox来测试一下。。。。。显然失败了,无法弹出这个用做提示的对话框。。。。怎么办,难道不是这样的吗?--- CRichEditView如何实现文件的拖拽功能?
1 void CTxt0721View::OnDropFiles(HDROP hDropInfo) 2 { 3 // TODO: 在此添加消息处理程序代码和/或调用默认值 4 MessageBox(L"CTxt0721View + 这不是为难老实人?"); 5 CRichEditView::OnDropFiles(hDropInfo); 6 }
。。。难搞啊,然后又发现了一个EN_DROPFILES,这简直是救星,山重水复疑无路,屁用没有,还是不会响应。。。看一下这个消息对应的程序,它的使用提示告诉我们什么
void CTxt0721View::OnEnDropfiles(NMHDR *pNMHDR, LRESULT *pResult) { ENDROPFILES *pEnDropFiles = reinterpret_cast<ENDROPFILES *>(pNMHDR); // TODO: 控件将不发送此通知,除非您重写 // CRichEditView::OnInitDialog() 函数,以将 EM_SETEVENTMASK 消息发送 // 到该控件,同时将 ENM_DROPFILES 标志“或”运算到 lParam 掩码中。 // TODO: 在此添加控件通知处理程序代码 *pResult = 0; }
CRichEditView::OnInitDialog() 函数,OnInitDialog() 这是什么函数,。。。这不是对话框的初始化函数吗,。。在View怎么重写这个函数。。。。欸,又是走不通的路,真的是没办法。(这里反而透露出了如果在对话框中使用RichEdit控件该如何实现这个拖拽功能,但是对本文无用)
后来我想,View不行,那我去MainFrame中试试,看看它能不能响应,
好家伙,CMainFrame中真的可以响应WM_DROPFILES消息,不过存在一点点BUG,那就是只能在框架处响应这个消息,在客户区还是无法响应,不过好歹算是解决了一部分问题,(关于怎么写函数OnDropFiles,以及怎么得到文件路径,自行百度,一堆),由于设计的文本路径 ,文本内容这些变量都是View中的成员变量,所以需要在MainFrame中获取到View指针,对这些数据进行操作,恰好有这个函数(CFrameWnd::GetActiveView , 调用此成员函数以获取指向附加到框架窗口 (CFrameWnd) 的活动视图(如果有)的指针。)
1 void CMainFrame::OnDropFiles(HDROP hDropInfo) 2 { 3 // TODO: 在此添加消息处理程序代码和/或调用默认值 4 5 // MessageBox(L"CMainFrame:: + 这不是为难老实人?"); 6 // 获取文件路径 7 wchar_t szFilePathName[_MAX_PATH + 1] = { 0 }; 8 UINT nNumOfFiles = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0); //得到文件个数 9 if (nNumOfFiles > 1) 10 { 11 MessageBox(L"Only the first file is loaded!"); 12 } 13 DragQueryFile(hDropInfo, 0, szFilePathName, _MAX_PATH); 14 // MessageBox(szFilePathName); 15 16 CTxt0721View * pView = (CTxt0721View *)GetActiveView(); 17 // MessageBox(L"我是文件内容" + pView->TxtFileString); 18 DragFinish(hDropInfo); 19 // ------------------------根据得到的文件路径调用 打开 函数即可 20 pView->DragAndDropOpenFile(szFilePathName); 21 pView->Number_QueryAccept = 0; // 控制拖拽事件响应函数,框架处响应不一致,故而直接置零,控制视图处响应 22 23 CFrameWndEx::OnDropFiles(hDropInfo); 24 }
。。。现在问题是怎么处理客户区拖拽问题。。。。。
找资料(虽然不知道怎么找资料,。。。。也不知道具体该查找什么关键字。。。反正就是找资料,广泛找,。。。皇天不负有心人,总算找到了(主要是不懂,就算找到了也可能因为看不懂而错过。。。。))
为什么CRichEditView 类无法响应OnDrop() OnDragOver()等拖放函数 急!通宵等答案 (和我一样的问题)
既然提到了这个函数,那我们就去试试,反正什么思路都没有。。。发现问题了,这个函数确实在这中间起到了作用,拖拽过去会产生一个图标,而这个图标的产生就由QueryAcceptData控制,那是不是可以重写这个函数,截断它显示图标,改让他显示Txt的文本内容呢?理论上来说是可以的,于是从这个函数入手
通过研究,发现在一次拖拽过程中,这个函数会响应两次(当鼠标把文件拖到客户区时响应一次,当鼠标左键松开时,响应第二次)但是我们只需要它响应一次,这只能手动控制了,没办法
因此,在View.h中添加了一个变量int Number_QueryAccept;用来专门记录调用了QueryAcceptData函数多少次,并设定,只有当Number_QueryAccept是偶数时,才做出响应,打开Txt文本。
问题又来了,前面不是在CMainFrame中响应了WM_DROPFILES消息,发现,如果是把文件拖拽到框架上,那么只会响应一次QueryAcceptData函数,这又给我们写代码的人添加工作了,由此根据这个特性,设定每次调用QueryAcceptData函数,标记Number_QueryAccept就自动加一,而当调用了CMainFrame::OnDropFiles函数时,标记Number_QueryAccept重置为0,这样解决了两者调用次数不同的问题。
事到如今,还有一个问题,怎么提取出文件路径,目前最有用的参数就是LPDATAOBJECT lpdataobj了,怎么提取出文件路径来呢?(查资料呗,我又不会)(求教怎么通过DoDragDrop参数获取源文件路径)
照着抄当然时无法提取到文件路径的,其实当初这个地方查了很久,总是不知道查什么关键词。。。。不过最后还是找到了,然后我现在忘记了。。。。(真的找不到了。。。好难过。。。所以没有链接了,不是我不想给原作者链接。。。。。)(某个例子)
QueryAcceptData函数:(调用DragAndDropOpenFile函数直接打开文本)
1 /* 2 重载函数CRichEditView::QueryAcceptData 3 QueryAcceptData由框架调用以将对象粘贴到丰富的编辑中。(文件拖拽功能 4 1.文件拖到框架上,响应此处,然后会自动调用CMainFrame中OnDropFiles(HDROP hDropInfo)函数处理 5 2.文件拖到视图上,响应此处,但不会调用函数处理 6 */ 7 HRESULT CTxt0721View::QueryAcceptData(LPDATAOBJECT lpdataobj, CLIPFORMAT* lpcfFormat, DWORD dwReco, BOOL bReally, HGLOBAL hMetaFile) 8 { 9 // TODO: 在此添加专用代码和/或调用基类 10 11 // 控制拖拽响应 Number_QueryAccept 偶数响应 12 Number_QueryAccept++; 13 14 // 获取当前界面文字 15 pRichEdit->GetWindowText(TxtFileString); 16 17 // 以下用于获取拖拽文件的文件路径 18 FORMATETC fmt = { CF_HDROP,0,DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; 19 STGMEDIUM stgmed = { 0 }; 20 if (SUCCEEDED(lpdataobj->GetData(&fmt, &stgmed))) 21 { 22 HDROP hDropInfo = (HDROP)::GlobalLock(stgmed.hGlobal); 23 wchar_t szFilePathName[_MAX_PATH + 1] = { 0 }; 24 UINT nNumOfFiles = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0); //得到文件个数 25 DragQueryFile(hDropInfo, 0, szFilePathName, _MAX_PATH); 26 // 打开文件 与否 27 if (Number_QueryAccept % 2 == 0) // 控制一下,QueryAcceptData 会被调用两次, 28 { 29 if (DragAndDropOpenFile(szFilePathName) == false) 30 { 31 // 是否正确打开拖拽文件 -- false 没有打开 32 // 重新显示Txt文本 -- 干掉出现的那个图标 33 pRichEdit->SetSel(0, -1); // 文本全选 34 // 使用下面两行的目的是为了替换的时候不至于出现 文字全选而出现的颜色, 35 pRichEdit->Clear(); 36 pRichEdit->SetSel(0, -1); 37 // 替换选中的文本,即输入读取的文本信息 38 pRichEdit->ReplaceSel(TxtFileString); 39 // MessageBox(L"这么晚?"); 40 } 41 } 42 GlobalUnlock(stgmed.hGlobal); 43 ReleaseStgMedium(&stgmed); 44 // return S_OK; // 会出现图标资源 45 return S_FALSE; // 不会出现图标资源 46 } 47 else 48 { 49 // 当拖拽其他,如文字时,调用其原本的函数,用于响应,拖拽文件,手写函数响应 50 return CRichEditView::QueryAcceptData(lpdataobj, lpcfFormat, dwReco, bReally, hMetaFile); 51 } 52 }
1 /* 2 功能:实现文件拖拽时的打开文本的功能 --> 实现拖拽文本所需功能 3 1.获取 并 保存--文件路径 4 2.保存当前文本 -- 询问 保存与否 ,取消 -- 退出文件拖拽这个事件 5 3.直接打开拖拽而来的文件 6 */ 7 bool CTxt0721View::DragAndDropOpenFile(CString FileLoad) 8 { 9 // TODO: 在此添加命令处理程序代码 10 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 11 // 保存当前文本 12 if (CTxt0721View::AskToSaveOrNot(pRichEdit) == false) 13 return false; 14 15 CTxt0721View::InitCTxt0721View(); // 重置各种参数 16 TxtFileLoad = FileLoad; // 保存拖拽文件路径 17 IsFileNew = false; // 非新建文件 18 CTxt0721View::ReadFileContents(); // TxtFileString -- 读取文件内容, 19 20 // 显示Txt文本 21 pRichEdit->SetSel(0, -1); // 文本全选 22 // 使用下面两行的目的是为了替换的时候不至于出现 文字全选而出现的颜色, 23 pRichEdit->Clear(); 24 pRichEdit->SetSel(0, -1); 25 // 替换选中的文本,即输入读取的文本信息 26 pRichEdit->ReplaceSel(TxtFileString); 27 // 用于记录文本是否修改过,FALSE未修改,刚打开的文档默认未修改 28 pRichEdit->SetModify(FALSE); 29 return true; 30 }
关于从LPDATAOBJECT lpdataobj中获取到文件路径,真的找了很久,现在竟然找不到了。。。。真难过。。
不过至此,问题差不多就解决了,能够在整个界面上都可以实现拖拽,
解决完问题之后,才发现好多资料。。。真需要的时候找不到。。。这也是没办法,关键在于不知道应该查找什么内容,只知道大概查找方向而不是精确的点,这点就是问题所在了,可是没有广泛的了解又哪里找得到关键点。。。没办法喽,学习的必经之路
总结:在CRichEditView中,无法使用WM_DROPFILES消息,但是在CMainFrame中还是可以正常使用的,但是功能只限于框架部分,不包括客户区;
可以通过调用QueryAcceptData函数来实现客户区的拖拽响应,利用LPDATAOBJECT lpdataobj来获取文件路径。
下一篇04:MFC实现Txt04之打开各种编码文本(ANSI,UTF-8,UTF-16)
2022-08-17(0726.。。)