06_多窗口文档编辑器(小钱版)[Qt开发][2012-03-07]




1) 引言


-> 模块01:程序简要说明


        1. 程序名称:多窗口文档编辑器(Multiple Text Editor)

        2. 运行环境:Window NT / 2000 / XP / VISTA / 7

        3. 软件特色:能多窗口交互式编辑支持格式的文档。

        4. 主要功能:编辑html、htm、plaintext格式的文档。

        5. 功能简要陈列:

            ① 文件:新建、打开、单个保存、多个保存、另存为、打印、打印预览、退出。

           ② 窗口:选择、平铺、层叠、上一窗口、下一窗口、关闭单个窗口、关闭所有窗口。

           ③ 编辑:撤消、重复、全选、剪切、复制、粘贴。

           ④ 格式:粗体、下划线、斜体、左对齐、右对齐、居中、两端对齐、颜色、基本设置。

           ⑤ 帮助:联系方式、关于软件。

 

 

->模块02:开发相关

 

1. 开发环境

        ① 电脑型号:    惠普HP Pavilion g4 Notebook PC 笔记本电脑

        ② 操作系统:    Microsoft Windows 7 旗舰版 (6.1.7601 Service Pack 1)

        ③ 处理器:        英特尔Core i3 M 390 @ 2.67GHz 双核笔记本处理器

        ④ 主板:            惠普1667 (英特尔HM55 芯片组)

        ⑤ 内存:            4 GB ( 尔必达 DDR3 1333MHz / 金士顿 DDR3 1333MHz )

        ⑥ 主硬盘:        希捷ST9640320AS ( 640 GB / 5400 转/分 )

        ⑦ 显卡:            ATI Radeon HD 6470M  ( 1 GB / 惠普)

        ⑧ 开发软件:    Microsoft Visual Studio 2010 + Qt-Win-OpenSource-4.8.0-VS2010

 

2. 文件说明:

        ① 程序运行文件:   01_MultipleTextEditor_Run.exe

        ② 程序说明书:       02_MultipleTextEditor _ Instruction.pdf

        ③ 源程序文件夹:   03_MultipleTextEditor_SourceDocument

        ④ 程序制作相关:   04_MultipleTextEditor_SoftwareRelate

 

3. 参考资料:

        ① 《C++GUI Qt4 编程(第二版)》Jasmin Blanchette 和MarkSummerfield 著

        ② 《零基础学Qt4编程》 吴迪 著

        ③ 《Qt学习之路》 豆子空间

        ④ 《QtAssistant》—— Qt SDK

        ⑤ 《Internet》—— Baidu, Google...

 

4. 联系方式:

        ① Name:    Neicole

        ② Blog:       http://blog.csdn.net/neicole

        ③ QQ:      932194884

        ④ E-Mail: 932194884@qq.com

 

 

2) 程序功能展示

 

        程序刚启动时,程序只允许五个操作,新建文件,打开文件,联系人帮助信息,软件帮助信息,退出程序。随着文件打开(包括新建的文件)的数目增多,软件编辑时文本状态变化,软件中的一些功能将会被激活或者禁用。(下图为软件刚启动时的截图)



-> 模块01:对文件进行操作的功能

 

① 新建文件


1. 操作执行:

        新建文件的执行操作很简单,只需要选择菜单栏上的“文件”->“新建”即可,或者,可以直接用鼠标点击工具栏上的“新建文件”的图标按钮,当然,也可以使用快捷键“Ctrl + N”以执行该操作。



2. 操作特性

     相信有些读者会注意到,上图中,新建的文件会显示在主窗口中的中心窗口区域内,并且,名字为“Document1”, 文件的默认名字会叫做Document,然后会在名字后面加序号,这个序号是本次打开软件所执行“新建”操作的次数,也就是说,如果将现在的窗口关闭,再次执行新建操作,这个数目也是会继续累积下去。(如下图,连续执行新建操作五次,关闭第五个窗口,再执行新建操作四次)



    上图中,除了刚刚所说的文件命名特性外,还有一个现象我们可以稍留意一下的,在这八个子窗口同时存在时,“窗口”功能的图标已经全“亮”,即它们的功能已经被激活了,文件菜单下的功能,在窗口中存在一个文档时,即可全部激活,而“窗口”功能,在存在两个文档时,会全都被激活。

 

② 打开文件

1. 操作执行:

    打开文件的执行操作很简单,只需要选择菜单栏上的“文件”->“打开”即可,或者,可以直接用鼠标点击工具栏上的“打开文件”的图标按钮,当然,也可以使用快捷键“Ctrl + O”以执行该操作。


2. 操作特性

    这里的一些窗口与文件的功能激活特性基本上与“新建”操作的一样,然后,只是命名方面有点不同,打开一个文件,它会以该文件的名字作为子窗口的名字,另外,在“打开”按钮被按下后,程序将会弹出一个文件选择的对话框,这时候用户只需要选中HTML, HTM,PLAINTEXT三种支持编辑的格式文件中的其中一种就可以打开成功打开这个文件了。

 

③ 保存操作

    保存操作,可以再分为三种,第一种是“保存当前文件”,第二种是“当前文件另存为”,第三种是“保存所有文件”,这三种操作,都可以从菜单栏上找到相对应的选项。需要注意的是,当文件是一个新建的文件,未被执行过“保存”操作时,选择第一种或者第三种操作都将会自动由系统跳转至“另存为”的操作。

    另外,在成功执行任意一种文件保存的功能后,这个文件的状态将会从“已被编辑状态”转变为“未编辑”状态,简单来说,就是本来窗口带 * 符号的,保存后,窗口的 * 符号就会消失了。(如下图)



④ 打印操作

打印文件操作的执行可以选择菜单栏上的“文件”->“打印”,另外,也可以使用快捷键“Ctrl + P”以执行该操作。(如下图)待打印对话框打开后,用户可选出符合自己需求的选项,然后点击“打印”进行打印。

 


⑤ 打印预览操作

打印文件预览操作的执行可以选择菜单栏上的“文件”->“打印预览”,另外,也可以点击工具栏上的打印预览图标。(如下图) 待打印预览对话框打开后,用户看到将要打印的文件的效果,当然,此时用户也可点击在打印预览对话框中的打印功能进行打印操作。

 


⑥ 退出

退出程序操作操作可以选择菜单栏上的“文件”->“退出”或点击右上角的退出按钮执行。(如下图) 当退出操作被激活后,如果程序发现还有未保存的文件,将会给出相应提示,用户可根据需要进行选择操作。



-> 模块02:对窗口进行操作的功能

    由于该软件可支持打开多个文档进行编辑,因此,也会有一系列的窗口动作可以执行。这些操作都可以通过菜单栏或者工具栏上的点击进行操作,下面就不再累赘了。


① 选择文件窗口
    当已经打开一个或以上的文档时,用户可以通过菜单栏中的“窗口”->“选择文件窗口”以快速选出自己需要操作的窗口(如下图)。



② 平铺
选择窗口平铺的功能后,已打开的文档将会将主窗口的中心窗口位置填满(如下图)。



③ 层叠
选择窗口层叠的功能后,已打开的文档将会在主窗口的中心窗口位置将各窗口层叠摆放(如下图)。




