在实现IM即时通讯软件聊天框中的图片文字等内容的混合复制(复制的内容既包括文字,也包括图片)与粘贴功能时,为了方便用户操作,要支持与QQ、微信、企业微信、邮箱等常用软件之间互通的。即从我们的IM软件中混合复制的内容粘贴到QQ等软件的聊天框中时要能正确显示出来内容,从QQ等其他软件中复制的内容粘贴到我们的IM软件中能正常显示。
要实现上述对通功能,就需要了解多种格式的剪切板数据格式,粘贴内容时就是要从剪切板中获取指定剪切板格式数据内容,将剪切板格式的数据解析出来,然后显示到目标窗口中。这就要求我们在复制内容时构造多种剪切板格式数据,也要在粘贴内容时能解析多种剪切板格式数据。
能否找到一种多种软件都支持的、比较通用的剪切板格式呢?确实是有的,HTML_FORMAT剪切板数据格式就是一种比较通用的,基本所有的IM软件在构建剪切板数据时都会构建HTML_FORMAT格式数据的,所以我们在复制时构造HTML_FORMAT格式数据,粘贴时解析HTML_FORMAT格式的数据即可。经过之前的测试,要将图片文字的混合内容复制到QQ中,QQ能识别出来并正常显示,必须要在复制内容时构建QQ_Unicode_RichEdit_Format剪切板格式数据,否则QQ不支持(这点没办法,QQ是主流IM即时通讯软件,我们必须向QQ“靠拢”,主动提供对QQ的对通支持)。下面我们主要讲HTML_FORMAT和QQ_Unicode_RichEdit_Format两种剪切板数据格式。
Widnows系统预先定义了一些通用的剪切板格式,比如CF_TEXT(文字)、CF_UNICODETEXT(Unicode文字)、CF_BITMAP(位图)等。对于HTML_FORMAT、QQ_Unicode_RichEdit_Format等这些系统未定义的剪切板格式,则需要调用API函数RegisterClipboardFormat向系统注册对听的格式,注册时将使用HTML_FORMAT、QQ_Unicode_RichEdit_Format名字去注册,RegisterClipboardFormat函数会返回对应剪切板格式id(DWORD整型)。不管是哪个软件注册的,只要注册时指定的名称相同,获取的剪切板数据格式id都是相等的。
如果是获取对应格式的剪切板数据,则需要调用GetClipboardData函数,传入对应的剪切板格式id。如果是要向剪切板中设置剪切板数据格式,则要调用SetClipboardData,传入对应的剪切板格式id。
首先我们来看如何查看到IM软件在复制内容都支持那些剪切板数据格式呢?XP系统中提供了一个叫做clipbrd.exe的剪切板查看工具。比如我们在QQ中复制了如下图的图片和文字的混合内容:
复制后,可以打开clipbrd,点击clipbrd工具栏的“查看”,即可看到QQ在复制内容时都构建了那些剪切板格式的数据,如下所示:
除了常用的CF_TEXT(文本)等格式外,我们看到了上面我们讲到的HTML_FORMAT和QQ_Unicode_RichEdit_Format两种自定义的剪切板数据格式。
这个clipbrd工具在win7及以上系统已经不自带这个工具了,如果需要这个工具可以到网上下载,可以直接拿到Win7及以上系统中运行。
在构造指定的剪切板格式之前,我们需要到剪切板中获取该种剪切板格式的数据内容,查看这些数据的构建内容与格式。以获取QQ_Unicode_RichEdit_Format剪切板格式数据为例,获取该种格式数据的测试代码如下所示:
HANDLE hClip = NULL;
char *pBuf = NULL;
WCHAR* pwchBuf = NULL;
char* pTemp = NULL;
m_dwClipFormatQQ = ::RegisterClipboardFormat( _T("QQ_Unicode_RichEdit_Format") );
BOOL bRet = ::OpenClipboard( NULL );
if ( !bRet )
{
return;
}
hClip = GetClipboardData( m_dwClipFormatQQ );
if ( NULL == hClip )
{
CloseClipboard();
return;
}
pBuf = (char*)GlobalLock( hClip );
if ( NULL == pBuf )
{
GlobalUnlock( hClip );
CloseClipboard();
return;
}
int nNum = MultiByteToWideChar( CP_UTF8, 0, pBuf, -1, NULL, 0 );
pwchBuf = new WCHAR[nNum];
MultiByteToWideChar( CP_UTF8, 0, pBuf, -1, pwchBuf, nNum );
CString strContent;
CopyUtf8ToCStringT( strContent, pBuf );
m_editContent.SetWindowText( strContent );
GlobalUnlock( hClip );
因为QQ_Unicode_RichEdit_Format剪切板格式数据,在设置到剪切板之前,是以utf-8编码的字符串,所以在调用GetClipboardData获取到字符串数据之后,需要将utf-8编码的字符串转成Unicode编码,才能查看到字符串中的文字。如果直接查看的utf-8编码字符串中有中文,utf-8编码的中文会显示乱码,所以要字符串转成Unicode编码的。
通过上述测试代码,可以查得到在执行数据复制时构造的HTML_FORMAT格式的数据:
Version:0.9
StartHTML:00000112
EndHTML:00000461
StartFragment:00000126
EndFragment:00000425
123
也可以得到如下的QQ_Unicode_RichEdit_Format格式数据:
我们可以参考上面获取到的HTML_FORMAT和QQ_Unicode_RichEdit_Format格式的剪切板数据的构成与格式,轻松地写出构造这两种剪切板格式的数据。
构造HTML_FORMAT剪切板格式数据的代码如下:
CString strHtml = _T("");
strHtml.Format( _T("Version:0.9\r\nStartHTML:%08d\r\nEndHTML:%08d\r\nStartFragment:%08d\r\nEndFragment:%08d\r\n")
, HTML_BEGIN, nHtmlEnd, FRAGMENT_BEGIN, nFragmentEnd );
strHtml += _T("\r\n\r\n");
strHtml += pBuff; // 包含图片路径和文字的字符串已经事先准备好
strHtml += _T("\r\n\r\n\r\n");
int nResLen = WideCharToMultiByte( CP_UTF8, 0, strHtml, -1, 0, 0, 0, 0 );
char* pHtmlChar = (char *)VirtualAlloc( 0, nResLen, MEM_COMMIT, PAGE_READWRITE );
memset( pHtmlChar, 0, nResLen );
WideCharToMultiByte( CP_UTF8, 0, strHtml, -1, pHtmlChar, nResLen, NULL, NULL );
构造QQ_Unicode_RichEdit_Format剪切板格式数据的代码如下:
wstring ConstructQQFormatData( WCHAR *pDst, WCHAR *pSrc, std::vector& objectList )
{
if ( m_pRichEdit == NULL )
{
return L"";
}
if ( pSrc == NULL && pDst == NULL )
{
return L"";
}
if ( objectList.size() == 0 )
{
return L"";
}
// 得到选择的区域
long lBeginPos;
long lEndPos;
m_pRichEdit->GetSel( lBeginPos, lEndPos );
// 此处保证不修改传入的指针变量
WCHAR* pTempSrc = pSrc;
WCHAR* pTempDst = pDst;
wstring strRes = L" ";
//CStringW strTemp;
long lPos = lBeginPos;
long lCount = 0;
for ( int j = 0; j < (int)objectList.size(); j++ )
{
lCount = objectList[j].cp - lPos;
if ( lCount > 0 )
{
ParseReturnForQQ( &pTempDst, &pTempSrc, lCount, TRUE );
//strTemp.Format( L" ", pTempDst );
//strRes += strTemp;
strRes += L" ";
}
lPos = objectList[j].cp + 1;
if ( objectList[j].clsid == CLSID_GifAnimator )
{
IGifAnimator *pGifAnimator = NULL;
LRESULT hr = objectList[j].poleobj->QueryInterface( __uuidof(IGifAnimator), (void**)&pGifAnimator );
if ( SUCCEEDED( hr ) )
{
BSTR path = NULL;
pGifAnimator->GetFilePath( &path );
pGifAnimator->Release();
CUIString strPicPath = (LPCTSTR)(_bstr_t)path;
//strTemp.Format( L" ", path );
//strRes += strTemp;
strRes += L" ";
if ( path != NULL )
{
::SysFreeString( path );
}
}
}
else if ( objectList[j].clsid == CLSID_linkole )
{
Ilinkole *pLinkOle = NULL;
LRESULT hr = objectList[j].poleobj->QueryInterface( __uuidof(Ilinkole), (void**)&pLinkOle );
if ( SUCCEEDED(hr) )
{
BSTR name = NULL;
pLinkOle->GetName( &name );
pLinkOle->Release();
//strTemp.Format( L" ", name );
//strRes += strTemp;
strRes += L" ";
if ( name != NULL )
{
::SysFreeString( name );
}
}
}
pTempSrc++;
}
lCount = lEndPos - lPos;
if ( lCount > 0 )
{
ParseReturnForQQ( &pTempDst, &pTempSrc, lCount, FALSE );
//strTemp.Format( L" ", pTempDst );
//strRes += strTemp;
strRes += L" ";
}
strRes += L" ";
return strRes;
}
上述代码经过大量的测试和验证,其稳定性和可靠性绝对是有保证的,大家可以放心使用。