第一章 文本编辑器的实现
本章的目的是建立一个文本编辑器,同时继续介绍控件的用法。有两类文本编辑器:单文档和多文档文本编辑器。单文档文本编辑器一次只能打开一个文件,如果要打开另一个文件,必须关闭当前打开的文件,微软的写字板程序就是单文档字处理程序。多文档文本编辑器允许同时打开多个文件,每个文件占用一个子窗口,微软的Word程序就是多文档字处理程序。本章首先介绍建立单文档文本编辑器的方法,然后介绍建立多文档文本编辑器的方法。
RichTextBox控件
RichTextBox控件可用来输入和编辑文本,该控件除具有TextBox控件所有功能,还能设定文字颜色、字体和段落格式,支持字符串查找功能,支持rtf格式等功能。这里只介绍RichTextBox控件新增属性、事件和方法,和TextBox控件相同部分就不介绍了,可参见3.6节。RichTextBox控件常用属性、事件和方法如下:
l 属性Dock:很多控件都有此属性,它设定控件在窗体中的位置,可以等于枚举类型DockStyle的成员None、Left、Right、Top、Bottom或Fill,分别表示控件可在窗体的任意位置、左侧、右侧、顶部、底部或充满客户区。在属性窗口中,属性Dock的值用周边4个矩形,中间一个矩形的图形来表示。
l 属性SelectedText、SelectionLength、SelectionStart:参见TextBox控件属性。
l 属性SelectionFont:如果已选定文本,得到或设置所选文本使用的字体,如果未选定文本,获取当前插入点字符采用的字体或设置以后输入字符采用的字体。
l 属性SelectionColor:如果已选定文本,获取或设置所选文本的颜色,如果未选定文本,获取当前插入点字符的颜色或设置以后输入字符的颜色。
l 属性Lines:字符串数组,记录输入到RichTextBox控件中的所有文本,每两个回车之间的字符串是数组的一个元素。
l 属性Modified:记录用户是否已修改控件中的文本内容。如已修改,自动设置为true,。
l 事件SelectionChange:控件中选定文本发生变化时产生的事件。
l 事件TextChanged:RichTextBox控件内的文本内容被改变时发生的事件。
l 方法Clear():清除RichTextBox控件中用户输入的所有内容,即清空属性Lines。
l 方法Copy()、Cut()、Paste():实现RichTextBox控件的剪贴板功能。
l 方法SelectAll():选择RichTextBox控件中的所有文本。
l 方法Find():实现查找功能。从第二个参数指定的位置,查找第一个参数指定的字符串,返回找到的第一个匹配字符串的位置,返回负值,表示未找到匹配字符串。第三个参数指定查找的一些附加条件,可以是枚举类型RichTextBoxFinds的成员:MatchCase(区分大小写)、Reverse(反向查找)等。方法Find()允许有1个、2个或3个参数。
l 方法SaveFile()和LoadFile():存取文件方法,有2个参数,参数1是要存取文件的全路径和文件名。参数2是文件类型:RichTextBoxStreamType.UnicodePlainText (Unicode编码的文本流),RichTextBoxStreamType.RichText(Rtf格式流),RichTextBoxStreamType.PlainText(纯文本)。注意存取文件的类型必须一致。
l 方法Undo():撤消RichTextBox控件中的上一个编辑操作。
l 方法Redo():重新完成RichTextBox控件中上次撤消的操作。
剪贴板功能
许多程序都支持剪贴板功能。通过剪贴板可以完成数据的剪切(Cut),复制(Copy),粘贴(Paste)等功能。剪贴板是一块由Windows操作系统控制的用来存储数据的公共区域,各个任务(运行程序)可把数据复制或剪切到剪贴板中,供所有任务使用,任何任务要用剪贴板中的数据时,可以用粘贴功能从剪贴板中把数据取出。存入剪贴板中的数据,可以是字符、位图或者其他格式数据。
【例4.1】实现文本编辑器的具体步骤如下,首先实现编辑和剪贴板功能。
(1) 新建项目。放RichTextBox控件到窗体。属性Name=richTextBox1,Dock=Fill,Text=""。
(2) 放MenuStrip控件到窗体。为菜单增加顶级菜单项:编辑,属性Name为mainMenuEdit,为其弹出菜单增加菜单项:剪切、复制、粘贴、撤销和恢复,属性Name分别为: menuItemEditCut、menuItemEditCopy、menuItemEditPaste、menuItemEditUndo、menuItemEditRedo。为各个菜单项增加单击事件处理函数如下:
private void menuItemEditCut_Click(object sender,EventArgs e)
{ richTextBox1.Cut(); } //剪切
private void menuItemEditCopy_Click(object sender,EventArgs e)
{ richTextBox1.Copy(); } //拷贝
private void menuItemEditPaste_Click(object sender,EventArgs e)
{ richTextBox1.Paste(); } //粘贴
private void menuItemEditUndo_Click(object sender,EventArgs e)
{ richTextBox1.Undo(); } //撤销
private void menuItemEditRedo_Click(object sender,EventArgs e)
{ richTextBox1.Redo(); } //恢复
(3) 编译,运行,输入一些字符后,选中若干字符,试验一下剪切、复制、粘贴等功能,并查看一下被复制或剪切到剪贴板中的字符是否能被粘贴到其他字处理软件中,例如写字板。查看一下撤销和恢复功能是否可用。
存取文件
文本编辑器都应具有文件存取功能,菜单顶级菜单项"文件"的弹出菜单中一般包括如下菜单项:新建、打开、关闭、保存和另存为等。本节实现以上菜单项。
OpenFileDialog和SaveFileDialog控件
OpenFileDialog对话框用来选择要打开的文件的路径及文件名,SaveFileDialog对话框用来选择要保存的文件的路径及文件名。两个对话框的外观如图4.1和图4.2,它们的属性和方法基本相同,这里在一起介绍。
l 属性Filter:字符串类型,决定在对话框中显示的文件类型。属性Filter有多项,中间用"|"分开,每两项是一组,每组的第一项将出现在对话框"文件类型(T)" 下拉列表组合框的下拉列表中(见图4.1),供用户选择,第二项表示如第一项被选中,对话框实际列出的文件类型。例如Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*",对话框的"文件类型(T)"下拉列表编辑框的下拉列表中有两项:纯文本文件(*.txt)、所有文件(*.*),供用户选择。如果选中"纯文本文件(*.txt)",对话框只列出所有文件夹及扩展名为.txt的文件,如果选中"所有文件(*.*)",对话框列出所有文件及文件夹。
图 4.1 打开文件对话框
l 属性FilterIndex:表示打开对话框后,对话框的"文件类型(T)" 下拉列表组合框的下拉列表中首先被选项的索引号。在设计阶段可以在属性窗口修改属性FilterIndex和Filter,也可在程序中用下列语句修改:openFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"和openFileDialog1.FilterIndex=1。
l 属性FileName:用户选取的文件路径和文件名。
l 属性InitialDirectory:打开对话框后,首先列出本属性指定的文件夹中的文件。
l 属性DefaultExt:如果用户未指定扩展名,自动增加本属性指定的文件扩展名。
l 方法ShowDialog():用此方法打开对话框,方法返回值指示用户单击了哪个按钮,用户单击了"取消"按钮,返回DialogResult.Cancle,用户单击了"打开"或"保存"按钮,返回DialogResult.OK。
图 4.2 保存文件对话框
存取文件功能实现
(4) 把OpenFileDialog和SaveFileDialog控件放到窗体中。
(5) 为菜单增加顶级菜单项:文件,为其弹出菜单增加菜单项:新建、打开...、保存...、另存为...、退出。修改这些菜单项的Name属性分别为:mainMenuFile、menuItemFileNew、menuItemFileOpen、menuItemFileSave、menuItemFileSaveAs、menuItemFileExit。
(6) 为Form1类中定义变量:string s_FileName="",记录当前编辑的文件名,如果字符串为空,表示还未记录文件名,即编辑的文件还没有名字,当单击菜单"文件|保存…"菜单项保存文件时,要请用户输入文件名。
(7) 为菜单"文件|新建"菜单项增加单击事件处理函数如下:
private void menuItemFileNew_Click(object sender,EventArgs e)
{ richTextBox1.Text=""; //或richTextBox1.Clear();
s_FileName=""; } //新建文件没有文件名。
(8) 为菜单"文件|打开…"菜单项增加单击事件处理函数如下:
private void menuItemFileOpen_Click(object sender,EventArgs e)
{ if(openFileDialog1.ShowDialog()==DialogResult.OK)
{ s_FileName=openFileDialog1.FileName;
richTextBox1.LoadFile(openFileDialog1.FileName,RichTextBoxStreamType.PlainText);
}
}
(9) 为菜单"文件|另存为..."菜单项增加单击事件处理函数如下:
private void menuItemFileSaveAs_Click(object sender,EventArgs e)
{ if(saveFileDialog1.ShowDialog()==DialogResult.OK)
{ s_FileName=saveFileDialog1.FileName;
richTextBox1.SaveFile(saveFileDialog1.FileName,RichTextBoxStreamType.PlainText);
} //注意存取文件类型应一致。
}
(10) 为菜单"文件|保存..."菜单项增加单击事件处理函数如下:
private void menuItemSaveFile_Click(object sender,EventArgs e)
{ if(s_FileName.Length!=0)
richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText);
else
menuItemFileSaveAs_Click(sender,e); //调用另存为菜单项事件处理函数
}
(11) 放SaveFileDialog控件
到窗体,程序运行后自动创建该控件对象,程序运行期间该控件对象将一直占用内存空间。实际上SaveFileDialog控件对象仅在"另存为..."菜单项单击事件处理函数中被使用。为了减少程序占用的内存,可以在"另存为..."菜单项单击事件处理函数中建立SaveFileDialog控件对象,退出该事件处理函数后,释放该对象。首先删除控件SaveFileDialog,修改菜单"文件|另存为..."菜单项事件处理函数如下:
private void menuItemFileSaveAs_Click(object sender,EventArgs e)
{ SaveFileDialog saveFileDialog1=new SaveFileDialog();
saveFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*";
saveFileDialog1.FilterIndex=1;
if(saveFileDialog1.ShowDialog()==DialogResult.OK)
{ s_FileName=saveFileDialog1.FileName;
richTextBox1.SaveFile(saveFileDialog1.FileName,RichTextBoxStreamType.PlainText);
} //也可以用此方法修改菜单"文件|打开…"菜单项事件处理函数。
}
(12) 为菜单"文件|退出"菜单项增加事件处理函数如下:
private void menuItemExit_Click(object sender,EventArgs e)
{ Close(); }
(13) 编译,运行,应该可以存取文件。
修改字体
为修改使用的字体,可打开字体对话框FontDialog,选择指定字体。可以按两种方式修改字体,如果未选中文本,表示以后键入的字符将按选定字体输入。如果选中文本,则仅修改选定文本的字体。修改字符颜色也根据同样原则。
FontDialog控件
可以用FontDialog对话框选定指定字体,FontDialog控件和OpenDialog控件的属性和方法基本相同,这里只介绍属性Font,它代表用FontDialog对话框选定的字体。FontDialog对话框显示效果如图4.3。
图 4.3 修改字体对话框
修改字体实现方法
(14) 放FontDialog控件到窗体,属性Name=fontDialog1。为菜单增加顶级菜单项:格式,属性Name为mainMenuModel,为其弹出菜单增加菜单项:字体,属性Name为menuItemModelFont,为菜单"格式|字体"菜单项增加单击事件处理函数如下:
private void menuItemModelFont_Click(object sender,EventArgs e)
{ if(fontDialog1.ShowDialog()==DialogResult.OK)
richTextBox1.SelectionFont=fontDialog1.Font;
}
(15) 编译运行,在选中文本和不选中文本两种情况下,单击菜单"格式|字体"菜单项修改字体,看一看哪些字符的字体被修改。
About对话框
前边介绍的OpenFileDialog、SaveFileDialog和FontDialog控件都是类库中预先定义的对话框,本节介绍如何创建满足一定要求的自定义对话框。对话框其实就是窗体,其基类和主窗体一样,是System.Windows.Forms.Form。只是一般对话框只有关闭按钮,没有最大化和最小化按钮,对话框的边界是固定的,不能改变。设计自定义对话框是经常遇到的工作。
(16) 在VS2005集成环境中,单击菜单"项目|添加Windows窗体"菜单项,弹出对话框(见图4.4),在"模板(T)"编辑框中选择"Windows窗体",在"名称栏(N)"编辑框中输入窗体文件名称:formAbout.cs,单击"添加(A)"按钮,可以见到一个新窗体。从文件formAbout.cs中可以看到新建窗体类名为formAbout。这个窗体将作为About对话框。
图 4.4 添加新项对话框
(17) 修改formAbout窗体属性StartPosition=CenterParent,表示打开窗体时,窗体在父窗口的中间;修改属性MaximizeBox=False,MinimizeBox=False,表示窗体没有最大化和最小化按钮,既窗体不能最大化和最小化;属性FormBorderStyle=FixedDialog,使窗体不能改变大小;属性Text="关于记事本"。可以在窗体中增加各种控件,例如,小图标,Label控件等。本例仅增加Label控件表示版权信息,其属性Text="版权所有"。一个按钮,属性Text="确定",该按钮单击事件处理函数如下:
private void button1_Click(object sender,EventArgs e)
{ Close(); }
(18) 为菜单增加顶级菜单项:帮助,为其弹出菜单增加标题为"关于…"的菜单项,属性Name为menuItemAbout。菜单"帮助|关于…"菜单项单击事件处理函数如下:
private void menuItemAbout_Click(object sender,EventArgs e)
{ formAbout AboutDialog=new formAbout();
AboutDialog.ShowDialog(this); //打开模式对话框
} //注意不能使用Show()函数,它打开非模式对话框
(19) 编译运行,单击菜单"帮助|关于…"菜单项,将打开formAbout对话框(如图4.5),并且不关闭此对话框,不能操作主窗体,一般把这样的对话框叫做模式对话框。 图 4.5运行界面
文本编辑器查找替换功能
本节首先介绍模式对话框和非模式对话框的概念。并使用非模式对话框在文本编辑器中实现查找和替换功能。
模式对话框和非模式对话框
模式对话框和非模式对话框的区别是:打开模式对话框后,只有关闭该模式对话框,才能操作其他窗体,例如前边讲到的SaveDialog和OpenDialog都是典型的模式对话框。而打开非模式对话框后,不必退出该模式对话框,就可以操作其他窗口,例如字处理程序中的查找和替换对话框就是典型的非模式对话框。两类对话框本质上都是窗体,是Form类的派生类,只是打开时使用的方法不一样,打开模式对话框,使用方法ShowDialog(),而打开非模式对话框,使用方法Show()。
查找替换功能的实现
(20) 参照4.5节的方法,建立"查找替换"对话框。设定窗体文件名称为formFindReplace.cs,从文件formFindReplace.cs可以看到新建窗体类名也为formFindReplace。
(21) 修改窗体formFindReplace属性StartPosition=CenterParent,表示打开对话框时,对话框在父窗口的中间;修改属性MaximizeBox=False,MinimizeBox=False,表示窗体没有最大化和最小化按钮,即窗体不能最大化和最小化;为了使窗口不能修改大小,设置属性FormBorderStyle=FixedDialog;属性Text="查找和替换"。在窗体中增加两个Label控件,属性Text分别为"查找字符串"和"替换字符串"。两个TextBox控件,属性Text=""。两个按钮,属性Text分别为"查找下一个"和"替换查到字符"。修改属性TopMost=true,使该窗口打开时总在其他窗体的前边。对话框界面如图4.6。
(22) 为formFindReplace类增加变量:Form1 MainForm1;
(23) 在创建窗体对象时,要自动调用构造函数。如果在formFindReplace类构造函数中,把主窗体对象的引用(即Form1类Name值)传递给"查找替换"窗体,"查找替换"窗体就可以用主窗体对象的引用调用主窗体中的方法了。修改formFindReplace类构造函数如下,阴影部分是所做的修改。 图 4.6 查找和替换对话框
public formFindReplace(Form1 form1) //增加的参数form1是主窗体属性Name的值
{ InitializeComponent();
MainForm1=form1; //新增语句,记录主窗体属性Name的值
} //有了Form1引用,可以在formFindReplace类中调用Form1类的公有方法
(24) 为主窗体Form1类增加方法如下,该方法将被窗体formFindReplace类的方法调用。
public void FindRichTextBoxString(string FindString) //查找方法
{} //以后步骤将在此方法中增加查找语句,参数为要查找的字符串。
(25) formFindReplace窗体中标题为"查找下一个"的按钮单击事件处理函数如下:
private void buttonFind_Click(object sender,EventArgs e)
{ if(textBox1.Text.Length!=0) //如果查找字符串不为空,调用主窗体查找方法
MainForm1.FindRichTextBoxString(textBox1.Text); //调用上步增加的查找方法
else
MessageBox.Show("查找字符串不能为空","提示",MessageBoxButtons.OK);
} //MessageBox是对话框,使用方法见4.7.1节
(26) 在Form1类中增加方法如下,该方法将被窗体formFindReplace类的方法调用。
public void ReplaceRichTextBoxString(string ReplaceString) //替换方法
{} //以后步骤将在此方法中增加替换语句,参数为要替换的字符串。
(27) formFindReplace窗体中标题为"替换查到字符"的按钮单击事件处理函数如下:
private void buttonReplace_Click(object sender,EventArgs e)
{ if(textBox2.Text.Length!=0) //如果替换字符串不为空,调用主窗体替换方法
MainForm1.ReplaceRichTextBoxString(textBox2.Text); //调用上步增加的替换方法
else
MessageBox.Show("替换字符串不能为空","提示", MessageBoxButtons.OK); }
(28) 为Form1类增加变量:int FindPostion=0,该变量用来记录下一次查找的开始位置。
(29) 为Form1窗体菜单的"编辑"顶级菜单项的弹出菜单增加菜单项:查找和替换,属性Name为menuItemFindReplace。"查找和替换"菜单项单击事件处理函数如下:
private void menuItemFindReplace_Click(object sender,EventArgs e)
{ FindPostion=0; //下句中的this是Form1类对象引用
formFindReplace FindReplaceDialog=new formFindReplace(this);
FindReplaceDialog.Show(); } //打开非模式对话框使用Show()方法
(30) 第24步在主窗体Form1类中定义了FindRichTextBoxString方法,现为其增加语句如下:
public void FindRichTextBoxString(string FindString)
{ if(FindPostion>=richTextBox1.Text.Length) //如已查到文本底部,提示用户
{ MessageBox.Show("已到文本底部,再次查找将从文本开始处查找",
"提示",MessageBoxButtons.OK);
FindPostion=0; //下次查找的开始位置
return;
} //用Find方法查找,返回找到的位置,返回-1,表示未找到,参数1是要找的字符串
//参数2是查找的开始位置,参数3是查找的一些选项,如大小写是否匹配,查找方向等
FindPostion=richTextBox1.Find(FindString,FindPostion,RichTextBoxFinds.MatchCase);
if(FindPostion==-1) //-1表示未找到匹配字符串,提示用户
{ MessageBox.Show("未找到匹配字符串,再次查找将从文本开始处查找",
"提示", MessageBoxButtons.OK);
FindPostion=0; //下次查找的开始位置
}
else //找到匹配字符串
{ richTextBox1.Focus(); //主窗体成为注视窗口
FindPostion+=FindString.Length;
} //下次查找的开始位置在此次找到字符串之后
}
(31) 第26步在Form1类增加了一个替换字符串的方法ReplaceRichTextBoxString,这里为其增加语句如下: 图 4.7 查找和替换运行结果
public void ReplaceRichTextBoxString(string ReplaceString)
{ if(richTextBox1.SelectedText.Length!=0) //如果选定了被替换字符串
richTextBox1.SelectedText=ReplaceString; } //替换被选定的字符串
(32) 编译运行,输入若干字符,单击应用程序菜单"编辑|查找和替换"菜单项,打开标题为"查找和替换"对话框。注意可以不关闭该对话框,操作主窗体,并且该对话框总是在主窗体的前边,它是一个典型的非模式对话框。在对话框中输入查找和替换的字符,单击标题为"查找下一个"的按钮,可以找到所选字符,并被选中,单击标题为"替换查到字符"的按钮,可以看到已被选中的查找到的字符被替换。运行效果如图4.7。
提示用户保存已被修改的文件
使用单文档文本编辑器,用户在新建文本,打开其他文件或者关闭文本编辑器时,如果用户对已打开的文件做了修改,或者是新建的文件,还未保存,就需要在关闭当前文件前,使用对话框提示用户是否保存已被修改的文本内容。本节实现此功能。
对话框MessageBox
使用方法MessageBox类可以打开一个模式对话框,用法如下:
MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?",
MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question);
第一个参数是对话框的父窗口,第二个参数是对话框的提示信息,第三个参数是对话框标题栏的内容,第四个参数是对话框有哪些按钮,此例有Yes,No, Cancel按钮,还可以使用AbortRetryIgnore(中止、重试和忽略按钮)、OK(确定按钮)、OKCancel(确定和取消按钮)、RetryCance(重试和忽略按钮)、YesNo(是和否按钮)等选项。第五个参数是对话框使用哪一个图标,此例是一个问号图标,还可以是Asterisk、Error、Exclamation、Hand、Stop、Warning等图标,如为None则无图标。Show方法返回值代表用户单击了哪一个按钮。如果单击Yes按钮,返回值是System.Windows.Forms.DialogResult.Yes,表示要保存已被修改的文件;如果单击Cancel按钮,返回值是System.Windows.Forms.DialogResult.Cancel,表示忽略此次操作;如果单击No按钮,返回值是System.Windows.Forms.DialogResult.No,表示不保存已被修改的文件。以上设计的对话框MessageBox界面如图4.8。 图 4.8 MessageBox对话框
提示保存已被修改的文件
(33) 为Form1类增加一个bool变量bSave=false作为标记,用来跟踪控件RichTextBox中的文本内容是否被修改。在程序开始运行、建立和打开一个新文件后,bSave=false,表示如果关闭当前文件,不必提示用户保存当前文件。当RichTextBox控件中的文本被修改,将激活RichTextBox控件TextChanged事件,在该事件处理函数中,使bSave=true,表示关闭当前文件前,要询问用户是否保存当前已被修改的文件。
(34) 首先在主窗体Form1类中增加一个函数,其功能是如果检查到当前文件已被修改,用对话框询问用户是否保存当前文件,根据用户的选择作相应的处理,该函数返回true,表示继续操作,该函数返回false,表示忽略此次操作。之所以要增加这个函数是因为有三处要用到此函数。该函数定义如下:
public bool IfSaveOldFile()
{ bool ReturnValue=true;
if(bSave)
{ System.Windows.Forms.DialogResult dr;
dr=MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?",
MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question);
switch(dr) //根据用户选择做相应处理
{ case System.Windows.Forms.DialogResult.Yes: //单击了yes按钮,保存修改
bSave=false; //保存文件后,应使bSave为false
if(s_FileName.Length!=0)
richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText);
else
{ SaveFileDialog saveFileDialog1=new SaveFileDialog();
if(saveFileDialog1.ShowDialog()==DialogResult.OK)
{ s_FileName=saveFileDialog1.FileName;
richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText);
}
}
ReturnValue=true; //返回true,通知调用本方法的程序,本次操作继续
break;
case System.Windows.Forms.DialogResult.No: //单击了no按钮,不保存
bSave=false;
ReturnValue=true;
break;
case System.Windows.Forms.DialogResult.Cancel: //单击了Cancel按钮
ReturnValue=false; //返回false,通知调用本方法的程序,本次操作取消
break;
}
}
return ReturnValue;
}
(35) 在菜单"文件|新建"和"文件|打开"菜单项的事件函数的第1条语句前增加如下语句:
if(!IfSaveOldFile()) //如果返回false,本次操作取消
return;
(36) 修改菜单"文件|保存... "菜单项单击事件处理函数如下:
private void menuItemSaveFile_Click(object sender,EventArgs e)
{ if(s_FileName.Length!=0)
{ bSave=false; //阴影为增加的语句
richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText);
}
else
menuItemSaveAs_Click(sender,e);
}
(37) 修改菜单"文件|另存为... "菜单项单击事件处理函数如下:
private void menuItemSaveAs_Click(object sender,EventArgs e)
{ SaveFileDialog saveFileDialog1=new SaveFileDialog();
saveFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*";
saveFileDialog1.FilterIndex=1;
if(saveFileDialog1.ShowDialog()==DialogResult.OK)
{ s_FileName=saveFileDialog1.FileName;
richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText);
bSave=false; //阴影为增加的语句
}
}
(38) 为RichTextBox控件TextChanged事件增加事件处理函数如下:
private void richTextBox1_TextChanged(object sender,EventArgs e)
{ bSave=true; }
(39) 窗体Form1的FormClosing事件是在关闭窗口之前产生的事件,此时,窗体中的控件还存在,还可以保存修改的内容,也可以不退出。增加Closing事件的事件处理函数如下:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{ if(!IfSaveOldFile())
e.Cancel=true; } //不退出,程序继续运行
(40) 编译运行,键入若干字符,单击菜单"文件"顶级菜单项的"新建"、"打开…"或"退出"菜单项,将看到提示信息,询问是否保存已被修改的文件。有三种选择:存文件,不存文件,忽略此次操作,试验一下单击不同按钮程序运行的效果。
打印和打印预览
打印和打印预览是一个编辑器必须具有的功能,本节介绍实现打印和打印预览的方法。一般要实现如下菜单项:打印、打印预览、页面设置。
PrintDocument类
PrintDocument组件是用于完成打印的类,其常用属性、方法和事件如下:
l 属性DocumentName:字符串类型,记录打印文档的名字,在打印状态对话框或打印机队列中显示这个属性值。
l 方法Print():调用此方法开始打印文档。
l 事件BeginPrint:在调用Print()方法后,在打印第一页之前发生。
l 事件PrintPage:需要打印新的一页时发生。
l 事件EndPrint:在打印完最后一页之后发生。
若要打印,第1步要创建PrintDocument组件的对象。第2步使用"页面设置"对话框PageSetupDialog(图4.9) 对打印页面进行设置,这些设置作为要打印的所有页的默认设置。使用"打印"对话框PrintDialog(图4.11)设置打印机参数。打开两个对话框前,它们的属性Document都要设置为第1步中创建的PrintDocument组件的对象,所做的设置都将保存到PrintDocument组件对象中。第三步是调用PrintDocument.Print()方法来实际打印文档。当调用Print()方法后,引发下列事件:BeginPrint、PrintPage、EndPrint。其中每打印一页都引发PrintPage事件,打印多页,要多次引发PrintPage事件。完成一次打印,可以引发一次或多次PrintPage事件。程序员要为这3个事件编写事件处理函数。BeginPrint事件处理函数中可以对打印进行初始化,一般设置所有打印页的共同属性或共用的资源,例如所有页共用的字体、建立要打印的文件流等。PrintPage事件处理函数负责打印一页数据。EndPrint事件处理函数完成打印善后工作。这三个事件处理函数的第2个参数System.Drawing.Printing.PrintEventArgs e提供了一些附加信息,主要有:
l e.Cancel:布尔变量,设置为true,将取消这次打印作业。
l e.Graphics:所使用的打印机的设备环境,参见第五章。
l e.HasMorePages:布尔变量。PrintPage事件处理函数打印一页后,如仍有数据未打印,退出事件处理函数前设置HasMorePages=true,退出PrintPage事件处理函数后,将再次引发PrintPage事件,打印下一页。
l e.MarginBounds:打印区域的大小,是Rectangle结构,元素包括左上角坐标:Left和Top,宽和高:Width和Height。单位为1/100英寸。
l e.PageSettings:PageSettings类对象,包含使用"页面设置"对话框PageSetupDialog设置的打印页面的全部信息。可用帮助查看PageSettings类的属性。
下边为这3个事件编写事件处理函数,完成打印和打印预览工作,具体步骤如下:
(41) 在主窗体文件Form1.cs中的最后一个using语句之后增加语句:
using System.IO; //处理文件必须引入的命名空间
using System.Drawing.Printing; //打印必须引入的命名空间
(42) 本例要打印或预览RichTextBox中的内容,在主窗体Form1类中增加变量:StringReader streamToPrint=null。如打印或预览文件则为:StreamReader streamToPrint,流的概念参见第六章。在主窗体Form1类中增加打印使用的字体的变量:Font printFont。
(43) 放PrintDocument控件到窗体,属性name为printDocument1。
(44) 为printDocument1增加BeginPrint事件处理函数如下:
private void printDocument1_BeginPrint(object sender,PrintEventArgs e)
{ printFont=richTextBox1.Font; //打印使用的字体
streamToPrint=new StringReader(richTextBox1.Text); //创建读字符串对象
} //如打印文件改为:streamToPrint=new StreamReader("文件的路径及文件名");
(45) printDocument1的PrintPage事件处理函数如下:
private void printDocument1_PrintPage(object sender,PrintPageEventArgs e)
{ float linesPerPage=0; //记录每页最大行数
float yPos=0; //记录将要打印的一行数据在垂直方向的位置
int count=0;//记录每页已打印行数
float leftMargin=e.MarginBounds.Left; //左边距
float topMargin=e.MarginBounds.Top; //顶边距
string line=null; //从RichTextBox中读取一段字符将存到line中
//每页最大行数=一页纸打印区域的高度/一行字符的高度
linesPerPage=e.MarginBounds.Height/printFont.GetHeight(e.Graphics);
//如果当前页已打印行数小于每页最大行数而且读出数据不为null,继续打印
while(count<linesPerPage&&((line=streamToPrint.ReadLine())!=null))
{ //yPos为要打印的当前行在垂直方向上的位置
yPos=topMargin+(count*printFont.GetHeight(e.Graphics));
e.Graphics.DrawString(line,printFont,Brushes.Black,
leftMargin,yPos,new StringFormat()); //打印,参见5.6.9节
count++; //已打印行数加1
}
if(line!=null) //是否需要打印下一页
e.HasMorePages=true; //需要打印下一页
else
e.HasMorePages=false; //不需要打印下一页
}
streamToPrint.ReadLine()读入一段数据,即两个回车之间的数据,可能打印多行。本事件处理函数将此段数据打印在一行上,因此方法必须改进。
(46) 为printDocument1增加EndPrint事件处理函数如下:
private void printDocument1_EndPrint (object sender,PrintEventArgs e)
{ if(streamToPrint!=null)
streamToPrint.Close(); } //释放不用的资源
打印设置对话框(PageSetupDialog)
PageSetupDialog控件是一个页面设置对话框,用于在Windows应用程序中设置打印页面的详细信息,对话框的外观如图4.9。使用此对话框能够设置纸张大小(类型)、纸张来源、纵向与横向打印、上下左右的页边距等。在打开PageSetupDialog对话框前,首先设置其属性Document为指定的PrintDocument类对象,用来把打印页面设置信息保存到这个PrintDocument类对象中。为文本编辑器增加页面设置功能的具体步骤如下:
(47) 为菜单"文件"顶级菜单项的弹出菜单增加菜单项:页面设置。
(48) 放PageSetupDialog控件到窗体,属性name为pageSetupDialog1。
(49) 为菜单"文件|页面设置"菜单项增加单击事件处理函数如下:
private void 页面设置ToolStripMenuItem_Click(object sender, EventArgs e)
{ pageSetupDialog1.Document=printDocument1;
pageSetupDialog1.ShowDialog(); }
(50) 打开对话框pageSetupDialog1后,如果单击了确定按钮,PageSetupDialog对话框中所做的页面设置被保存到PrintDocument类对象printDocument1中,如果单击了取消按钮,不保存这些修改,维持原来的值。当调用PrintDocument.Print方法来实际打印文档时,引发PrintPage事件,该事件处理函数的第二个参数e提供了这些设置信息。
图 4.9 页面设置对话框
打印预览
PrintPreviewDialog控件可以在屏幕上显示使用PrintDocument类实现的打印效果,即打印预览,如图4.10。实现打印预览的具体步骤如下:
(51) 为菜单"文件"顶级菜单项的弹出菜单增加菜单项:打印预览。
(52) 放PrintPreviewDialog控件到窗体,属性name为printPreviewDialog1。
(53) 为菜单"文件|打印预览"菜单项增加单击事件处理函数如下:
private void 打印预览ToolStripMenuItem_Click(object sender, EventArgs e)
{ printPreviewDialog1.Document=printDocument1;
printPreviewDialog1.ShowDialog(); }
(54) 编译,运行,打开一个文本文件,试验一下预览的效果(必须安装打印机驱动程序)。
图 4.10 打印预览运行界面
用打印对话框PrintDialog实现打印
PrintDialog控件用来设置打印机的一些参数,包括打印机名称、要打印的页(全部打印或指定页的范围)、打印的份数以及是否打印到文件等。在打开对话框前,首先设置其属性Document为指定的PrintDocument类对象,打开PrintDialog对话框后,修改的设置将保存到PrintDocument类的对象中。PrintDialog对话框的外观如图4.11。增加打印功能的具体步骤如下:
图 4.11 打印设置对话框
(55) 放PrintDialog控件到窗体,属性Name=printDialog1。
(56) 为菜单"文件"顶级菜单项的弹出菜单增加菜单项:打印。
(57) 为菜单"文件|打印"菜单项增加单击事件处理函数如下:
private void 打印ToolStripMenuItem_Click(object sender, EventArgs e)
{ printDialog1.Document=printDocument1;
if(printDialog1.ShowDialog(this)==DialogResult.OK)
printDocument1.Print(); }
(58) 编译运行,打开一个文本文件,试验一下打印效果(必须安装打印机驱动程序)。
编写多文档界面应用程序
本节首先介绍如何建立类似Microsoft Word的多文档文本编辑器,然后介绍如何建立类似VS2005集成环境中的文件编辑器那样有多个选项卡页的文本编辑器。
多文档文本编辑器
建立一个类似Microsoft Word的编辑器,有多页,每页处理一个文档。多文档界面(MDI)应用程序有一个父窗体(主窗体),父窗体在其工作区内包含一组子窗体。每个子窗体都是一个限制为只能在父窗体内出现的窗体。这些子窗体通常共享父窗体的菜单栏、工具栏以及其他部分。
【例4.2】创建多文档文本编辑器的具体步骤如下,运行效果如图4.12,主窗体中的子窗体为层叠排列。
(1) 新建一个新项目。修改Form1窗体属性IsMdiContainer=true,表示主窗体是一个子窗体容器。 图 4.12 多文档编辑器
(2) 放菜单控件MenuStrip到主窗体。为菜单增加"文件"顶级菜单项。为"文件"顶级菜单项的弹出菜单增加菜单项:新建、打开、另存为、关闭当前窗口、退出。
(3) 为菜单增加"窗口"顶级菜单项。在"窗口"顶级菜单项的弹出菜单中增加菜单项:水平平铺、层叠、垂直平铺。设置菜单控件MenuStrip的MdiWindowListItem属性为"窗口"顶级菜单项的Name属性值,使"窗口"顶级菜单项的弹出菜单下部增加子窗口列表。
(4) 在VS2005集成环境中,单击菜单"项目|添加Windows窗体"菜单项,创建子窗体。窗体文件名称为FormChild.cs,窗体的类名也定义为FormChild。(参见4.5节)
(5) 放RichTextBox1控件到子窗体。修改属性Dock=Fill,Modifiers=public,使RichTextBox1为公有成员,在主窗体可以访问RichTextBox1。
(6) 为主窗体菜单"文件|新建"菜单项增加单击事件处理函数如下:
private void 新建ToolStripMenuItem_Click(object sender, EventArgs e)
{ FormChild formChild=new FormChild();
formChild.MdiParent=this;
formChild.Show(); }
(7) 放OpenFileDialog控件到主窗体。菜单"文件|打开"菜单项事件处理函数如下:
private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
{ if(openFileDialog1.ShowDialog(this)==DialogResult.OK)
{ FormChild ChildForm=new FormChild();
ChildForm.MdiParent=this;
ChildForm.richTextBox1.LoadFile(openFileDialog1.FileName,
RichTextBoxStreamType.PlainText);
ChildForm.Show();
}
}
(8) 放SaveFileDialog控件到子窗体。菜单"文件|另存为"菜单项事件处理函数如下:
private void 另存为ToolStripMenuItem_Click(object sender, EventArgs e)
{ if(saveFileDialog1.ShowDialog(this)==DialogResult.OK)
{ FormChild ChildForm=(FormChild)this.ActiveMdiChild;
ChildForm.richTextBox1.SaveFile(saveFileDialog1.FileName,
RichTextBoxStreamType.PlainText);
}
}
(9) 为主窗体菜单"文件|关闭当前窗口"菜单项增加单击事件处理函数如下:
private void 关闭当前窗口ToolStripMenuItem_Click(object sender,EventArgs e)
{ this.ActiveMdiChild.Close(); }
(10) 为主窗体菜单"文件|退出"菜单项增加单击事件函数如下:
private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
{ Close(); } //退出运行程序
(11) 为主窗体菜单"窗口|水平平铺"菜单项增加单击事件函数如下:
private void 水平平铺ToolStripMenuItem_Click(object sender, EventArgs e)
{ this.LayoutMdi(MdiLayout.TileHorizontal); }
(12) 为主窗体菜单"窗口|层叠"菜单项增加单击事件函数如下:
private void 层叠ToolStripMenuItem_Click(object sender, EventArgs e)
{ this.LayoutMdi(MdiLayout.Cascade); }
(13) 为主窗体菜单"窗口|垂直平铺"菜单项增加单击事件函数如下:
private void 垂直平铺ToolStripMenuItem_Click(object sender, EventArgs e)
{ this.LayoutMdi(MdiLayout. TileVertical); }
多选项卡页的文本编辑器
【例4.3】VS2005的文件编辑器有多个选项卡页,可以编辑多个文件。可以建立类似VS2005的文件编辑器的文本编辑器。如果建立的应用程序选项卡页数固定,每选项卡页显示一行文本,建立这个应用程序的具体实现步骤如下:
(1) 新建项目。放TabControl控件到子窗体。修改属性Dock=Fill。
(2) 单击TabControl属性TabPages右侧标题为"…"的按钮,打开"TabPage集合编辑器"(图4.13),单击添加按钮,增加2个选项卡页,修改属性Text分别为:第一页,第二页。
图 4.13 TabPage集合编辑器
(3) 选中第一页,可以在页中放置控件,例如放置Label控件,属性Text="这是第一个选项卡页"。同样在第二页中也放置Label控件,属性Text="这是第二个选项卡页"。如果放置RichTextBox控件,可以做成多文档编辑器。
(4) 运行,可以看到多页,单击每页的标题,可以转换选项卡页。运行效果图4.14。
【例4.4】如设计一个有多个选项卡页文本编辑器,每选项卡页处理一个文档,并能动态增加新选项卡页,关闭当前选项卡页,文本编辑器实现步骤如下: 图 4.14 运行界面
(1) 新建项目。放MenuStrip控件到主窗体。为菜单增加顶级菜单项:文件,为其弹出菜单增加4个菜单项:新页、关闭当前页、打开、另存为。
(2) 放TabControl控件到子窗体。修改属性Dock=Fill。删除两个初始选项卡页。
(3) 为主窗体Form1类增加一个新方法MakeNewTbpage()如下:
private object MakeNewTbpage()
{ TabPage tabPage1=new TabPage(); //增加选项卡页TabPage
tabControl1.Controls.Add(tabPage1); //将tabPage1放到tabControl1中
tabPage1.Location=new Point(4, 21);
tabPage1.Size=new Size(284, 248);
tabPage1.Text="第"+tabPage1.TabIndex.ToString()+"页";
RichTextBox richTextBox1=new RichTextBox(); //增加RichTextBox
richTextBox1.Dock=DockStyle.Fill;
richTextBox1.Size=new Size(284, 248);
richTextBox1.Text="";
tabPage1.Controls.Add(richTextBox1); //将richTextBox1放到tabPage1中
return (object)richTextBox1;
}
(4) 为主窗体菜单"文件|新页"菜单项增加单击事件函数如下:
private void 新页ToolStripMenuItem_Click(object sender, EventArgs e)
{ MakeNewTbpage(); }
(5) 为主窗体菜单"文件|关闭当前页"菜单项增加单击事件函数如下:
private void 关闭当前页ToolStripMenuItem_Click(object sender, EventArgs e)
{ TabPage tabPage1=tabControl1.SelectedTab; //得到当前选定的选项卡页
tabControl1.Controls.Remove(tabPage1); //从tabControl1中移走该页
//得到当前选定的选项卡页中第0个控件,即RichTextBox控件
RichTextBox richTextBox1=(RichTextBox)tabPage1.Controls[0];
if(richTextBox1!=null)
richTextBox1.Dispose(); //删除当前选定选项卡页中RichTextBox控件对象
if(tabPage1!=null)
tabPage1.Dispose(); //删除当前选定的选项卡页
}
(6) 放OpenFileDialog控件到主窗体。主窗体菜单"文件|打开"菜单项单击事件函数如下:
private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
{ if(openFileDialog1.ShowDialog()==DialogResult.OK)
{ RichTextBox richTextBox1=(RichTextBox)MakeNewTbpage();
richTextBox1.LoadFile(openFileDialog1.FileName,RichTextBoxStreamType.PlainText);
}
}
(7) 放SaveFileDialog控件到主窗体。主窗体菜单"文件|另存为"菜单项单击事件函数如下:
private void 另存为ToolStripMenuItem_Click(object sender, EventArgs e)
{ if(saveFileDialog1.ShowDialog()==DialogResult.OK)
{ TabPage tabPage1=tabControl1.SelectedTab;
RichTextBox richTextBox1=(RichTextBox)tabPage1.Controls[0];
richTextBox1.SaveFile(saveFileDialog1.FileName,RichTextBoxStreamType.PlainText);
}
}
(8) 编译,运行,建立新文件,关闭当前选项卡页,打开新文件,存文件,看是否正常。