④ “上一窗口”操作 或 “下一窗口”操作
    选择“上一窗口”操作或“下一窗口”操作后,系统将会将激活的操作文档转移到上一窗口或下一窗口中。

⑥ “关闭窗口”操作 或 “关闭所有窗口”操作
     选择“关闭窗口”操作将会关闭当前活动的文档,选择“关闭所有窗口”的操作将会关闭现在打开的全部文档,需要注意的是,这两个操作执行前,系统都会自动检测所要求关闭的文档是否已经保存,如果还未保存,将会给出相应的用户提示,用户可根据需要选择需要执行的操作。


 


-> 模块03:对文档进行编辑的功能


        该软件支持一般文档编辑器中的基本编辑功能,即:撤消、重复、全选、剪切、复制、粘贴这些操作。这里需要注意的是,该软件还支持同步更新这些操作的可执行性,如:当用户需要执行撤消操作时,系统会先检查是否存在可执行的撤消操作,如果检查结果为不可执行,用户将无法从工具栏或者菜单栏上使用该功能,这个功能也可以给用户起到一个提醒的作用(如下图),另外,如果同时开多个窗口,这些检测功能还能应用到单独的窗口当中。



 

-> 模块04:文档字体格式设置功能
        本软件支持一般文档编辑器中的基本文字设置功能——粗体、下划线、斜体、左对齐、右对齐、居中、两端对齐、颜色、基本设置这些操作均支持(如下图所示)。在文字设置光标移动选择过程中,软件还能自动检测出当前文字的状态,然后,在工具栏的相对应按钮同步这些文字状态,如当文字为粗体时,工具栏中的粗体状态将会看见已被按下。




-> 模块05:软件帮助信息显示功能
      当用户选中帮助菜单时,会有两个可选项,一个是“联系方式”,另一个是“关于软件”,这两个功能,都是显示一些简单的相关信息,具体如下图所示。

① 联系方式




② 关于软件




 

 

3) 程序各类间关系图及类说明

    在这个程序中,主要使用了九个类,其中,有四个类来自于Qt的库函数,另外的有四个是继承Qt库的类,一个是自定义枚举类。类与类之间是如何联系起来呢?存在什么样的关系呢?具体请看下面介绍。

 

-> 模块00:程序各类间关系图



-> 模块01:actionType类
    actionType类为枚举类,它所定义的成员可看作是全局变量,使用主要用于定义各种动作名为常量,方便整个程序中switch语句等的使用,同时,该类将会被程序的全部类用到。这里指的全部类包括:mainWindow类,mdiWindow类,signalText类和actionSignal类。

-> 模块02:actionSignal类
    actionSignal类,公用继承了QObject类,它主要用于收集mdiWindow类和singleText类的信号,然后可以在其它类中通过调用actionSignal的public函数,然后从本public函数中发射出本类的信号,通知其它可以使用这些相对应的槽执行需要的操作。简单来说,这个类就是为了connect(signal, SIGNAL(signalFunction()), slot, SLOT(slotFunction())中的指定signal和SIGNAL的。

-> 模块03:mainWindow类
    mainWindow类公用继承了QMainWindow类,负责制作程序整个窗品的框架,其中包括:菜单栏,工具栏,中心窗口的制定,同时定义与各个功能相连的QAction。而这些QAction的功能实现,将会通过信号/槽被连接到类外,mdiWindow类与sigleText类中实现。

-> 模块04:mdiWindow类
    mdiWindow类,公用继承了QMdiArea类,主要用于制定mainWindow中的中心窗口部件, 另外,还将实现各个功能的连接,可以说是mainWindow类与singleText类的桥梁——将mainWindow中需要实现的功能通过mdiWindow确定活动窗口然后连接到singleText中执行。
由于mdiWindow类是多窗口类,所以除对窗口的一些操作外,其它具体的功能实现还是主要依赖singleText这个单窗口类。

-> 模块05:singleText类
    singleText类公用继承了QTextEdit类,它主要用于定义在mainWindow中的中心窗口部件多个窗口中的单个窗口,另外,还具体定义绝大多数的功能函数,关于singletText类也可以理解为实现文档编辑功能的基类。

 


 

3) 程序源程序模块设计说明
  这里的程序源程序模块设计说明,主要根据程序所实现的功能而编写,而不是像以往那样按单类中的每个功能编写,这是为了方便看出一个完整的功能是如何通过各个类来实现的。


 

-> 模块01:新建文件功能

  新建操作的功能实现,主要汲及到的用户自定义类有mainWindow类,mdiWindow类和singleText类。
下面先讲一下在单窗口中实现新建功能的思路,也就是说,在singleText中的实现方法:



接下来我们可以看一下这个在singleText中的新建操作的源代码:

/**
 * 操作:void singleText::fileNew(int number)[public]
 * 功能:新建一个文件
 * 输入:int number 为第几个新建文件的名称,默认值为1.
 * 前置条件:save()函数的beforeAttentionSaveMessage()函数已定义,Modified已被初始化。
 * 后置条件:清空现存的Widget,重新创建一个空的Widget于用户操作。
 **/
bool singleText::fileNew(int number = 1)
{
// 1. check if need to save
	if (this -> document() -> isModified() == true)
	{
		int saveOrNot = this -> beforeAttentionSaveMessage();	// 保存提示
		if (QMessageBox::Save == saveOrNot)
		{
			bool saveSuccess = this -> fileSaveSingle();
			if (false == saveSuccess)
			{
				return false;	// 保存失败则返回
			}
		}
		else if(QMessageBox::Cancel == saveOrNot)
		{
			return false;	// 取消现在操作
		}
		else	// unsave
		{
			// 不保存时,继续现在“打开“
		}
	}	// End if -> true == hasBeenEditStatus()
// 2. set fileName
	fileName = tr("Document %1").arg(number);
	setWindowTitle(fileName + "[*]");
// 3. set each status
	emit this -> thismodificationChanged(false);
	this -> setIsNewFile(true);
	return true;
}
    在单文件的类singleText中实现后,我们需要知道的是如何将其放到多窗口中去呢?在解决这个问题前,先要知道的不是直接在mdiWindow中如何定义这些内容,而是在mainWindow中有哪些与这个newFile设定有关的内容再对mdiWindow的这一操作进行定义。
    在mainWindow中,主要有一个QAction * fileNewA是与这些操作有关的,也就是说,这个变量就是用户操作与软件程序运行的交互接口,在多窗口文档编辑器中,我们需要知道的问题是当一个窗口新建时,对这几个类会有哪些影响?从而再考虑的与QAction * fileNewA的响应的操作要有哪些。
    当然,首先,新建一个窗口,必要先最基本的实现新建操作,将实现新建的操作通过mdiWindow的对象连接到singleText中,当新建操作执行成功后,会对主窗口mainWindow产生的影响是,当新建窗口达到哪个数目时,哪些QAction需要激活,这里,我们可以先不去考虑有哪些要被激活的问题,这个问题可放到后面去解决,这时,我们只需要知道的是,给程序发出一个信息,让它知道,这时候的子窗口有多少个(这时的已经打开的文档有多少个),为解决窗口数目(信号)影响到哪些QAction的问题,这里特意还新建了一个类actionSignal类,可以作为它们之间信号的传递,在mainWindow中主要作为信号接收方,而在mdiWindow中则作为信号发送方,这是因为窗口的数目主要还是要由mdiWindow去管理的,知道了这些以后,我们还是回到正题,执行“新建”操作。
    现在可以确定的是,mainWindow中,关于newFile的操作,只有一个,将新建操作连接到mdiWindow去执行,使用信号槽操作可以轻松执行这一功能,这也是为什么要在mainWindow中定义一个mdiWindow私有成员对象的原因了。
    在mdiWindow中,“新建”操作的执行很简单,请看下面代码及解释:
