Introduction
The Syntax Highlight add-in is a COM add-in for Microsoft Word. Through a COM
add-in, we can add a menu item or toolbar button onto Word, implementing special
functions which we need.
The add-in on this page can change the color or background color of a
selected text. How to do this is not fixed. Anyone can define a new rule for
changing color, by implementing the functions which are declared in the
interface header file. This add-in provides an open method to operate with text
in Word. If you are interested in parsing text, this add-in can help you parse
text too.
This software consists of a main module (sntxaddn.dll) and a set of
highlight rules.
References
Basically, as an add-in, the principle of this software is the same as the
one in: Building an Office2K COM add-in with VC++/ATL, which is an
add-in for Outlook.
So, this article will mostly describe the special points of this add-in
itself.
Interesting & Special Points
1. Permanent, not temporary CommandBar
A temporary command bar will be removed when Word is closed, and will be
re-created when Word is opened. If you drag the newly created command bar to a
position, the position will be lost after Word is re-opened. Instead, a
permanent command bar will not be removed when Word is closed.
But a new problem is emitted: "When to remove the command bar if the user
want to uninstalls it?" My solution: "Remove the command bar in
DllUnregisterServer
."
Create permanent, not temporary CommandBar:
CComPtr <_CommandBars> spCmdBars = wordApp->
GetCommandBars();
CComPtr <CommandBar> spNewCmdBar = NULL;
HRESULT hr = spCmdBars->get_Item(_variant_t(RSTR(
IDS_SYNTAX_BAR)), &spNewCmdBar);
if( FAILED(hr) || spNewCmdBar == NULL )
{
CComVariant vName (RSTR(IDS_SYNTAX_BAR));
CComVariant vPos (msoBarFloating);
CComVariant vTemp (VARIANT_FALSE);
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
}
Attach old button if exists:
int btncnt = spBarControls->GetCount();
for(i=btncnt; i>=1; i--)
{
CComPtr <CommandBarControl> spNewBar =
spBarControls->GetItem(_variant_t((long)i));
CComQIPtr <_CommandBarButton> spButton( spNewBar );
_bstr_t tag = spButton->GetTag();
CSyntaxModule * pSyntax = NULL;
for(int j=0; j<MAX_BUTTONS; j++)
{
if( m_pSyntax[j] == NULL )
break;
if( m_pSyntax[j]->m_szSyntaxName == tag )
{
pSyntax = m_pSyntax[j];
break;
}
}
if( pSyntax != NULL )
{
m_spButtons[nAttachIndex ++] = spButton;
DispEventAdvise(nAttachIndex, (IDispatch*)spButton);
pSyntax->m_bAttached = TRUE;
}
else
{
spButton->Delete();
bUpdated = TRUE;
}
}
Remove command bar in 'DllUnregisterServer':
CComPtr <_Application> CWordSntxAddnApp::AttachRunningWord()
{
HRESULT hr;
CLSID clsid;
hr = ::CLSIDFromProgID(L"Word.Application", &clsid);
if(FAILED(hr))
{
return NULL;
}
IUnknown * pUnknown = NULL;
hr = ::GetActiveObject(clsid, NULL, &pUnknown);
if( !FAILED(hr) )
{
return CComQIPtr <_Application> ( pUnknown );
}
else
{
COleDispatchDriver ddrv;
ddrv.CreateDispatch(clsid);
return CComQIPtr <_Application> (ddrv.m_lpDispatch);
}
}
VOID CWordSntxAddn::RemoveCommandBar(CComPtr <_Application> & wordApp)
{
CComPtr <_CommandBars> spCmdBars = wordApp->GetCommandBars();
CComPtr <CommandBar> spSyntaxCmdBar = NULL;
HRESULT hr =
spCmdBars->get_Item(_variant_t(RSTR(IDS_SYNTAX_BAR)),
&spSyntaxCmdBar);
if( !FAILED(hr) && spSyntaxCmdBar != NULL )
{
spSyntaxCmdBar->Delete();
CComQIPtr <Template> custom( wordApp->GetCustomizationContext() );
custom->Save();
}
}
2. Transparent button icon
In order to be compatible with Office 2000, I used 'PasteFace' to transfer a
button icon. According to Microsoft documentation, I was
going to use "Toolbar Button Face" and "Toolbar Button Mask" as the clipboard
format names.
But I met a problem, 'PasteFace' failed to transfer the transparent button
icon if the Office software is not the English version. Indeed, the clipboard
format name should be localized if Office software is the German or Chinese
version etc.
I asked in many forums, but nobody was able to say anything about it.
Afterwards, I found a way to determine the format names by myself.
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP,
::LoadBitmap(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_HIGHLIGHT)));
::CloseClipboard();
spButton->PasteFace();
spButton->CopyFace();
_tcscpy(m_szFaceFormatName, _T("Toolbar Button Face"));
_tcscpy(m_szMaskFormatName, _T("Toolbar Button Mask"));
::OpenClipboard(NULL);
UINT format = 0, n = 0, fmtsize[2] = {0};
TCHAR fmtnames[2][100] = {0};
while( (format = ::EnumClipboardFormats(format)) != 0 )
{
if( format > 17 && GetClipboardFormatName(format,
fmtnames[n], 100) != 0 )
{
fmtsize[n++] = ::GlobalSize(::GetClipboardData(format));
if( n >= 2 ) break;
}
}
if( fmtsize[0] > fmtsize[1] )
{
if( fmtnames[0][0] )
_tcscpy(m_szFaceFormatName, fmtnames[0]);
if( fmtnames[1][0] )
_tcscpy(m_szMaskFormatName, fmtnames[1]);
}
else
{
if( fmtnames[1][0] )
_tcscpy(m_szFaceFormatName, fmtnames[1]);
if( fmtnames[0][0] )
_tcscpy(m_szMaskFormatName, fmtnames[0]);
}
::CloseClipboard();
3. Backup and restore clipboard data around using 'PasteFace'
The 'CopyFace' and 'PasteFace' operations will destroy any old clipboard
data, which is copied into the clipboard before Word is opened, and the old
clipboard data may be what you are going to paste into Word.
Then, backup the old clipboard data before the toolbar button's Create
process:
if( ! ::OpenClipboard(NULL) )
return FALSE;
UINT format = 0;
while( (format = ::EnumClipboardFormats(format)) != 0 )
{
ClipboardData data;
data.m_nFormat = format;
if( format == CF_BITMAP || format == CF_METAFILEPICT ||
format == CF_PALETTE || format == CF_OWNERDISPLAY ||
format == CF_DSPMETAFILEPICT || format == CF_DSPBITMAP ||
( format >= CF_PRIVATEFIRST && format <= CF_PRIVATELAST ) )
{
continue;
}
if( format <= 14 )
data.m_szFormatName[0] = 0;
else if( GetClipboardFormatName(format,
data.m_szFormatName, 100) == 0 )
data.m_szFormatName[0] = 0;
HANDLE hMem = ::GetClipboardData( format );
if( hMem == NULL )
continue;
switch( format )
{
case CF_ENHMETAFILE:
case CF_DSPENHMETAFILE:
data.m_hData =
::CopyMetaFile((HMETAFILE)hMem, NULL);
break;
default:
{
int size = ::GlobalSize(hMem);
LPVOID pMem = ::GlobalLock( hMem );
data.m_hData = ::GlobalAlloc( GMEM_MOVEABLE |
GMEM_DDESHARE, size );
LPVOID pNewMem = ::GlobalLock( data.m_hData );
memcpy(pNewMem, pMem, size);
::GlobalUnlock(hMem);
::GlobalUnlock(data.m_hData);
}
}
m_lstData.AddTail(data);
}
::CloseClipboard();
Restore the clipboard after the toolbar button's creation is completed:
if( ! ::OpenClipboard(NULL) )
return FALSE;
::EmptyClipboard();
POSITION pos = m_lstData.GetHeadPosition();
while( pos != NULL )
{
ClipboardData & data = m_lstData.GetNext( pos );
UINT format = data.m_nFormat;
if( data.m_szFormatName[0] != 0 )
{
UINT u = RegisterClipboardFormat( data.m_szFormatName );
if( u > 0 ) format = u;
}
::SetClipboardData( format, data.m_hData );
}
::CloseClipboard();
4. Interface of the custom syntax rule
This add-in can load custom rules dynamically. The interface header file
is:
#define SYNTAX_MOD_API __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif
LPCSTR SYNTAX_MOD_API GetCaption();
LPCSTR SYNTAX_MOD_API GetTooltip();
HBITMAP SYNTAX_MOD_API GetBtnFace();
VOID SYNTAX_MOD_API Parse(LPWSTR text, INT len);
BOOL SYNTAX_MOD_API GetNextPos(INT & pos, INT & len);
BOOL SYNTAX_MOD_API GetColor(INT & color);
BOOL SYNTAX_MOD_API GetBgColor(INT & bgcolor);
#ifdef __cplusplus
}
#endif
How to implement 'Syntax Rule' easily
Each 'syntax rule' is a single dll file which implements those API's above.
I have provided a 'static library' (lib_syntax.lib) to help you to write 'syntax rule'. This
'static library' has implemented the key API's: 'Parse', 'GetNextPos', and 'GetColor'.
Everything you need to do is to provide a 'regular expression pattern' and a
'color definition map'. For example:
LPCWSTR pattern = L"(?<upper>[A-Z]+)|(?<lower>[a-z]+)|(?<number>[0-9]+)";
ColorMap map[] =
{
{ L"upper" , RGB(0xff, 0, 0) },
{ L"lower" , RGB(0, 0xff, 0) },
{ L"number", RGB(0, 0, 0xff) },
};
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if( ul_reason_for_call == DLL_PROCESS_ATTACH )
{
InitSyntax(
pattern,
0,
map,
sizeof(map)/sizeof(ColorMap)
);
}
return TRUE;
}
The main point about the 'regular expression pattern' is that: I used 'Named
group' of 'regular expression'.
DEELX regular expression engine for C++ supports 'Named group', so I used
DEELX in the lib_syntax.lib. DEELX regular expression engine is discussed at
codeproject.com, and introduced at DEELX homepage:
http://www.regexlab.com/deelx/ .
Please pay attention to the 'run-time library' of your dll project and the
lib_syntax.lib library. They must be the same before they can be linked
successfully.
References and Acknowledgements