Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C++
Article

A Word add-in to syntax highlight selected text

Rate me:
Please Sign up or sign in to vote.
4.77/5 (12 votes)
2 Feb 2007CPOL3 min read 116.4K   3.1K   67   28
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:
C++
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:
C++
// 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':
C++
//
// 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.

C++
// 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:

C++
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:

C++
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:

C++
// 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:

C++
// 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)


Written By
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 Pin
Member 964871812-Aug-12 8:58
Member 964871812-Aug-12 8:58 
AnswerRe: Causes Word 2010 to crash Pin
sswater shi12-Aug-12 13:23
sswater shi12-Aug-12 13:23 
GeneralMy vote of 5 Pin
Wonkey6-May-11 4:20
Wonkey6-May-11 4:20 
GeneralProposed feature: config files Pin
JARivera17-Feb-11 3:54
JARivera17-Feb-11 3:54 
GeneralRe: Proposed feature: config files Pin
sswater shi17-Feb-11 17:58
sswater shi17-Feb-11 17:58 
QuestionDoes it support Office 2007? Pin
Jeason Zhao17-Aug-10 21:23
Jeason Zhao17-Aug-10 21:23 
AnswerRe: Does it support Office 2007? Pin
sswater shi18-Aug-10 4:15
sswater shi18-Aug-10 4:15 
General_commandbarbutton Image problem Pin
Member 354972315-Apr-09 19:30
Member 354972315-Apr-09 19:30 
GeneralSet button Image Pin
Member 354972314-Apr-09 2:26
Member 354972314-Apr-09 2:26 
GeneralRe: Set button Image Pin
sswater shi14-Apr-09 4:00
sswater shi14-Apr-09 4:00 
AnswerSet image on the button Pin
Zhefu Zhang6-Aug-08 18:39
Zhefu Zhang6-Aug-08 18:39 
I just came across here and saw your code of pasting image to button THRU clipboard. It seems not a good idea for apparent reason.
The following code excerpt shows how to load an image source as the button image:

IPicture *Ipic = NULL;
//Do not use BITMAP resource for it ripped the bitmap file header
HRSRC hResInfo = ::FindResource(Inst, (LPTSTR)IDR_XXX, _T("SomeResType"));
HGLOBAL hxx = ::LoadResource(Inst, hResInfo);
IStream* pIStream = NULL;
void* buffer = ::GlobalLock(hxx);
DWORD ss = SizeofResource(hInstance, hResInfo);
void* buffer2 = ::GlobalAlloc(GPTR, ss);
memcpy(buffer2, buffer, ss);
GlobalUnlock(hxx);
hr = ::CreateStreamOnHGlobal(buffer2, FALSE, &pIStream);
hr = ::OleLoadPicture(pIStream, ss, FALSE, IID_IPicture, (LPVOID*)&Ipic);
hr = yourCmdButton->put_Picture((IPictureDisp*)Ipic);
GlobalFree(buffer2);

Cheers.
GeneralRe: Set image on the button Pin
sswater shi15-Oct-08 23:43
sswater shi15-Oct-08 23:43 
GeneralRe: Set image on the button Pin
Member 354972315-Apr-09 0:56
Member 354972315-Apr-09 0:56 
QuestionProblem with normal.dot Pin
programvinod25-Jul-07 23:10
programvinod25-Jul-07 23:10 
AnswerRe: Problem with normal.dot Pin
Oren K.22-Oct-07 3:55
Oren K.22-Oct-07 3:55 
AnswerRe: Problem with normal.dot Pin
sswater shi15-Oct-08 23:55
sswater shi15-Oct-08 23:55 
GeneralRemove Buttons from "Standard" Menu Bar Pin
klarz25-Jul-07 3:52
klarz25-Jul-07 3:52 
AnswerRe: Remove Buttons from "Standard" Menu Bar Pin
sswater shi25-Jul-07 5:02
sswater shi25-Jul-07 5:02 
Questionno matching symbolic information found? Pin
Yakie27-Mar-07 13:13
Yakie27-Mar-07 13:13 
AnswerRe: no matching symbolic information found? Pin
sswater shi27-Mar-07 14:43
sswater shi27-Mar-07 14:43 
QuestionRe: no matching symbolic information found? Pin
Yakie6-Apr-07 12:58
Yakie6-Apr-07 12:58 
AnswerRe: no matching symbolic information found? Pin
sswater shi6-Apr-07 15:23
sswater shi6-Apr-07 15:23 
QuestionChinese Resources Pin
ianormy8-Jun-06 1:05
ianormy8-Jun-06 1:05 
AnswerRe: Chinese Resources Pin
sswater shi8-Jun-06 4:29
sswater shi8-Jun-06 4:29 
QuestionHow did you automatically install the toolbar? Pin
ianormy7-Jun-06 3:31
ianormy7-Jun-06 3:31 

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

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