/**
 * 操作:void mdiWindow::fileNew()[public slots]
 * 功能:新建子窗口,并设定子窗口的相关参数,发送窗口数目变动状态。
 **/ 
void mdiWindow::fileNew()
{
	bool newfileOK;
	singleText * file = new singleText(controlAction);
	newfileOK = file -> fileNew(fileNumber);
	if (!newfileOK)
	{
		return;
	}
	++fileNumber;
	QMdiSubWindow * subFile = this -> QMdiArea::addSubWindow(file, Qt::Widget);
	subFile -> resize (500, 500);
	subFile -> setWindowIcon(QIcon(":/mainWindow/Resources/texticon.png"));
	subFile -> show();
	++windowNumber;
	emit windowNumberChange();
	return;
}

    在第3行(不计前面的注释行),申请一个bool型的newfileOK变量,这是用于确认在下一步新建文档是否成功,假如新建失败的话,就会直接退出新建功能,如果新建singleText对象成功了,将继续下一步的执行,所新建的窗口对象,将会暂时存放于内存的堆中,当然,也可以从QMdiArea中找出它的地址,因为它将会被放在mdiWindow中进行管理(第11行),同时,在成功新建了一个子窗口后,将会在类中增加子窗口的数目,而这个记录子窗口数目的就是mdiWindow的私有成员windowNumber(第15行),此外,fileNumber(第10行)就是之前在功能演示中的记录成功执行新建窗口次数的变量,在新建了子窗口后,还需要对这个子窗口的一些基本属进行设置(大小,图标),更重要的一步是,得把它显示出来(第14行),不然的话,新建就没有意义了,在新建的最后一步,就是发送一个windowNumber变化的信号,让相应的函数做出处理。



->模块02:打开文件功能

    打开文件操作的功能的实现与新建文件操作的执行思路很相似,在mainWindow中是一样的,然后在mdiWindow中也是相差不多,只是在singleText中稍有不同,两种操作同是操作开始时需要检查modified的状态,操作结束时设置文件名称,设置文件状态,稍有不同只是中间的两步的处理,在singleText的文件打开的操作代码如下:

/**
 * 操作:void singleText::fileOpen()[public]
 * 功能:打开已存在的文件(HTML, HTM, ODT)并载入这文件数据。
 * 前置条件:save()函数的beforeAttentionSaveMessage()函数已定义,Modified已被初始化。
 * 后置条件:将打开的文件的数据内容加载到this的Widget中。
 **/
bool singleText::fileOpen()
{
// 1. check if need to save
	if (this -> document() -> isModified() == true)
	{
		int saveOrNot = this -> beforeAttentionSaveMessage();	// 保存提示
		if (QMessageBox::Save == saveOrNot)
		{
			bool saveSuccess = this -> fileSaveSingle();
			if (false == saveSuccess)
			{
				return false;	// 保存失败则返回
			}
		}
		else if(QMessageBox::Cancel == saveOrNot)
		{
			return false;	// 取消现在操作
		}
		else	// unsave
		{
			// 不保存时,继续现在“新建操作“
		}
	}	// End if -> true == hasBeenEditStatus()
// 2. Gain the file name And address.
	QString fileNameTemp = QFileDialog::getOpenFileName(this, tr("打开文件"), QString(), tr("HTML-Files (*.htm *.html);;PLAINTTEXT-Files (*.plaintext)"));
	// 上面操作获取了完整的文件路径
	if (fileNameTemp.isEmpty())
	{
		return false;	// 创建失败
	}
// 3. load file data
	if( !QFile::exists(fileNameTemp)) // 指定文件不存在
	{
		QMessageBox msgBox;
		msgBox.setText("无法打开指定文件!");
		msgBox.setIcon(QMessageBox::Warning);
		msgBox.setWindowTitle("打开错误");
		msgBox.exec();
		return false;
	}
	QFile file(fileNameTemp);
	if( !file.open(QFile::ReadOnly))	// 不能以只读方式打开
	{
		QMessageBox msgBox;
		msgBox.setText("无法打开指定文件!");
		msgBox.setIcon(QMessageBox::Warning);
		msgBox.setWindowTitle("不能以只读方式打开编辑");
		msgBox.exec();
		return false;
	}
	QByteArray data = file.readAll();
	file.close();
	QTextCodec * codec = Qt::codecForHtml(data);
	QString str = codec -> toUnicode(data);
	if(Qt::mightBeRichText(str))
	{
		this -> setHtml(str);	// 成功转换为Html方式打开则加载内容
	}
	else
	{	// 否则将以PlainText的方式加载至窗口
		str = QString::fromLocal8Bit(data);
		this -> setPlainText(str);
	}
// 4. Change all status
	fileAddress = fileNameTemp;
	fileName = QFileInfo(fileAddress).fileName();	// 使用QFileInfo提取文件名
	this -> setIsNewFile(false);
	this -> setWindowTitle(fileName + "[*]");
	emit this -> thismodificationChanged(false);
	return true;
}

    如代码所示,其中标注的第二步和第三步,2.Gain the file name and Address 和 3.load file data是所说的与“新建操作”不同的地方,在“新建”操作时,名字是系统默认定义的,而在“打开”操作时,文件名是通过QFileDialog的调用而获取,因此,它也不需要再用一个变量去记录打开次数而去区分文件的不同了,在“打开”操作中,最为重要的是第三步了,对文件进行读取操作,将其放入已初始化的singleText对象中,在读取时,可以直接使用QByteArray类暂时记录从QFile打开文件中所读取的内容,然后通过调用codecForHtml(),和toUnicode()这些函数来将其转化为QString型的数据,再使用在父类QTextEdit函数setHteml(QString)将其写入现在的对象中,一句话,读取转化的目的就是为了能使用父类的QTextEdit进行数据载入。


 

-> 模块03:文件“编辑改变”状态辨认功能

    在保存文件功能实现之前必须去解决一个问题,如何判定这个文件是需要保存的?如何知道这个文件是被用户改动过的文件?这时可以首先想到的一个办法是,设标记。在用户对文档进行过编辑处理后,我们可以将预设好的一个标记状态设为true状态。其实,这么一个思路,在我们想到的同时,Qt的库函数也早就帮我们想到了,在Qt里面,它有一个void QTextDocument::modificationChanged ( bool changed ) [signal] 信号函数,我们可以在需要的时候去发射这个信号,也可以将这个信号根据自己需要连接到需要使用的地方,下面是关于这个信号,关于如何解决这个“保存状态”的相关信号/槽连接及其它的处理:

在singleText的构造函数中:

