用VC++6.0编程的时候,文本编辑控件Edit Box是一个经常用到的控件。如果你是用它输入一些简单的文字、数字等信息,直接拿来用就可以了,但如果你用它制作大文本的编辑软件,就会觉得不好控制,许多功能无法实现,即便用CEditView,也只会生成一个类似于记事本的东西,再想加入些自己编写的功能也很困难。下面我以CEdit为基类定义了一个CEditBox类,加入了许多文本编辑时经常要用到的接口函数,用它来控制Edit Box控件就很容易制作出具有较强文本编辑功能的编辑软件了。
在这个扩展类中主要增加了以下功能:
①增加控件的容量,使它能容纳大文本;
②可设置编辑控件文字颜色、背景色和字体;
③对控件内的文本和选择文本的访问;
④直接装入文件到控件和保存控件内容到文件;
⑤自定义的右键菜单;
⑥多重ReDo/UnDo功能。
这些功能基本上都是独立的,实际使用时可根据需要选用所需功能。
准备工作:用ClassWizard在工程中加入一个新类,基类选为CEdit,类名设置为CEditBox。
一、设置控件的容量:
EditBox控件默认情况下只能装入64K的文本,如果超出,多出部分会被自动截掉。利用CEdit类的SetLimitText()函数可重新设置控件容量。
函数原型为:
void SetLimitText(UINT nMax);
参数为nMax为控件可接收的文本最大字节数。
设置方法:用ClassWizard在CEditBox类中添加消息函数PreSubclassWindow(),把设置文本容量的语句放在里面即可。
void CEditBox::PreSubclassWindow() { SetLimitText( -1 ); //设置编辑控件可接收的最大字节数 CEdit::PreSubclassWindow(); } |
因为nMax为无符号整型,-1是把它设置为可以取到的最大值。你也可以根据需要设置控件的容量。
注意:在不同操作系统下,控件可设置的最大容量也不同。如果是Windows98,这个值就是64K,无法再增大了,而在Windows2000和WindowsXP下这个值要大得多,才可以起到增加控件容量的目的。
二、设置编辑控件的前景色、背景色和字体:
在CEditBox的头文件中加入以下变量定义:
COLORREF m_ForeColor; //文本颜色 COLORREF m_BackColor; //背景色 CBrush m_BkBrush; //背景刷 CFont* p_Font; //字体指针 int m_FontSize; //字体大小(1/10点) CString m_FontName; //字体名 |
在CEditBox的构造函数中设置它们的初值:
CEditBox::CEditBox() { m_ForeColor = RGB(0,0,0); //文字颜色(黑) m_BackColor = RGB(255,255,255); //文字背景色(白) m_BkBrush.CreateSolidBrush(m_BackColor); //背景刷 p_Font = NULL; //字体指针 } |
在CEditBox的析构函数中回收创建的字体资源:
CEditBox::~CEditBox() { if( p_Font ) delete p_Font; //回收字体资源 } |
这里只设置了前景色和背景色的默认值,如果想设置默认字体,可在上面的PreSubclassWindow()函数中进行设置:
void CEditBox::PreSubclassWindow() { SetLimitText( -1 ); //设置编辑控件可接收的最大字节数 m_FontSize = 100; m_FontName = _T("宋体"); p_Font = new CFont; //生成字体对象 p_Font->CreatePointFont( m_FontSize, m_FontName ); //创建字体 SetFont( p_Font ); //设置控件默认字体 CEdit::PreSubclassWindow(); } |
这里使用了比较简单的CreatePointFont()函数创建字体,它只需给出字体尺寸和字体名。如果想创建更复杂的字体,可以改用CreateFont()函数。本例中设置控件的初始字体为尺寸为100(0.1点)的“宋体”字。
如果你想用EditBox本身的默认字体作为初始字体,就不要在PreSubclassWindow()函数中加入这些语句。
用ClassWizard添加消息反射函数CtlColor()来修改控件的文本颜色和背景色。
注意:在ClassWizard下可看到有两个很相似的消息,一个是“=WM_CTLCOLOR”消息,另一个是“WM_CTLCOLOR”消息,这里必须用“=WM_CTLCOLOR”消息添加函数。如果误用了“WM_CTLCOLOR”消息将得不到想要的效果。
HBRUSH CEditBox::CtlColor(CDC* pDC, UINT nCtlColor) { pDC->SetTextColor( m_ForeColor ); //设置控件文字颜色 pDC->SetBkColor( m_BackColor ); //设置文字的背景色 return (HBRUSH)m_BkBrush.GetSafeHandle(); //控件背景刷 } |
PreSubclassWindow()和CtlColor()函数都是消息函数,为了设置控件颜色和字体,还需定义接口函数在使用时调用:
//设置文本颜色 void CEditBox::SetForeColor(COLORREF color) { m_ForeColor = color; Invalidate(); } //获取文本颜色 COLORREF CEditBox::GetForeColor() { return m_ForeColor; } //设置背景颜色 void CEditBox::SetBkColor(COLORREF color) { m_BackColor = color; m_BkBrush.Detach(); m_BkBrush.CreateSolidBrush( m_BackColor ); Invalidate(); } //获取背景色 COLORREF CEditBox::GetBkColor() { return m_BackColor; } //设置字体 void CEditBox::SetTextFont(int FontSize,LPCTSTR FontName) { if ( p_Font ) delete p_Font; p_Font = new CFont; p_Font->CreatePointFont( FontSize, FontName ); SetFont( p_Font ); m_FontSize = FontSize; m_FontName = FontName; } //获取字体大小 int CEditBox::GetFontSize() { return m_FontSize; } //获取字体名 CString CEditBox::GetFontName() { return m_FontName; } |
至此,用CEditBox类可以定义出可设置颜色和字体的Edit Box控件了。使用时,先在对话框中加入一个Edit Box控件,用ClassWizard为定义一个控制变量m_Edit,类型设定为CEditBox。然后用m_Edit.SetForeColor(color)、m_Edit.SetBkColor()和m_Edit.SetTextFont(FontHight,FontName)为控件设置颜色和字体,这样就可以作出一个美观的文本框了。
说明:Edit Box控件只能放入纯文本,不支持对文本格式的设置,也就不能对局部的文字颜色和字体进行设置,所以,以上设置都是针对整个控件的。
三、访问编辑控件的内容:
Edit Box控件已经提供了几种访问控件内容的方法:
①定义一个与控件关联的变量,类型可设置为CString或其它类型,用UpdateData()函数来更新控件或变量。
②用GetWindowText()获取控件内文本,用SetWindowText()设置控件文本。
③用SetSel()设置控件内的选择区,用GetSel()获取控件中选择文本的位置,用ReplaceSel()替换选择的文本。
但只用这几种方法还是不太方便,所以在CEditBox类中又增加了几个访问接口函数。
1、读取控件文本ReadText()
int CEditBox::ReadText(CString& str) { GetWindowText( str ); //获取控件文本 return str.GetLength(); //文本长度 } |
参数str是字符串的引用,用于接收读取的控件内容,返回值是控件中文本字节数。
2、用字符串设置控件内容SetText()
void CEditBox::SetText(LPCTSTR str) { SetSel( 0, -1, true ); //全选 ReplaceSel( str ); //替换 SetSel(0); //设置插入点为起始位置 } |
参数str是准备设置控件的内容,要求是字符串。
3、读取当前选择的文本ReadSelText()
int CEditBox::ReadSelText(CString& str) { int selStart, selEnd; GetSel( selStart, selEnd ); //获取当前选择的位置 int selLen = selEnd-selStart; //求选择区长度 if( selLen ) { CString text; GetWindowText( text ); //获取控件文本 str = text.Mid( selStart, selLen ); //获取选择的文本 } else str = _T(""); return selLen; } |
参数str是字符串的引用,用于接收读出的文本,返回值是读出的文本字节数。
如果当前控件中有内容被选择,则读出选择文本,并返回长度;如果没有选择的文本,读出的是空串,返回为0。
4、设置选择区SetSelText()
void CEditBox::SetSelText(int nStartChar,int nSelLen) { SetSel(nStartChar,nStartChar+nSelLen); } |
参数nStartChar为选择区起点(从0算起),nSelLen为选择区长度。
功能是把控件的指定区域设置为选择的状态。
5、当前是否有选择isSelect()
BOOL CEditBox::isSelect() { int selStart, selEnd; GetSel( selStart, selEnd ); //获取当前选择的位置 return selEnd-selStart; } |
如果当前控件中有选择的文本,返回非0值,否则返回0。
以上是为了使控件访问更方便而增加的接口函数。再配合CEdit本身提供的访问函数,很多操作都可轻易实现了。
CEdit控件提供访问函数主要有:
int GetWindowText(LPCTSTR lpszStringBuf,int nMaxCount);
获取控件文本,与ReadText()功能相同。
void SetWindowText(LPCTSTR lpszString);
设置控件文本。
void GetSel(int& nStartChar,int& nEndChar);
获取选择区的位置
void SetSel(int nStartChar,int nEndChar,BOOL bNoScroll=FALSE);
设置选择区,参数为起点和终点,用SetSel(0,-1)可设置为全选
void ReplaceSel(LPCTSTR lpszNewText,BOOL bCanUndo=FALSE);
用字符串替换选择的文本
四、与文件的接口:
这部分接口函数供“打开文件”和“保存文件”等操作调用。把它们定义在CEditBox类中,增强了控件的封装性,也可以简化应用中“打开”和“保存”的操作。
1、文件内容装入Edit控件
void CEditBox::LoadFile(LPCTSTR PathName) { CFile file; //构造一个CFile类的对象 if( file.Open( PathName, CFile::modeRead )==0 ) //以读方式打开文件 return; int len = file.GetLength(); //求文件长度 CString text = _T(""); file.Read( text.GetBufferSetLength(len), len ); //读文件 text.ReleaseBuffer(); file.Close(); //关闭文件 SetText( text ); //装入编辑控件 m_PathName = PathName; SetModify( false ); //清除修改标志 } |
参数PathName为文件路径名,调用该函数可以把指定文件装入编辑控件。如果文件不存在,直接返回。
2、保存编辑控件内容到文件
void CEditBox::SaveFile(LPCTSTR PathName) { CFile file; if( file.Open( PathName, CFile::modeCreate | CFile::modeWrite )==0 ) return; CString text; int textLen = ReadText( text ); file.Write( (LPCTSTR)text, textLen ); //把字符串内容写入文件 file.Close(); //关闭文件 m_PathName = PathName; SetModify( false ); //清除修改标志 } |
参数PathName为文件路径名,调用该函数可以把控件内容写入指定文件。如果建立文件失败,直接返回。
3、新建文件
void CEditBox::NewFile() { SetSel( 0, -1, true ); //全选 Clear(); //清除 m_PathName = _T(""); SetModify( false ); //清除修改标志 } |
供“新建”文件菜单消息调用,功能是清空控件。
4、是否有文件打开
BOOL CEditBox::isOpenFile() { return !(m_PathName.IsEmpty()); } |
如果控件中已经有打开的文件,返回非0,否则返回0。
5、获取打开的文件名
CString CEditBox::GetPathName() { return m_PathName; } |
如果控件中已经有打开的文件,返回文件路径名,否则返回空串。
这5个函数中的m_PathName是在CEditBox中定义的字符串变量,并初始化为空串。
五、自定义右键菜单:
文本编辑框已经提供了一个默认右键菜单,如果你想重新定义一个来代替它,可以按下面的方法制作。
在VC++的Project菜单下选择Add To Project下的Components and Controls,在弹出的对话框中打开Visual C++ Components,找到Pop-up Menu,单击Insert按钮,选择加入的类为CEditBox,确定。关闭对话框。
这时,你在CEditBox类中会看到已经加入了下面的代码:
void CEditBox::OnContextMenu(CWnd*, CPoint point) { // CG: This block was added by the Pop-up Menu component { if (point.x == -1 && point.y == -1){ //keystroke invocation CRect rect; GetClientRect(rect); ClientToScreen(rect); point = rect.TopLeft(); point.Offset(5, 5); } CMenu menu; VERIFY(menu.LoadMenu(CG_IDR_POPUP_EDIT_BOX)); CMenu* pPopup = menu.GetSubMenu(0); ASSERT(pPopup != NULL); CWnd* pWndPopupOwner = this; while (pWndPopupOwner->GetStyle() & WS_CHILD) pWndPopupOwner = pWndPopupOwner->GetParent(); pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWndPopupOwner); } } |
再到资源的Menu下,你可以找到一个ID号为CG_IDR_POPUP_EDIT_BOX的新菜单,编辑它就可得到你想要的右键菜单了。这和其它菜单的做法没有区别,我就不再详细介绍了。
http://221.199.150.103/jsj/Html/vc/wen/vcwen10.htm
六、多重UnDo/ReDo功能:
Edit Box控件提供了UnDo功能,但只能撤销一次操作,要想实现多重UnDo/ReDo功能需要自己设计。
UnDo:撤销上一次修改操作,实现时应保存最近几次修改。
ReDo:重做上一次撤销的操作,如果你撤销后后悔了,就ReDo吧。
1、数据结构
利用一个结构数组作为栈保存最近几次修改操作,定义如下:
#define UNDOMAX 30 //栈深度(最大Undo次数) //可撤销的操作名 #define OP_DELSEL 1 //删除选择(剪切) #define OP_REPLACE 2 //替换选择 #define OP_DELETE 3 //删除 #define OP_BACK 4 //Backspace #define OP_INPUT 5 //输入 //Undo/Redo栈结构 typedef struct { int op; //操作名 int pos; //操作的位置 CString str1; //旧内容 CString str2; //新内容 }STACKNODE; private: STACKNODE m_Stack[UNDOMAX]; //工作栈 int utop; //Undo栈顶指针 int ubottom; //Undo栈底指针 int rtop; //Redo栈顶指针 int rbottom; //Redo栈底指针 BOOL b_DelFlag; //删除标志 |
UNDOMAX是预定义的栈深度,这里定义为30,表示可撤销最近的30步操作。
STACKNODE结构用来定义栈节点,对每一次修改操作,需要纪录修改的位置,修改前的内容和修改后的内容。而且不同的修改操作在撤销时会略有不同,所以还需纪录修改操作名。
所有修改可归结为5种:
OP_DELSEL:删除选择的文本,此时str1保存被删除的文本,str2为空;
OP_REPLACE:替换选择的文本,str1为被换掉的文本,str2为新文本;
OP_DELETE:用Del键删除文本,str1保存被删除的文本,str2为空;
OP_BACK:用BackSpace键删除文本,str1保存被删除的文本,str2为空;
OP_INPUT:键盘输入新文本,str1为空,str2为新输入的文本。
其它的操作都可归纳到这5种之内,如剪切就是OP_DELSEL,粘贴就是OP_REPLACE。
m_Stack是长度为UNDOMAX的栈,它既是UnDo栈,也是ReDo栈,栈指针utop、ubottom确定UnDo栈位置,rtop、rbottom确定ReDo栈位置。
2、栈操作
①初始化工作栈
void CEditBox::InitStack() { for( int i=0; i<UNDOMAX; i++ ) //栈空间 { m_Stack[i].op = -1; m_Stack[i].str1 = _T(""); m_Stack[i].str2 = _T(""); } utop = 0; //栈指针 ubottom = 0; rtop = 0; rbottom = 0; } |
②入栈
void CEditBox::Push(STACKNODE *pNode) { utop = (utop+1)%UNDOMAX; //修改栈顶指针 rtop = utop; //清空Redo栈 rbottom = utop; if( utop==ubottom ) //如果栈满 ubottom = (ubottom+1)%UNDOMAX; //修改栈底指针 m_Stack[utop] = *pNode; //入栈 } |
每次修改操作时,把纪录修改的节点推入栈中。栈采用环形结构,当栈满时,新入栈的节点覆盖栈底节点,也就淘汰了最早进入栈内节点。
③UnDo出栈
STACKNODE *CEditBox::UnDoPop() { if( utop==ubottom ) //栈空 return NULL; STACKNODE *p = &m_Stack[utop]; rtop = utop; //Redo入栈 utop = utop-1; //退栈 if( utop<0 ) utop = UNDOMAX-1; return p; //返回退栈节点 } |
当进行UnDo操作时,从utop指示的栈顶弹出记录最近一次修改的节点,但这个节点并不删除,通过修改rtop使它进入ReDo栈,供ReDo操作时重做被撤销的操作。所以,这个操作既是UnDo出栈,也是ReDo进栈。
④ReDo出栈
STACKNODE *CEditBox::RedoPop() { if( rtop == utop ) return NULL; STACKNODE *p = &m_Stack[rtop]; utop = (utop+1)%UNDOMAX; //Undo入栈 if( rtop==rbottom ) //如果Redo栈满 { rtop = utop; //修改栈底指针 rbottom = utop; } else rtop = (rtop+1)%UNDOMAX; //修改栈顶指针 return p; //返回出栈的节点 } |
当进行ReDo操作时,从rtop指示的栈顶弹出记录去恢复被撤销的操作,同时通过修改utop使它重新进入UnDo栈,供UnDo操作使用。所以,这个操作执行了ReDo出栈,也执行了UnDo进栈。
这个栈的变化方式如图所示:
UnDo栈和ReDo栈共享一个数组空间,ub和ut是UnDo栈的栈底指针和栈顶指针,rb和rt是ReDo栈的栈底指针和栈顶指针。初始时它们均相等,这时为空栈(图a)。当进行修改操作时,修改结构进栈,修改ut到新的栈顶,同时rb和rt同步移动,ReDo栈仍为空(图b)。当进行撤销操作时,把ut指示的修改结构取出恢复内容,修改ut表示结构从UnDo栈退出,同时修改rt,使结构进入ReDo栈(图c)。恢复撤销时,用rt指示的结构进行恢复。如果不恢复,新的修改进入UnDo栈,同时让rt和rb跟踪ut,使ReDo栈清空。
这个空间是个环形空间,栈满时修改栈底指针淘汰陈旧内容。
⑤取栈顶
STACKNODE *CEditBox::GetTop() { if( utop==ubottom ) return NULL; return &m_Stack[utop]; } |
取UnDo栈的栈顶元素,但不修改栈。
3、UnDo/ReDo操作
①保存可撤销的操作
void CEditBox::SetUndo(int op,int pos,LPCTSTR str1,LPCTSTR str2) { STACKNODE *ptop = GetTop(); if( op==OP_DELETE && ptop && ptop->op==OP_DELETE && ptop->pos==pos ) { ptop->str1+=str1; return; } STACKNODE node; node.op = op; node.pos = pos; node.str1 = str1; node.str2 = str2; Push( &node ); //入栈 } |
每做一次修改操作,通过调用SetUndo()函数把修改操作保存到栈中。参数依次为操作名、修改位置、旧内容和新内容。
函数中先根据参数填写好STACKNODE节点,再把它推入栈内即可。
这里对OP_DELETE操作做了一个特殊处理,当我们用Del键在同一位置连续删除多个字符时,应该记录为一个操作,在撤销时一次性恢复。所以当操作为OP_DELETE时,如果发现上一次操作也是OP_DELETE,且位置相同,则把新删除的字符连接到上次删除的字符串中就行了。
②处理Del键和BackSpace键
用按键消息检测是否进行了Del和BackSpace键操作,并进行相应处理。
void CEditBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CString Text, SelStr; int pos, SelLen; GetText( pos, Text, SelStr, SelLen ); if( nChar==VK_DELETE && !Text.IsEmpty() ) //Del键 { if( SelLen ) SetUndo( OP_DELSEL, pos, SelStr, _T("") ); //保存删除内容 else { if( Text[pos]<0 || Text[pos]==0x0D || Text[pos]==0x0A ) SetUndo( OP_DELETE, pos, Text.Mid(pos,2), _T("") ); //删除的是汉字或回车 else SetUndo( OP_DELETE, pos, Text.Mid(pos,1), _T("") ); //删除的是字符 } b_DelFlag = true; } else if( nChar==VK_BACK && pos>=0 ) //BackSpace键 { if( SelLen ) SetUndo( OP_DELSEL, pos, SelStr, _T("") ); //保存删除的内容 else { if( pos>0 && Text[pos-1]<127 && Text[pos-1]>=0x20 ) SetUndo( OP_BACK, pos-1, Text.Mid(pos-1,1), _T("") ); else if( pos>1 && (Text[pos-2]<0 || Text[pos-1]==0x0D || Text[pos-1]==0x0A) ) SetUndo( OP_BACK, pos-2, Text.Mid(pos-2,2), _T("") ); } b_DelFlag = true; } CEdit::OnKeyDown(nChar, nRepCnt, nFlags); } |
OnKeyDown()函数用ClassWizard添加到CEditBox类中。当检测到Del键或BackSpace键时,如果此时有选择的文本,就保存为“删除选择OP_DELSEL”操作,如果没有选择的文本再保存为“OP_DELETE”操作或“OP_BACK”操作。另外,删除字符和删除汉字也有所区别,如果删除位置为半角字符,就取一个字符进入UnDo栈,如果删除位置处为汉字,就取两个字节进入UnDo栈。
③处理键盘输入
用ClassWizard添加OnChar()函数处理键盘输入,与OnKeyDown()函数不同,OnChar()函数只在输入可打印字符时才响应,用它可过滤掉各种控制键,而OnKeyDown()函数对任何按键都要响应,所以Del键只能用它来处理。
void CEditBox::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { CString SelStr; ReadSelText( SelStr ); int pos1, end, pos2; GetSel( pos1, end ); //获取输入前的光标位置 CEdit::OnChar(nChar, nRepCnt, nFlags); //处理键盘输入 if( !b_DelFlag ) { CString Text, str; GetWindowText( Text ); GetSel( pos2, end ); //获取输入后的光标位置 str = Text.Mid(pos1,pos2-pos1); SetUndo( OP_INPUT, pos1, SelStr, str ); //保存到UnDo栈 } b_DelFlag = false; } |
为了处理汉字的输入,在处理按键函数CEdit::OnChar前纪录光标位置,这个位置是输入文字前的位置,在CEdit::OnChar函数后再求得的光标位置是输入文字后的位置,两者之间的符号就是应该保存的撤销内容。
这里有个缺陷,就是当输入汉字词组后,撤销时只能一个字一个字的撤销,不能一下撤销整个数组,不知该如何改进。
b_DelFlag用于防止Del键和BackSpace键被重复处理。
4、接口函数
CEditBox增加了UnDo/ReDo功能后,原来的那些涉及修改控件内容的操作就需要重新设计,使它们能够被撤销,所以设计了新的接口函数。
①isCanUndo()和isCanRedo()
判断当前是否有可撤销和可重做的操作。用于激活或禁止UnDo/ReDo菜单。
//判断能否撤销 BOOL CEditBox::isCanUndo() { return utop!=ubottom; } //判断能否重做 BOOL CEditBox::isCanRedo() { return utop!=rtop; } |
当存在可撤销或可重做的操作(栈非空)时返回TRUE,否则返回FALSE。
②EditUndo()和EditRedo()
EditUndo()函数执行撤销操作,EditRedo()函数执行重做操作。供UnDo/ReDo菜单调用。
//撤销 void CEditBox::EditUndo() { STACKNODE *p = UnDoPop(); //出栈 if( p ) { SetSelText( p->pos,p->str2.GetLength() ); //选择文本 ReplaceSel( p->str1 ); //恢复内容 } } //重做 void CEditBox::EditRedo() { STACKNODE *p = RedoPop(); //出栈 if( p ) { SetSelText( p->pos,p->str1.GetLength() ); //选择文本 ReplaceSel( p->str2 ); //恢复内容 } } |
③EditReplace()和RepAll()
EditReplace()函数是用指定文本替换选择的文本。RepAll()是用指定文本重置编辑控件内容。
//替换选择 void CEditBox::EditReplace(LPCTSTR str) { CString Text, SelStr; int pos, SelLen; GetText( pos, Text, SelStr, SelLen ); SetUndo( OP_REPLACE, pos, SelStr, str ); //保存到Undo ReplaceSel( str ); //替换选择 } //替换全部 void CEditBox::RepAll(LPCTSTR str) { SetSel( 0, -1 ); //全选 EditReplace( str ); //替换 SetSel(0); //设置插入点为起始位置 } |
EditReplace()函数用来代替CEdit类的ReplaceSel()函数,两者功能一致,但用EditReplace()函数做的操作可以撤销,而ReplaceSel()函数做的操作不能撤销。
RepAll()函数用来代替SetWindowText()和前面定义的SetText(),用它做的操作可以撤销。
④剪切和粘贴
CEdit类的Cut()和Paste()函数都需要重新设计,而复制操作Copy()由于不修改文本,可照常使用。
//剪切(代替Cut()) void CEditBox::EditCut() { CString Text, SelStr; int pos, SelLen; GetText( pos, Text, SelStr, SelLen ); SetUndo( OP_DELSEL, pos, SelStr, _T("") ); //保存到Undo Cut(); //剪切 } //粘贴(代替Paste()) void CEditBox::EditPaste() { OpenClipboard(); //打开剪贴板 HANDLE StrHandle; StrHandle = ::GetClipboardData(CF_TEXT); char* pMem; pMem = (char*)::GlobalLock(StrHandle); CString str; str = pMem; ::GlobalUnlock(StrHandle); CloseClipboard(); //关闭剪贴板 if( !str.IsEmpty() ) EditReplace( str ); //粘贴 } //全部剪切 void CEditBox::EditCutAll() { SetSel( 0, -1 ); //全选 EditCut(); //剪切 } //全部复制 void CEditBox::EditCopyAll() { SetSel( 0, -1 ); //全选 Copy(); //复制 } //全部删除 void CEditBox::EditClearAll() { SetSel( 0, -1 ); //全选 EditReplace( _T("") ); //清空 } |
七、CEditBox类的使用:
CEditBox类与CEdit类的用法基本相同。你可以在对话框里加入一个Edit Box控件,设置属性Multiline(多行文本)、Vertical scroll(垂直滚动条)、Want return(接收回车),并取消Auto HScroll属性(自动换行);用ClassWizard为控件添加变量,类型设置为CEditBox;再加入头文件#include "EditBox.h";之后就可以根据需要编程控制这个编辑控件了。
再解决一个小问题,就是Tab键的输入问题。
在界面上,Tab键起到选择控件的功能,这导致无法在编辑控件中输入Tab符。
解决方法是:
先在资源中定义一个Tab快捷键:打开资源的Accelerator下的IDR_MAINFRAME,在快捷键表中添加一个VK_TAB的快捷键,假设ID设置为ID_KEY_TAB;
回到视类,用ClassWizard为ID_KEY_TAD添加消息函数OnKeyTab(),在其中加入代码:
//处理Tab输入 void CEditTestView::OnKeyTab() { m_EditBox.EditReplace( _T("\t") ); } |
这里的m_EditBox就是编辑控件的控制变量。
这样CEditBox类就做好了,它的完整代码见示例程序中的EditBox.h和EditBox.cpp。
示例程序是利用CEditBox类制作的一个文本编辑器,它具有了记事本的多数功能。它还具有以下特征:可设置编辑区的字体、颜色;利用ini文件保存设置;有字数统计功能,支持查找/替换操作等。它本身就是一个可代替记事本的好用的编辑器。在此基础上,你可以根据需要添加更多的功能。
示例程序界面图:
http://221.199.150.103/jsj/Html/vc/wen/vcwen11.htm