其实最初是因为在项目中使用了html网页编辑器,通过ie的com组件和javascript通讯完成一些事情,其中有一个功能是插入表格,我们原本使用的range.pasteHTML(HTMLstr);根据用户传入的行和列等参数在javascript端创建好用户想要的表格的html字串,然后的然后,测试人员就发现一个bug,因为这种pasterHTML会破坏TextArea原本的剪切板内容,会直接导致无法撤销到插入表格之前(包含插入表格)的状态。
有一个同事想出一个方法,使用剪切板来代替pasterHTML这样的操作,因为粘贴一个html有格式的内容是可以撤销的,这样就又会涉及到一个问题,如果借用了剪切板的内容就需要备份之前的内容,并在使用完之后恢复,不管原来是图片、纯文本、还是word、表格、带格式的复杂的内容。
首先在MSDN上找到这篇文章:http://msdn.microsoft.com/en-us/library/windows/desktop/ms649015%28v=vs.85%29.aspx
因为我需要加入的是一个html表格所以找来了它所需要的格式,并使用SetClipboardData进行设置。
官方格式示例:
Version:0.9 StartHTML:71 EndHTML:170 StartFragment:140 EndFragment:160 StartSelection:140 EndSelection:160 <!DOCTYPE> <HTML> <HEAD> <TITLE> The HTML Clipboard</TITLE> <BASE HREF="http://sample/specs"> </HEAD> <BODY> <UL> <!--StartFragment --> <LI> The Fragment </LI> <!--EndFragment --> </UL> </BODY> </HTML>
构造header代码:
1 int ClipboardHTMLHeader::size() const 2 { 3 const int numSpaces = 8; 4 int headerSIZE = 8/*strlen("Version:")*/ + strlen(version); 5 headerSIZE += 10/*strlen("StartHTML:")*/ + numSpaces; 6 headerSIZE += 8/*strlen("EndHTML:")*/ + numSpaces; 7 headerSIZE += 14/*strlen("StartFargment:")*/ + numSpaces; 8 headerSIZE += 12/*strlen("EndFargment:")*/ + numSpaces; 9 //headerSIZE += 15/*strlen("StartSelection:")*/ + numSpaces; 10 //headerSIZE += 13/*strlen("EndSelection:")*/ + numSpaces; 11 headerSIZE += 5/*fields*/ * 1; 12 return headerSIZE; 13 } 14 15 std::ostream& operator <<(std::ostream& os, const ClipboardHTMLHeader& header) 16 { 17 using namespace std; 18 const int numSpaces = 8; 19 const int headerSIZE = header.size(); 20 21 os << "Version:" << header.version << endl 22 << "StartHTML:" << setw(numSpaces) << setfill('0') << (header.StartHTML < 0 ? -1 : headerSIZE + header.StartHTML) << endl 23 << "EndHTML:" << setw(numSpaces) << setfill('0') << (header.EndHTML < 0 ? -1 : headerSIZE + header.EndHTML) << endl 24 << "StartFragment:" << setw(numSpaces) << setfill('0') << (header.StartFragment < 0 ? -1 : headerSIZE + header.StartFragment) << endl 25 << "EndFragment:" << setw(numSpaces) << setfill('0') << (header.EndFragment < 0 ? -1 : headerSIZE + header.EndFragment) << endl; 26 //<< "StartSelection:" << setw(numSpaces) << setfill('0') << (header.StartSelection < 0 ? -1 : headerSIZE + header.StartSelection) << endl 27 //<< "EndSelection:" << setw(numSpaces) << setfill('0') << (header.EndSelection < 0 ? -1 : headerSIZE + header.EndSelection) << endl; 28 return os; 29 } 30 31 bool CRichEditor::CopyHTMLToClipboard(LPCWSTR lpszWide) 32 { 33 using namespace std; 34 35 string html = "<!--StartFragment-->" + decode(lpszWide, CP_UTF8) + "<!--EndFragment-->"; 36 char docBegin[] = "<HTML><HEAD><TITLE>*</TITLE></HEAD><BODY>"; 37 char docEnd[]="</BODY></HTML>"; 38 39 ClipboardHTMLHeader h; 40 h.version = "0.9"; 41 h.StartHTML = 0; 42 h.EndHTML = sizeof(docBegin) + html.length() + sizeof(docEnd); 43 h.StartFragment = sizeof(docBegin); 44 h.EndFragment = sizeof(docBegin) + html.length(); 45 //h.StartSelection = h.StartFragment; 46 //h.EndSelection = h.EndFragment; 47 48 stringstream ss; 49 ss << h; 50 ss << docBegin; 51 ss << html; 52 ss << docEnd; 53 54 55 56 // Get clipboard id for HTML format... 57 static int cfid = 0; 58 cfid = RegisterClipboardFormat(L"HTML Format"); 59 // Open the clipboard... 60 if(::OpenClipboard(0)) { 61 EmptyClipboard(); 62 63 HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE/* |GMEM_DDESHARE*/, (int)ss.tellp() + 1); 64 char* buf = (char*)GlobalLock(hMem); 65 ss.read( buf, ss.tellp()); 66 buf[ss.tellp()] = 0; 67 GlobalUnlock(hMem); 68 69 ::SetClipboardData(cfid, hMem); 70 CloseClipboard(); 71 GlobalFree(hMem); 72 } 73 74 return false; 75 }
其中selection是可选的,重载了运算符<<,且定义了很多int类型记录它们的偏移量。
接下来需要在insert table的之前深拷贝剪切板的内容到内存中,在时候恢复内存的数据到剪切板中,这其中对用户都是不可知的并且速度也是相当快的。
刚开始我们以为GlobalLock剪切板所获得的内存块是安全的,所以写了代码在push的时候lock,然后在pop的时候unlock,谁知道调用EmptyClipboard后就照样清空了,只能老老实实的拷贝全部的内存数据了,我们采用了map来暂存剪切板中各种类型的数据,针对不同的format的clipboard分别存到不同的map中。
1 std::map<UINT, HGLOBAL> _clipdata; 2 void CRichEditor::popClipboardData() 3 { 4 ::OpenClipboard(0); 5 ::EmptyClipboard(); 6 for (auto it = _clipdata.begin(); it != _clipdata.end(); ++it) 7 ::SetClipboardData(it->first, it->second); 8 ::CloseClipboard(); 9 10 for (auto it = _clipdata.begin(); it != _clipdata.end(); ++it) 11 ::GlobalFree(it->second); 12 13 _clipdata.clear(); 14 } 15 16 void CRichEditor::pushClipboardData() 17 { 18 ::OpenClipboard(0); 19 20 for (UINT next = ::EnumClipboardFormats(0); next != 0; next = ::EnumClipboardFormats(next)) 21 { 22 if(::IsClipboardFormatAvailable(next)) 23 { 24 HGLOBAL hmem = ::GetClipboardData(next); 25 void* src = ::GlobalLock(hmem); 26 SIZE_T bytes = ::GlobalSize(hmem); 27 28 _clipdata[next] = ::GlobalAlloc( GMEM_MOVEABLE, bytes ); 29 30 void* dst = ::GlobalLock( _clipdata[next] ); 31 memcpy(dst, src, bytes); 32 ::GlobalUnlock(_clipdata[next]); 33 34 ::GlobalUnlock(hmem); 35 } 36 } 37 38 ::EmptyClipboard(); 39 ::CloseClipboard(); 40 }
切记第10、11行必须这样写,必须在CloseClipboard之后来GlobalFree,否则就没办法恢复到备份clipboard之前的状态了。
差不多到这里就是今天一天的奇遇的全部内容了。。。
相信很多人碰到这个问题的时候最开始都受到
http://stackoverflow.com/questions/15962982/how-to-set-html-unicode-text-to-clipboard-in-vc
所误导,根本就不能用,问题是代码写的乱七八糟,怕是只有自己能看得懂。例如105这个数值是怎么来的?length+4又为什么?
希望祖国的花朵们不要再受到外国人的毒代码摧残了。。.今天就写到这里,希望black早点回家~