connect(this -> document(), SIGNAL(modificationChanged(bool)), this -> document(), SLOT(setModified(bool)));
connect(this -> document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool)));
connect(this, SIGNAL(thismodificationChanged(bool)), this -> document(), SLOT(setModified(bool))); 
connect(this, SIGNAL(thismodificationChanged(bool)), this, SLOT(setWindowModified(bool)));

emit this -> thismodificationChanged(false);

第一二个connect,可以将库函数中发出的“文档被改变了”的信号发送,然后setModified接收,这是因为我们需要用到它的isModified()函数以确认状态信息,modificationChanged(bool),setModified(bool),isModified()可以理解成是针对发送修改检测同一个私有成员的数据,关于检测文档是否被编辑过了,可以看见在函数定义中的“新建”操作,“打开”操作都有类似下面的代码:if (this -> document() -> isModified() == true){}

另外,对窗口的显示 * 的状态,我们还可以通过setWindowModified()来设定,只是,在QTextEdit中,这个函数不是slot函数,所以我们还需要定义一个slot函数通过调用QTextEdit中的这个函数来响应这操作:

void singleText::setWindowModified(bool set){
	this -> QTextEdit::setWindowModified(set);
	return;
}

为了方便我们操作,还可以自定义一个函数以手动改变这个文件的状态,因此,我们在singleTex中新建了一个信号:signals: void thismodificationChanged(bool); 然后,再通过第三第四个构造函数中的第三个connect将其与modified的状态相连,当我们想自定义改变这个状态时,在本类中,直接使用下面这个语句发送信号就可以了emit this -> thismodificationChanged(false);

当然,也可以借助下图来理清各个signal与slot和类之间的关系:


 

 

-> 模块04:保存文件功能
        关于保存文件的功能,这个程序中按照功能划分,分为三种,第一种是保存本文档,第二种是将本文档另存为,第三种是将保存所有已被打开或新建的文档。
第一二种操作,都可以直接在singleText类中执行,而第三种操作,则需要依赖mdiWindow了,这是因为它是一个汲及到多个窗口的功能。
下面是开始逐个操作的分析:

/**
 * 操作:void singleText::fileSaveSingle()[public]
 * 功能:保存修改过的文件数据。
 * 前置条件:canNotSaveInformation()函数和fileSaveAs()函数已定义,Modified已被初始化。
 * 后置条件:将文件数据保存于磁盘中。
 **/
bool singleText::fileSaveSingle()
{
// 1. Check if ever been edited
	if (false == this -> document() -> isModified())
	{
		return false;
	}
// 2. Check if a new file
	if (true == isNewFileStatus())
	{
		return this -> fileSaveAs();
	}
// 3. use QTextDocument to save this file
	QTextDocumentWriter writer(fileAddress);
	bool canSave = writer.write(this -> document());
	if (!canSave)
	{
		int userChoice = this -> canNotSaveInformation();
		if (QMessageBox::Save == userChoice)
			return this -> fileSaveAs();
		else
			return false;
	}
// 4. changed the status
	emit this -> thismodificationChanged(false);
	return true;
}


第一种操作,“保存本文档”,如程序注释代码,这里将其分为四个步骤:
第一步:检查文档是否被编辑过,如果没有编辑过的话,可以直接返回false,告诉程序这个文档不需要保存操作也行。
第二步:检查文档是不是由“新建”操作得来的一个文件,这可以通过私有成员isNewFile来判定,如果是的话,可以告诉程序跳至“另存为”操作。
第三步:如果前面的检测都通过的了话,就可以正式开始保存操作了,使用QTextDocument中的write()函数可以轻松实现这一功能,它将singleText中的所有编辑面板中的数据保存至现在路径文件中,这个路径是在文件打开或者另存为操作时确定的路径,路径数据保存在singleText类的私有成员fileAddress中。
第四步:待保存工作成功完成后,就可以开始改变文本的编辑状态了,这时可以直接发射信号实现功能。具体实现方式已在上一小节中提到。

/**
 * 操作:void singleText::fileSaveAs()[public]
 * 功能:将现文件数据存放于另外的文件当中。
 * 前置条件:save()函数已定义。
 * 后置条件:将新保存后的文件的数据内容加载到this的Widget中。
 **/
bool singleText::fileSaveAs()
{
// 1. Gain the fileName and fileAddress
	QString fileNameTemp = QFileDialog::getSaveFileName(this, fileName + "   Save File", QString(), tr("HTML-Files(*.htm *.html);;PLAINTTEXT file(*.plaintext)"));
	if (fileNameTemp.isEmpty())
	{
		return false;
	}
// 2. Check if the name is true
	if(!(fileNameTemp.endsWith(".plaintext", Qt::CaseInsensitive) || fileNameTemp.endsWith(".htm", Qt::CaseInsensitive) || fileNameTemp.endsWith(".html", Qt::CaseInsensitive)))
	{
		fileNameTemp.append(".html");	// 默认的扩展名是.html
	}
// 3. use QTextDocument to save this file
	QTextDocumentWriter writer(fileNameTemp);
	bool canSave = writer.write(this -> document());
	if (!canSave)
	{
		 QMessageBox msgBox;
		 msgBox.setText("未能正确保存文件!");
		 msgBox.setInformativeText(tr("请重新执行操作后检查路径。"));
		 msgBox.setIcon (QMessageBox::Warning);
		 msgBox.exec();
		 return false;
	}
// 4. Change all status
	fileAddress = fileNameTemp;
	fileName = QFileInfo(fileAddress).fileName();	// 使用QFileInfo提取文件名
	this -> setIsNewFile(false);
	this -> setWindowTitle(fileName + "[*]");
	emit this -> thismodificationChanged(false);
	return true;
}

第二种操作,“另存为”,如程序注释代码,这里也可将其分为四个步骤:

第一步:通过QFileDialog获取文件的完整路径,当获取失败时,可以直接返回函数,不执行其它的操作。

第二步:检查文件的扩展名是否正确,当扩展名不是所支持的类型时,程序将自动为其加上扩展名.html以支持继续操作。

第三步:开始保存文件,这里的保存操作方式是与上一种操作一样的,使用QTextDocument中的write()函数可以实现这一功能,它将singleText中的所有编辑面板中的数据保存至刚刚所确定的路径文件。

第四步:当成功另存为文件数据成功后,还需要改变是的现在这个对象中的文件名,文件路径,还有文件的编辑状态,另外还有窗口的名字。

/**
 * 操作:bool mdiWindow::fileSaveAll()[public slots]
 * 功能:保存所有子窗口的数据。
 **/ 
bool mdiWindow::fileSaveAll()
{
	bool ok(false);
	if (this -> QMdiArea::activeSubWindow () == NULL)
	{
		return ok;
	}
	bool returnOk(true);
	QList<QMdiSubWindow *> list = subWindowList();
	int number = list.length();
	int i(0);
	while(i++ != number)
	{
		ok = qobject_cast<singleText *>(this -> QMdiArea::activeSubWindow () -> QMdiSubWindow::widget()) -> singleText::fileSaveSingle(); 
		if (false == ok)
		{
			returnOk = false;
		}
		activateNextSubWindow ();
	}
	return returnOk;
}


