项目中要使用代码编辑器,搜索之后,发现了强大的编辑器控件Scintilla。
1.简介
Scintilla是一款开源的语法高亮编辑器控件,官方网站:http://www.scintilla.org/。Scintilla是最优秀的编辑控件之一,实现了语法高亮,代码折叠,书签,自动完成等等诸多功能,速度快,源代码也比较好理解,易于扩展,易于增加对新语言的支持。 比较著名的scite,Notepad++,Notepad2都是基于Scintilla开发的。
Scintilla提供了Win32版本和Linux版本,在Windows下,它是一个窗体控件,对它的控制都通过发送消息来完成:
LRESULT SendMessage(HWND hWndScintilla,UINT Msg,WPARAM wParam,LPARAM lParam);
Scintilla提供了大量的消息API,每个消息可以带有0个、1个或2个参数。SendMessage函数中的消息,通常带有2个参数:wParam和lParam,没有使用的参数,则设置为0。对于大多数SCI_SETxxxxx设置类消息,都会有一个对应的SCI_GETxxxxx查询消息。
下面的图片是一个数控NC程序编辑器Demo:
2.在MFC中的简单使用
2.1 载入Scintilla链接库
首先,将SciLexer.dll复制到项目中。
a.在CwinApp下添加成员:
HMODULE m_hDll;
并初始化为NULL。
b.在InitInstance中载入DLL:
C++代码
- m_hDll = LoadLibrary(_T("SciLexer.dll"));
- if (m_hDll==NULL)
- {
- AfxMessageBox("LoadLibrary SciLexer.dll failure...");
- }
c.在ExitInstance中卸载DLL:
C++代码
- if (m_hDll != NULL)
- {
- FreeLibrary(m_hDll);
- }
2.2 创建一个封装Scintilla的类 — CScintillaWnd
C++代码
-
-
- #pragma once
-
-
- #include "Scintilla.h"
- #include "SciLexer.h"
-
- class CScintillaWnd : public CWnd
- {
- DECLARE_DYNAMIC(CScintillaWnd)
- public:
- CScintillaWnd();
- virtual ~CScintillaWnd();
- protected:
- DECLARE_MESSAGE_MAP()
- public:
- virtual BOOL Create(
- DWORD dwExStyle, DWORD dwStyle,const RECT& rect,
- CWnd* pParentWnd, UINT nID);
-
- };
-
-
-
-
- #include "stdafx.h"
- #include "ColorTextBox.h"
- #include "ScintillaWnd.h"
-
-
- IMPLEMENT_DYNAMIC(CScintillaWnd, CWnd)
- CScintillaWnd::CScintillaWnd()
- {
- }
-
- CScintillaWnd::~CScintillaWnd(){}
-
- BEGIN_MESSAGE_MAP(CScintillaWnd, CWnd)
- END_MESSAGE_MAP()
-
-
- BOOL CScintillaWnd::Create(DWORD dwExStyle, DWORD dwStyle,const RECT& rect, CWnd* pParentWnd, UINT nID)
-
- {
-
- return CWnd::CreateEx(dwExStyle,"Scintilla","",dwStyle,rect,pParentWnd,nID);
- }
2.3 CScintillaWnd类的使用举例
将CScintillaWnd类的对象作为目标窗体类的一个成员,比如某个对话框CColorTextBoxDlg 。
C++代码
- class CColorTextBoxDlg : public CDialog
- {
-
- public:
- CColorTextBoxDlg(CWnd* pParent = NULL);
-
- enum { IDD = IDD_COLORTEXTBOX_DIALOG };
- protected:
- virtual void DoDataExchange(CDataExchange* pDX);
-
- public:
- CScintillaWnd m_ScintillaWnd;
- afx_msg int OnCreate(LPCreateSTRUCT lpCreateStruct);
- afx_msg void OnSize(UINT nType, int cx, int cy);
- };
a.在CcolorTextBoxDlg的OnCreate中创建Scintilla窗口:
C++代码
- m_ScintillaWnd.Create(
- WS_EX_CLIENTEDGE,
- WS_CHILD | WS_VISIBLE,
- CRect(0,0,lpCreateStruct->cx,lpCreateStruct->cy),
- this,10000);
b.在CcolorTextBoxDlg的OnSize中调整Scintilla窗口的大小:
C++代码
- m_ScintillaWnd.MoveWindow(0,0,cx,cy);
2.4 接收来自Scintilla控件的通知
当Scintilla控件发生事件时,会用WM_NOTITY消息通知父窗体。
2.4.1在MFC中
C++代码
- CxxxxWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
- {
-
- LPNMHDR pnmh = (LPNMHDR)lParam;
- struct SCNotification* scn = (struct SCNotification*)lParam;
-
- switch(pnmh->code)
- {
- case SCN_CHARADDED:
-
-
-
-
- break;
-
-
- }
- }
2.4.2在SDK中
C++代码
- NMHDR *lpnmhdr;
-
- case WM_NOTIFY:
- lpnmhdr = (LPNMHDR) lParam;
- if(lpnmhdr->hwndFrom==hwndScintilla)
- {
- switch(lpnmhdr->code)
- {
- case SCN_CHARADDED:
-
-
-
- break;
- }
- }
- break;
2.5 例:行号的显示(MFC)
要显示行号,首先需要在m_ScintillaWnd创建后立即调用以下函数,并在Scintilla通知主窗口SCN_MODIFIED,SCN_ZOOM时调用:
C++代码
- void UpdateLineNumberWidth(void)
- {
- char tchLines[32];
- int iLineMarginWidthNow;
- int iLineMarginWidthFit;
- wsprintf(tchLines," %i ",
- m_ScintillaWnd.SendMessage(SCI_GETLINECOUNT,0,0));
- iLineMarginWidthNow = m_ScintillaWnd.SendMessage(
- SCI_GETMARGINWIDTHN,0,0);
- iLineMarginWidthFit = m_ScintillaWnd.SendMessage(
- SCI_TEXTWIDTH,STYLE_LINENUMBER,(LPARAM)tchLines);
-
- if (iLineMarginWidthNow != iLineMarginWidthFit)
- {
- m_ScintillaWnd.SendMessage(SCI_SETMARGINWIDTHN,0,
- iLineMarginWidthFit);
- }
- }
-
- BOOL OnNotify(
- WPARAM wParam, LPARAM lParam, LRESULT* pResult)
- {
- LPNMHDR pnmh = (LPNMHDR)lParam;
- struct SCNotification* scn = (struct SCNotification*)lParam;
-
- switch(pnmh->code)
- {
- case SCN_MODIFIED:
-
-
-
- case SCN_ZOOM:
-
-
-
- UpdateLineNumberWidth();
- break;
-
- }
- return CDialog::OnNotify(wParam, lParam, pResult);
- }
2.6 例:设置文本,打开已有文件(MFC)
C++代码
- void OnMenuFileOpen()
- {
- CFileDialog fDlg(TRUE);
- if (fDlg.DoModal()==IDOK)
- {
- char *pBuffer;
- CString strFilePath=fDlg.GetPathName();
- CStdioFile stdFile(strFilePath,CFile::modeRead);
- UINT nFileLength=stdFile.GetLength();
- pBuffer=new char[nFileLength+1];
- nFileLength=stdFile.Read(pBuffer,nFileLength);
- stdFile.Close();
-
- if (nFileLength>0)
- {
- if (m_ScintillaWnd.SendMessage(SCI_GETREADONLY,0,0))
- {
- m_ScintillaWnd.SendMessage(SCI_SETREADONLY,FALSE,0);
- }
- m_ScintillaWnd.SendMessage(SCI_CANCEL,0,0);
- m_ScintillaWnd.SendMessage(SCI_SETUNDOCOLLECTION,0,0);
- m_ScintillaWnd.SendMessage(SCI_EMPTYUNDOBUFFER,0,0);
-
-
- m_ScintillaWnd.SendMessage(SCI_CLEARALL,0,0);
-
-
- m_ScintillaWnd.SendMessage(SCI_MARKERDeleteALL,
- (WPARAM)-1,0);
- m_ScintillaWnd.SendMessage(SCI_ADDTEXT,
- nFileLength,(LPARAM)pBuffer);
- m_ScintillaWnd.SendMessage(SCI_SETUNDOCOLLECTION,1,0);
- m_ScintillaWnd.SendMessage(EM_EMPTYUNDOBUFFER,0,0);
- m_ScintillaWnd.SendMessage(SCI_SETSAVEPOINT,0,0);
- m_ScintillaWnd.SendMessage(SCI_GOTOPOS,0,0);
- m_ScintillaWnd.SendMessage(SCI_CHOOSECARETX,0,0);
- UpdateLineNumberWidth();
- }
- delete [] pBuffer;
- }
- }
2.7 例:设置默认字体以及字体大小(MFC)
C++代码
- void SetDefaultFont(int nSize,const char* face)
- {
- m_ScintillaWnd.SendMessage(SCI_STYLESETFORE,
- STYLE_DEFAULT, RGB(0x00,0x00,0x00));
- m_ScintillaWnd.SendMessage(SCI_STYLESETBACK,
- STYLE_DEFAULT, RGB(0xff,0xff,0xff));
- m_ScintillaWnd.SendMessage(SCI_STYLESETSIZE,
- STYLE_DEFAULT, nSize);
- m_ScintillaWnd.SendMessage(SCI_STYLESETFONT,
- STYLE_DEFAULT, reinterpret_cast<LPARAM>(face));
- }
2.8 Lexer的编写
要实现一个新的语言(比如M语言)的语法高亮,需要在源代码级别对Scintilla做一点修改,并重新编译Scintilla:
2.8.1 加入一个源代码文件:
LexM.cxx
2.8.2 实现如下函数:
C++代码
- static void ColouriseMDoc (
unsigned int startPos,
int length,
int initStyle,
WordList *keywordlists[],
Accessor &styler);
styler参数是一个Accessor对象,Lexer必须用这个对象来访问将要颜色化的文本。
Lexer用styler.SafeGetCharAt(i)来取得位置在i的字符;
startPos和length参数表示需要颜色化的文本的范围;
Lexer为所有在startPos到startPos+length范围内的字符决定恰当的颜色。
initStyle参数表示最初的状态, 也就是startPos之前一个字符的状态。状态也是表示指定范围内的文本的颜色。
注意: startPos位置的字符被事先假定为一行的开始, 如果新的一行终止了inisStyle状态Lexer应该进入默认状态(或者说任何状态应该在initStyle之后).
keywordlists参数指定Lexer应该识别的关键词,WordList对象包含识别关键词的方法,Present Lexers 用一个classifyWordLLL函数来识别关键词.这些函数展示怎么用keywordlists参数去识别关键词.
2.8.3 通过如下方式调用该函数:
C++代码
- LexerModule lmM(SCLEX_M, ColouriseMDoc, "m", 0, asmWordListDesc);
2.8.4 在KeyWords.cxx文件中调用LINK_LEXER(lmM)宏。
3.备注
细节请参看Scintilla的帮助文档:http://www.scintilla.org/ScintillaDoc.html。