VC++ IM即时通讯软件中的剪切板数据对通的实现细节(附源码)

       在实现IM即时通讯软件聊天框中的图片文字等内容的混合复制(复制的内容既包括文字,也包括图片)与粘贴功能时,为了方便用户操作,要支持与QQ微信企业微信邮箱等常用软件之间互通的。即从我们的IM软件中混合复制的内容粘贴到QQ等软件的聊天框中时要能正确显示出来内容,从QQ等其他软件中复制的内容粘贴到我们的IM软件中能正常显示。

VC++ IM即时通讯软件中的剪切板数据对通的实现细节(附源码)_第1张图片

       要实现上述对通功能,就需要了解多种格式的剪切板数据格式,粘贴内容时就是要从剪切板中获取指定剪切板格式数据内容,将剪切板格式的数据解析出来,然后显示到目标窗口中。这就要求我们在复制内容时构造多种剪切板格式数据,也要在粘贴内容时能解析多种剪切板格式数据。

1、剪切板数据格式

       能否找到一种多种软件都支持的、比较通用的剪切板格式呢?确实是有的,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两种剪切板数据格式。

VC++ IM即时通讯软件中的剪切板数据对通的实现细节(附源码)_第2张图片

        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。

2、使用Clipbrd工具查看剪切板中都有哪些剪切板数据格式

       首先我们来看如何查看到IM软件在复制内容都支持那些剪切板数据格式呢?XP系统中提供了一个叫做clipbrd.exe的剪切板查看工具。比如我们在QQ中复制了如下图的图片和文字的混合内容:

VC++ IM即时通讯软件中的剪切板数据对通的实现细节(附源码)_第3张图片

复制后,可以打开clipbrd,点击clipbrd工具栏的“查看”,即可看到QQ在复制内容时都构建了那些剪切板格式的数据,如下所示:

VC++ IM即时通讯软件中的剪切板数据对通的实现细节(附源码)_第4张图片

除了常用的CF_TEXT(文本)等格式外,我们看到了上面我们讲到的HTML_FORMAT和QQ_Unicode_RichEdit_Format两种自定义的剪切板数据格式。

       这个clipbrd工具在win7及以上系统已经不自带这个工具了,如果需要这个工具可以到网上下载,可以直接拿到Win7及以上系统中运行。

3、通过测试代码查看指定剪切板数据的构成与格式

       在构造指定的剪切板格式之前,我们需要到剪切板中获取该种剪切板格式的数据内容,查看这些数据的构建内容与格式。以获取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格式数据










4、通过代码去构造HTML_FORMAT和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;
}

上述代码经过大量的测试和验证,其稳定性和可靠性绝对是有保证的,大家可以放心使用。

你可能感兴趣的:(VC++常用功能代码封装,即时通讯,剪切板,HTML_FORMAT,QQ剪切板格式,clipbrd)