第三种操作,“保存所有的文件”:

   该操作执行的关键点在于遍历子窗口,使用QMdiArea中的函数获得全部子窗口地址,然后将地址存放于QList<QMdiSubWindow*>当中,再在遍历的过程中,调用本在singleText中写好的函数,此处需要注意的是,在调用的过程,需要进行一定的强制转换,因为提取存放的子窗口的类型是QWidget型的,需要使用qobject_cast<>()将其转为singleText*型再执行操作,否则编译软件将会给出错误提示。



->模块05:关闭文件功能

/**
 * 操作:void singleText::closeEvent(QCloseEvent *event)[protected]
 * 功能:关闭当前文件及文件窗口。
 * 前置条件:int singleText::beforeAttentionSaveMessage()函数已定义。
 **/
 void singleText::closeEvent(QCloseEvent * event)
{
	// check if need to save
	if (this -> document() -> isModified() == true){
		int saveOrNot = this -> beforeAttentionSaveMessage();	// 保存提示
		if (QMessageBox::Save == saveOrNot){
			bool saveSuccess = this -> fileSaveSingle();
			if (false == saveSuccess){
				event -> ignore();
				return;				// 保存失败则返回
			}else{
				controlAction -> emitWindowCloseNumberChange();
				event -> accept();
				return;
			}
		}
		else if(QMessageBox::Cancel == saveOrNot){
			event -> ignore();
			return;	// 取消现在操作
		}else	// unsave	{
			controlAction -> emitWindowCloseNumberChange();
			 event -> accept();		// 不保存时,接受操作
			 return;
		}
	}else{
      controlAction -> emitWindowCloseNumberChange();
      event -> accept();
	  return;
	}
	return;
}

在执行关闭文件的功能时,用到了事件处理机制,这里继承定义QTextEdit的关闭事件函数,需要注意的地方有两点:
1. 窗口数目的减少,每当在能功能执行关闭事件时,都要发射一次关闭了一个窗口所以使窗口数目变化的信号。
2. 保存状态的确定,在关闭操作执行前,应先判断正在编辑的文档是否已经保存,防止用户的数据丢失。



->模块06:打印文件功能

    这一功能的实现并不难,调用QPrintDialog即可实现相关的功能,其中有一个比较特别的地方是当文本有被光标选中时,这时候打印的内容将会被设定是光标选定内容,另外,实现打印功能可以直接使用QTextEdit中的print()函数。具体实现方法如下所示:

/**
 * 操作:void singleText::filePrint()[public]
 * 功能:显示打印对话框,并实现当前页面打印的功能。
 **/
void singleText::filePrint()
{
	QPrinter printer(QPrinter::HighResolution);
	QPrintDialog * dlg = new QPrintDialog(&printer, this);
	if(this -> textCursor().hasSelection())	// 光标有选定文本时
	{
		dlg -> addEnabledOption(QAbstractPrintDialog::PrintSelection);
	}
	dlg -> setWindowTitle(fileName + tr(" 文件打印"));
	if (dlg -> exec() == QDialog::Accepted)
	{
		this -> print(&printer);
	}
	return;
}


 

->模块07:打印文件预览功能

    打印文件预览功能,这与QPrintPreviewDialog的使用方法有关:

1. 首先新建一个QPrinter对象,将然将这个QPrinter对象作为参数用以构造QPrintPreviewDialog对象。

2. 将信号paintRequested()连接到一个槽,当对话框需要产生一系列预览页时,paintRequested()信号将被发出。所连接的槽要求是可以实现打印功能的槽,这个槽有参数QPrinter,因此,在下面代码中,就再新建了一个函数void singleText::filePrint(QPrinter * printer)。以实现打印功能。

3. 最后调用exec()显示打印预览框。

/**
 * 操作:void singleText::filePrintPreview()[public]
 * 功能:显示打印对话框,并实现当前页面打印预览的功能。
 * 前置条件:已定义响应打印预览打印功能的slot函数 void singleText::filePrint(QPrinter *);
 **/
void singleText::filePrintPreview()
{
	QPrinter printer(QPrinter::HighResolution);
	QPrintPreviewDialog preview(&printer, this);
	preview.setWindowTitle(fileName + " 文件打印预览"); 
	connect(&preview, SIGNAL(paintRequested(QPrinter *)), this, SLOT(filePrint(QPrinter *)));
	preview.exec();
	return;
}
/**
 * 操作:void singleText::filePrint(QPrinter * printer)[public slots]
 * 功能:辅助void singleText::filePrintPreview()函数完成打印预览的功能,该函数有打印的功能。
 **/
void singleText::filePrint(QPrinter * printer)
{
	this -> print(printer);
	return;
}

 

 

-> 模块08:选择文件窗口功能

        这一功能的实现,主要在mainWindow类中进行,这是因为选择文件窗口功能的菜单在mainWindow类中,另外,也在mainWindow中定义了mdiWindow的对象mdi,实现这个功能的时候,在mainWindow中实现起来更方便,更符合逻辑。

在实现该功能前,需要先去了解一个类,QSignalMapper,它可以将来自identifiable 发送信息者的一系列的 “信号” 捆绑。 换一种理解就是,这个类主要用于发送不同的信号到相同的槽中,可以使代码更简洁,而且也不用添加累赘的相同的多个连接语句。这一个类收集了一系列没有限定的信号,并且用整型或字符串型或者窗口型的参数与原类型相一致的重新发送这些信号。使用setMapping()函数可以将信号还有信号附带的参数添加进这个对象中,另外,可以通过槽map()去触发这个信号,通过发送mapped()信号激活想要实现的SLOT函数功能。

下面是与这个功能有关的函数代码:

// Window 的 windowMenuShowFile 设置
connect(windowMenuShowFile, SIGNAL(aboutToShow()), this, SLOT(windowMenushowFileUpdate()));
windowSignalMapper = new QSignalMapper(this);
connect(windowSignalMapper, SIGNAL(mapped(QWidget *)), mdi, SLOT(setActiveSubWindow(QWidget *)));

这三个语句,主要是为windowMenuShow的调用做一个铺垫,第一个connect的作用是当用户选择“窗口”-> “选择文件窗口”时,触发一个自定义的槽函数windowMenushowFileUpdate(),至于这个信号aboutToShow()是QMenu中的一个信号,这是当用户执行选中窗口,需要显示窗口时所发出的一个信号,第二第三句是关于QSignalMapper的使用的,第三句将mapped(QWidget *)与mdi对象的setActiveSubWindow(QWidget *)相连接,这样所产生的效果是当用户选中在展开的菜单中的某一个QAction时,它就会被触发,然后,将与这个QAction相对应的子窗口激活,这样一来,就可以完成选择显示窗口的功能了。而下面的函数,则是实现了将菜单显示和将QAction与相对应的子窗口连接的功能:

/**
 * 操作:void mainWindow::windowMenushowFileUpdate()[public slots]
 * 功能:在菜单栏的窗口菜单中的“已打开文件”的菜单更新显示。
 **/
