Click here to Skip to main content
Click here to Skip to main content

A Word add-in to syntax highlight selected text

, 2 Feb 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
A Word addin to syntax highlight selected text. The toolbar is permanent, with a transparent button icon.

Sample Image - Addin_Shot.gif

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;
 
// attach command bar if already exists
HRESULT hr = spCmdBars->get_Item(_variant_t(RSTR(
    IDS_SYNTAX_BAR)), &spNewCmdBar);
 
if( FAILED(hr) || spNewCmdBar == NULL )
{
    // New Create if not exists
    CComVariant vName (RSTR(IDS_SYNTAX_BAR));
    CComVariant vPos  (msoBarFloating);
    CComVariant vTemp (VARIANT_FALSE); //menu is NOT temporary 
    CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
    spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
}
Attach old button if exists:
// attach buttons
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 )
    {
        // keep reference to button disp
        m_spButtons[nAttachIndex ++] = spButton;
        DispEventAdvise(nAttachIndex, (IDispatch*)spButton);
        pSyntax->m_bAttached = TRUE;
    }
    else
    {
        spButton->Delete();
        bUpdated = TRUE;
    }
}
Remove command bar in 'DllUnregisterServer':
//
// Attach to Running Word instance
// or Create New Word Application instance
//
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();
 
        // Save
        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.

// to determine localized 'format name'
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, 
     ::LoadBitmap(_Module.GetResourceInstance(), 
     MAKEINTRESOURCE(IDB_HIGHLIGHT)));
::CloseClipboard();
 
spButton->PasteFace();
spButton->CopyFace();
 
// Init
_tcscpy(m_szFaceFormatName, _T("Toolbar Button Face"));
_tcscpy(m_szMaskFormatName, _T("Toolbar Button Mask"));
 
// loop
::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;

    // skip some formats
    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;
    }

    // get format name
    if( format <= 14 )
        data.m_szFormatName[0] = 0;
    else if( GetClipboardFormatName(format, 
                  data.m_szFormatName, 100) == 0 )
        data.m_szFormatName[0] = 0;

    // get handle
    HANDLE hMem = ::GetClipboardData( format );
    if( hMem == NULL )
        continue;
 
    // copy handle
    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:

// API
#define SYNTAX_MOD_API __declspec(dllexport)
 
// extern "C"
#ifdef __cplusplus
    extern "C" {
#endif
 
// Caption
LPCSTR SYNTAX_MOD_API GetCaption();
 
// Tooltip ( Optional )
LPCSTR SYNTAX_MOD_API GetTooltip();
 
// Button Face ( Optional )
HBITMAP SYNTAX_MOD_API GetBtnFace();
 
// Parse
VOID SYNTAX_MOD_API Parse(LPWSTR text, INT len);
 
// Nextpos
BOOL SYNTAX_MOD_API GetNextPos(INT & pos, INT & len);
 
// Color
BOOL SYNTAX_MOD_API GetColor(INT & color);
 
// Background Color ( Optional )
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:

// regular expression pattern
LPCWSTR pattern = L"(?<upper>[A-Z]+)|(?<lower>[a-z]+)|(?<number>[0-9]+)";

// color
ColorMap map[] =
{
    { L"upper" , RGB(0xff, 0, 0) },
    { L"lower" , RGB(0, 0xff, 0) },
    { L"number", RGB(0, 0, 0xff) },
};

// entrance
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD ul_reason_for_call, 
                       LPVOID lpReserved
)
{
    if( ul_reason_for_call == DLL_PROCESS_ATTACH )
    {
        // when initialization
        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

sswater shi
Software Developer (Senior)
China China
Begin coding from basic, since 1994. Interested in coding and database and website constructing.
My website: http://www.regexlab.com/ - Regular Expression Laboratory
The easiest regex engine: http://www.regexlab.com/deelx/

Comments and Discussions

 
QuestionCauses Word 2010 to crash PinmemberMember 855191012-Aug-12 9:58 
AnswerRe: Causes Word 2010 to crash Pinmembersswater shi12-Aug-12 14:23 
GeneralMy vote of 5 PinmemberWonkey6-May-11 5:20 
GeneralProposed feature: config files PinmemberJARivera17-Feb-11 4:54 
GeneralRe: Proposed feature: config files Pinmembersswater shi17-Feb-11 18:58 
QuestionDoes it support Office 2007? Pinmemberjeasonzhao17-Aug-10 22:23 
AnswerRe: Does it support Office 2007? Pinmembersswater shi18-Aug-10 5:15 
General_commandbarbutton Image problem PinmemberMember 354972315-Apr-09 20:30 
GeneralSet button Image PinmemberMember 354972314-Apr-09 3:26 
GeneralRe: Set button Image Pinmembersswater shi14-Apr-09 5:00 
AnswerSet image on the button PinmemberZhefu Zhang6-Aug-08 19:39 
GeneralRe: Set image on the button Pinmembersswater shi16-Oct-08 0:43 
GeneralRe: Set image on the button PinmemberMember 354972315-Apr-09 1:56 
QuestionProblem with normal.dot Pinmemberprogramvinod26-Jul-07 0:10 
AnswerRe: Problem with normal.dot PinmemberOren K.22-Oct-07 4:55 
AnswerRe: Problem with normal.dot Pinmembersswater shi16-Oct-08 0:55 
GeneralRemove Buttons from "Standard" Menu Bar Pinmemberklarz25-Jul-07 4:52 
AnswerRe: Remove Buttons from "Standard" Menu Bar Pinmembersswater shi25-Jul-07 6:02 
Questionno matching symbolic information found? PinmemberYakie27-Mar-07 14:13 
AnswerRe: no matching symbolic information found? Pinmembersswater shi27-Mar-07 15:43 
QuestionRe: no matching symbolic information found? PinmemberYakie6-Apr-07 13:58 
AnswerRe: no matching symbolic information found? Pinmembersswater shi6-Apr-07 16:23 
QuestionChinese Resources Pinmemberianormy8-Jun-06 2:05 
AnswerRe: Chinese Resources Pinmembersswater shi8-Jun-06 5:29 
QuestionHow did you automatically install the toolbar? Pinmemberianormy7-Jun-06 4:31 
AnswerRe: How did you automatically install the toolbar? Pinmembersswater shi7-Jun-06 16:01 
GeneralRe: How did you automatically install the toolbar? Pinmemberianormy7-Jun-06 22:55 
GeneralRe: How did you automatically install the toolbar? Pinmembersswater shi7-Jun-06 23:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 2 Feb 2007
Article Copyright 2006 by sswater shi
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid