I found a few articles on interacting with the MS-Word spell-checker from C# and managed C++ using .NET.
(For anyone interested: this and this)
But I couldn't find anything comparable on doing it through standard unmanaged C++ in an MFC app, using COM. I assume the .NET examples are actually using COM meaning it's possible, but is it likely to be horrific and ugly to do?
I did this. It is not that complicated. I packed the whole thing in a DLL and made the suggestions-dialog myself.
Basically it is just opening word and asking it to do a spell-check for a specific single word. If the check failed, you ask for suggestions.
There are only two points where you can fail:
1. a document must be open, that means you always have to create one in code
2. for different word versions, the dictionary sometimes is stored as unicode and sometimes (older versions) not. Of course this is only important if you want to store new words to the user dictionary.
Ok, here's the code:
header file:
#pragma once
#include "msword.h"
class CWord
{
public:
CWord();
bool Initialize();
void Terminate();
bool CheckSpelling(CString text);
bool CorrectSpelling(CString text,CString& corrected,CWnd* pParent,CPoint point);
private:
_Application m_word;
COleVariant m_vTrue;
COleVariant m_vFalse;
COleVariant m_vOpt;
bool m_bInit;
void AddToDictionary(CString text);
CString OleErrorMsg(COleDispatchException* e);
};
And the implementation (only the most important parts, sorry for the german comments -> I will translate them if needed)
CWord::CWord()
{
// häufig gebrauchte Variants
m_vFalse = COleVariant((short) FALSE);
m_vTrue = COleVariant((short) TRUE);
m_vOpt = COleVariant((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
m_bInit = false;
}
// sinnvolle Fehlermeldung erstellen
CString CWord::OleErrorMsg(COleDispatchException* e)
{
CString msg;
if(!e->m_strSource.IsEmpty())
msg = e->m_strSource + " - ";
if(!e->m_strDescription.IsEmpty())
msg += e->m_strDescription;
else
msg += "Unbekannter Fehler.";
return msg;
}
// Word starten
bool CWord::Initialize()
{
try
{
if(!m_bInit)
{
m_word.CreateDispatch("Word.Application");
m_bInit = true;
}
}
catch(COleDispatchException* e)
{
AfxMessageBox(OleErrorMsg(e),MB_ICONEXCLAMATION);
e->Delete();
return false;
}
return true;
}
// Aufräumen
void CWord::Terminate()
{
try
{
if(m_word != NULL)
{
m_word.Quit(m_vFalse,m_vOpt,m_vOpt);
m_word.DetachDispatch();
m_word = NULL;
m_bInit = false;
}
}
catch(COleDispatchException* e)
{
AfxMessageBox(OleErrorMsg(e),MB_ICONEXCLAMATION);
e->Delete();
}
}
// ein Wort auf Rechtschreibung überprüfen
bool CWord::CheckSpelling(CString text)
{
try
{
if(m_word == NULL)
{
AfxMessageBox("Word nicht initialisiert!",MB_ICONINFORMATION);
return false;
}
int res = m_word.CheckSpelling((LPCTSTR) text,m_vOpt,m_vFalse,m_vOpt,
m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt);
return res != 0;
}
catch(COleDispatchException* e)
{
AfxMessageBox(OleErrorMsg(e),MB_ICONEXCLAMATION);
e->Delete();
}
return false;
}
// Dialog mit Möglichkeiten anzeigen und Auswahl zurückgeben
bool CWord::CorrectSpelling(CString text,CString& corrected,CWnd* pParent,CPoint /*point*/)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
ASSERT(pParent != NULL);
bool ret = false;
CVorschlagDlg dlg(pParent);
dlg.m_strWort = text;
try
{
// ein Dokument muss geöffnet sein, sonst verweigert GetSpellingSuggestions!
Documents docs = m_word.GetDocuments();
_Document doc = docs.Add(m_vOpt,m_vOpt,m_vOpt,m_vTrue);
// jetzt die Vorschläge holen
SpellingSuggestions spells = m_word.GetSpellingSuggestions((LPCTSTR) text,m_vOpt,m_vOpt,m_vOpt,
m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt,m_vOpt);
// in die Stringlist des Dialogs einfüllen
for(int i = 1;i <= spells.GetCount();i++)
{
SpellingSuggestion ss = spells.Item(i);
dlg.m_slVorschlaege.AddTail((LPCTSTR) ss.GetName());
}
// das Dokument wieder schliessen
doc.SetSaved(TRUE);
doc.Close(m_vFalse,m_vOpt,m_vOpt);
}
catch(COleDispatchException* e)
{
AfxMessageBox(OleErrorMsg(e),MB_ICONEXCLAMATION);
e->Delete();
return false;
}
// Dialog öffnen und Ergebnis auswerten
// ACHTUNG: im Switch fällt das Ergebnis durch bis zu 3 Cases durch!
switch(dlg.DoModal())
{
case IDOK:
// noch zum Word-Wörterbuch hinzufügen
AddToDictionary(dlg.m_strWort);
case IDYES:
case IDIGNORE:
corrected = dlg.m_strWort;
ret = true;
break;
default:
break;
} // switch
return ret;
}
void CWord::AddToDictionary(CString text)
{
CString strFilename;
CStdioFile datei;
try
{
// den Dateinamen herausfinden
Dictionaries dics = m_word.GetCustomDictionaries();
Dictionary dic = dics.GetActiveCustomDictionary();
strFilename = dic.GetPath() + "\\" + dic.GetName();
}
catch(COleDispatchException* e)
{
AfxMessageBox(OleErrorMsg(e),MB_ICONEXCLAMATION);
e->Delete();
return;
}
try
{
if(!datei.Open(strFilename, CFile::modeReadWrite))
{
AfxMessageBox("Fehler beim Öffnen des Wörterbuches!",MB_ICONEXCLAMATION);
return;
}
// herausfinden ob Datei UNICODE - kodiert - für Office 2007
bool bUnicode = false;
unsigned char cBOM[2];
const unsigned char UNICODE_BOM[2] = {unsigned char(0xFF),unsigned char(0xFE)};
if(datei.GetLength() > 2)
{
datei.Read((LPVOID) cBOM,2);
bUnicode = cBOM[0] == UNICODE_BOM[0] && cBOM[1] == UNICODE_BOM[1];
}
datei.SeekToEnd();
if(bUnicode)
{
USES_CONVERSION;
LPCWSTR lpsz = T2W(text);
datei.Write(lpsz,wcslen(lpsz) * sizeof(WCHAR));
}
else
{
datei.WriteString(text + "\n");
}
datei.Close();
// jetzt noch das CRLF im Unicode nachschreiben
if(bUnicode)
{
using namespace std;
char crlf[4] = {13,0,10,0};
//ofstream of(strFilename,ios_base::binary | ios_base::_Openmode::app);
ofstream of(strFilename,ios_base::binary | ios_base::app);
of.write(crlf,4);
of.close();
}
}
catch(CException* e)
{
e->ReportError();
e->Delete();
}
}