void mainWindow::windowMenushowFileUpdate()
{
// 清空选单
	windowMenuShowFile -> clear();
// 获取QList文件目录选单列表
	QList<QMdiSubWindow * > windows = mdi -> subWindowList();
	if ( 0 == windows.size())
	{
		QAction * nothing = windowMenuShowFile -> addAction(tr("无")); // 之后再将该Action禁用
		nothing -> setEnabled(false);
		return;
	}
// 获取文件名,为文件名添加序号,增加QAction连接到相对应的窗口中
	int i(0);
	foreach(QMdiSubWindow * windowChild, windows)
	{
		QString windowName = tr( "%1 %2").arg(++i).arg( qobject_cast<singleText *>(windowChild -> QMdiSubWindow::widget()) -> singleText::getFileName());
// 连接信号/槽
		QAction * windowAction = windowMenuShowFile -> addAction(windowName);
		windowAction -> setCheckable(true);
		windowAction -> setChecked(windowChild == mdi -> QMdiArea::activeSubWindow());
		connect(windowAction, SIGNAL(triggered()), windowSignalMapper, SLOT(map()));
		windowSignalMapper -> setMapping(windowAction, windowChild);
	}
	return;
}

    制作这一系列的窗口菜单的方法,还是遍历,调用QMdiArea函数中的subWindowList()功能得出所有的子窗口组成的QList链表,然后,将链表遍历,同时将这些子窗口的名字提取出来,作为一个新建的QAction的名字,然后,再将这些新建的QAction加入到菜单中,实现遍历的同时,还将这些QAction的触发信号与windowSignalMapper的map()相连接,这样可以让类windowSignalMapper知道触发时刻,当QAction被triggered()时,windowSignalMapper就会发出mapped(QWidget*)的信号。在循环中的最后一句,还将这个QAction与相关的子窗口相对应上,这样就可以让windowSignalMapper发送mapped(QWidget*)信号时带上相关的参数了。

也可以根据下面的关系图以理解这几个信号/槽之间的关系:


 

->模块09:按钮状态对应功能

   为了能接收各个类中的改变状态信号,统一管理这些信号,所以,最后阶段,再新建了两个类,然后,在mainWindow类中新增了几个slot函数。

新增了一个actionSignal类,这个类主要用于发送信号,下面看看这个类是如何声明的:

// actionsignal.h
/******************************* 
 * Header File -- actionsignal.h
 * 文件功能:主要用于类actionSignal的声明。
 * 文件关联:actionSignal主要需要用到的文件有 actionType.h
 * 类继承:actionSignal类,公用继承了QObject类。
 * 类作用:actionSignal类主要用于收集mdiWindow类和singleText类的信号:
 *         在类外,通过调用actionSignal的public函数,然后从本public函数中发射出本类的信号。
 *****************************************************************************************/
#ifndef ACTIONSIGNAL_H
#define ACTIONSIGNAL_H
#include <qobject.h>
#include "actiontype.h"

QT_FORWARD_DECLARE_CLASS(QString);
QT_FORWARD_DECLARE_CLASS(QFont);

class actionSignal: public QObject
{
	Q_OBJECT
public:
	actionSignal(QObject * parent = 0);
	~actionSignal();
signals:
	void setActionCheck(actionType, bool);
	void setActionEnable(actionType, bool);
	void windowCloseNumberChange();
	void textPointSizeAndFontFamily(const double &, const QFont &);
public:
	void emitSetActionCheck(actionType, bool);
	void emitSetActionEnable(actionType, bool);
	void emitWindowCloseNumberChange();
	void emitTextPointSizeAndFontFamily(const double &, const QFont &);
};
#endif	// ACTIONSIGNAL_H

        这个类的总体结构很简单,一个最基本的构造函数与析构函数,四个SIGNAL函数,再加上四个对应的public函数,其中,为什么是四个相应的public函数呢?因为,这四个pulic函数都只有一个简单的作用,发送对应的signal信号,这是因为,在其它类中,不能发送这个signal信号,所以,我们可以依靠这些public函数的调用,从这个类里面发信号。如:

void actionSignal::emitSetActionCheck(actionType type, bool status)
{
	emit setActionCheck(type, status);
	return;
}

在构建这个类时,我们可以看见这个类中好像还需要一个用以区分类型的东西,因此,我再次新建了一个类actionType,它只是一个简单的enum类,用以定义各个类型:

// actionType.h
/************* 
 * Header File -- actiontype.h
 * 文件功能:主要用于杖举类actionType的声明。
 * 类作用:actionType类所定义的成员可看作是全局变量,使用主要用于定义各种动作名为常量,
 *         方便整个程序中switch语句等的使用,同时,该类将会被程序的全部类用到。
 *************************************************************************************/
#ifndef ACTIONTYPE_H
#define ACTIONTYPE_H
enum actionType
{
	// file action
	fileNewType,
	fileOpenType,
	fileSaveSingleType,
	...
	// edit action
	editCutType,
	editCopyType,
	...
	
	// text action
	textBoldType,
	...
	// window action
    	windowCloseSingleType,
	...
};

而这一个类,是放在main函数及各个类中的,可以以全局函数的形式存在,也就是说,不管在哪个位置使用,直接以这样的switch(type){case fileNewType ...}形式使用就可以了。
在定义好信号的管理类后,我们要做的就是定义信号在何时发射,何时响应了。先回顾我们需要完成的功能,从而再去解决这些问题,现在,我们所需要完成的功能是:
1. 当无创建任何子窗口时,我们要做的是只把helpPeopleA, helpSoftwareA, fileOpenA, fileNewA, fileExit这五个在mainWindow的私有成员的setEnabled设置为true,而在mainWindow中的其它QAction类型的私有成员的setEnabled设置为false.
2. 当子窗口存在时,再判断子窗口的数量,然后,只要存在窗口,将与file有关的QAction的全部enable状态设为true,当窗口只有一个时,将与window有关的QAction的tile, cascade, previous, next这些的enable状态设为false,其它设为true,再将与edit,text有关的功能全部的enable状态设为true.
3.功能状态检测,这里的edit与text的QAction有些特别,我们需要实现在文档活动过程中,状态与其相对应的功能状态是否被应用上来进行对应,如,当粗体功能正在应用时,那么textBold的checkable状态就应被设为true,否则,应设为false.

知道问题,再去想问题的解决方法,这就容易多了,简单地来说,我们就是要确定两种信号,第一种,窗口变动的信号,第二种,文本格式被改变的信号。
窗口变动的信号,我们需要在mdiWindow中发射,文本格式被改变的信号,我们需要在singleText中发射。这样,就会产生一个新的问题,如何能在多个不同的类中,向同一对象发射相同的信号?
此时,我们可以利用构造函数,请看下面的几个定义:

class mainWindow : public QMainWindow
{...
 private:
mdiWindow * mdi;
...
};

第二个类:

class mdiWindow: public QMdiArea
{
 public:
	mdiWindow(actionSignal * , QWidget * parent = 0);
private:
	actionSignal * controlAction;
};

第三个类:

class singleText: public QTextEdit
{
 public:
	singleText(actionSignal * , QWidget * parent = 0);
private:
	actionSignal * controlAction;
};

就这样,通过每个类中都定义一个actionSignal类型,然后每个类中都初始化这个类型,以达到使用相同的信号的对象的目的。
在解决了一系列的信号/槽相关的问题后,我们就可以开始去考虑在哪里需要具体如何发射信号,在哪里需要具体如何去定义接收信号的槽了。
信号的发射比较简单,这里就只列举一个例子了:

