2.4工作任务二:系统设计
2.4.1任务描述
“连连看游戏”软件概要设计的基本任务在软件需求分析阶段,已经搞清楚了软件“做什么”的问题,并把这些需求通过规格说明书描述了出来,这也是目标系统的逻辑模型。进入了设计阶段,要把软件“做什么”的逻辑模型变换为“怎么做”的物理模型,即着手实现软件的需求,并将设计的结果反映在“设计规格说明书”文档中,所以软件设计是一个把软件需求转换为软件表示的过程,最初这种表示只是描述了软件的总的体系结构,称为软件概要设计或结构设计,是软件设计人员做详细设计的依据。
2.4.2任务目的
本任务的目的旨在推动软件的规范化,使软件设计人员遵循统一的书写规范,节省制作文档的时间,降低系统应用开发的风险,做到软件资料的规范性与全面性,以利于系统的实现、测试、维护、版本升级等。在需求分析上做系统设计,最终形成“连连看游戏”的系统设计规格说明书,提高学生团队合作精神和分析问题、解决问题的能力。
2.4.3知识准备
本文章原创于www.yafeilinux.com转载请注明出处。
前面已经将界面做好了,这里我们为其添加代码,实现文本编辑的功能。
首先实现新建文件,文件保存,和文件另存为的功能。
(我们先将上次的工程文件夹进行备份,然后再对其进行修改。在写较大的程序时,经常对源文件进行备份,是个很好的习惯。)
在开始正式写程序之前,我们先要考虑一下整个流程。因为我们要写记事本一样的软件,所以最好先打开windows中的记事本,进行一些简单的操作,然后考虑怎样去实现这些功能。再者,再强大的软件,它的功能也是一个一个加上去的,不要设想一下子写出所有的功能。我们这里先实现新建文件,保存文件,和文件另存为三个功能,是因为它们联系很紧,而且这三个功能总的代码量也不是很大。
因为三个功能之间的关系并不复杂,所以我们这里便不再画流程图,而只是简单描述一下。
新建文件,那么如果有正在编辑的文件,是否需要保存呢?
如果需要进行保存,那这个文件以前保存过吗?如果没有保存过,就应该先将其另存为。
下面开始按这些关系写程序。
1.打开Qt Creator,在File菜单中选择Open,然后在工程文件夹中打开MainWindow.pro工程文件。
先在main.cpp文件中加入以下语句,让程序中可以使用中文。
在其中加入#include <QtextCodec>头文件包含,再在主函数中加入下面一行:
QtextCodec::setCodecForTr(QtextCodec::codecForLocale());
这样在程序中使用中文,便能在运行时显示出来了。更改后文件如下图。
2.在mainwindow.h文件中的private下加入以下语句。
bool isSaved; //为true时标志文件已经保存,为false时标志文件尚未保存
QString curFile; //保存当前文件的文件名
void do_file_New(); //新建文件
void do_file_SaveOrNot(); //修改过的文件是否保存
void do_file_Save(); //保存文件
void do_file_SaveAs(); //文件另存为
bool saveFile(const QString& fileName); //存储文件
这些是变量和函数的声明。其中isSaved变量起到标志的作用,用它来标志文件是否被保存过。然后我们再在相应的源文件里进行这些函数的定义。
3.在mainwindow.cpp中先加入头文件#include <QtGui>,然后在构造函数里添加以下几行代码。
isSaved = false; //初始化文件为未保存过状态
curFile = tr(“未命名.txt”); //初始化文件名为“未命名.txt”
setWindowTitle(curFile); //初始化主窗口的标题
这是对主窗口进行初始化。效果如下。
4.然后添加“新建”操作的函数定义。
void MainWindow::do_file_New() //实现新建文件的功能
{
do_file_SaveOrNot();
isSaved = false;
curFile = tr(“未命名.txt”);
setWindowTitle(curFile);
ui->textEdit->clear(); //清空文本编辑器
ui->textEdit->setVisible(true); //文本编辑器可见
}
新建文件,先要判断正在编辑的文件是否需要保存。然后将新建的文件标志为未保存过状态。
5.再添加do_file_SaveOrNot函数的定义。
void MainWindow::do_file_SaveOrNot() //弹出是否保存文件对话框
{
if(ui->textEdit->document()->isModified()) //如果文件被更改过,弹出保存对话框
{
QMessageBox box;
box.setWindowTitle(tr(“警告”));
box.setIcon(QMessageBox::Warning);
box.setText(curFile + tr(” 尚未保存,是否保存?”));
box.setStandardButtons(QMessageBox::Yes |QMessageBox::No);
if(box.exec() == QMessageBox::Yes) //如果选择保存文件,则执行保存操作
do_file_Save();
}
}
这个函数实现弹出一个对话框,询问是否保存正在编辑的文件。
6.再添加“保存”操作的函数定义。
void MainWindow::do_file_Save() //保存文件
{
if(isSaved){ //如果文件已经被保存过,直接保存文件
saveFile(curFile);
}
else{
do_file_SaveAs(); //如果文件是第一次保存,那么调用另存为
}
}
对文件进行保存时,先判断其是否已经被保存过,如果没有被保存过,就要先对其进行另存为操作。
7.下面是“另存为”操作的函数定义。
void MainWindow::do_file_SaveAs() //文件另存为
{
QString fileName = QFileDialog::getSaveFileName(this,tr(“另存为”),curFile);
//获得文件名
if(!fileName.isEmpty()) //如果文件名不为空,则保存文件内容
{
saveFile(fileName);
}
}
这里弹出一个文件对话框,显示文件另存为的路径。
8.下面是实际文件存储操作的函数定义。
bool MainWindow::saveFile(const QString& fileName)
//保存文件内容,因为可能保存失败,所以具有返回值,来表明是否保存成功
{
QFile file(fileName);
if(!file.open(QFile::WriteOnly | QFile::Text))
//以只写方式打开文件,如果打开失败则弹出提示框并返回
{
QMessageBox::warning(this,tr(“保存文件”),
tr(“无法保存文件 %1:\n %2″).arg(fileName)
.arg(file.errorString()));
return false;
}
//%1,%2表示后面的两个arg参数的值
QtextStream out(&file); //新建流对象,指向选定的文件
out <<ui->textEdit->toPlainText(); //将文本编辑器里的内容以纯文本的形式输出到流对象中
isSaved = true;
curFile = QFileInfo(fileName).canonicalFilePath(); //获得文件的标准路径
setWindowTitle(curFile); //将窗口名称改为现在窗口的路径
return true;
}
这个函数实现将文本文件进行存储。下面我们对其中的一些代码进行讲解。
QFile file(fileName);一句,定义了一个QFile类的对象file,其中filename表明这个文件就是我们保存的的文件。然后我们就可以用file代替这个文件,来进行一些操作。Qt中文件的操作和C,C++很相似。对于QFile类对象怎么使用,我们可以查看帮助。
点击Qt Creator最左侧的Help,在其中输入QFile,在搜索到的列表中选择QFile即可。这时在右侧会显示出QFile类中所有相关信息以及他们的用法和说明。
//
我们往下拉,会发现下面有关于怎么读取文件的示例代码。
//
//
再往下便能看到用QtextStream类对象,进行字符串输入的例子。下面也提到了QFileInfo和QDir等相关的类,我们可以点击它们去看一下具体的使用说明。
上面只是做了一个简单的说明。以后我们对自己不明白的类都可以去帮助里进行查找,这也许是我们以后要做的最多的一件事了。对于其中的英文解释,我们最好想办法弄明白它的大意,其实网上也有一些中文的翻译,但最好还是从一开始就尝试着看英文原版的帮助,这样以后才不会对中文翻译产生依赖。
我们这次只是很简单的说明了一下怎样使用帮助文件,这不表明它不重要,而是因为这里不可能将每个类的帮助都解释一遍,没有那么多时间,也没有那么大的篇幅。而更重要的是因为,我们这个教程只是引你入门,所以很多东西需要自己去尝试。
在以后的教程里,如果不是特殊情况,就不会再对其中的类进行详细解释,文章中的重点是对整个程序的描述,其中不明白的类,自己查看帮助。
9.双击mainwindow.ui文件,在图形界面窗口下面的Action Editor动作编辑器里,我们右击“新建”菜单一条,选择Go to slot,然后选择triggered(),进入其触发事件槽函数。
同理,进入其他两个菜单的槽函数,将相应的操作的函数写入槽函数中。如下。
void MainWindow::on_action_New_triggered() //信号和槽的关联
{
do_file_New();
}
void MainWindow::on_action_Save_triggered()
{
do_file_Save();
}
void MainWindow::on_action_SaveAs_triggered()
{
do_file_SaveAs();
}
最终的mainwindow.cpp文件如下。
最终的mainwindow.h文件如下。
这时点击运行,就能够实现新建文件,保存文件,文件另存为的功能了。
然后实现打开,关闭,退出,撤销,复制,剪切,粘贴的功能。
先备份上次的工程文件,然后再将其打开。
1.先在mainwindow.h文件中加入函数的声明。
void do_file_Open(); //打开文件
bool do_file_Load(const QString& fileName); //读取文件
2.再在mainwindow.cpp文件中写函数的功能实现。
void MainWindow::do_file_Open()//打开文件
{
do_file_SaveOrNot();//是否需要保存现有文件
QString fileName =QFileDialog::getOpenFileName(this);
//获得要打开的文件的名字
if(!fileName.isEmpty())//如果文件名不为空
{
do_file_Load(fileName);
}
ui->textEdit->setVisible(true);//文本编辑器可见
}
bool MainWindow::do_file_Load(const QString&fileName) //读取文件
{
QFile file(fileName);
if(!file.open(QFile::ReadOnly | QFile::Text))
{
QMessageBox::warning(this,tr(“读取文件”),tr(“无法读取文件 %1:\n%2.”).arg(fileName).arg(file.errorString()));
return false; //如果打开文件失败,弹出对话框,并返回
}
QtextStream in(&file);
ui->textEdit->setText(in.readAll()); //将文件中的所有内容都写到文本编辑器中
curFile = QFileInfo(fileName).canonicalFilePath();
setWindowTitle(curFile);
return true;
}
上面的打开文件函数与文件另存为函数相似,读取文件的函数与文件存储函数相似。
3.然后按顺序加入更菜单的关联函数,如下。
void MainWindow::on_action_Open_triggered() //打开操作
{
do_file_Open();
}
//
void MainWindow::on_action_Close_triggered() //关闭操作
{
do_file_SaveOrNot();
ui->textEdit->setVisible(false);
}
//
void MainWindow::on_action_Quit_triggered() //退出操作
{
on_action_Close_triggered(); //先执行关闭操作
qApp->quit(); //再退出系统,qApp是指向应用程序的全局指针
}
//
void MainWindow::on_action_Undo_triggered() //撤销操作
{
ui->textEdit->undo();
}
//
void MainWindow::on_action_Cut_triggered() //剪切操作
{
ui->textEdit->cut();
}
//
void MainWindow::on_action_Copy_triggered() //复制操作
{
ui->textEdit->copy();
}
//
void MainWindow::on_action_Past_triggered() //粘贴操作
{
ui->textEdit->paste();
}
因为复制,撤销,全选,粘贴,剪切等功能,是TextEdit默认就有的,所以我们只需调用一下相应函数就行。
到这里,除了查找和帮助两个菜单的功能没有加上以外,其他功能都已经实现了。
现在加上查找菜单的功能。因为这里要涉及关于Qt Creator的很多实用功能,所以单独用一篇文章来介绍。
以前都用设计器设计界面,而这次我们用代码实现一个简单的查找对话框。对于怎么实现查找功能的,我们详细地分步说明了怎么进行类中方法的查找和使用。其中也将Qt Creator智能化的代码补全功能和程序中函数的声明位置和定义位置间的快速切换进行了介绍。
1.首先还是保存以前的工程,然后再将其打开。
我们发现Qt Creator默认的字体有点小,可以按下Ctrl键的同时按两下+键,来放大字体。也可以选择Edit->Advanced->IncreaseFont Size。
2.在mainwindow.h中加入#include <QLineEdit>的头文件包含,在private中添加
QLineEdit *find_textLineEdit; //声明一个行编辑器,用于输入要查找的内容
在privateslots中添加
void show_findText();
在该函数中实现查找字符串的功能。
3.我们进入查找菜单的触发事件槽函数,更改如下。
void MainWindow::on_action_Find_triggered()
{
QDialog *findDlg = new QDialog(this);
//新建一个对话框,用于查找操作,this表明它的父窗口是MainWindow。
findDlg->setWindowTitle(tr(“查找”));
//设置对话框的标题
find_textLineEdit = new QLineEdit(findDlg);
//将行编辑器加入到新建的查找对话框中
QPushButton *find_Btn = new QPushButton(tr(“查找下一个”),findDlg);
//加入一个“查找下一个”的按钮
QVBoxLayout* layout = new QVBoxLayout(findDlg);
layout->addWidget(find_textLineEdit);
layout->addWidget(find_Btn);
//新建一个垂直布局管理器,并将行编辑器和按钮加入其中
findDlg ->show();
//显示对话框
connect(find_Btn,SIGNAL(clicked()),this,SLOT(show_findText()));
//设置“查找下一个”按钮的单击事件和其槽函数的关联
}
这里我们直接用代码生成了一个对话框,其中一个行编辑器可以输入要查找的字符,一个按钮可以进行查找操作。我们将这两个部件放到了一个垂直布局管理器中。然后显示这个对话框。并设置了那个按钮单击事件与show_findText()函数的关联。
5.下面我们开始写实现查找功能的show_findText()函数。
void MainWindow::show_findText()//“查找下一个”按钮的槽函数
{
QString findText = find_textLineEdit->text();
//获取行编辑器中的内容
}
先用一个QString类的对象获得要查找的字符。然后我们一步一步写查找操作的语句。
6.在下一行写下ui,然后直接按下键盘上的“<.”键,这时系统会根据是否是指针对象而自动生成“->”或“.”,因为ui是指针对象,所以自动生成“->”号,而且弹出了ui中的所有部件名称的列表。如下图。
7.我们用向下的方向键选中列表中的textEdit。或者我们可以先输入text,这时能缩减列表的内容。
8.如上图我们将鼠标放到textEdit上,这时便出现了textEdit的类名信息,且后面出现一个F1按键。我们按下键盘上的F1,便能出现textEdit的帮助。
9.我们在帮助中向下拉,会发现这里有一个find函数。
10.我们点击find,查看其详细说明。
11.可以看到find函数可以实现文本编辑器中字符串的查找。其中有一个FindFlags的参数,我们点击它查看其说明。
12.可以看到它是一个枚举变量(enum),有三个选项,第一项是向后查找(即查找光标以前的内容,这里的前后是相对的说法,比如第一行已经用完了,光标在第二行时,把第一行叫做向后。),第二项是区分大小写查找,第三项是查找全部。
13.我们选用第一项,然后写出下面的语句。
ui->textEdit->find(findText,QtextDocument::FindBackward);
//将行编辑器中的内容在文本编辑器中进行查找
当我们刚打出“f”时,就能自动弹出textEdit类的相关属性和方法。
可以看到,当写完函数名和第一个“(”后,系统会自动显示出该函数的函数原型,这样可以使我们减少出错。
14.这时已经能实现查找的功能了。但是我们刚才看到find的返回值类型是bool型,而且,我们也应该为查找不到字符串作出提示。
if(!ui->textEdit->find(findText,QtextDocument::FindBackward))
{
QMessageBox::warning(this,tr(“查找”),tr(“找不到 %1″)
.arg(findText);
}
因为查找失败返回值是false,所以if条件加了“!”号。在找不到时弹出警告对话框。
15.到这里,查找功能就基本上写完了。show_findText()函数的内容如下。
我们会发现随着程序功能的增强,其中的函数也会越来越多,我们都会为查找某个函数的定义位置感到头疼。而在QtCreator中有几种快速定位函数的方法,我们这里讲解三种。
第一,在函数声明的地方直接跳转到函数定义的地方。
如在do_file_Load上点击鼠标右键,在弹出的菜单中选择Follow Symbolunder Cursor或者下面的Switch between MethodDeclaration/Definition。
这时系统就会自动跳转到函数定义的位置。如下图。
第二,快速查找一个文件里的所有函数。
我们可以点击窗口最上面的下拉框,这里会显示本文件中所有函数的列表。
第三,利用查找功能。
1.我们先将鼠标定位到一个函数名上。
2.然后选择Edit->Find/Replace->FindDialog。
3.这时会出现一个查找对话框,可以看到要查找的函数名已经写在里面了。
4.当我们按下Search按钮后,会在查找结果窗口显示查找到的结果。
5.我们点击第二个文件。会发现在这个文件中有两处关键字是高亮显示。
6.我们双击第二项,就会自动跳转到函数的定义处。
文章讲到这里,我们已经很详细地说明了怎样去使用一个类里面没有用过的方法函数;也说明了Qt Creator中的一些便捷操作。可以看到,Qt Creator开发环境,有很多很人性化的设计,我们应该熟练应用它们。
在以后的文章中,我们不会再很详细地去用帮助来说明一个函数是怎么来的,该怎么用,这些应该自己试着去查找。
在程序主窗口Mainwindow中,有菜单栏,工具栏,中心部件和状态栏。前面几个已经讲过了,这次讲解状态栏的使用。
程序中有哪些不明白的类或函数,请自己查看帮助。
1.我们在mainwindow.h中做一下更改。
加入头文件包含: #include <QLabel>
加入私有变量和函数:
QLabel* first_statusLabel; //声明两个标签对象,用于显示状态信息
QLabel* second_statusLabel;
void init_statusBar(); //初始化状态栏
加入一个槽函数声明:void do_cursorChanged(); //获取光标位置信息
2.在mainwindow.cpp中加入状态栏初始化函数的定义。
void MainWindow::init_statusBar()
{
QStatusBar* bar = ui->statusBar; //获取状态栏
first_statusLabel = new QLabel; //新建标签
first_statusLabel->setMinimumSize(150,20); //设置标签最小尺寸
first_statusLabel->setFrameShape(QFrame::WinPanel); //设置标签形状
first_statusLabel->setFrameShadow(QFrame::Sunken); //设置标签阴影
second_statusLabel = new QLabel;
second_statusLabel->setMinimumSize(150,20);
second_statusLabel->setFrameShape(QFrame::WinPanel);
second_statusLabel->setFrameShadow(QFrame::Sunken);
bar->addWidget(first_statusLabel);
bar->addWidget(second_statusLabel);
first_statusLabel->setText(tr(“欢迎使用文本编辑器”)); //初始化内容
second_statusLabel->setText(tr(“yafeilinux制作!”));
}
这里将两个标签对象加入到了主窗口的状态栏里,并设置了他们的外观和初值。
3.在构造函数里调用状态栏初始化函数。
init_statusBar();
这时运行程序,效果如下。
4.在mainwindow.cpp中加入获取光标位置的函数的定义。
void MainWindow::do_cursorChanged()
{
int rowNum =ui->textEdit->document()->blockCount();
//获取光标所在行的行号
const QtextCursor cursor =ui->textEdit->textCursor();
int colNum = cursor.columnNumber();
//获取光标所在列的列号
first_statusLabel->setText(tr(“%1行 %2列”).arg(rowNum).arg(colNum));
//在状态栏显示光标位置
}
这个函数可获取文本编辑框中光标的位置,并显示在状态栏中。
5.在构造函数添加光标位置改变信号的关联。
connect(ui->textEdit,SIGNAL(cursorPositionChanged()),this,SLOT(do_cursorChanged()));
这时运行程序。效果如下。
6.在do_file_Load函数的最后添加下面语句。
second_statusLabel->setText(tr(“打开文件成功”));
7.在saveFile函数的最后添加以下语句。
second_statusLabel->setText(tr(“保存文件成功”));
8.在on_action_Find_triggered函数的后面添加如下语句。
second_statusLabel->setText(tr(“正在进行查找”));
9.在on_action_Close_triggered函数最后添加如下语句。
first_statusLabel->setText(tr(“文本编辑器已关闭”));
second_statusLabel->setText(tr(“yafeilinux制作!”));
最终的mainwindow.cpp文件内容如下。
最终的mainwindow.h文件如下。
到这里整个文本编辑器的程序就算写完了。我们这里没有写帮助菜单的功能实现,大家可以自己添加。而且程序中也有很多漏洞和不完善的地方,如果有兴趣,大家也可以自己修改。因为时间和篇幅的原因,我们这里就不再过多的讲述。
四、Qt Creator中鼠标键盘事件的处理实现自定义鼠标指针
我们前面一直在说信号,比方说用鼠标按了一下按钮,这样就会产生一个按钮的单击信号,然后我们可以在相应的槽函数里进行相应功能的设置。其实在按下鼠标后,程序要先接收到鼠标按下的事件,然后将这个事件按默认的设置传给按钮。可以看出,事件和信号并不是一回事,事件比信号更底层。而我们以前把单击按钮也叫做事件,这是不确切的,不过大家都知道是什么意思,所以当时也没有细分。
Qt中的事件可以在QEvent中查看。下面我们只是找两个例子来进行简单的演示。
1.还是先建立一个Qt4 Gui Application工程,我这里起名为event。
2.添加代码,让程序中可以使用中文。
即在main.cpp文件中加入#include <QtextCodec>的头文件包含。
再在下面的主函数里添加QtextCodec::setCodecForTr(QtextCodec::codecForLocale());
3.在mainwindow.h文件中做一下更改。
添加#include <QtGui>头文件。因为这样就包含了QtGui中所有的子文件。
在public中添加两个函数的声明
void mouseMoveEvent(QMouseEvent *);
void keyPressEvent(QKeyEvent *);
4.我们在mainwindow.ui中添加一个Label和一个PushButton,将他们拉长点,因为一会要在上面显示标语。
5.在mainwindow.cpp中的构造函数里添加两个部件的显示文本。
ui->label->setText(tr(“按下键盘上的A键试试!”));
ui->pushButton->setText(tr(“按下鼠标的一个键,然后移动鼠标试试”));
6.然后在下面进行两个函数的定义。
/*以下是鼠标移动事件*/
void MainWindow::mouseMoveEvent(QMouseEvent *m)
{//这里的函数名和参数不能更改
QCursor my(QPixmap(“E:/Qt/Qt-Creator-Example/event/time.png”));
//为鼠标指针选择图片,注意这里如果用绝对路径,要用“/”,而不能用“\”
//也可以将图片放到工程文件夹得debug文件夹下,这样用相对路径”time.png”就可以了
QApplication::setOverrideCursor(my);
//将鼠标指针更改为自己设置的图片
int x = m->pos().x();
int y = m->pos().y();
//获取鼠标现在的位置坐标
ui->pushButton->setText(tr(“鼠标现在的坐标是(%1,%2), 哈哈好玩吧”).arg(x).arg(y));
//将鼠标的位置坐标显示在按钮上
ui->pushButton->move(m->pos());
//让按钮跟随鼠标移动
}
/*以下是键盘按下事件*/
void MainWindow::keyPressEvent(QKeyEvent *k)
{
if(k->key() == Qt::Key_A) //判断是否是A键按下
{
ui->label->setPixmap(QPixmap(“E:/Qt/Qt-Creator-Example/event/linux.jpg”));
ui->label->resize(100,100);
//更改标签图片和大小
}
}
注意:这两个函数不是自己新建的,而是对已有函数的重定义,所有函数名和参数都不能改。第一个函数对鼠标移动事件进行了重写。其中实现了鼠标指针的更改,和按钮跟随鼠标移动的功能。
第二个函数对键盘的A键按下实现了新的功能。
效果如下。
有两种方法实现定时器。
第一种。自己建立关联。
1.新建Gui工程,工程名可以设置为timer。并在主界面上添加一个标签label,并设置其显示内容为“0000-00-00 00:00:00 星期日”。
2.在mainwindow.h中添加槽函数声明。
private slots:
void timerUpDate();
3.在mainwindow.cpp中添加代码。
添加#include <QtCore>的头文件包含,这样就包含了QtCore下的所有文件。
构造函数里添加代码:
Qtimer *timer = new Qtimer(this);
//新建定时器
connect(timer,SIGNAL(timeout()),this,SLOT(timerUpDate()));
//关联定时器计满信号和相应的槽函数
timer->start(1000);
//定时器开始计时,其中1000表示1000ms即1秒
4.然后实现更新函数。
void MainWindow::timerUpDate()
{
QDateTime time = QDateTime::currentDateTime();
//获取系统现在的时间
QString str = time.toString(“yyyy-MM-dd hh:mm:ss dddd”);
//设置系统时间显示格式
ui->label->setText(str);
//在标签上显示时间
}
5.运行程序,效果如下。
第二种。使用事件。(有点像单片机中的定时器啊)
1.新建工程。在窗口上添加两个标签。
2.在main.cpp中添加代码,实现中文显示。
#include <QtextCodec>
QtextCodec::setCodecForTr(QtextCodec::codecForLocale());
3.在mainwindow.h中添加代码。
void timerEvent(QtimerEvent *);
4.在mainwindow.cpp中添加代码。
添加头文件#include <QtCore>
在构造函数里添加以下代码。
startTimer(1000); //其返回值为1,即其timerId为1
startTimer(5000);//其返回值为2,即其timerId为2
startTimer(10000); //其返回值为3,即其timerId为3
添加了三个定时器,它们的timerId分别为1,2,3。注意,第几个定时器的返回值就为几。所以要注意定时器顺序。
在下面添加函数实现。
void MainWindow::timerEvent(QtimerEvent *t) //定时器事件
{
switch(t->timerId()) //判断定时器的句柄
{
case 1 : ui->label->setText(tr(“每秒产生一个随机数:%1″).arg(qrand()%10));break;
case 2 : ui->label_2->setText(tr(“5秒后软件将关闭”));break;
case 3 : qApp->quit();break; //退出系统
}
}
这里添加了三个定时器,并都在定时器事件中判断它们,然后执行相应的功能。这样就不用每个定时器都写一个关联函数和槽函数了。
随机数的实现:
上面程序中的qrand(),可以产生随机数,qrand()%10可以产生0-9之间的随机数。要想产生100以内的随机数就%100。以此类推。
但这样每次启动程序后,都按同一种顺序产生随机数。为了实现每次启动程序产生不同的初始值。我们可以使用qsrand(time(0));实现设置随机数的初值,而程序每次启动时time(0)返回的值都不同,这样就实现了产生不同初始值的功能。
我们将qsrand(time(0));一句加入构造函数里。
程序最终运行效果如下。
2.4.4任务实现
整体设计思想:进入游戏后,有三个按钮可供玩家选择:开始游戏,游戏设置,退出游戏,在进入相应子菜单后也可返回到主菜单,每个菜单的具体设计将在后面介绍,以下是总体的游戏流程图。

图4-1 游戏总体流程图设计
此模块为本次设计的核心模块,玩家在主菜单设置好游戏选项后点击开始按钮后开始游戏,并进入游戏窗口,游戏窗口大体是这样设计的:首先最上面一栏显示当前处于第几关,游戏区域分为两部分,主游戏区显示当前游戏画面,其宽设置为10个单位的小方块图片的边长,高为20,游戏区域右边的上面部分设置了3个功能按钮:重新开始游戏、暂停、返回主菜单按钮;游戏区域右边的下面部分则显示主菜单界面,该界面在游戏开始后处于隐藏状态,当玩家点击“返回主菜单”按钮后,主菜单会重新显示出来,同时游戏界面处于隐藏状态。在开始游戏后,当玩家获得一定积分后,会进入下一关,如果失败,则必须重新开始。玩家在顺利完成第五关后,这时,玩家就能进入神秘的隐藏关,一次来增加游戏难度(相信那将会很有趣)。
游戏设置模块提供了3个设置选项:是否显示游戏网格、音量设置以及音效设置,这些设置的灵活性可以方便不同水平用户的自由选择,以便一步步晋级。而音效效果则可以增加用户玩游戏的乐趣。设置好游戏选项后,即可返回主菜单开始游戏,在游戏的过程中也可以随时暂停返回主菜单,再进行重新设置游戏选项,这样更加方便用户的使用。
连连看所要求的是:
1:两个目标是相同的
2:两个目标之间连接线的折点不超过两个。(连接线由x轴和y轴的平行线组成)那么分析一下连接的情况可以看到,一般分三种情况
1:直线相连 2:一个折点 3:两个折点:
可以发现,如果有折点,每个折点必定有且至少有一个坐标(x或者y)是和其中一个目标点是相同的,也就是说,折点必定在两个目标点所在的x方向或y方向的直线上。
所以设计思路就是:
假设目标点p1 , p2 ,如果有两个折点分别为z1 , z2 那么,所要进行的是
1:如果验证p1, p2 直线连线,则连接成立
2:搜索以p1,p2的x,y方向四条直线(可能某两条直线会重合)上的有限点,每次取两点作为z1,z2 ,验证p1到z1/z1到z2/z2到p2 是否都能直线相连 ,是则连接成立。
连连看消去算法实现
在检验两张图片能否消掉的时候,我们要让两张图片同时满足两个条件才行,就是两者配对并且连线成功。
分3种情况:(从下面的这三种情况,我们可以知道,需要三个检测,这三个检测分别检测一条直路经。这样就会有三条路经。若这三条路经上都是空按钮,那么就刚好是三种直线(两个转弯点)把两个按钮连接起来了)
1.相邻
2. 若不相邻的先在第一个按钮的同行找一个空按钮。1).找到后看第二个按钮横向到这个空按钮所在的列是否有按钮。2).没有的话再看第一个按钮到与它同行的那个空按钮之间是否有按钮。3).没有的话,再从与第一个按钮同行的那个空按钮竖向到与第二个按钮的同行看是否有按钮。没有的话路经就通了,可以消了.
3.若2失败后,再在第一个按钮的同列找一个空按钮。1).找到后看第二个按钮竖向到这个空按钮所在的行是否有按钮 2).没有的话,再看第一个按钮到与它同列的那个空按钮之间是否有按钮。3).没有的话,再从与第一个按钮同列的那个空按钮横向到与第二个按钮同列看是否有按钮。没有的话路经就通了,可以消了。
若以上三步都失败,说明这两个按钮不可以消去。