/**
 * 操作:void singleText::actionStatusChangeSlot()[public]
 * 功能:当文档文字的状态改变,文档光标位置改变,文档可编辑状态改变时,通知actionSignal类发出信号让mainWindow采取相关操作。
 * 前置条件:已初始化actionSignal类的变量controlAction
 * 后置条件:完成各种状态的信号/槽的连接。
 **/
void singleText::actionStatusChangeSlot()
{
// edit Action 的相关信息连接
	connect(this, SIGNAL(copyAvailable (bool)), this, SLOT(copyAndCutSlot(bool)));
	connect(this, SIGNAL(undoAvailable (bool)), this, SLOT(undoSlot(bool)));
	connect(this, SIGNAL(redoAvailable (bool)), this, SLOT(redoSlot(bool)));
	
	if (const QMimeData *md = QApplication::clipboard() -> mimeData())
	{
         controlAction -> emitSetActionEnable(editPasteType, true);
	}
	else
	{
		controlAction -> emitSetActionEnable(editPasteType, false);
	}
	connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(alignChanged()));
	connect(this, SIGNAL(currentCharFormatChanged ( const QTextCharFormat & )), this, SLOT(CharFormatChanged( const QTextCharFormat & )));
	return;
}
/**
 * 操作:void singleText::copyAndCutSlot(bool status)[public slots]
 * 功能:发送将cut和copy的action的enable状态改变的信号。
 **/
void singleText::copyAndCutSlot(bool status)
{
	controlAction -> emitSetActionEnable(editCutType, status);
	controlAction -> emitSetActionEnable(editCopyType, status);
	return;
}
这里的信号/槽的连接非常清晰,先在本地判断这个功能的状态,然后,再从根据这个功能的状态,给自身调用一个slot,再从这个slot中,发射出能给mainWindow的信号emitSetActionEnable(...):

接下来是在mainWindow当中slot的定义了,这里用到了几个函数:

/**
 * 操作:void mainWindow::controlActionFunction()[private]
 * 功能:初始化类中的QAction的enable和checkable状态,并将创立能控制QAction这两种状态的信号/槽机制。
 * 前置条件:已初始化私有成员 actionSignal * controlAction;用于接收信号。
 *           已初始化用于接收信号的三个槽:
 *           1. void setActionCheck(actionType, bool);
 * 		 2. void setActionEnable(actionType, bool);
 *		 3. void textPointSizeAndFontFamilyChange(const double &, const QFont &); 
 **/
void mainWindow::controlActionFunction()
{
。。。// connect
	connect(controlAction, SIGNAL(setActionCheck(actionType, bool)), this, SLOT(setActionCheck(actionType, bool)));
	connect(controlAction, SIGNAL(setActionEnable(actionType, bool)), this, SLOT(setActionEnable(actionType, bool)));
	connect(controlAction, SIGNAL(textPointSizeAndFontFamily(const double &, const QFont &)), this, SLOT(textPointSizeAndFontFamilyChange(const double &, const QFont &)));
	return;
}
可以看到,这里统一设定了连接机制,然后,将信号发送到几个不同功能中的函数去处理,下面我们跳转到这些函数中的其中一个:

/**
 * 操作:void mainWindow::setActionCheck(actionType, bool)[public slots]
 * 功能:根据调入的参数type, 选择出需要更新的QAction然后设定check状态为调入参数bool。
 **/
void mainWindow::setActionCheck(actionType type, bool status)
{
	switch(type)
	{	// all are text action
	case textBoldType :
		{
			textBoldA -> setChecked(status);  
			break;
		}
	case textUnderlineType: 
		{
			textUnderlineA-> setChecked(status);  
			break;
		}
	... // 此处省略
	default:
		{
			break;
		}
	}	// end-switch
	return;
}
这些槽函数的结构反而更加简单,只需要用一个switch控制,然后,找到相对应的类型后,则开始对这个类型进行状态设置。这样一来,就完成了所有的这些状态的设置了。
我们可以以下图简单地回顾一个这个处理的方法:


 

5) 总结与感悟  

          回想从学习Qt的第一个程序“Hello Qt!”开始,到现在已经是一个半月了,这一个半月里面,除了十来天因为做数据结构大作业,春节,还有开学时候的开学外,几乎都在学习Qt的知识了,可能算起来,也是差不多一个月的时间去学习了Qt。

        而在2012年02月21日的那天起,也终于开始化第一个Qt小程序的练手,之前Qt的学习,自己动手写出程序的写得少,一般都只是仿写已存在的例子,然后做下学习笔记。02月21日,也是我学校开学的第一天,在开学白天整天上课的这些天,每天只能利用晚上的时间去完成自己想做的这个小程序了——多窗口文档编辑器,也终于,在2012年03月07日的今天,将关于这个小程序的全部内容完成了,也算是告一段落了,真想不到这次写这个小程序需要这么长的时间,十六天的时间,纯自己手动编写图形界面代码,二千九百五十二行的代码,现在对着这接近三千行的代码,很是感慨,编写这个程序的这些天,真是遇上了与以住编写程序时的很多不一样的体验,这也可能是因为是我第一次编写图形界面软件的缘故吧。

      这次编写这个多窗口文档编辑器的其中一个原因是为了练一下手,想更加熟悉一下如何使用Qt来写程序,我想,这次完工后,还真的让我对Qt的一些内容熟悉了不少了。

      这次,我写这个程序时,如果按程序开发过程,经历了这么几个阶段:

1. 程序相关知识内容准备 ->

2. 定下整个程序组成框架 ->

3. 边学习边编写程序代码 ->

4. 程序调试->

5. 程序功能强化->

6. 程序再调试->

7. 编写说明书。

      在第1阶段——程序相关知识内容准备,我主要找出与可以与我编写的这个程序相似的实例作为参考,于是,我从Qt Demo里面找到了Text Edit 和 MDI Example这两个例子,然后,稍对这两个例子进行了分析,知道了在编写mdi程序的时候,一些子窗口的编写,可以脱离总的MainWindow来写出它来,只需将其功能一个个实现就可以了,也知道了,这次的程序编写,我主要大概是用到三个需要继承类库中的类的自定义类。

        在第2阶段――定下整个程序组成框架,在这一阶段,我开始认真分析这个程序可以是怎样组成,这时候,我并没有去参考任何一个资料,而是凭着自己这学习十来天的Qt的学习经验开始对其进行深入分析,这时,我初步将它划分为三个类,第一个类,负责单个文档的处理,第二个类,负责管理由这些单个文档组成的多个窗口,第三个类,负责管理这整个程序的窗口框架。然后,在定下了这些以后,我再开始分析,我的第一个类里面,需要使用到功能,如:文件新建,打开,保存,退出等的功能的分析,找出同种类型中的操作之间不同函数的联系点,确保这个程序写起来不需要打上太多的补丁。

        在第3阶段――边学习边编写程序代码,在完成了框架的制定后,就开始着手将一个个的功能具体的实现方法以代码的形式将它敲下来了,这时候,并没有写一个功能就调试一个功能,而是按照这时自己所定下的框架,然后全部写下,在这过程,由于mdi的知识和QTextEdit的知识我是第一次接触,编写代码的过程需要不断地查阅Qt Assistant和两个实例从而不断找出自己需要解决的问题的方法。

        在第4阶段——程序调试,一个程序初步编写完,如果基础掌握得好,架构写得好,当然程序调试就不用花多少的时间了,只是,在这次程序调试,我花上了有十个小时左右吧,分散开来,大概是两天到三天,程序刚刚编写完,还记得没有一个功能可以实现,很是纠结,对着那么一大堆只有按钮却按不出任何功能来的界面,又是喜,又是悲,哈哈,不过,总算是能看见自己想看到的界面了,毕竟是自己第一次写出这么一个功能稍完整一点的图形界面程序,对着这些按不动的功能,我就只能再次回到自己的代码中去了,然后看看这些代码究竟是连接点出现问题,还是功能的具体实现方法出现问题。

        在第5阶段——程序的功能强化,在完成了最基本的功能后,我开始着手加上几个稍特殊一点的功能上去了,第一个是窗口的陈列选择,第二个是各个功能按钮与功能能否实现的同步(因为有些功能是在一个窗口或多个窗口同时存在的时候才能实现,有些功能又是需要满足一些前提条件才能实现),第三个是文本功能按钮与文本之间的状态同步更新。这个阶段,花费的时间最长,因为要解决的问题对于当时我来说,实在是一个难题,自己苦想了好几天,终于由刚刚开始的没有半点头绪到后期的思路逐渐清晰。

        在第6阶段——程序再调试,在上一阶段将功能写出来以后,又是要开始调试阶段了,话说,在上一阶段的编写过程,又添加了两个类了,然后,再在原代码中插入了不少的代码,值得开心的是,整个程序的脉胳依然清晰,并没有因为后期的代码插入而变得混乱,我想这也跟刚刚开始所制定的框架有关吧。所在,在这次的调试中,也方便多了,能很多地找出错误所在位置,然后再重新有针对地思考改正,这次调试,反而是不怎么花时间了,这或许是因为在上一阶段所花的时间多了。

        在第7阶段——编写说明书,这一阶段,按照惯例,都是用来巩固这些天所写下的程序所学到的知识,这个阶段,我想不到的是,原来以为需要一周的时间去写的,现在只花了三天,大概也是七个小时吧,在编写说明书的时候,我发觉现在好像都统一了自己所写东西的风格了,先差不多一样的封面设计,然后是目录,之后再根据目录逐步写下。

        七个阶段的回顾,让我快速回想起了每一个过程,在这些过程中,我学习到最多的是编写程序的领悟,其实,很多知识,还真的不是要我们会部掌握起来,而是要即学即用,一旦需要用上,就得马上去学习,然后直接运用到实际当中去,可能,这常常是一个新手的做法,对于一些经验老到的前辈,可以根本就用不上这点了,不过,我想,这就是常常听别人所说的自学能力吧。不断查阅Qt Assistant,里面全是英文,也逐渐,有点习惯了这些全英文的文档了,哈哈,也可能是因为没有其它的资料可以参考,我就不得不去看这全是英文的帮助文档了,这过程,还真能让我英文上一个层次,有时候最麻烦的一点就是当自己翻译错误的时候了,翻译错了,然后,功能也就容易实现不出来了,后来就只能自己在不断调试过程中领悟这功能的实现方法了。学习Qt,还有一个问题,当初,听说在Qt方面的资料不多,在编写程序时,也终于体会到为什么写书人,还有一些论坛中特意要提到这点了,因为,这使在程序开发阶段,不得不减少对互联网的依赖了,在网上难以找到自己所需要解决问题的方案,一切还是得靠自己去完成,这可能花的时间会比较长,不过却能让自己的对知识的印象更加深刻。

         用一句最为老套的话来总结最后的总结吧,这次多窗口文档编辑器的编写,让我受益匪浅。


6) 附录

 

-> 模块01:程序制作过程记录

 

2012.02.21

一。开始编写主函数(main),设定主窗口的大小,图标,标题。

二。然后开始定下主窗口类的框架,先声明并定义好各工具栏,菜单栏的动作。

三。此时定义动作只定义简单的要素:图标,标题,快捷键,先不与功能相连。

猜想:关于多文档,可能只是动作的连接不同,其它的动作基本相同

四。初定程序编写过程

1.菜单栏,工具栏,动作的简单初始化

2.制作单个文档窗口类

3.制作mdi并开始连接单个单个动作

4.添加防止内存溢出等特别功能,考虑细化因素。

 

 

2012.02.23

一。完成菜单栏,工具栏的编写,菜单栏还差快捷键功能未完成。

二。开始创建单个文档窗口类,创此类时,先确定需要解决的问题。

1. 创此类时,需要带的响应动作的函数有哪些,有哪些是需要在主窗口类中直接实现的。

2. 每个功能的具体实现方法。

 

 

2012.02.24

准备开始编写分析单个功能时,发觉原来一个功能可能与其它功能相关,于是,重新分析一下关于每类功能的每个功能是否有哪些关联,如:文件的“创建”操作,此时,先分析出“文件”操作有哪些,每个功能先知道设计思路,关于“文件”类操作有哪些会有关联,然后开始确定要素,私有成员,公有成员之类的重叠部分,然后再开始逐个“文件”的功能编写。

 

 

2012.02.25

  分析出关于文件的“新建”“打开”“保存”“另存为”“关闭”各个功能之间的联系,然后着手写代码。

 再开始完成“编辑”中的功能。

 

 

2012.02.26

 完成在单文档中的各个功能后,开始设计QMdiArea类中需要哪些功能实现,新建一个类,继承QMdiArea。

  各函数基本编写,动作连接完成,开始调试。打算:调试完毕后,强化一些功能,如,在未有窗口时,不得激活某些功作。调试时,逐个功能记录。

 

2012.02.27

  调试分析测试,关于信号/槽的应用。

 

2012.02.28

  继续各功能调试,解决文件保存关闭新建打开的状态确认问题。

  接下来任务思路:

  1.TextEdit中的文本改变功能(颜色,斜体,下划线,粗体等的调试)。

  2. 关于各对话框与QTextEdit在Qt论坛的提问。

  3. 如完成全部调试功能,开始扩展功能,

301.先扩展窗口“列表”的功能

302.扩展Edit工具栏的功能

303.完成最后Help的简单编写。

304.工具栏调整。

 

2012.02.29

继续功能调试,扩展功能,完成工具栏的调整,完成TextEdit的文本功能调试。开始准备添加“窗口列表”功能。

 

 

2012.03.01

完成“窗口列表”功能的添加,学会了QSignalMapper的使用。

接下来,开始对特殊状态进行处理:

1.当文本未选中时,禁用一些编辑功能,当剪粘版为空时,未有文档时,禁用粘贴功能......

2.将一些文本的文字状态与工具栏上的相对应上。

 

2012.03.02

继续调试一下那三个未能实现的功能。QAction * filePrintPreview;         QAction * filePrintA;  QAction * filePagesetupA;整理文件解决思路。

 

2012.03.03

终于知道了信号/槽的一些继承规则,然后,也从之前的一个思路中跳了出来,用新建了另外一个类的方面,特意用于,完成各Action的动作状态设置。

 

2012.03.02

已解决filePrintPreview 和 filePrint的问题,只差一个filePagesetup的功能未解决。

 

2012.03.03 ~2012.03.07

完成代码的格式优化,每个函数前的小说明,开始撰写说明书。说明书的开始编写。




  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值