• 1. Qt简介 Qt的作用 Qt的特性及优势 包含Qt的系统的架构 如何学习Qt
  • 2. Qt简介 图形用户界面 (Graphical User Interface)是指采用图形方式显示的计算机操作用户界面 对比:早期的操作系统,如DOS,CUI(Command line User Interface)命令行模式的人机接口 组成部分:桌面、视窗、菜单、按钮、图标等 Qt是跨平台的C++应用程序和UI开发的框架 Qt4有超过500个类和9000多个函数,使用Qt可以迅速开发出期望的应用程序
  • 3. Qt在整个产品开发中的作用 构建桌面环境; 为应用程序提供可视化的、友好的界面; 利用Qt类库自带的功能构建复杂应用程序; 使用Qt构建的产品: http://www.qtsoftware.com/qt-in-use
  • 4. Qt的特性及优势 易于获取,个人应用完全免费 全面的、艺术级的应用程序框架 良好的跨平台性,一劳永逸 多语言的支持
  • 5. 包含Qt的系统的架构
  • 6. 包含Qt的系统的架构
  • 7. 学习Qt可用的资源 NO1: Qt参考文档,包括类的简介、类相关函数的介绍、自带例程的源码及讲解、函数的查找和使用、核心特性、关键技术等 NO2:www.qtcn.org Qt中文论坛 NO3:《 C++GUI Programming with Qt4,Second Edition 》 --官方参考文档,讲解精到
  • 8. Qt学习方法 NO1:学习Qt自带教程,Qt的example及其参考代码。参考qtdemo程序,学习demo完成自己的程序。 NO2: 阅读书籍,随书进行编程练习。如《C++GUI Qt4编程》,提供有完善的代码
  • 9. Qt程序开发 QtCreator介绍 Hello Qt!程序开发流程 几个Qt练习
  • 10. Qt Creator的设计目标是使开发人员能够利用Qt 这个应 用程序框架更加快速及轻易的完成开发任务。 Nokia 收购Qt之后在Qt的工具上做了很大的工作,推出的 一款新的轻量级集成开发环境(IDE), 即QtCreator。 QtCreator IDE 能够跨平台运行,支持的系统包括 Linux (32 位及 64 位)、Mac OS、Windows等。
  • 11. 功能介绍: 项目生成向导 高级 C++ 代码编辑器 文件及类管理工具 集成了Qt Designer 集成了qmake 构建工具 集成了图形化的 GDB 调试前端
  • 12. 利用QtCreator开发Qt应用程序的基本流程: 创建工程 项工程中添加文件 设计界面 编写代码实现功能 调试运行
  • 13. 创建工程 打开QtCreator,“File->New File or Project…”,选择 “Qt4 Gui Application”
  • 14. 输入工程名称:如ex01_helloQt 选择工程路径:如D:\project
  • 15. 根据应用选择功能模块,此工程保持默认即可。
  • 16. 创建Qt4 Gui Application时,向导会自动生成一个新类,将来可在 该类中完成应用程序的功能。 此步设置该类名称,选择基类名称,及设置该类代码的文件名称 另外,设置是否要生成UI文件,如果生成则,将来可以在UI文件 中来绘制界面。
  • 17. 最后一步“Finish”即可完成工程创建
  • 18. 在工程管理窗口中双击Forms下的mywidget.ui(UI文件),即可打开Qt Designer(Qt界面设计器) 在Designer中设计界面
  • 19. 编译运行程序:在工程名上右键,选择Run
  • 20. 运行效果:
  • 21. 练习1:隐藏“HelloQt!” 设计两个按钮和一个Label,当点击“show”按钮时显示“HelloQt!”,点击“Hide”按钮时隐藏“HelloQt!”。
  • 22. 按照HelloQt方法创建工程,并绘制界面。
  • 23. 添加功能: Qt使用信号和槽机制可以很容易的实现对象之间的通信,当某些 事件发生时,对应的信号会被发送。 可以将一个对象的信号和其他对象的槽相连,这样,当信号发送 是,和他相连的槽函数即可被调用。
  • 24. 编辑信号和槽:  Edit->Edit signal/slots (F4) 编辑对象:  Edit->Edit Widgets (F3) F4之后,左键拖动“Show”到“HelloQT”上,释放鼠标,会弹出信 号和槽对话框
  • 25. 选择连接clicked()信号和show()槽
  • 26. 同样的方法连接“Hide”的clicked()信号和“HelloQt”的show()槽 连接好后如下图示 如果要编辑部件,按F3回到部件编辑状态即可 最后编译运行程序,观察现象
  • 27. 练习2:控制LCDNumber显示 通过slider(滑块)和dial(旋钮)控制 LCDNumber上显示的数字
  • 28. Qt Creator编译的程序,在其工程文件夹下会有一个debug文件夹,其中有程序的.exe可执行文件。但Qt Creator默认是用动态链接的,就是可执行程序在运行时需要相应的.dll文件。我们点击生成的.exe文件,首先可能显示“没有找到mingwm10.dll,因此这个应用程序未能启动。重新安装应用程序可能会修复此问题。”表示缺少mingwm10.dll文件。
  • 29. 解决这个问题我们可以将相应的.dll文件放到系统中。在Qt Creator的安装目录的qt文件下的bin文件夹下(比如安装在了D盘,所以路径是D:\Qt\2009.04\qt\bin),可以找到所有的相关.dll文件。 方法一:在这里找到mingwm10.dll文件,将其复制到C:\WINDOWS\system文件夹下即可。下面再提示缺少什么dll文件,都像这样解决就可以了。 方法二:将这些dll文件都与.exe文件放到同一个文件夹下。不过这样每个.exe文件都要放一次。 方法三:将D:\Qt\2009.04\qt\bin加入系统Path环境变量。右击我的电脑->属性->高级->环境变量->在系统变量列表中找到Path,将路径加入其中即可。
  • 30. 用纯源码编写: 1.新建空的Qt工程。
  • 31. 2.工程名为hello world,并选择工程保存路径
  • 32. 3.在新建好的工程中添加文件。右击工程文件夹,弹出的菜单中选择Add New。
  • 33. 4.选择普通文件。点击Ok
  • 34. 5.文件名为main.cpp,点击Next进入下一步。
  • 35. 6.这里自动将这个文件添加到了新建的工程中。保持默认设置,点击完成。
  • 36. 7.在main.cpp文件中添加代码。
  • 37. 8.这时点击运行,程序执行了,但看不到效果,因为程序里什么也没做。我们点击信息框右上角的红色方块,停止程序运行。
  • 38. 9.我们再更改代码。添加一个对话框对象。
  • 39. 10.运行效果如下。
  • 40. 11.我们更改代码如下,在对话框上添加一个标签对象,并显示hello world。
  • 41. 12.运行效果如下。
  • 42. 二、Qt Creator编写多窗口程序 实现功能:        程序开始出现一个对话框,按下按钮后便能进入主窗口,如果直接关闭这个对话框,便不能进入主窗口,整个程序也将退出。当进入主窗口后,我们按下按钮,会弹出一个对话框,无论如何关闭这个对话框,都会回到主窗口。 实现原理:        程序里我们先建立一个主工程,作为主界面,然后再建立一个对话框类,将其加入工程中,然后在程序中调用自己新建的对话框类来实现多窗口。
  • 43. 实现过程: 1.首先新建Qt4 Gui Application工程,工程名为nGui,Base class选为QWidget。建立好后工程文件列表如下图。
  • 44. 2.新建对话框类,如下图,在新建中,选择Qt Designer Form Class。
  • 45. 3.选择Dialog without Buttons。
  • 46. 4.类名设为myDlg。
  • 47. 5.点击Finish完成。注意这里已经默认将其加入到了我们刚建的工程中了。
  • 48. 6.如下图,在mydlg.ui中拖入一个Push Button,将其上的文本改为“进入主窗口”,在其属性窗口中将其objectName改为enterBtn(Push Button名字),在下面的Signals and slots editor中进行信号和槽的关联,其中,Sender设为enterBtn,Signal设为clicked(),Receive设为myDlg,Slot设为accept()。这样就实现了单击这个按钮使这个对话框关闭并发出Accepted信号的功能。下面我们将利用这个信号。
  • 49. 7.修改主函数main.cpp,如下: #include #include “widget.h” #include “mydlg.h”       //加入头文件 int main(int argc, char *argv[]) {     QApplication a(argc, argv);     Widget w;    myDlg my1;       //建立自己新建的类的对象my1     if(my1.exec()==QDialog::Accepted)    //利用Accepted信号判断enterBtn是否被按下     {         w.show();         //如果被按下,显示主窗口         return a.exec();       //程序一直执行,直到主窗口关闭     }     else return 0;     //如果没被按下,则不会进入主窗口,整个程序结束运行 } 主函数必须这么写,才能完成所要的功能。
  • 50. 到这里,我们就实现了一个界面结束执行,然后弹出另一个界面的程序。下面我们在主窗口上加一个按钮,按下该按钮,弹出一个对话框,但这个对话框关闭,不会使主窗口关闭。 8.如下图,在主窗口加入按钮,显示文本为“弹出一个对话框”,在其上点击鼠标右键,在弹出的菜单中选择go to slot。
  • 51. 9.我们选择单击事件clicked()。
  • 52. 10.我们在弹出的槽函数中添加一句: my2.show();    my2为我们新建对话框类的另一个对象,但是my2我们还没有定义,所以在widget.h文件中添加相应代码,如下,先加入头文件,再加入my2的定义语句,这里我们将其放到private里,因为一般的函数都放在public里,而变量都放在private里。
  • 53. #ifndef WIDGET_H #define WIDGET_H #include #include “mydlg.h”    //包含头文件 namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; myDlg my2;       //对my2进行定义 private slots: void on_pushButton_clicked(); }; #endif // WIDGET_H
  • 54. 到这里,再运行程序,便能完成我们实验要求的功能了。整个程序里,我们用两种方法实现了信号和槽函数的关联,第一个按钮我们直接在设计器中实现其关联;第二个按钮我们自己写了槽函数语句,其实图形的设计与直接写代码效果是一样的。     
  • 55. 三、Qt Creator登录对话框 实现功能: 在弹出对话框中填写用户名和密码,按下登录按钮,如果用户名和密码均正确则进入主窗口,如果有错则弹出警告对话框。 实现原理: 通过上节的多窗口原理实现由登录对话框进入主窗口,而用户名和密码可以用if语句进行判断。
  • 56. 实现过程: 1.先新建Qt4 Gui Application工程,工程名为mainWidget,选用QWidget作为Base class,这样便建立了主窗口。文件列表如下:
  • 57. 2.然后新建一个Qt Designer Form Class类,类名为loginDlg,选用Dialog without Buttons,将其加入上面的工程中。文件列表如下:
  • 58. 3.在logindlg.ui中设计下面的界面:行输入框为Line Edit。其中用户名后面的输入框在属性中设置其object Name为usrLineEdit,密码后面的输入框为pwdLineEdit,登录按钮为loginBtn,退出按钮为exitBtn。
  • 59. 4.将exitBtn的单击后效果设为退出程序,关联如下:
  • 60. 5.右击登录按钮选择go to slot,再选择clicked(),然后进入其单击事件的槽函数,写入一句 void loginDlg::on_loginBtn_clicked() {     accept(); }
  • 61. 6.改写main.cpp: #include #include “widget.h” #include “logindlg.h” int main(int argc, char *argv[]) {     QApplication a(argc, argv);     Widget w;     loginDlg login;     if(login.exec()==QDialog::Accepted)     {         w.show();         return a.exec();     }     else return 0; }
  • 62. 7.这时执行程序,可实现按下登录按钮进入主窗口,按下退出按钮退出程序。 8.添加用户名密码判断功能。将登陆按钮的槽函数改为: void loginDlg::on_loginBtn_clicked() { if (ui->usrLineEdit->text()==tr(“qt”)&&ui->pwdLineEdit->text()==tr(“123456″)) //判断用户名和密码是否正确 accept(); else{ QMessageBox::warning(this,tr(“Warning”),tr(“user name or password error!”),QMessageBox::Yes); //如果不正确,弹出警告对话框 } } 并在logindlg.cpp中加入#include 的头文件。如果不加这个头文件,QMessageBox类不可用。
  • 63. 9.这时再执行程序,输入用户名为qt,密码为123456,按登录按钮便能进入主窗口了,如果输入错了,就会弹出警告对话框。
  • 64. 如果输入错误,便会弹出警告提示框:
  • 65. 10.在logindlg.cpp的loginDlg类构造函数里,添上初始化语句,使密码显示为小黑点。 loginDlg::loginDlg(QWidget *parent) : QDialog(parent), ui(new Ui::loginDlg) { ui->setupUi(this); ui-> pwdLineEdit-> setEchoMode(QLineEdit::Password); }
  • 66. 效果如下:
  • 67. 11.如果输入如下图中的用户名,在用户名前不小心加上了一些空格,结果程序按错误的用户名对待了。
  • 68. 我们可以更改if判断语句,使这样的输入也算正确。 void loginDlg::on_loginBtn_clicked() { if(ui->usrLineEdit->text().trimmed()==tr(“qt”)&&ui->pwdLineEdit->text()==tr(“123456″)) accept(); else{ QMessageBox::warning(this,tr(“Warning”),tr(“user name or password error!”),QMessageBox::Yes); } } 加入的这个函数的作用就是移除字符串开头和结尾的空白字符。
  • 69. 12.最后,如果输入错误了,重新回到登录对话框时,我们希望可以使用户名和密码框清空并且光标自动跳转到用户名输入框,最终的登录按钮的单击事件的槽函数如下: void loginDlg::on_loginBtn_clicked() { if(ui->usrLineEdit->text().trimmed()==tr(“qt”)&&ui->pwdLineEdit->text()==tr(“123456″)) //判断用户名和密码是否正确 accept(); else{ QMessageBox::warning(this,tr(“Warning”),tr(“user name or password error!”),QMessageBox::Yes); //如果不正确,弹出警告对话框 ui->usrLineEdit->clear();//清空用户名输入框 ui->pwdLineEdit->clear();//清空密码输入框 ui->usrLineEdit->setFocus();//将光标转到用户名输入框 } }
  • 70. 最终的loginDlg.cpp文件如下图:
  • 71. 四、Qt Creator添加菜单图标 1.新建Qt4 Gui Application工程,将工程命名为MainWindow,其他选项默认即可。 生成的窗口界面如下图。其中最上面的为菜单栏。
  • 72. 2.我们在Type Here那里双击,并输入“文件(&F)”,这样便可将其文件菜单的快捷键设为Alt+F。(注意括号最好用英文半角输入,这样看着美观)
  • 73. 3.输入完按下Enter键确认即可,然后在子菜单中加入“新建(&N)”,确定后,效果如下图。
  • 74. 4.我们在下面的动作编辑窗口可以看到新加的“新建”菜单。
  • 75. 5.双击这一条,可打开它的编辑对话框。我们看到Icon项,这里可以更改“新建”菜单的图标。
  • 76. 6.我们点击后面的…号,进入资源选择器,但现在这里面是空的。所以下面我们需要给该工程添加外部资源。
  • 77. 7.添加资源有两种方法。一种是直接添加系统提供的资源文件,然后选择所需图标。另一种是自己写资源文件。我们主要介绍第一种。新建Qt Resources file,将它命名为menu。其他默认。
  • 78. 8.添加完后如下图。可以看到添加的文件为menu.qrc。
  • 79. 9.我们最好先在工程文件夹里新建一个文件夹,如images,然后将需要的图标文件放到其中。
  • 80. 10.在Qt Creator的menu.qrc文件中,我们点击Add下拉框,选择Add Prefix。我们可以将生成的/new/prefix前缀改为其他名字,如/File。
  • 81. 11.然后再选择Add下拉框,选择Add Files。再弹出的对话框中,我们到新建的images文件夹下,将里面的图标文件全部添加过来。
  • 82. 12.添加完成后,我们在Qt Creator的File菜单里选择Save All选项,保存所做的更改。(注意:一定要先保存刚才的qrc文件,不然在资源管理器中可能看不见自己添加的资源!)
  • 83. 13.这时再打开资源选择器,可以看到我们的图标都在这里了。(注意:如果不显示,可以按一下上面的Reload按钮)
  • 84. 14.我们将new.png作为“新建”菜单的图标,然后点击Shortcut,并按下Crtl+N,便能将Crtl+N作为“新建”菜单的快捷键。
  • 85. 15.这时打开文件菜单,可以看到“新建”菜单已经有图标了。
  • 86. 运行程序后效果如下。
  • 87. 16.我们在工程文件夹下查看建立的menu.qrc文件,可以用写字板将它打开。
  • 88. 其具体内容如下。
  • 89. 第二种添加资源文件的方法。 1.首先右击工程文件夹,在弹出的菜单中选择Add New,添加新文件。也可以用File中的添加新文件。
  • 90. 2.我们选择文本文件。
  • 91. 3.将文件名设置为menu.qrc。
  • 92. 4.添加好文件后将其内容修改如下。可以看到就是用第一种方法生成的menu.qrc文件的内容。 5.保存文件后,在资源管理器中可以看到添加的图标文件。
  • 93. 五、Qt Creator布局管理器的使用 首先对菜单进行完善。 1.我们在上一次的基础上再加入一些常用菜单。“文件”的子菜单如下图。中间的分割线可以点击Add Separator添加。
  • 94. “编辑”子菜单的内容如下。
  • 95. “帮助”子菜单的内容如下。
  • 96. 2.我们在动作编辑器中对各个菜单的属性进行设置。
  • 97. 3.我们拖动“新建”菜单的图标,将其放到工具栏里。拖动“新建”菜单的图标。
  • 98. 将其放到菜单栏下面的工具栏里。
  • 99. 4.我们再添加其他几个图标。使用Append Separator可以添加分割线。 5. 如果需要删除图标,可以在图标上点击右键选择Remove action即可。
  • 100. 布局管理器。(这里主要以垂直布局管理器进行讲解,其他类型管理器用法与之相同,其效果可自己验证。) 1.在左边的器件栏里拖入三个PushButton和一个Vertical Layout(垂直布局管理器)到中心面板。如下图。
  • 101. 2.将这三个按钮放入垂直布局管理器,效果如下。可以看到按钮垂直方向排列,并且宽度可以改变,但高度没有改变。
  • 102. 3.我们将布局管理器整体选中,按下上面工具栏的Break Layout按钮,便可取消布局管理器。(我们当然也可以先将按钮移出,再按下Delete键将布局管理器删除。)
  • 103. 4.下面我们改用分裂器部件(QSplitter)。 先将三个按钮同时选中,再按下上面工具栏的Lay Out Vertically in Splitter(垂直分裂器)。
  • 104. 效果如下图。可以看到按钮的大小可以随之改动。这也就是分裂器和布局管理器的分别。
  • 105. 5.其实布局管理器不但能控制器件的布局,还有个很重要的用途是,它能使器件的大小随着窗口大小的改变而改变。 我们先在主窗口的中心拖入一个文本编辑器Text Edit。
  • 106. 这时直接运行程序,效果如下。可以看到它的大小和位置不会随着窗口改变。
  • 107. 下面我们选中主窗口部件,然后在空白处点击鼠标右键,选择Layout->Lay Out in a Grid,使整个主窗口的中心区处于网格布局管理器中。
  • 108. 可以看到,这时文本编辑器已经占据了整个主窗口的中心区。
  • 109. 运行一下程序,可以看到无论怎样拉伸窗口,文本编辑框的大小都会随之改变。
  • 110. 六、Qt Creator实现文本编辑 在开始正式写程序之前,我们先要考虑一下整个流程。因为我们要写记事本一样的软件,所以最好先打开windows中的记事本,进行一些简单的操作,然后考虑怎样去实现这些功能。再者,再强大的软件,它的功能也是一个一个加上去的,不要设想一下子写出所有的功能。我们这里先实现新建文件,保存文件,和文件另存为三个功能,是因为它们联系很紧,而且这三个功能总的代码量也不是很大。 因为三个功能之间的关系并不复杂,所以我们这里便不再画流程图,而只是简单描述一下。  新建文件,那么如果有正在编辑的文件,是否需要保存呢?  如果需要进行保存,那这个文件以前保存过吗?如果没有保存过,就应该先将其另存为。 
  • 111. 下面开始按这些关系写程序。  1.打开Qt Creator,在File菜单中选择Open,然后在工程文件夹中打开MainWindow.pro工程文件。  先在main.cpp文件中加入以下语句,让程序中可以使用中文。  在其中加入#include 头文件包含,再在主函数中加入下面一行:  QTextCodec::setCodecForTr(QTextCodec::codecForLocale());  这样在程序中使用中文,便能在运行时显示出来了。更改后文件如下图。
  • 112. 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变量起到标志的作用,用它来标志文件是否被保存过。然后我们再在相应的源文件里进行这些函数的定义。 
  • 113. 3.在mainwindow.cpp中先加入头文件#include ,然后在构造函数里添加以下几行代码。  isSaved = false; //初始化文件为未保存过状态  curFile = tr(“未命名.txt”); //初始化文件名为“未命名.txt”  setWindowTitle(curFile); //初始化主窗口的标题  这是对主窗口进行初始化。效果如下。 
  • 114. 4.然后添加“新建”操作的函数定义。  void MainWindow::do_file_New() //实现新建文件的功能  {  do_file_SaveOrNot();  isSaved = false;  curFile = tr(“未命名.txt”);  setWindowTitle(curFile);  ui->textEdit->clear(); //清空文本编辑器  ui->textEdit->setVisible(true); //文本编辑器可见  }  新建文件,先要判断正在编辑的文件是否需要保存。然后将新建的文件标志为未保存过状态。 
  • 115. 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();  }  } 
  • 116. 这个函数实现弹出一个对话框,询问是否保存正在编辑的文件。
  • 117. 6.再添加“保存”操作的函数定义。  void MainWindow::do_file_Save() //保存文件  {  if(isSaved){ //如果文件已经被保存过,直接保存文件  saveFile(curFile);  }  else{  do_file_SaveAs(); //如果文件是第一次保存,那么调用另存 为  }  }  对文件进行保存时,先判断其是否已经被保存过,如果没有被保存过,就要先对其进行另存为操作。
  • 118. 7.下面是“另存为”操作的函数定义。  void MainWindow::do_file_SaveAs() //文件另存为  {  QString fileName = QFileDialog::getSaveFileName(this,tr(“另存为”),curFile); //获得文件名  if(!fileName.isEmpty()) //如果文件名不为空,则保存文件内容  {  saveFile(fileName);  }  }   
  • 119. 这里弹出一个文件对话框,显示文件另存为的路径。
  • 120. 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;  } 
  • 121. 这个函数实现将文本文件进行存储。下面我们对其中的一些代码进行讲解。  QFile file(fileName);一句,定义了一个QFile类的对象file,其中filename表明这个文件就是我们保存的的文件。然后我们就可以用file代替这个文件,来进行一些操作。Qt中文件的操作和C,C++很相似。对于QFile类对象怎么使用,我们可以查看帮助。   点击Qt Creator最左侧的Help,在其中输入QFile,在搜索到的列表中选择QFile即可。这时在右侧会显示出QFile类中所有相关信息以及他们的用法和说明。 
  • 122. (本页无文本内容)
  • 123. 我们往下拉,会发现下面有关于怎么读取文件的示例代码。
  • 124. 再往下便能看到用QTextStream类对象,进行字符串输入的例子。下面也提到了QFileInfo和QDir等相关的类,我们可以点击它们去看一下具体的使用说明。 
  • 125. 上面只是做了一个简单的说明。以后我们对自己不明白的类都可以去帮助里进行查找,这也许是我们以后要做的最多的一件事了。对于其中的英文解释,我们最好想办法弄明白它的大意,其实网上也有一些中文的翻译,但最好还是从一开始就尝试着看英文原版的帮助,这样以后才不会对中文翻译产生依赖。 
  • 126. 9.双击mainwindow.ui文件,在图形界面窗口下面的Action Editor动作编辑器里,我们右击“新建”菜单一条,选择Go to slot,然后选择triggered(),进入其触发事件槽函数。
  • 127. 同理,进入其他两个菜单的槽函数,将相应的操作的函数写入槽函数中。如下。  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();  } 
  • 128. 最终的mainwindow.cpp文件如下。 
  • 129. (本页无文本内容)
  • 130. (本页无文本内容)
  • 131. 最终的mainwindow.h文件如下。 这时点击运行,就能够实现新建文件,保存文件,文件另存为 的功能了。
  • 132. 实现打开,关闭,退出,撤销,复制,剪切,粘贴的功能。  先备份上次的工程文件,然后再将其打开。  1.先在mainwindow.h文件中加入函数的声明。  void do_file_Open(); //打开文件  bool do_file_Load(const QString& fileName); //读取文件 
  • 133. 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);//文本编辑器可见  } 
  • 134. (本页无文本内容)
  • 135. 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;  } 
  • 136. 上面的打开文件函数与文件另存为函数相似,读取文件的函数与 文件存储函数相似。 
  • 137. 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是指向应用程序的全局指针  }  //
  • 138. 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();  } 
  • 139. (本页无文本内容)
  • 140. 因为复制,撤销,全选,粘贴,剪切等功能,是TextEdit默认 就有的,所以我们只需调用一下相应函数就行。到这里,除了 查找和帮助两个菜单的功能没有加上以外,其他功能都已经实 现了。 
  • 141. 七、Qt Creator实现文本查找 以前都用设计器设计界面,而这次我们用代码实现一个简单的查找对话框。对于怎么实现查找功能的,我们详细地分步说明了怎么进行类中方法的查找和使用。其中也将Qt Creator智能化的代码补全功能和程序中函数的声明位置和定义位置间的快速切换进行了介绍。 1.首先还是保存以前的工程,然后再将其打开。 我们发现Qt Creator默认的字体有点小,可以按下Ctrl键的同时按两下+键,来放大字体。也可以选择Edit->Advanced->Increase Font Size。
  • 142. 2.在mainwindow.h中加入#include 的头文件包含,在private中添加 QLineEdit *find_textLineEdit; //声明一个行编辑器,用于输入要查找的内容 在private slots中添加 void show_findText(); 在该函数中实现查找字符串的功能。
  • 143. 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())); //设置“查找下一个”按钮的单击事件和其槽函数的关联 }
  • 144. 4.这里我们直接用代码生成了一个对话框,其中一个行编辑器可以输入要查找的字符,一个按钮可以进行查找操作。我们将这两个部件放到了一个垂直布局管理器中。然后显示这个对话框。并设置了那个按钮单击事件与show_findText()函数的关联。
  • 145. 5.下面我们开始写实现查找功能的show_findText()函数。 void MainWindow::show_findText()//“查找下一个”按钮的槽函数 { QString findText = find_textLineEdit->text(); //获取行编辑器中的内容 } 先用一个QString类的对象获得要查找的字符。然后我们一步一步写查找操作的语句。
  • 146. 6.在下一行写下ui,然后直接按下键盘上的“<.”键,这时系统会根据是否是指针对象而自动生成“->”或“.”,因为ui是指针对象,所以自动生成“->”号,而且弹出了ui中的所有部件名称的列表。如下图。
  • 147. 7.我们用向下的方向键选中列表中的textEdit。或者我们可以先输入text,这时能缩减列表的内容。
  • 148. 8.如上图我们将鼠标放到textEdit上,这时便出现了textEdit的类名信息,且后面出现一个F1按键。我们按下键盘上的F1,便能出现textEdit的帮助。
  • 149. 9.我们在帮助中向下拉,会发现这里有一个find函数。
  • 150. 10.我们点击find,查看其详细说明。
  • 151. 11.可以看到find函数可以实现文本编辑器中字符串的查找。其中有一个FindFlags的参数,我们点击它查看其说明。
  • 152. 12.可以看到它是一个枚举变量(enum),有三个选项,第一项是向后查找(即查找光标以前的内容,这里的前后是相对的说法,比如第一行已经用完了,光标在第二行时,把第一行叫做向后。),第二项是区分大小写查找,第三项是查找全部。 13.我们选用第一项,然后写出下面的语句。 ui->textEdit->find(findText,QTextDocument::FindBackward);
  • 153. 当写完函数名和第一个“(”后,系统会自动显示出该函数的函数原型,这样可以使我们减少出错。
  • 154. 14.这时已经能实现查找的功能了。但是我们刚才看到find的返回值类型是bool型,而且,我们也应该为查找不到字符串作出提示。 if(!ui->textEdit-find(findText,QTextDocument::FindBackward)) { QMessageBox::warning(this,tr(“查找”),tr(“找不到 %1″) .arg(findText)); } 因为查找失败返回值是false,所以if条件加了“!”号。在找不到时弹出警告对话框。
  • 155. (本页无文本内容)
  • 156. 15.到这里,查找功能就基本上写完了。show_findText()函数的内容如下。
  • 157. 我们会发现随着程序功能的增强,其中的函数也会越来越多,我们都会为查找某个函数的定义位置感到头疼。而在Qt Creator中有几种快速定位函数的方法,我们这里讲解三种。 第一,在函数声明的地方直接跳转到函数定义的地方。 如在do_file_Load上点击鼠标右键,在弹出的菜单中选择Follow Symbol under Cursor或者下面的Switch between Method Declaration/Definition。
  • 158. 第二,快速查找一个文件里的所有函数。 我们可以点击窗口最上面的下拉框,这里会显示本文件中所有函数的列表。
  • 159. 第三,利用查找功能。 1.我们先将鼠标定位到一个函数名上。 2.然后选择Edit->Find/Replace->Find Dialog。 3.这时会出现一个查找对话框,可以看到要查找的函数名已经写在里面了。
  • 160. 4.当我们按下Search按钮后,会在查找结果窗口显示查找到的结果。
  • 161. 5.我们点击第二个文件。会发现在这个文件中有两处关键字是高亮显示。
  • 162. 6.我们双击第二项,就会自动跳转到函数的定义处。
  • 163. 八、Qt Creator实现状态栏显示 1.我们在mainwindow.h中做一下更改。 加入头文件包含: #include 加入私有变量和函数: QLabel* first_statusLabel; //声明两个标签对象,用于显示状态信息 QLabel* second_statusLabel; void init_statusBar(); //初始化状态栏 加入一个槽函数声明:void do_cursorChanged(); //获取光标位置信息
  • 164. 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制作!”)); } 这里将两个标签对象加入到了主窗口的状态栏里,并设置了他们的外观和初值。
  • 165. 3.在构造函数里调用状态栏初始化函数。 init_statusBar(); 这时运行程序,效果如下。
  • 166. 4.在mainwindow.cpp中加入获取光标位置的函数的定义。 void MainWindow::do_cursorChanged() { int rowNum = ui->textEdit->document()->blockCount(); const QTextCursor cursor = ui->textEdit->textCursor(); int colNum = cursor.columnNumber(); //获取光标所在列的列号 rowNum = cursor.blockNumber();//获取光标所在行的行号 first_statusLabel->setText(tr(“%1行 %2列”).arg(rowNum).arg(colNum)); //在状态栏显示光标位置 } 这个函数可获取文本编辑框中光标的位置,并显示在状态栏中。
  • 167. 5.在构造函数添加光标位置改变信号的关联。 connect(ui->textEdit,SIGNAL(cursorPositionChanged()),this,SLOT(do_cursorChanged())); 这时运行程序。效果如下。
  • 168. 6.在do_file_Load函数的最后添加下面语句。 second_statusLabel->setText(tr(“打开文件成功”));
  • 169. 7.在saveFile函数的最后添加以下语句。 second_statusLabel->setText(tr(“保存文件成功”));
  • 170. 8.在on_action_Find_triggered函数的后面添加如下语句。 second_statusLabel->setText(tr(“正在进行查找”));
  • 171. 9.在on_action_Close_triggered函数最后添加如下语句。 first_statusLabel->setText(tr(“文本编辑器已关闭”)); second_statusLabel->setText(tr(“yafeilinux制作!”));
  • 172. 最终的mainwindow.cpp文件内容如下。
  • 173. (本页无文本内容)
  • 174. (本页无文本内容)
  • 175. (本页无文本内容)
  • 176. (本页无文本内容)
  • 177. (本页无文本内容)
  • 178. 最终的mainwindow.h文件如下。
  • 179. (本页无文本内容)
  • 180. 九、Qt Creator中鼠标键盘事件的处理实现自定义鼠标指针 Qt中的事件可以在QEvent中查看。下面我们只是找两个例子来进行简单的演示。 1.还是先建立一个Qt4 Gui Application工程,我这里起名为event。 2.添加代码,让程序中可以使用中文。 即在main.cpp文件中加入#include 的头文件包含。 再在下面的主函数里添加QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); 3.在mainwindow.h文件中做一下更改。 添加#include 头文件。因为这样就包含了QtGui中所有的子文件。 在public中添加两个函数的声明 void mouseMoveEvent(QMouseEvent *); void keyPressEvent(QKeyEvent *);
  • 181. 4.我们在mainwindow.ui中添加一个Label和一个PushButton,将他们拉长点,因为一会要在上面显示标语。 5.在mainwindow.cpp中的构造函数里添加两个部件的显示文本。 ui->label->setText(tr(“按下键盘上的A键试试!”)); ui->pushButton->setText(tr(“按下鼠标的一个键,然后移动鼠标试试”));
  • 182. 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()); //让按钮跟随鼠标移动 }
  • 183. /*以下是键盘按下事件*/ 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); //更改标签图片和大小 } }
  • 184. 注意:这两个函数不是自己新建的,而是对已有函数的重定义,所有函数名和参数都不能改。第一个函数对鼠标移动事件进行了重写。其中实现了鼠标指针的更改,和按钮跟随鼠标移动的功能。 第二个函数对键盘的A键按下实现了新的功能。 效果如下。
  • 185. 按下鼠标的一个键,并移动鼠标。
  • 186. 按下键盘上的A键。
  • 187. 十、Qt Creator中实现定时器和产生随机数 1.新建Gui工程,工程名可以设置为timer。并在主界面上添加一个标签label,并设置其显示内容为“0000-00-00 00:00:00 星期日”。 2.在mainwindow.h中添加槽函数声明。 private slots: void timerUpDate(); 3.在mainwindow.cpp中添加代码。 添加#include 的头文件包含,这样就包含了QtCore下的所有文件。 构造函数里添加代码: QTimer *timer = new QTimer(this); //新建定时器 connect(timer,SIGNAL(timeout()),this,SLOT(timerUpDate())); //关联定时器计满信号和相应的槽函数 timer->start(1000); //定时器开始计时,其中1000表示1000ms即1秒
  • 188. 4.然后实现更新函数。 void MainWindow::timerUpDate() { QDateTime time = QDateTime::currentDateTime(); //获取系统现在的时间 QString str = time.toString(“yyyy-MM-dd hh:mm:ss dddd”); //设置系统时间显示格式 ui->label->setText(str); //在标签上显示时间 }
  • 189. 5.运行程序,效果如下。
  • 190. 产生随机数,使用事件。 1.新建工程。在窗口上添加两个标签。 2.在main.cpp中添加代码,实现中文显示。 #include QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); 3.在mainwindow.h中添加代码。 void timerEvent(QTimerEvent *); 4.在mainwindow.cpp中添加代码。 添加头文件#include 在构造函数里添加以下代码。 startTimer(1000); //其返回值为1,即其timerId为1 startTimer(5000);//其返回值为2,即其timerId为2 startTimer(10000); //其返回值为3,即其timerId为3  添加了三个定时器,它们的timerId分别为1,2,3。注意,第几个定时器的返回值就为几。所以要注意定时器顺序。
  • 191. 在下面添加函数实现。 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; //退出系统 } } 这里添加了三个定时器,并都在定时器事件中判断它们,然后执行相应的功能。这样就不用每个定时器都写一个关联函数和槽函数了。
  • 192. 随机数的实现: 上面程序中的qrand(),可以产生随机数,qrand()%10可以产生0-9之间的随机数。要想产生100以内的随机数就%100。以此类推。 但这样每次启动程序后,都按同一种顺序产生随机数。为了实现每次启动程序产生不同的初始值。我们可以使用qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); 实现设置随机数的初值,而程序每次启动时返回的值都不同,这样就实现了产生不同初始值的功能。 我们将qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));一句加入构造函数里。
  • 193. 程序最终运行效果如下。
  • 194. 十一、Qt 2D绘图(一)绘制简单图形 1.新建Qt4 Gui Application工程,我这里使用的工程名为painter01,选用QDialog作为Base class。 2.在dialog.h文件中声明重绘事件函数 void paintEvent(QPaintEvent *); 3.在dialog.cpp中添加绘图类QPainter的头文件包含 #include
  • 195. 4.在下面进行该函数的重定义。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.drawLine(0,0,100,100); } 其中创建了QPainter类对 象,它是用来进行绘制图 形的,我们这里画了一条 线Line,其中的参数为线 的起点(0,0),和终点 (100,100)。这里的数 值指的是像素,详细的坐 标设置我们以后再讲,这里知道(0,0)点指的是窗口的左上角即可。运行效果如图
  • 196. 5.在qt的帮助里可以查看所有的绘制函数,而且下面还给出了相关的例子。
  • 197. 6.我们下面将几个知识点说明一下,帮助大家更快入门。 将函数改为如下: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPen pen; //画笔     pen.setColor(QColor(255,0,0));     QBrush brush(QColor(0,255,0,125)); //画刷     painter.setPen(pen); //添加画笔     painter.setBrush(brush); //添加画刷     painter.drawRect(100,100,200,200); //绘制矩形 } 这里的pen用来绘制边框,brush用来进行封闭区域的填充,QColor类用来提供颜色,我们这里使用了rgb方法来生成颜色,即(red,green,blue),它们取值分别是0-255,例如(255,0,0)表示红色,而全0表示黑色,全255表示白色。后面的(0,255,0,125),其中的125是透明度(alpha)设置,其值也是从0到255,0表示全透明。最后将画笔和画刷添加到painter绘制设备中,画出图形。这里的Rect是长方形,其中的参数为(100,100)表示起始坐标,200,200表示长和宽。效果如下:
  • 198. (本页无文本内容)
  • 199. 7.其实画笔和画刷也有很多设置,大家可以查看帮助。(在帮助里可以看到所有的风格。) QPainter painter(this);     QPen pen(Qt::DotLine);     QBrush brush(Qt::blue);     brush.setStyle(Qt::HorPattern);     painter.setPen(pen);     painter.setBrush(brush);     painter.drawRect(100,100,200,200); 这里我们设置了画笔的风格为点线,画刷的风格为并行横线,效果如下:
  • 200. (本页无文本内容)
  • 201. 我们这里用了Qt::blue,Qt自定义的几个颜色如下:
  • 202. 8.画弧线,这是帮助里的一个例子。 QRectF rectangle(10.0, 20.0, 80.0, 60.0); //矩形      int startAngle = 30 * 16;     //起始角度      int spanAngle = 120 * 16;   //跨越度数      QPainter painter(this);      painter.drawArc(rectangle, startAngle, spanAngle); 这里要说明的是,画弧线时,角度被分成了十六分之一,就是说,要想为30度,就得是30*16。它有起始角度和跨度,还有位置矩形,要想画出自己想要的弧线,就要有一定的几何知识了。这里就不再祥述。
  • 203. 十二、Qt 2D绘图(二)渐变填充 在qt中提供了三种渐变方式,分别是线性渐变,圆形渐变和圆锥渐变。如果能熟练应用它们,就能设计出炫目的填充效果。 线性渐变 1.更改函数如下: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QLinearGradient linearGradient(100,150,300,150);     //从点(100,150)开始到点(300,150)结束,确定一条直线     linearGradient.setColorAt(0,Qt::red);     linearGradient.setColorAt(0.2,Qt::black);     linearGradient.setColorAt(0.4,Qt::yellow);     linearGradient.setColorAt(0.6,Qt::white);     linearGradient.setColorAt(0.8,Qt::green);     linearGradient.setColorAt(1,Qt::blue);     //将直线开始点设为0,终点设为1,然后分段设置颜色     painter.setBrush(linearGradient);     painter.drawRect(100,100,200,100);     //绘制矩形,线性渐变线正好在矩形的水平中心线上 }
  • 204. 效果如下:
  • 205. 圆形渐变: 1.更改函数内容如下:    QRadialGradient radialGradient(200,100,100,200,100);    //其中参数分别为圆形渐变的圆心(200,100),半径100,和焦点(200,100)     //这里让焦点和圆心重合,从而形成从圆心向外渐变的效果     radialGradient.setColorAt(0,Qt::black);     radialGradient.setColorAt(1,Qt::yellow);     //渐变从焦点向整个圆进行,焦点为起始点0,圆的边界为1     QPainter painter(this);     painter.setBrush(radialGradient);     painter.drawEllipse(100,0,200,200);    //绘制圆,让它正好和上面的圆形渐变的圆重合
  • 206. 效果如下:
  • 207. 2.要想改变填充的效果,只需要改变焦点的位置和渐变的颜色位置即可。 改变焦点位置: QRadialGradient radialGradient(200,100,100,100,100); 效果如下:
  • 208. 锥形渐变: 1.更改函数内容如下: //圆锥渐变     QConicalGradient conicalGradient(50,50,0);     //圆心为(50,50),开始角度为0     conicalGradient.setColorAt(0,Qt::green);     conicalGradient.setColorAt(1,Qt::white);    //从圆心的0度角开始逆时针填充     QPainter painter(this);     painter.setBrush(conicalGradient);     painter.drawEllipse(0,0,100,100);
  • 209. 效果如下:
  • 210. 2.可以更改开始角度,来改变填充效果 QConicalGradient conicalGradient(50,50,30); 开始角度设置为30度,效果如下:
  • 211. 十三、Qt 2D绘图(三)绘制文字 1.绘制最简单的文字。 我们更改重绘函数如下: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.drawText(100,100,”QTtest”); } 我们在(100,100)的位置显示了一行文字.
  • 212. 2.为了更好的控制字体的位置。我们使用另一个构造函数。在帮助里查看drawText,如下。这里我们看到了构造函数的原型和例子。其中的flags参数可以控制字体在矩形中 的位置。我们更改函数内容如下。
  • 213. void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QRectF ff(100,100,300,200);     //设置一个矩形     painter.drawRect(ff);     //为了更直观地看到字体的位置,我们绘制出这个矩形     painter.setPen(QColor(Qt::red));     //设置画笔颜色为红色     painter.drawText(ff,Qt::AlignHCenter,”yafeilinux”);     //我们这里先让字体水平居中 }
  • 214. 效果如下 可以看到字符串是在最上面水平居中的。如果想让其在矩形正中间,我们可以使 用Qt::AlignCenter。
  • 215. 这里我们也可以使用两个枚举变量进行按位与操作,例如可以使用Qt::AlignBottom|Qt::AlignHCenter实现让文字显示在矩形下面的正中间。效果如下。
  • 216. 对于较长的字符串,我们也可以利用“\n”进行换行,例如”yafei\nlinux”。效果如下。
  • 217. 3.如果要使文字更美观,我们就需要使用QFont类来改变字体。先在帮助中查看一下这个类。
  • 218. 可以看到它有好几个枚举变量来设置字体。下面的例子我们对主要的几个选项进行演示。 更改函数如下。 void Dialog::paintEvent(QPaintEvent *) {     QFont font(“Arial”,20,QFont::Bold,true);     //设置字体的类型,大小,加粗,斜体     font.setUnderline(true);     //设置下划线     font.setOverline(true);     //设置上划线     font.setCapitalization(QFont::SmallCaps);     //设置大小写     font.setLetterSpacing(QFont::AbsoluteSpacing,5);     //设置间距     QPainter painter(this);     painter.setFont(font);     //添加字体     QRectF ff(100,100,300,200);     painter.drawRect(ff);     painter.setPen(QColor(Qt::red));     painter.drawText(ff,Qt::AlignCenter,”yafeilinux”); }
  • 219. 效果如下
  • 220. 这里的所有字体我们可以在设计器中进行查看。如下。
  • 221. 十四、Qt 2D绘图(四)绘制路径 在窗体上绘制路径。QPainterPath这个类很有用,这里我们只是说明它最常使用的功能,更深入的以后再讲。 1.我们更改paintEvent函数如下 void Dialog::paintEvent(QPaintEvent *) {     QPainterPath path;     path.addEllipse(100,100,50,50);     path.lineTo(200,200);     QPainter painter(this);     painter.setPen(Qt::green);     painter.setBrush(Qt::yellow);     painter.drawPath(path); }
  • 222. 这里我们新建了一个painterPath对象,并加入了一个圆和一条线。然后绘制这个路径。效果如下
  • 223. 2.上面绘制圆和直线都有对应的函数啊,为什么还要加入一个painterPath呢? 我们再添加几行代码,你就会发现它的用途了。 void Dialog::paintEvent(QPaintEvent *) {     QPainterPath path;     path.addEllipse(100,100,50,50);     path.lineTo(200,200);     QPainter painter(this);     painter.setPen(Qt::green);     painter.setBrush(Qt::yellow);     painter.drawPath(path);     QPainterPath path2;     path2.addPath(path);     path2.translate(100,0);     painter.drawPath(path2); }
  • 224. 效果如下这里我们又新建了一个painterPath对象path2,并将以前的path添加到它上面, 然后我们更改了原点坐标为(100,0),这时你发现我们复制了以前的图形。 这也就是painterPath类最主要的用途,它能保存你已经绘制好的图形。
  • 225. 3.这里我们应该注意的是绘制完一个图形后,当前的位置在哪里。 例如: void Dialog::paintEvent(QPaintEvent *) {     QPainterPath path;     path.lineTo(100,100);     path.lineTo(200,100);     QPainter painter(this);     painter.drawPath(path); }可以看到默认是从原点(0,0)开始绘 图的,当画完第一条直线后,当前点应 该在(100,100)处,然后画第二条直 线。
  • 226. 再如: void Dialog::paintEvent(QPaintEvent *) {     QPainterPath path;     path.addRect(50,50,40,40);     path.lineTo(200,200);     QPainter painter(this);     painter.drawPath(path); }可见画完矩形后,当前点在矩形的左上角顶 点,然后从这里开始画直线。
  • 227. 我们可以自己改变当前点的位置。 void Dialog::paintEvent(QPaintEvent *) {     QPainterPath path;     path.addRect(50,50,40,40);     path.moveTo(100,100);     path.lineTo(200,200);     QPainter painter(this);     painter.drawPath(path); }可见moveTo函数可以改变当前点的 位置。 这里我们只讲解了绘制路径类最简单的应用,其实这个类很有用, 利用它可以设计出很多特效。有兴趣的同学可以查看一下它的帮 助。
  • 228. 十五、Qt 2D绘图(五)显示图片 现在我们来实现在窗口上显示图片,并学习怎样将图片进行平移,缩放,旋转和扭曲。这里我们是利用QPixmap类来实现图片显示的。 一、利用QPixmap显示图片。 1.将以前的工程文件夹进行复制备份,我们这里将工程文件夹改名为painter05。(以前已经说过,经常备份工程目录,是个很好的习惯) 2.在工程文件夹的debug文件夹中新建文件夹,我这里命名为images,用来存放要用的图片。我这里放了一张jpg的图片。如下图所示。
  • 229. 3.在Qt Creator中打开工程。(即打开工程文件夹中的.pro文件),如图。
  • 230. 4.将dialog.cpp文件中的paintEvent()函数更改如下。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix;     pix.load(“images/文件名.jpg”);     painter.drawPixmap(0,0,100,100,pix); }
  • 231. 这里新建QPixmap类对象,并为其添加图片,然后在以(0,0)点开始的宽和高都为100的矩形中显示该图片。你可以改变矩形的大小,看一下效果啊。最终程序运行效果如下。
  • 232. 二、利用更改坐标原点实现平移。 Qpainter类中的translate()函数实现坐标原点的改变,改变原点后,此点将会成为新的原点(0,0); 例如: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix;     pix.load(“images/linux.jpg”);     painter.drawPixmap(0,0,100,100,pix);     painter.translate(100,100); //将(100,100)设为坐标原点     painter.drawPixmap(0,0,100,100,pix); } 这里将(100,100)设置为了新的坐标原点,所以下面在(0,0)点贴图,就相当于在以前的(100,100)点贴图。效果如下。
  • 233. (本页无文本内容)
  • 234. 三、实现图片的缩放。 我们可以使用QPixmap类中的scaled()函数来实现图片的放大和缩小。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix;     pix.load(“images/linux.jpg”);     painter.drawPixmap(0,0,100,100,pix);     qreal width = pix.width(); //获得以前图片的宽和高     qreal height = pix.height();     pix = pix.scaled(width*2,height*2,Qt::KeepAspectRatio);     //将图片的宽和高都扩大两倍,并且在给定的矩形内保持宽高的比值     painter.drawPixmap(100,100,pix); } 其中参数Qt::KeepAspectRatio,是图片缩放的方式。我们可以查看其帮助。将鼠标指针放到该代码上,当出现F1提示时,按下F1键,这时就可以查看其帮助了。当然我们也可以直接在帮助里查找该代码。
  • 235. 这是个枚举变量,这里有三个值,只看其图片就可大致明白,Qt::IgnoreAspectRatio是 不保持图片的长宽比,Qt::KeepAspectRatio是在给定的矩形中保持长宽比,最后一个也 是保持长宽比,但可能超出给定的矩形。这里给定的矩形是由我们显示图片时给定的参 数决定的,例如painter.drawPixmap(0,0,100,100,pix);就是在以(0,0)点为起始点的 宽和高都是100的矩形中。
  • 236. 程序运行效果如下。
  • 237. 四、实现图片的旋转。 旋转使用的是QPainter类的rotate()函数,它默认是以原点为中心进行旋转的。我们要改变旋转的中心,可以使用前面讲到的translate()函数完成。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix;     pix.load(“images/linux.jpg”);     painter.translate(50,50); //让图片的中心作为旋转的中心     painter.rotate(90); //顺时针旋转90度     painter.translate(-50,-50); //使原点复原     painter.drawPixmap(0,0,100,100,pix); } 这里必须先改变旋转中心,然后再旋转,然后再将原点复原,才能达到想要的效果。
  • 238. 运行程序,效果如下
  • 239. 五、实现图片的扭曲。 实现图片的扭曲,是使用的QPainter类的shear(qreal sh,qreal sv)函数完成的。它有两个参数,前面的参数实现横行变形,后面的参数实现纵向变形。当它们的值为0时,表示不扭曲。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix;     pix.load(“images/linux.jpg”);     painter.drawPixmap(0,0,100,100,pix);     painter.shear(0.5,0); //横向扭曲     painter.drawPixmap(100,0,100,100,pix); }
  • 240. 效果如下:
  • 241. 其他扭曲效果: painter.shear(0,0.5); //纵向扭曲                            painter.shear(0.5,0.5); //横纵扭曲
  • 242. 十六、Qt 2D绘图(六)坐标系统 一、坐标系简介。 Qt中每一个窗口都有一个坐标系,默认的,窗口左上角为坐标原点,然后水平向右依次增大,水平向左依次减小,垂直向下依次增大,垂直向上依次减小。原点即为(0,0)点,然后以像素为单位增减。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.setBrush(Qt::red);     painter.drawRect(0,0,100,100);     painter.setBrush(Qt::yellow);     painter.drawRect(-50,-50,100,100); }
  • 243. 我们先在原点(0,0)绘制了一个长宽都是100像素的红色矩形,又在(-50,-50)点绘制了一个同样大小的黄色矩形。可以看到,我们只能看到黄色矩形的一部分。效果如下图
  • 244. 二、坐标系变换。 坐标系变换是利用变换矩阵来进行的,我们可以利用QTransform类来设置变换矩阵,因为一般我们不需要进行更改,所以这里不在涉及。下面我们只是对坐标系的平移,缩放,旋转,扭曲等应用进行介绍。 1.利用translate()函数进行平移变换。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.setBrush(Qt::yellow);     painter.drawRect(0,0,50,50);    painter.translate(100,100); //将点(100,100)设为原点     painter.setBrush(Qt::red);     painter.drawRect(0,0,50,50);    painter.translate(-100,-100);     painter.drawLine(0,0,20,20); }
  • 245. 效果如下 这里将(100,100)点作为了原点,所以此时(100,100)就是(0,0)点, 以前的(0,0)点就是(-100,-100)点。要想使原来的(0,0)点重新成为 原点,就是将(-100,-100)设为原点。
  • 246. 2.利用scale()函数进行比例变换,实现缩放效果。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.setBrush(Qt::yellow);     painter.drawRect(0,0,100,100);     painter.scale(2,2); //放大两倍     painter.setBrush(Qt::red);     painter.drawRect(50,50,50,50); }
  • 247. 效果如下 可以看到,painter.scale(2,2),是将横纵坐标都扩大了两倍,现在的(50,50)点 就相当于以前的(100,100)点。
  • 248. 3.利用shear()函数就行扭曲变换。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.setBrush(Qt::yellow);     painter.drawRect(0,0,50,50);     painter.shear(0,1); //纵向扭曲变形     painter.setBrush(Qt::red);     painter.drawRect(50,0,50,50); }
  • 249. 效果如下 这里,painter.shear(0,1),是对纵向进行扭曲,0表示不扭曲,当将第一个0更改 时就会对横行进行扭曲,关于扭曲变换到底是什么效果,你观察一下是很容易发 现的。
  • 250. 4.利用rotate()函数进行比例变换,实现缩放效果。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.drawLine(0,0,100,0);     painter.rotate(30); //以原点为中心,顺时针旋转30度     painter.drawLine(0,0,100,0);    painter.translate(100,100);     painter.rotate(30);     painter.drawLine(0,0,100,0); }
  • 251. 效果如下 因为默认的rotate()函数是以原点为中心进行顺时针旋转的,所以我们要想使其 以其他点为中心进行旋转,就要先进行原点的变换。
  • 252. 这里的painter.translate(100,100)将(100,100)设置为新的原点,想让直线以其为中心进行旋转,可是你已经发现效果并非如此。是什么原因呢?我们添加一条语句,如下: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.drawLine(0,0,100,0);     painter.rotate(30); //以原点为中心,顺时针旋转30度     painter.drawLine(0,0,100,0);    painter.rotate(-30);     painter.translate(100,100);     painter.rotate(30);     painter.drawLine(0,0,100,0); }
  • 253. 效果如下 这时就是我们想要的效果了。我们加的一句代码为painter.rotate(-30),这是因为前 面已经将坐标旋转了30度,我们需要将其再旋转回去,才能是以前正常的坐标系统。 不光这个函数如此,这里介绍的这几个函数均如此,所以很容易出错。下面我们将 利用两个函数来很好的解决这个问题。
  • 254. 三、坐标系状态的保护。 我们可以先利用save()函数来保存坐标系现在的状态,然后进行变换操作,操作完之后,再用restore()函数将以前的坐标系状态恢复,其实就是一个入栈和出栈的操作。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.save(); //保存坐标系状态     painter.translate(100,100);     painter.drawLine(0,0,50,50);     painter.restore(); //恢复以前的坐标系状态     painter.drawLine(0,0,50,50); }
  • 255. 效果如下 利用好这两个函数,可以实现快速的坐标系切换,绘制出不同的图形。
  • 256. 十七、Qt 2D绘图(七)Qt坐标系统深入 为了更清楚地获得坐标信息,我们这里利用鼠标事件,让鼠标点击左键时输出该点的坐标信息。 1.在工程中的dialog.h文件中添加代码。 添加头文件: #include 在public中添加函数声明: void mousePressEvent(QMouseEvent *); 然后到dialog.cpp文件中: 添加头文件: #include 定义函数: void Dialog::mousePressEvent(QMouseEvent *event) {    qDebug() << event->pos(); }
  • 257. 这里应用了qDebug()函数,利用该函数可以在程序运行时将程序中的一些信息输出,在Qt Creator中会将信息输出到其下面的Application Output窗口。这个函数很有用,在进行简单的程序调试时,都是利用该函数进行的。我们这里利用它将鼠标指针的坐标值输出出来。
  • 258. 2.然后更改重绘事件函数。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.drawRect(0,0,50,50); } 我们绘制了一个左上顶点为(0,0),宽和高都是50的矩形。
  • 259. 3.这时运行程序。并在绘制的矩形左上顶点点击一下鼠标左键。效果如下。因为鼠标点的不够准确,所以输出的是(1,0),我们可以认为左上角 就是原点(0,0)点。你可以再点击一下矩形的右下角,它的坐标应该 是(50,50)。这个方法掌握了以后,我们就开始研究这些坐标了。
  • 260. 研究放大后的坐标 1.我们现在进行放大操作,然后查看其坐标的变化。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     painter.scale(2,2);    //横纵坐标都扩大2倍     painter.drawRect(0,0,50,50); } 我们将横纵坐标都扩大2倍,然后运行程序,查看效果:
  • 261. 我们点击矩形右下顶点,是(100,100),比以前的(50,50)扩大了2倍。
  • 262. 研究QPixmap或QImage的坐标 对于QWidget,QPixmap或QImage等都是绘图设备,我们都可以在其上利用QPainter进行绘图。现在我们研究一下QPixmap的坐标(QImage与其效果相同)。 1.我们更改重绘事件函数如下。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix(200,200);     pix.fill(Qt::red);   //背景填充为红色     painter.drawPixmap(0,0,pix); } 这里新建了一个宽、高都是200像素的QPixmap类对象,并将其背景颜色设置为红色,然后从窗口的原点(0,0)点添加该QPixmap类对象。为了表述方便,在下面我们将这个QPixmap类对象pix称为画布。
  • 263. 我们运行程序,并在画布的左上角和右下角分别点击一下,效果如下 可以看到其左上角为(0,0)点,右下角为(200,200)点,是没有问题的。
  • 264. 2.我们再将函数更改如下。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix(200,200);     pix.fill(Qt::red);   //背景填充为红色     painter.drawPixmap(100,100,pix); } 这时我们从窗口的(100,100)点添加该画布,那么此时我们再点击画布的右上角,其坐标会是多少呢?
  • 265. 可以看到,它是(100,100),没错,这是窗口上的坐标,那么这是 不是画布上的坐标呢?
  • 266. 3.我们接着更改函数。 void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix(200,200);     pix.fill(Qt::red);   //背景填充为红色     QPainter pp(&pix);   //新建QPainter类对象,在pix上进行绘图     pp.drawLine(0,0,50,50);     //在pix上的(0,0)点和(50,50)点之间绘制直线     painter.drawPixmap(100,100,pix); } 这里我们又新建了一个QPainter类对象pp,其中pp(&pix)表明,pp所进行的绘图都是在画布pix上进行的。
  • 267. 现在先说明一下: QPainter painter(this) ,this就表明了是在窗口上进行绘图,所以利用painter进行的绘图都是在窗口上的,painter进行的坐标变化,是变化的窗口的坐标系;而利用pp进行的绘图都是在画布上进行的,如果它进行坐标变化,就是变化的画布的坐标系。
  • 268. 我们在画布上的(0,0)点和(50,50)点之间绘制了一条直线。这时运行程序,点击这条直线的两端,看看其坐标值。 结果是直线的两端的坐标分别是(100,100),(150,150)。我们从中可以 得出这样的结论: 1. QWidget和QPixmap各有一套坐标系统,它们互不影响。可以看到,无论画 布在窗口的什么位置,它的坐标原点依然在左上角,为(0,0)点,没有变。 2. 我们所得到的鼠标指针的坐标值是窗口提供的,不是画布的坐标。
  • 269. 4.比较下面两个例子。 例子一: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix(200,200);     qDebug() << pix.size();   //放大前输出pix的大小     pix.fill(Qt::red);       QPainter pp(&pix);       pp.scale(2,2);           //pix的坐标扩大2倍     pp.drawLine(0,0,50,50);     //在pix上的(0,0)点和(50,50)点之间绘制直线     qDebug() << pix.size();    //放大后输出pix的大小     painter.drawPixmap(0,0,pix); }
  • 270. (本页无文本内容)
  • 271. 例子二: void Dialog::paintEvent(QPaintEvent *) {     QPainter painter(this);     QPixmap pix(200,200);     qDebug() << pix.size();   //放大前输出pix的大小     painter.scale(2,2);     //窗口坐标扩大2倍     pix.fill(Qt::red);     QPainter pp(&pix);     pp.drawLine(0,0,50,50);     //在pix上的(0,0)点和(50,50)点之间绘制直线     qDebug() << pix.size();    //放大后输出pix的大小     painter.drawPixmap(0,0,pix); }
  • 272. 两个例子中都使直线的长度扩大了两倍,但是第一个例子是扩大的画布的坐标系, 第二个例子是扩大的窗口的坐标系,你可以看一下它们的效果。你仔细看一下输 出,两个例子中画布的大小都没有变。其实只需记住一句话: 所有的绘图设备都有自己的坐标系统,它们互不影响。
  • 273. 十八、Qt 2D绘图(八)涂鸦板 简单的涂鸦板: 1.我们再在程序中添加函数。 我们在dialog.h里的public中再添加鼠标移动事件和鼠标释放事件的函数声明: void mouseMoveEvent(QMouseEvent *); void mouseReleaseEvent(QMouseEvent *); 在private中添加变量声明: QPixmap pix; QPoint lastPoint; QPoint endPoint; 因为在函数里声明的QPixmap类对象是临时变量,不能存储以前的值,所以为了实现保留上次的绘画结果,我们需要将其设为全局变量。 后两个QPoint变量存储鼠标指针的两个坐标值,我们需要用这两个坐标值完成绘图。
  • 274. 2.在dialog.cpp中进行修改。 在构造函数里进行变量初始化。 resize(600,500);    //窗口大小设置为600*500 pix = QPixmap(200,200); pix.fill(Qt::white); 然后进行其他几个函数的定义: void Dialog::paintEvent(QPaintEvent *) {       QPainter pp(&pix);     pp.drawLine(lastPoint,endPoint);   //根据鼠标指针前后两个位置就行绘制直线     lastPoint = endPoint;   //让前一个坐标值等于后一个坐标值,这样就能实现画出连续的线     QPainter painter(this);     painter.drawPixmap(0,0,pix); }
  • 275. void Dialog::mousePressEvent(QMouseEvent *event) {     if(event->button()==Qt::LeftButton) //鼠标左键按下         lastPoint = event->pos(); } void Dialog::mouseMoveEvent(QMouseEvent *event) {     if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标     {         endPoint = event->pos();         update();     } }
  • 276. void Dialog::mouseReleaseEvent(QMouseEvent *event) {     if(event->button() == Qt::LeftButton) //鼠标左键释放     {         endPoint = event->pos();         update();     } } 这里的update()函数,是进行界面重绘,执行该函数时就会执行那个重绘事件函数。
  • 277. 3.这时运行程序,效果如下。
  • 278. 放大后再进行涂鸦: 1.添加放大按钮。 在dialog.h中添加头文件声明: #include 在private中添加变量声明: int scale; QPushButton *pushBtn; 然后再在下面写上按钮的槽函数声明: private slots:     void zoomIn(); 2.在dialog.cpp中进行更改。 在构造函数里添加如下代码: scale =1;   //设置初始放大倍数为1,即不放大 pushBtn = new QPushButton(this); //新建按钮对象 pushBtn->setText(tr(“zoomIn”));   //设置按钮显示文本 pushBtn->move(500,450);    //设置按钮放置位置 connect(pushBtn,SIGNAL(clicked()),this,SLOT(zoomIn())); //对按钮的单击事件和其槽函数进行关联
  • 279. 再在构造函数以外进行zoomIn()函数的定义: void Dialog::zoomIn() //按钮单击事件的槽函数 {     scale *=2;     update(); }
  • 280. 3.通过上一节的学习,我们应该已经知道想让画布的内容放大有两个办法,一个是直接放大画布的坐标,一个是放大窗口的坐标。 void Dialog::paintEvent(QPaintEvent *) {       QPainter pp(&pix);     pp.drawLine(lastPoint,endPoint);   //根据鼠标指针前后两个位置就行绘制直线     lastPoint = endPoint;   //让前一个坐标值等于后一个坐标值,这样就能实现画出连续的线     QPainter painter(this);     painter.scale(scale,scale); //进行放大操作     painter.drawPixmap(0,0,pix); } 这时运行程序。 先随意画一个图形,如下图。
  • 281. 再按下“zoomIn”按钮,进行放大两倍。可以看到图片放大 了,效果如下。
  • 282. 这时我们再进行绘图,绘制出的线条已经不能和鼠标指针的轨迹重合了。效果如下图。
  • 283. 有了前面一节的知识,我们就不难理解出现这个问题的原因了。窗口的坐标扩大了,但是画布的坐标并没有扩大,而我们画图用的坐标值是鼠标指针的,鼠标指针又是获取的窗口的坐标值。现在窗口和画布的同一点的坐标并不相等,所以就出现了这样的问题。 其实解决办法很简单,窗口放大了多少倍,就将获得的鼠标指针的坐标值缩小多少倍就行了。 void Dialog::paintEvent(QPaintEvent *) {       QPainter pp(&pix);     pp.drawLine(lastPoint/scale,endPoint/scale);     lastPoint = endPoint;     QPainter painter(this);     painter.scale(scale,scale); //进行放大操作     painter.drawPixmap(0,0,pix); }
  • 284. 运行程序,效果如下:此时已经能进行正常绘图了。这种用改变窗口坐标大小来改变画布面积的方法,实际上是 有损图片质量的。就像将一张位图放大一样,越放大越不清 晰。原因就是,它的像素的个数没有变,如果将可视面积放 大,那么单位面积里的像素个数就变少了,所以画质就差了。
  • 285. 下面我们简单说说另一种方法。 放大画布坐标。 void Dialog::paintEvent(QPaintEvent *) {       QPainter pp(&pix);     pp.scale(scale,scale);     pp.drawLine(lastPoint/scale,endPoint/scale);     lastPoint = endPoint;     QPainter painter(this);     painter.drawPixmap(0,0,pix); } 效果如下:
  • 286. 此时,画布中的内容并没有放大,而且画布也没有变大,不是 我们想要的,所以我们再更改一下函数。
  • 287. void Dialog::paintEvent(QPaintEvent *) {       if(scale!=1) //如果进行放大操作     {         QPixmap copyPix(pix.size()*scale); //临时画布,大小变化了scale倍         QPainter pter(©Pix);         pter.scale(scale,scale);         pter.drawPixmap(0,0,pix);   //将以前画布上的内容复制到现在的画布上         pix = copyPix;     //将放大后的内容再复制回原来的画布上,这样只传递内容,不传递坐标系         scale =1; //让scale重新置1     }     QPainter pp(&pix);     pp.scale(scale,scale);     pp.drawLine(lastPoint/scale,endPoint/scale);     lastPoint = endPoint;     QPainter painter(this);     painter.drawPixmap(0,0,pix); }
  • 288. 此时运行效果如下:这样就好了。可以看到,这样放大后再进行绘制,出来的效果 是不同的。
  • 289. 十九、Qt 2D绘图(九)双缓冲绘图简介 上面一节我们实现了涂鸦板的功能,但是如果我们想在涂鸦板上绘制矩形,并且可以动态地绘制这个矩形,也就是说我们可以用鼠标画出随意大小的矩形,那该怎么办呢? 我们先进行下面的三步,最后引出所谓的双缓冲绘图的概念。 第一步: 我们更改上一节的那个程序的重绘函数。 void Dialog::paintEvent(QPaintEvent *) {       QPainter painter(this);     int x,y,w,h;     x = lastPoint.x();     y = lastPoint.y();     w = endPoint.x() – x;     h = endPoint.y() – y;     painter.drawRect(x,y,w,h); }
  • 290. 然后运行,效果如下。这时我们已经可以拖出一个矩形了,但是这样直接在窗口上 绘图,以前画的矩形是不能保存住的。所以我们下面加入画 布,在画布上进行绘图。
  • 291. 第二步: 我们先在构造函数里将画布设置大点:pix = QPixmap(400,400); 然后更改函数,如下: void Dialog::paintEvent(QPaintEvent *) {       int x,y,w,h;     x = lastPoint.x();     y = lastPoint.y();     w = endPoint.x() – x;     h = endPoint.y() – y;     QPainter pp(&pix);     pp.drawRect(x,y,w,h);     QPainter painter(this);     painter.drawPixmap(0,0,pix); }
  • 292. 这时运行程序,效果如下:现在虽然能画出矩形,但是却出现了无数个矩形,这不是我们 想要的结果,我们希望能像第一步那样绘制矩形,所以我们再 加入一个临时画布。
  • 293. 第三步: 首先,我们在dialog.h中的private里添加变量声明: QPixmap tempPix; //临时画布 bool isDrawing;   //标志是否正在绘图 然后在dialog.cpp中的构造函数里进行变量初始化: isDrawing = false;
  • 294. 最后更改函数如下: void Dialog::paintEvent(QPaintEvent *) {       int x,y,w,h;     x = lastPoint.x();     y = lastPoint.y();     w = endPoint.x() – x;     h = endPoint.y() – y;     QPainter painter(this);     if(isDrawing)     //如果正在绘图     {         tempPix = pix;    //将以前pix中的内容复制到tempPix中,这样实现了交互绘图         QPainter pp(&tempPix);         pp.drawRect(x,y,w,h);         painter.drawPixmap(0,0,tempPix);     }     else     {         QPainter pp(&pix);         pp.drawRect(x,y,w,h);         painter.drawPixmap(0,0,pix);     } }
  • 295. void Dialog::mousePressEvent(QMouseEvent *event) {     if(event->button()==Qt::LeftButton) //鼠标左键按下     {         lastPoint = event->pos();         isDrawing = true;   //正在绘图     } } void Dialog::mouseMoveEvent(QMouseEvent *event) {     if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标     {         endPoint = event->pos();         update();     } }
  • 296. void Dialog::mouseReleaseEvent(QMouseEvent *event) {     if(event->button() == Qt::LeftButton) //鼠标左键释放     {         endPoint = event->pos();         isDrawing = false;    //结束绘图         update();     } } 我们使用两个画布,就解决了绘制矩形等图形的问题。 其中tempPix = pix;一句代码很重要,就是它,才实现了消除那些多余的矩形。
  • 297. 双缓冲绘图简介: 根据我的理解,如果将第一步中不用画布,直接在窗口上进行绘图叫做无缓冲绘 图,那么第二步中用了一个画布,将所有内容都先画到画布上,在整体绘制到窗 口上,就该叫做单缓冲绘图,那个画布就是一个缓冲区。这样,第三步,用了两 个画布,一个进行临时的绘图,一个进行最终的绘图,这样就叫做双缓冲绘图。 我们已经看到,利用双缓冲绘图可以实现动态交互绘制。其实,Qt中所有部件进 行绘制时,都是使用的双缓冲绘图。就算是第一步中我们没有用画布,Qt在进行 自身绘制时也是使用的双缓冲绘图,所以我们刚才那么说,只是为了更好地理解 双缓冲的概念。
  • 298. 二十、Qt 2D绘图(十)图形视图框架简介 我们前面用基本的绘图类实现了一个绘图软件,但是,我们无法做出像Word或者Flash中那样,绘制出来的图形可以作为一个元件进行任意变形。我们要想很容易地做出那样的效果,就要使用Qt中的图形视图框架。 The QGraphics View Framework(图形视图框架),在Qt Creator中的帮助里可以查看它的介绍,当然那是英文的,这里有一篇中文的翻译,大家可以看一下:Qt的graphics View框架. 如果你的程序中要使用大量的2D图元,并且想要这些图元都能进行单独或群组的控制,你就要使用这个框架了。比方说像Flash一样的矢量绘图软件,各种游戏软件。但是因为这里涉及的东西太多了,不可能用一两篇文章就介绍清楚,所以这里我们只是提及一下,让一些刚入门的朋友知道有这样一个可用的框架。
  • 299. 最简单的使用: The QGraphics View Framework包含三个大类:QGraphicsItem 项类(或者叫做图元类),QGraphicsScene 场景类,和QGraphicsView 视图类。 QGraphicsItem 用来绘制你所要用到的图形,QGraphicsScene 用来包含并管理所有的图元,QGraphicsView 用来显示所有场景。而他们三个都拥有自己各自的坐标系统。我们下面就来建立一个工程,完成一个最简单的例子。
  • 300. 1.新建空的Qt工程:
  • 301. 2.更改工程名和存放路径。
  • 302. 3.然后新建C++类。
  • 303. 4.更改类名为MyItem,基类填写为QGraphicsItem,如下图:
  • 304. 5.可以看到新建的类默认已经添加到了工程里。
  • 305. 6.新建C++ Source File,更改名字为main.cpp,如下图:
  • 306. 7.然后更改各文件的内容。 更改完成后,myitem.h文件内容如下:
  • 307. myitem.cpp文件的内容如下:
  • 308. main.cpp的内容如下:
  • 309. 运行程序,最终效果如下:这里我们只是演示了一下使用这个框架完成最简单的程序的过程, 只起到抛砖引玉的作用。这个框架很复杂,但是功能也很强大, Qt Creator中自带了几个相关的例子(在帮助中查找Graphics View Examples即可),你可以参考一下。
  • 310. 二十一、Qt数据库(一)简介 数据库几乎是每个较大的软件所必须应用的,而在Qt中也使用QtSql模块实现了对数据库的完美支持。我们在Qt Creator的帮助中查找QtSql Module,其内容如下图:可以看到这个模块是一组类的集合,使用这个模块我们需要加 入头文件#include ,而在工程文件中需要加入一行代 码:QT += sql
  • 311. 这里每个类的作用在后面都有简单的介绍,你也可以进入其中查看其详细内容。下面我们先简单的说一下QSqlDatabase类和QSqlQuery类。 QSqlDatabase类实现了数据库连接的操作,现在Qt支持的数据库类型有如下几种:
  • 312. 而现在我们使用的免费的Qt只提供了SQLite和ODBC数据库的驱动(我们可以在Qt Creator安装目录下的qt\plugins\sqldrivers文件夹下查看),而其他数据库的驱动需要我们自己添加。SQLite是一个小巧的嵌入式数据库,关于它的介绍你可以自己在网上查找。 QSqlQuery类用来执行SQL语句。(关于SQL语句:在我的教程中只会出现很简单的SQL语句,你没有相关知识也可以看懂,但是如果想进行深入学习,就需要自己学习相关知识了。) 下面我们就先利用这两个类来实现最简单的数据库程序,其他的类我们会在以后的教程中逐个学习到。
  • 313. 1.新建Qt控制台工程。
  • 314. 2.选择上QtSql模块,这样就会自动往工程文件中添加QT += sql 这行代码了.
  • 315. 3.修改main.cpp中的内容如下。 #include #include int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);     QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”); //添加数据库驱动     db.setDatabaseName(“:memory:”); //数据库连接命名     if(!db.open()) //打开数据库     {         return false;     }     QSqlQuery query; //以下执行相关QSL语句     query.exec(“create table student(id int primary key,name varchar)”);     //新建student表,id设置为主键,还有一个name项     query.exec(“insert into student values(1,’xiaogang’)”);     query.exec(“insert into student values(2,’xiaoming’)”);     query.exec(“insert into student values(3,’xiaohong’)”);     //向表中插入3条记录     query.exec(“select id,name from student where id >= 2″);     //查找表中id >=2 的记录的id项和name项的值     while(query.next())       //query.next()指向查找到的第一条记录,然后每次后移一条记录     {         int ele0 = query.value(0).toInt();        //query.value(0)是id的值,将其转换为int型         QString ele1 =query.value(1).toString();         qDebug() << ele0 <
  • 316. 我们使用了SQLite数据库,连接名为“:memory:”表示这是建立在内存中的数据库,也就是说该数据库只在程序运行期间有效。如果需要保存该数据库文件,我们可以将它更改为实际的文件路径。 4.最终效果如下。
  • 317. 5.我们可以将主函数更改如下。 int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);     qDebug() << “Available drivers:”;     QStringList drivers = QSqlDatabase::drivers();     foreach(QString driver, drivers)     qDebug() << “\t” << driver;     return a.exec(); } 这样运行程序就可以显示现在所有能用的数据库驱动了。
  • 318. 可以看到现在可用的数据库驱动只有三个。
  • 319. 二十二、Qt数据库(二)添加MySQL数据库驱动插件 在上一节的末尾我们已经看到,现在可用的数据库驱动只有3种,那么怎样使用其他的数据库呢?在Qt中,我们需要自己编译其他数据库驱动的代码,让它们以插件的形式来使用。下面我们就以现在比较流行的MySQL数据库为例,说明一下怎样在Qt Creator中添加数据库驱动插件。 在讲述之前,我们先看一下Qt Creator中数据库的插件到底放在哪里。 我们进入Qt Creator的安装目录,然后进入相对应的文件夹下,比方我这里是 D:\Qt\2010.02.1\qt\plugins\sqldrivers 在这里我们可以看见几个文件,如下图:
  • 320. (本页无文本内容)
  • 321. 根据名字中的关键字,我们可以判断出这就是ODBC数据库和SQLite数据库的驱动插件。下面我们编译好MySQL数据库驱动后,也会在这里出现相对应的文件。 首先:我们查看怎样安装数据库插件。 我们打开Qt Creator,在帮助中搜索SQL Database Drivers关键字。这里列出了编译Qt支持的所有数据库的驱动的方法。 我们下拉到在windows上编译QMYSQL数据库插件的部分,其内容如下:
  • 322. 这里详细介绍了整个编译的过程,其可以分为以下几步: 第一,下载MySQL的安装程序,在安装时选择定制安装,这时选中安装Libs和Include文件。安装位置可以是C:\MySQL 。 注意:安装位置不建议改动,因为下面进行编译的命令中使用了安装路径,如果改动,那么下面也要进行相应改动。 第二,进行编译。我们按照实际情况输入的命令如下。 cd %QTDIR%\src\plugins\sqldrivers\mysql qmake “INCLUDEPATH+=C:\MySQL\include” “LIBS+=C:\MySQL\lib\opt\libmysql.lib” mysql.pro mingw32-make 注意:在上面的命令中qmake之后如果加上“-o Makefile”选项,那么这个插件只能在以release模式编译程序时才能使用,所以我们上面没有加这个选项。
  • 323. 然后:我们按照上面的过程进行相应操作。 1.我们先下载MySQL的安装文件。 我们可以到MySQL的官方主页 http://www.mysql.com 进行下载最新的MySQL的windows版本,现在具体的下载页面地址为: http://www.mysql.com/downloads/mirror.php?id=383405#mirrors 我们不进行注册,直接点击其下面的 No thanks, just take me to the downloads! 可以在其中选择一个镜像网点进行下载,我使用的是Asia下的最后一个,就是台湾的镜像网点下载的。 下载到的文件名为:mysql-essential-5.1.44-win32 ,其中的win32表明是32位的windows系统,这一点一定要注意。文件大小为40M左右。 当然你也可以到中文网站上进行下载:http://www.mysql.cn/,随便下一个windows的版本就行。
  • 324. 2.安装软件。 我们选择定制安装Custom。
  • 325. 然后选中安装Include文件和Lib文件。
  • 326. 我们将安装路径更改为:C:\MySQL 。
  • 327. 最终的界面如下。
  • 328. 安装完成后,我们不进行任何操作,所以将两个选项都取消。
  • 329. 3.进行编译。 我们在桌面上开始菜单中找到Qt Creator的菜单,然后打开Qt Command Prompt。
  • 330. 然后输入第一条命令cd %QTDIR%\src\plugins\sqldrivers\mysql 后按回车,运行效果如下。
  • 331. 然后输入第二条命令: qmake “INCLUDEPATH+=C:\MySQL\include” “LIBS+=C:\MySQL\lib\opt\libmysql.lib” mysql.pro 按回车后运行效果如下:
  • 332. 最后输入:mingw32-make ,按下回车后经过几秒的编译,最终效果如下:整个编译过程中都没有出现错误提示,可以肯定插件已经编译完 成了。
  • 333. 4.我们再次进入Qt Creator安装目录下存放数据库驱动插件的文件夹。 我这里是D:\Qt\2010.02.1\qt\plugins\sqldrivers 其内容如下:可以看到已经有了和MySQL相关的文件了。
  • 334. 最后:我们编写程序测试插件。 1.我们将上一次的主函数更改如下。 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QSqlDatabase db = QSqlDatabase::addDatabase(“QMYSQL”); //添加数据库驱动 return a.exec(); }
  • 335. 运行程序,效果如下。这里提示:QSqlDatabase: QMYSQL driver not loaded 。
  • 336. 2.这时我们需要将C:\MySQL\bin目录下的libmySQL.dll文件复制到我们Qt Creator安装目录下的qt\bin目录中。 如下图:
  • 337. 3.这时再运行程序,就没有提示了。
  • 338. 4.我们再将主函数更改一下,测试这时可用的数据库驱动。 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << “Available drivers:”; QStringList drivers = QSqlDatabase::drivers(); foreach(QString driver, drivers) qDebug() << “\t” << driver; return a.exec(); }
  • 339. 运行效果如下:可以看到,现在已经有了MySQL的数据库驱动了。 我们这里只介绍了MySQL驱动插件在windows下的编译方法,其他数据库和 其他平台的编译方法可以按照帮助中的说明进行,我们不再介绍。其实Qt 不仅可以编译现成的数据库驱动插件,我们也可以编写自己的数据库驱动 插件,当然这是一件相当复杂的事情,我们这里也就不再进行介绍。
  • 340. 二十三、Qt数据库(三)利用QSqlQuery类执行SQL语句(一) SQL即结构化查询语言,是关系数据库的标准语言。前面已经提到,在Qt中利用QSqlQuery类实现了执行SQL语句。需要说明,我们这里只是Qt教程,而非专业的数据库教程,所以我们不会对数据库中的一些知识进行深入讲解,下面只是对最常用的几个知识点进行讲解。 我们下面先建立一个工程,然后讲解四个知识点,分别是: 一,操作SQL语句返回的结果集。 二,在SQL语句中使用变量。 三,批处理操作。 四,事务操作。
  • 341. 我们新建Qt4 Gui Application工程,我这里工程名为query ,然后选中QtSql模块,Base class选QWidget。工程建好后,添加C++ Header File ,命名为connection.h ,更改其内容如下: #ifndef CONNECTION_H #define CONNECTION_H #include #include #include static bool createConnection() {     QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”);     db.setDatabaseName(“:memory:”);     if (!db.open()) {         QMessageBox::critical(0, qApp->tr(“Cannot open database”),             qApp->tr(“Unable to establish a database connection.”                      ), QMessageBox::Cancel);         return false;     }     QSqlQuery query;     query.exec(“create table student (id int primary key, ”                “name varchar(20))”);     query.exec(“insert into student values(0, ‘first’)”);     query.exec(“insert into student values(1, ‘second’)”);     query.exec(“insert into student values(2, ‘third’)”);     query.exec(“insert into student values(3, ‘fourth’)”);     query.exec(“insert into student values(4, ‘fifth’)”);     return true; } #endif // CONNECTION_H
  • 342. 然后更改main.cpp的内容如下: #include #include “widget.h” #include “connection.h” int main(int argc, char *argv[]) {     QApplication a(argc, argv);         if (!createConnection())         return 1;         Widget w;     w.show();     return a.exec(); } 可以看到,我们是在主函数中打开数据库的,而数据库连接用一个函数完成,并单独放在一个文件中,这样的做法使得主函数很简洁。我们今后使用数据库时均使用这种方法。我们打开数据库连接后,新建了一个学生表,并在其中插入了几条记录。
  • 343. 表中的一行就叫做一条记录,一列是一个属性。这个表共有5条 记录,id和name两个属性。程序中的“id int primary key”表 明id属性是主键,也就是说以后添加记录时,必须有id项。
  • 344. 下面我们打开widget.ui文件,在设计器中向界面上添加一个Push Button ,和一个Spin Box 。将按钮的文本改为“查询”,然后进入其单击事件槽函数,更改如下。 void Widget::on_pushButton_clicked() {     QSqlQuery query;     query.exec(“select * from student”);     while(query.next())     {         qDebug() << query.value(0).toInt() << query.value(1).toString();     } }
  • 345. 我们在widget.cpp中添加头文件: #include #include 然后运行程序,单击“查询”按钮,效果如下:可以看到在输出窗口,表中的所有内容都输出出来了。这表明 我们的数据库连接已经成功建立了。
  • 346. 一,操作SQL语句返回的结果集。 在上面的程序中,我们使用query.exec(“select * from student”);来查询出表中所有的内容。其中的SQL语句“select * from student”中“*”号表明查询表中记录的所有属性。而当query.exec(“select * from student”);这条语句执行完后,我们便获得了相应的执行结果,因为获得的结果可能不止一条记录,所以我们称之为结果集。 结果集其实就是查询到的所有记录的集合,而在QSqlQuery类中提供了多个函数来操作这个集合,需要注意这个集合中的记录是从0开始编号的。最常用的有: seek(int n) :query指向结果集的第n条记录。 first() :query指向结果集的第一条记录。 last() :query指向结果集的最后一条记录。 next() :query指向下一条记录,每执行一次该函数,便指向相邻的下一条记录。 previous() :query指向上一条记录,每执行一次该函数,便指向相邻的上一条记录。 record() :获得现在指向的记录。
  • 347. value(int n) :获得属性的值。其中n表示你查询的第n个属性,比方上面我们使用“select * from student”就相当于“select id, name from student”,那么value(0)返回id属性的值,value(1)返回name属性的值。该函数返回QVariant类型的数据,关于该类型与其他类型的对应关系,可以在帮助中查看QVariant。 at() :获得现在query指向的记录在结果集中的编号。 需要说明,当刚执行完query.exec(“select * from student”);这行代码时,query是指向结果集以外的,我们可以利用query.next(),当第一次执行这句代码时,query便指向了结果集的第一条记录。当然我们也可以利用seek(0)函数或者first()函数使query指向结果集的第一条记录。但是为了节省内存开销,推荐的方法是,在query.exec(“select * from student”);这行代码前加上query.setForwardOnly(true);这条代码,此后只能使用next()和seek()函数。
  • 348. 下面将“查询”按钮的槽函数更改如下: void Widget::on_pushButton_clicked() {     QSqlQuery query;     query.exec(“select * from student”);     qDebug() << “exec next() :”;     if(query.next())     //开始就先执行一次next()函数,那么query指向结果集的第一条记录     {         int rowNum = query.at();         //获取query所指向的记录在结果集中的编号         int columnNum = query.record().count();         //获取每条记录中属性(即列)的个数         int fieldNo = query.record().indexOf(“name”);         //获取”name”属性所在列的编号,列从左向右编号,最左边的编号为0         int id = query.value(0).toInt();         //获取id属性的值,并转换为int型        QString name = query.value(fieldNo).toString();         //获取name属性的值         qDebug() << “rowNum is : ” << rowNum //将结果输出                 << ” id is : ” << id                 << ” name is : ” << name                 << ” columnNum is : ” << columnNum;     }     qDebug() << “exec seek(2) :”;    
  • 349. if(query.seek(2))     //定位到结果集中编号为2的记录,即第三条记录,因为第一条记录的编号为0     {         qDebug() << “rowNum is : ” << query.at()                 << ” id is : ” << query.value(0).toInt()                 << ” name is : ” << query.value(1).toString();     }     qDebug() << “exec last() :”;     if(query.last())     //定位到结果集中最后一条记录     {         qDebug() << “rowNum is : ” << query.at()                 << ” id is : ” << query.value(0).toInt()                 << ” name is : ” << query.value(1).toString();     } }
  • 350. 然后在widget.cpp文件中添加头文件。 #include 运行程序,结果如下:
  • 351. 二十四、Qt数据库(四)利用QSqlQuery类执行SQL语句(二) 在SQL语句中使用变量。我们先看下面的一个例子,将“查询”按钮的槽函数更改如下: void Widget::on_pushButton_clicked() {     QSqlQuery query;     query.prepare(“insert into student (id, name) ”                   “values (:id, :name)”);     query.bindValue(0, 5);     query.bindValue(1, “sixth”);     query.exec();     //下面输出最后一条记录     query.exec(“select * from student”);     query.last();     int id = query.value(0).toInt();     QString name = query.value(1).toString();     qDebug() << id << name; }
  • 352. 运行效果如下:可以看到,在student表的最后又添加了一条记录。在上面的 程序中,我们先使用了prepare()函数,在其中利用了“:id” 和“:name”来代替具体的数据,而后又利用bindValue()函数 给id和name两个属性赋值,这称为绑定操作。其中编号0和1分 别代表“:id”和“:name”,就是说按照prepare()函数中出现 的属性从左到右编号,最左边是0 。这里的“:id”和“:name”, 叫做占位符,这是ODBC数据库的表示方法,还有一种Oracle的表 示方法就是全部用“?”号。如下:
  • 353. query.prepare(“insert into student (id, name)”                   “values (?, ?)”); query.bindValue(0, 5); query.bindValue(1, “sixth”); query.exec(); 我们也可以利用addBindValue()函数,这样就可以省去编号,它是按顺序给属性赋值的,如下: query.prepare(“insert into student (id, name)”                   “values (?, ?)”); query.addBindValue(5); query.addBindValue(“sixth”); query.exec();
  • 354. 当用ODBC的表示方法时,我们也可以将编号用实际的占位符代替,如下: query.prepare(“insert into student (id, name) ”                       “values (:id, :name)”); query.bindValue(“:id”, 5); query.bindValue(“:name”, “sixth”); query.exec(); 以上各种形式的表示方式效果是一样的。特别注意,在最后一定要执行exec()函数,所做的操作才能被真正执行。
  • 355. 下面我们就可以利用绑定操作在SQL语句中使用变量了。 void Widget::on_pushButton_clicked() {     QSqlQuery query;     query.prepare(“select name from student where id = ?”);     int id = ui->spinBox->value(); //从界面获取id的值     query.addBindValue(id); //将id值进行绑定     query.exec();     query.next(); //指向第一条记录     qDebug() << query.value(0).toString(); }
  • 356. 运行程序,效果如下:我们改变spinBox的数值大小,然后按下“查询”按钮,可以 看到对应的结果就出来了。
  • 357. 批处理操作。当要进行多条记录的操作时,我们就可以利用绑定进行批处理。看下面的例子。 void Widget::on_pushButton_clicked() {     QSqlQuery q;     q.prepare(“insert into student values (?, ?)”);     QVariantList ints;     ints << 10 << 11 << 12 << 13;     q.addBindValue(ints);     QVariantList names;     names << “xiaoming” << “xiaoliang” << “xiaogang” << QVariant(QVariant::String);     //最后一个是空字符串,应与前面的格式相同    q.addBindValue(names);     if (!q.execBatch()) //进行批处理,如果出错就输出错误         qDebug() << q.lastError();     //下面输出整张表     QSqlQuery query;     query.exec(“select * from student”);     while(query.next())     {         int id = query.value(0).toInt();         QString name = query.value(1).toString();         qDebug() << id << name;     } }
  • 358. 然后在widget.cpp文件中添加头文件 #include 。 我们在程序中利用列表存储了同一属性的多个值,然后进行了值绑定。最后执行execBatch()函数进行批处理。注意程序中利用QVariant(QVariant::String)来输入空值NULL,因为前面都是QString类型的,所以这里要使用QVariant::String 使格式一致化。 运行效果如下:
  • 359. 事务操作: 事务是数据库的一个重要功能,所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。在Qt中用transaction()开始一个事务操作,用commit()函数或rollback()函数进行结束。commit()表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据库的更新写回到数据库,事务正常结束。rollback()表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态。
  • 360. 如下面的例子: void Widget::on_pushButton_clicked() {     if(QSqlDatabase::database().driver()->hasFeature(QSqlDriver::Transactions))     {     //先判断该数据库是否支持事务操作         QSqlQuery query;         if(QSqlDatabase::database().transaction()) //启动事务操作         {             //             //下面执行各种数据库操作             query.exec(“insert into student values (14, ‘hello’)”);             query.exec(“delete from student where id = 1″);             //             if(!QSqlDatabase::database().commit())             {                 qDebug() << QSqlDatabase::database().lastError(); //提交                 if(!QSqlDatabase::database().rollback())                     qDebug() << QSqlDatabase::database().lastError(); //回滚             }         }         //输出整张表         query.exec(“select * from student”);         while(query.next())             qDebug() << query.value(0).toInt() << query.value(1).toString();     } }
  • 361. 然后在widget.cpp文件中添加头文件 #include 。 QSqlDatabase::database()返回程序前面所生成的连接的QSqlDatabase对象。hasFeature()函数可以查看一个数据库是否支持事务。 运行结果如下:
  • 362. 二十五、Qt数据库(五)QSqlQueryModel 在上一篇的最后我们说到,Qt中使用了自己的机制来避免使用SQL语句,它为我们提供了更简单的数据库操作和数据显示模型。它们分别是只读的QSqlQueryModel,操作单表的QSqlTableModel和以及可以支持外键的QSqlRelationalTableModel。这次我们先讲解QSqlQueryModel。 QSqlQueryModel类为SQL的结果集提供了一个只读的数据模型,下面我们先利用这个类进行一个最简单的操作。 我们新建Qt4 Gui Application工程,我这里工程名为queryModel ,然后选中QtSql模块,Base class选QWidget。工程建好后,添加C++ Header File ,命名为database.h,更改其内容如下:
  • 363. #ifndef DATABASE_H #define DATABASE_H #include #include static bool createConnection() {     QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”);     db.setDatabaseName(“database.db”);     if(!db.open()) return false;     QSqlQuery query;     query.exec(“create table student (id int primary key, name vchar)”);     query.exec(“insert into student values (0,’yafei0′)”);     query.exec(“insert into student values (1,’yafei1′)”);     query.exec(“insert into student values (2,’yafei2′)”);     return true; } #endif // DATABASE_H 这里我们使用了db.setDatabaseName(“database.db”); ,我们没有再使用以前的内存数据库,而是使用了真实的文件,这样后面对数据库进行的操作就能保存下来了。然后进入main.cpp,将其内容更改如下:
  • 364. #include #include “widget.h” #include “database.h” int main(int argc, char *argv[]) {     QApplication a(argc, argv);     if(!createConnection())         return 1;     Widget w;     w.show();     return a.exec(); }
  • 365. 下面我们在widget.ui中添加一个显示为“查询”的Push Button,并进入其单击事件槽函数,更改如下: void Widget::on_pushButton_clicked() {     QSqlQueryModel *model = new QSqlQueryModel;     model->setQuery(“select * from student”);     model->setHeaderData(0, Qt::Horizontal, tr(“id”));     model->setHeaderData(1, Qt::Horizontal, tr(“name”));     QTableView *view = new QTableView;     view->setModel(model);     view->show(); } 我们新建了QSqlQueryModel类对象model,并用setQuery()函数执行了SQL语句“(“select * from student”);”用来查询整个student表的内容,可以看到,该类并没有完全避免SQL语句。然后我们设置了表中属性显示时的名字。最后我们建立了一个视图view,并将这个model模型关联到视图中,这样数据库中的数据就能在窗口上的表中显示出来了。 我们在widget.cpp中添加头文件: #include #include
  • 366. 我们运行程序,并按下“查询”按键,效果如下:
  • 367. 我们在工程文件夹下查看数据库文件:
  • 368. 下面我们利用这个模型来操作数据库。 1.我们在void Widget::on_pushButton_clicked()函数中添加如下代码: int column = model->columnCount(); //获得列数 int row = model->rowCount();    // 获得行数 QSqlRecord record = model->record(1); //获得一条记录 QModelIndex index = model->index(1,1);   //获得一条记录的一个属性的值 qDebug() << “column num is:” << column << endl             << “row num is:” << row << endl             <<”the second record is:” << record << endl             << “the data of index(1,1) is:”<< index.data();
  • 369. 在我们在widget.cpp中添加头文件: #include #include #include 此时运行程序,效果如下:
  • 370. 2.当然我们在这里也可以使用前面介绍过的query执行SQL语句。 例如我们在void Widget::on_pushButton_clicked()函数中添 加如下代码: QSqlQuery query = model->query(); query.exec(“select name from student where id = 2 “); query.next(); qDebug() << query.value(0).toString(); 这样就可以输出表中的值了,你可以运行程序测试一下。
  • 371. 3.当我们将函数改为如下。 void Widget::on_pushButton_clicked() {     QSqlQueryModel *model = new QSqlQueryModel;     model->setQuery(“select * from student”);     model->setHeaderData(0, Qt::Horizontal, tr(“id”));     model->setHeaderData(1, Qt::Horizontal, tr(“name”));     QTableView *view = new QTableView;     view->setModel(model);     view->show();   QSqlQuery query = model->query();     query.exec(“insert into student values (10,’yafei10′)”);     //插入一条记录 }
  • 372. 这时我们运行程序,效果如下:我们发现表格中并没有增加记录,怎么回事呢? 我们关闭程序,再次运行,效果如下:
  • 373. 发现这次新的记录已经添加了。在上面我们执行了添加记录的 SQL语句,但是在添加记录之前,就已经进行显示了,所以我们 的更新没能动态的显示出来。为了能让其动态地显示我们的更 新,我们可以将函数更改如下:
  • 374. void Widget::on_pushButton_clicked() {     QSqlQueryModel *model = new QSqlQueryModel;     model->setQuery(“select * from student”);     model->setHeaderData(0, Qt::Horizontal, tr(“id”));     model->setHeaderData(1, Qt::Horizontal, tr(“name”));     QTableView *view = new QTableView;     view->setModel(model);     view->show(); QSqlQuery query = model->query();     query.exec(“insert into student values (20,’yafei20′)”);     //插入一条记录    model->setQuery(“select * from student”); //再次查询整张表     view->show(); //再次进行显示 }
  • 375. 这时运行程序,效果如下:可以看到,这时已经将新添的记录显示出来了。
  • 376. 刚开始我们就讲到,这个模型默认是只读的,所以我们在窗口上并 不能对表格中的内容进行修改。但是我们可以创建自己的模型,然 后按照我们自己的意愿来显示数据和修改数据。要想使其可读写, 需要自己的类继承自QSqlQueryModel,并且重写setData() 和 flags() 两个函数。如果我们要改变数据的显示,就要重写data() 函数。 下面的例子中我们让student表的id属性列显示红色,name属性 可编辑。
  • 377. 1.我们在工程中添加C++ Class,然后Class name设为MySqlQueryModel,Base Class设为QSqlQueryModel,如下:
  • 378. 2.我们将mysqlquerymodel.h中的内容更改如下: class MySqlQueryModel : public QSqlQueryModel { public:     MySqlQueryModel();     //下面三个函数都是虚函数,我们对其进行重载     Qt::ItemFlags flags(const QModelIndex &index) const;     bool setData(const QModelIndex &index, const QVariant &value, int role);     QVariant data(const QModelIndex &item, int role=Qt::DisplayRole) const;     // private:     bool setName(int studentId, const QString &name);     void refresh(); };
  • 379. 然后将mysqlquerymodel.cpp文件更改如下: #include “mysqlquerymodel.h” #include #include MySqlQueryModel::MySqlQueryModel() { } Qt::ItemFlags MySqlQueryModel::flags(         const QModelIndex &index) const //返回表格是否可更改的标志 {     Qt::ItemFlags flags = QSqlQueryModel::flags(index);     if (index.column() == 1) //第二个属性可更改         flags |= Qt::ItemIsEditable;     return flags; }
  • 380. bool MySqlQueryModel::setData(const QModelIndex &index, const QVariant &value, int /* role */)         //添加数据 {     if (index.column() < 1 || index.column() > 2)         return false; QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);     int id = data(primaryKeyIndex).toInt(); //获取id号 clear(); bool ok;     if (index.column() == 1) //第二个属性可更改         ok = setName(id, value.toString());   refresh();     return ok; }
  • 381. void MySqlQueryModel::refresh() //更新显示 {     setQuery(“select * from student”);     setHeaderData(0, Qt::Horizontal, QObject::tr(“id”));     setHeaderData(1, Qt::Horizontal, QObject::tr(“name”)); } bool MySqlQueryModel::setName(int studentId, const QString &name) //添加name属性的值 {     QSqlQuery query;     query.prepare(“update student set name = ? where id = ?”);     query.addBindValue(name);     query.addBindValue(studentId);     return query.exec(); } QVariant MySqlQueryModel::data(const QModelIndex &index, int role) const         //更改数据显示样式 {     QVariant value = QSqlQueryModel::data(index, role);    if (role == Qt::TextColorRole && index.column() == 0)         return qVariantFromValue(QColor(Qt::red)); //第一个属性的字体颜色为红色     return value; }
  • 382. 在widget.cpp文件中添加头文件:#include“mysqlquerymodel.h” 然后更改函数如下: void Widget::on_pushButton_clicked() {     QSqlQueryModel *model = new QSqlQueryModel;     model->setQuery(“select * from student”);     model->setHeaderData(0, Qt::Horizontal, tr(“id”));     model->setHeaderData(1, Qt::Horizontal, tr(“name”));     QTableView *view = new QTableView;     view->setModel(model);     view->show(); MySqlQueryModel *myModel = new MySqlQueryModel; //创建自己模型的对象     myModel->setQuery(“select * from student”);     myModel->setHeaderData(0, Qt::Horizontal, tr(“id”));     myModel->setHeaderData(1, Qt::Horizontal, tr(“name”));     QTableView *view1 = new QTableView;     view1->setWindowTitle(“mySqlQueryModel”); //修改窗口标题     view1->setModel(myModel);     view1->show(); }
  • 383. 运行效果如下:
  • 384. 二十六、Qt数据库(六)QSqlTableModel 在上一篇我们讲到只读的QSqlQueryModel也可以使其可编辑,但是 很麻烦。Qt提供了操作单表的QSqlTableModel,如果我们需要对表 的内容进行修改,那么我们就可以直接使用这个类。 QSqlTableModel,该类提供了一个可读写单张SQL表的可编辑数据 模型。我们下面就对其的几个常用功能进行介绍,分别是修改,插 入,删除,查询,和排序。 在开始讲之前,我们还是新建Qt4 Gui Application工程,我这里工 程名为tableModel ,然后选中QtSql模块,Base class选QWidget。 工程建好后,添加C++ Header File ,命名为database.h,更改其 内容如下:
  • 385. #ifndef DATABASE_H #define DATABASE_H #include #include #include static bool createConnection() {     QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”);     db.setDatabaseName(“database.db”);     if(!db.open()) return false;     QSqlQuery query;     query.exec(QObject::tr(“create table student (id int primary key, name vchar)”));     query.exec(QObject::tr(“insert into student values (0,’刘明’)”));     query.exec(QObject::tr(“insert into student values (1,’陈刚’)”));     query.exec(QObject::tr(“insert into student values (2,’王红’)”));     return true; } #endif // DATABASE_H
  • 386. 为了在数据库中能使用中文,我们使用了QObject类的tr()函数。 而在下面的main()函数中我们也需要添加相应的代码来支持中文。 然后将main.cpp的文件更改如下: #include #include “widget.h” #include “database.h” #include int main(int argc, char *argv[]) {     QApplication a(argc, argv);     QTextCodec::setCodecForTr(QTextCodec::codecForLocale());     if(!createConnection())         return 1;     Widget w;     w.show();     return a.exec(); }
  • 387. 下面我们打开widget.ui,设计界面如下:
  • 388. 其中的部件有Table View 和 Line Edit,其余是几个按钮部件。 然后在widget.h中加入头文件: #include 在private中声明对象:QSqlTableModel *model; 因为我们要在不同的函数中使用model对象,所以我们在这里声明它。 我们到widget.cpp文件中的构造函数里添加如下代码: model = new QSqlTableModel(this); model->setTable(“student”); model->setEditStrategy(QSqlTableModel::OnManualSubmit); model->select(); //选取整个表的所有行 // model->removeColumn(1); //不显示name属性列,如果这时添加记录,则该属性的值添加不上 ui->tableView->setModel(model); // ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);    //使其不可编辑
  • 389. 此时运行程序,效果如下:可以看到,这个模型已经完全脱离了SQL语句,我们只需要执行 select()函数就能查询整张表。上面有两行代码被注释掉了,你 可以取消注释,测试一下它们的作用。
  • 390. 第一,修改操作。 1.我们进入“提交修改”按钮的单击事件槽函数,修改如下: void Widget::on_pushButton_clicked() //提交修改 {     model->database().transaction(); //开始事务操作     if (model->submitAll()) {         model->database().commit(); //提交     } else {         model->database().rollback(); //回滚         QMessageBox::warning(this, tr(“tableModel”),                              tr(“数据库错误: %1″)                              .arg(model->lastError().text()));     } } 这里用到了事务操作,真正起提交操作的是model->submitAll()一 句,它提交所有更改。
  • 391. 2.进入“撤销修改”按钮单击事件槽函数,并更改如下: void Widget::on_pushButton_2_clicked() //撤销修改 {      model->revertAll(); } 它只有简单的一行代码。 我们需要在widget.cpp文件中添加头文件: #include #include 此时运行程序,效果如下:
  • 392. 我们将“陈刚”改为“李强”,如果我们点击“撤销修改”,那么它就会重新改为“陈刚”,而当我们点击“提交修改”后它就会保存到数据库,此时再点击“撤销修改”就修改不回来了。 可以看到,这个模型可以将所有修改先保存到model中,只有当我们执行提交修改后,才会真正写入数据库。当然这也是因为我们在最开始设置了它的保存策略: model->setEditStrategy(QSqlTableModel::OnManualSubmit); OnManualSubmit表明我们要提交修改才能使其生效。
  • 393. 第二,查询操作。 1.我们进入“查询”按钮的单击事件槽函数,更改如下: void Widget::on_pushButton_7_clicked() //查询 {     QString name = ui->lineEdit->text();     model->setFilter(QObject::tr(“name = ‘%1′”).arg(name)); //根据姓名进行筛选     model->select(); //显示结果 } 我们使用setFilter()函数进行关键字筛选,这个函数是对整个结 果集进行查询。为了使用变量,我们使用了QObject类的tr()函数。
  • 394. 2.我们进入“返回全表”按钮的单击事件槽函数,更改如下: void Widget::on_pushButton_8_clicked() //返回全表 {     model->setTable(“student”);   //重新关联表     model->select();   //这样才能再次显示整个表的内容 } 为了再次显示整个表的内容,我们需要再次关联这个表。运行效果 如下:我们输入一个姓名,点 击“查询”按钮后,就 可以显示该记录了。再 点击“返回全表”按钮 则返回。
  • 395. 第三,排序操作。 我们分别进入“按id升序排列”和“按id降序排列”按钮的单击事 件槽函数,更改如下: void Widget::on_pushButton_5_clicked() //升序 {     model->setSort(0,Qt::AscendingOrder); //id属性,即第0列,升序排列     model->select(); } void Widget::on_pushButton_6_clicked() //降序 {     model->setSort(0,Qt::DescendingOrder);     model->select(); }
  • 396. 我们这里使用了setSort()函数进行排序,它有两个参数,第一个 参数表示按第几个属性排序,表头从左向右,最左边是第0个属 性,这里就是id属性。第二个参数是排序方法,有升序和降序两种。 运行程序,效果如下:这是按下“按id降序排列”按钮后的效果。
  • 397. 第四,删除操作。 我们进入“删除选中行”按钮单击事件槽函数,进行如下更改: void Widget::on_pushButton_4_clicked() //删除当前行 {     int curRow = ui->tableView->currentIndex().row();     //获取选中的行     model->removeRow(curRow);     //删除该行     int ok = QMessageBox::warning(this,tr(“删除当前行!”),tr(“你确定”                                                            “删除当前行吗?”),                          QMessageBox::Yes,QMessageBox::No);     if(ok == QMessageBox::No)     {        model->revertAll(); //如果不删除,则撤销     }     else model->submitAll(); //否则提交,在数据库中删除该行 }
  • 398. 这里删除行的操作会先保存在model中,当我们执行了submitAll()函数后才会真正的在数据库中删除该行。这里我们使用了一个警告框来让用户选择是否真得要删除该行。 运行效果如下:
  • 399. 我们点击第二行,然后单击“删除选中行”按钮,出现了警告框。 这时你会发现,表中的第二行前面出现了一个小感叹号,表明该行 已经被修改了,但是还没有真正的在数据库中修改,这时的数据有 个学名叫脏数据(Dirty Data)。当我们按钮“Yes”按钮后数据库 中的数据就会被删除,如果按下“No”,那么更改就会取消。
  • 400. 第五,插入操作。 我们进入“添加记录”按钮的单击事件槽函数,更改如下: void Widget::on_pushButton_3_clicked() //插入记录 {     int rowNum = model->rowCount(); //获得表的行数    int id = 10;     model->insertRow(rowNum); //添加一行     model->setData(model->index(rowNum,0),id);     //model->submitAll(); //可以直接提交 } 我们在表的最后添加一行,因为在student表中我们设置了id号是 主键,所以这里必须使用setData函数给新加的行添加id属性的 值,不然添加行就不会成功。这里可以直接调用submitAll()函数 进行提交,也可以利用“提交修改”按钮进行提交。
  • 401. 运行程序,效果如下:按下“添加记录”按钮后,就添加了一行,不过在该行的前面 有个星号,如果我们按下“提交修改”按钮,这个星号就会消失。 当然,如果我们将上面代码里的提交函数的注释去掉,也就不会 有这个星号了。
  • 402. 可以看到这个模型很强大,而且完全脱离了SQL语句,就算你不怎 么懂数据库,也可以利用它进行大部分常用的操作。我们也看到 了,这个模型提供了缓冲区,可以先将修改保存起来,当我们执行 提交函数时,再去真正地修改数据库。当然,这个模型比前面的模 型更高级,前面讲的所有操作,在这里都能执行。
  • 403. 二十七、Qt数据库(七)QSqlRelationalTableModel 讲完QSqlTableModel了,我们这次讲这个类的扩展类QSqlRelationalTableModel,它们没有太大的不同,唯一的就是后者在前者的基础之上添加了外键(或者叫外码)的支持。 QSqlRelationalTableModel,该类为单张的数据库表提供了一个可编辑的数据模型,它支持外键。 我们还是新建Qt4 Gui Application工程,我这里工程名为relationalTableModel ,然后选中QtSql模块,Base class选QWidget。工程建好后,添加C++ Header File ,命名为database.h,更改其内容如下:
  • 404. #ifndef DATABASE_H #define DATABASE_H #include #include static bool createConnection() {     QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”);     db.setDatabaseName(“database.db”);     if(!db.open()) return false;     QSqlQuery query;     query.exec(“create table student (id int primary key, name vchar,course int)”);     query.exec(“insert into student values (1,’yafei0′,1)”);     query.exec(“insert into student values (2,’yafei1′,1)”);     query.exec(“insert into student values (3,’yafei2′,2)”);    query.exec(“create table course (id int primary key, name vchar, teacher vchar)”);     query.exec(“insert into course values (1,’Math’,'yafeilinux1′)”);     query.exec(“insert into course values (2,’English’,'yafeilinux2′)”);     query.exec(“insert into course values (3,’Computer’,'yafeilinux3′)”);     return true; } #endif // DATABASE_H
  • 405. 我们在这里建立了两个表,student表中有一项是course,它是int型的,而course表的主键也是int型的。如果要将course项和course表进行关联,它们的类型就必须相同,一定要注意这一点。 然后将main.cpp中的内容更改如下: #include #include “widget.h” #include “database.h” int main(int argc, char *argv[]) {     QApplication a(argc, argv);     if(!createConnection()) return 1;     Widget w;     w.show();     return a.exec(); }
  • 406. 我们在widget.h中添加头文件: #include 然后在private中声明对象:    QSqlRelationalTableModel *model; 我们在widget.ui中添加一个Table View部件到窗体上,然后到widget.cpp中的 构造函数里添加如下代码: model = new QSqlRelationalTableModel(this);     model->setEditStrategy(QSqlTableModel::OnFieldChange); //属性变化时写入数据库     model->setTable(“student”);     model->setRelation(2,QSqlRelation(“course”,”id”,”name”));     //将student表的第三个属性设为course表的id属性的外键,并将其显示为course表的name属性的值     model->setHeaderData(0, Qt::Horizontal, QObject::tr(“ID”));     model->setHeaderData(1, Qt::Horizontal, QObject::tr(“Name”));     model->setHeaderData(2, Qt::Horizontal, QObject::tr(“Course”));     model->select();     ui->tableView->setModel(model); 我们修改了model的提交策略,OnFieldChange表示只要属性被改动就马上写入 数据库,这样就不需要我们再执行提交函数了。setRelation()函数实现了创建外 键,注意它的格式就行了。
  • 407. 运行效果如下:可以看到Course属性已经不再是编号,而是具体的课程了。关 于外键,你也应该有一定的认识了吧,说简单点就是将两个相 关的表建立一个桥梁,让它们关联起来。
  • 408. 那么我们也希望,如果用户更改课程属性,那么他只能在课程表中有的课程中进行选择,而不能随意填写课程。在Qt中的QSqlRelationalDelegate委托类就能实现这个功能。我们只需在上面的构造函数的最后添加一行代码: ui->tableView->setItemDelegate(new QSqlRelationalDelegate(ui->tableView)); 添加代理(委托),在我这里不知为什么会出现SqlRelationalDelegate is not a type name的提示,不过可以编译通过。 我们需要在widget.cpp中添加头文件: #include
  • 409. 运行效果如下:可以看到这时修改Course属性时,就会出现一个下拉框,只能选择course表中的几个值。 而利用这个类来操作数据库,与前面讲到的QSqlTableModel没有区别,这就 不再重复。这几篇文章一共讲了好几种操作数据库的方法,到底应该使用哪个呢?那就看你的需求了,根据这几种方法的特点进行选择吧。
  • 410. 二十八、Qt数据库(八)XML(一) 在Qt中提供了QtXml模块实现了对XML数据的处理,我们在Qt帮助中 输入关键字QtXml Module,可以看到该模块的类表。在这里我们可以看到所有相关的类,它们主要是服务于两种操 作XML文档的方法:DOM和SAX。Dom(Document Object Model, 即文档对象模型)把XML文档转换成应用程序可以遍历的树形结 构,这样便可以随机访问其中的节点。它的缺点是需要将整个XML 文档读入内存,消耗内存较多。对于SAX我们放到后面再讲。
  • 411. 除了上面的两种方法外,Qt还提供了简单的QXmlStreamReader和QXmlStreamWriter对XML文档进行读写。下面我们先介绍使用DOM的方式来操作XML文档。 下面是一个规范的XML文档:   //XML说明   //根元素   //library元素的第一个子元素,“id”是其属性 Qt   //book元素的子元素,“Qt”是元素的文本 shiming   //book元素的子元素,title元素的兄弟元素   //结束标记名 Linux yafei (我们为了讲述方便使用了//注释,其实XML文档中是没有这些注释的)
  • 412. 可以看到,一个规范的XML文档,是用XML说明开始的,主要由各元 素组成。XML文档第一个元素就是根元素,XML文档必须有且只有一 个根元素。元素是可以嵌套的。 下面我们就使用程序读出该文档中所有信息。 在Qt Creator中新建控制台工程Qt4 Console Application,工程 名为xml01,在选择模块页选中QtXml(如果在这里没有添加,就需 要在工程文件中手动添加QT += xml )。下面我们更改main.cpp的 内容如下:
  • 413. #include #include int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QDomDocument doc;   //新建QDomDocument类对象,它代表一个XML文档 QFile file(“my.xml”);   //建立指向“my.xml”文件的QFile对象 if (!file.open(QIODevice::ReadOnly)) return 0;  //以只读方式打开 if (!doc.setContent(&file)) { file.close(); return 0; } //将文件内容读到doc中 file.close(); //关闭文件 QDomNode firstNode = doc.firstChild();//获得doc的第一个节点,即XML说明 qDebug() << firstNode.nodeName()   //输出XML说明 << firstNode.nodeValue(); return a.exec(); }
  • 414. 我们先运行一下程序。然后在工程文件夹的debug文件夹下单击鼠 标右键,新建文本文档,改名为“my.xml”,这里一定要注意把原 来的“.txt”后缀改为“.xml” 。然后我们利用记事本打开该文 件,并将上面的XML文档的信息写入其中,不要写注释信息,然后 保存。
  • 415. 再次运行程序,效果如下:
  • 416. 我们不愿意让输出信息出现双引号,可以更改程序代码: qDebug() << qPrintable(firstNode.nodeName())   //输出XML说明 << qPrintable(firstNode.nodeValue()); 这里利用了qPrintable()函数。效果如下:
  • 417. 下面我们在return a.exec();代码前继续添加代码: QDomElement docElem = doc.documentElement();  //返回根元素 QDomNode n = docElem.firstChild();   //返回根节点的第一个子节点 while(!n.isNull()) {   //如果节点不为空 if (n.isElement())  //如果节点是元素 { QDomElement e = n.toElement();  //将其转换为元素 qDebug() << qPrintable(e.tagName())   //返回元素标记 << qPrintable(e.attribute(“id”));  //返回元素id属性的值 } n = n.nextSibling();  //下一个兄弟节点 }
  • 418. 这样便能输出根元素及其子元素了。我们这里使用了firstChild() 函数和nextSibling()函数,然后利用while()循环来实现对所有子 元素的遍历。运行结果如下:
  • 419. 下面我们更改if()语句中的代码,用另一种方法遍历book元素的所有子元素。 if (n.isElement())  //如果节点是元素 { QDomElement e = n.toElement();  //将其转换为元素 qDebug() << qPrintable(e.tagName())   //返回元素标记 << qPrintable(e.attribute(“id”));  //返回元素id属性的值 QDomNodeList list = e.childNodes(); //获得元素e的所有子节点的列表 for(int i=0; i
  • 420. 这里使用了childNodes()函数获得了元素所有子节点的列表,然后 通过遍历这个列表实现了遍历其所有子元素。运行程序,效果如下:
  • 421. 通过上面的例子,我们实现了对一个XML文档的读取。可以看到,在QDom中,是将整个XML文件读到内存中的doc对象中的。然后使用节点(QDomNode )操作doc对象,像XML说明,元素,属性,文本等等都被看做是节点,这样就使得操作XML文档变得很简单,我们只需通过转换函数将节点转换成相应的类型,如 QDomElement e = n.toElement();
  • 422. 二十九、Qt数据库(九)XML(二) 在上一节中我们用手写的方法建立了一个XML文档,并且用DOM的方法对其进行了读取。现在我们使用代码来创建那个XML文档,并且对它实现查找,更新,插入等操作。 首先,我们新建Qt4 Gui Application工程,工程名为xml02,然后添加QtXml模块,我们选择QWidget为Base class 。 为了在程序中可以使用中文,我们先在main.cpp文件中添加头文件: #include 然后在main()函数中添加一行代码: QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
  • 423. 然后到widget.ui文件中,将界面设计如下: 其中用到的部件有Push Button,ListWidget,Label和Line Edit 。
  • 424. 我们再到widget.cpp文件中,添加头文件:#include 然后在构造函数中添加代码如下: QFile file(“my.xml”); if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return ; //只写方式打开,并清空以前的信息 QDomDocument doc; QDomProcessingInstruction instruction;  //添加处理指令 instruction = doc.createProcessingInstruction(“xml”,“version=\”1.0\” encoding=\”UTF-8\”"); doc.appendChild(instruction); QDomElement root = doc.createElement(tr(“书库”)); doc.appendChild(root); //添加根元素
  • 425. //添加第一个book元素及其子元素 QDomElement book = doc.createElement(tr(“图书”)); QDomAttr id = doc.createAttribute(tr(“编号”)); QDomElement title = doc.createElement(tr(“书名”)); QDomElement author = doc.createElement(tr(“作者”)); QDomText text; id.setValue(tr(“1″)); book.setAttributeNode(id); text = doc.createTextNode(tr(“Qt”)); title.appendChild(text); text = doc.createTextNode(tr(“shiming”)); author.appendChild(text); book.appendChild(title); book.appendChild(author); root.appendChild(book);
  • 426. //添加第二个book元素及其子元素 book = doc.createElement(tr(“图书”)); id = doc.createAttribute(tr(“编号”)); title = doc.createElement(tr(“书名”)); author = doc.createElement(tr(“作者”)); id.setValue(tr(“2″)); book.setAttributeNode(id); text = doc.createTextNode(tr(“Linux”)); title.appendChild(text); text = doc.createTextNode(tr(“yafei”)); author.appendChild(text); book.appendChild(title); book.appendChild(author); root.appendChild(book); QTextStream out(&file); doc.save(out,4);   //将文档保存到文件,4为子元素缩进字符数 file.close();
  • 427. 这样我们就建立起了一个XML文档,过程并不复杂,只要注意一下 元素的父子关系就可以了。最后我们用save()函数将数据从doc中 保存到文件中。 下面我们读取整个XML文档,我们从widget.ui中单击“查看全部信 息”按钮,进入它的单击事件槽函数,并更改如下: void Widget::on_pushButton_clicked()  //显示全部 { ui->listWidget->clear(); //先清空显示 QFile file(“my.xml”); if (!file.open(QIODevice::ReadOnly)) return ; QDomDocument doc; if (!doc.setContent(&file)) { file.close(); return ; } file.close();
  • 428. //返回根节点及其子节点的元素标记名 QDomElement docElem = doc.documentElement();  //返回根元素 QDomNode n = docElem.firstChild();   //返回根节点的第一个子节点 while(!n.isNull())  //如果节点不为空 { if (n.isElement()) //如果节点是元素 { QDomElement e = n.toElement(); //将其转换为元素 ui->listWidget->addItem(e.tagName()+e.attribute(tr(“编号”))); QDomNodeList list = e.childNodes(); for(int i=0; ilistWidget->addItem(“   “+node.toElement().tagName() +” : “+node.toElement().text()); } } n = n.nextSibling();  //下一个兄弟节点 } }
  • 429. 这里的代码就是上一节我们讲的读取XML文档所用的代码,只是将 以前的qDebug()输出换成了在listWidget上进行输出。运行程序, 单击按键,效果如下:
  • 430. 下面我们加入添加功能,进入“添加”按键的单击事件槽函数,更改如下: void Widget::on_pushButton_5_clicked()  //添加 { ui->listWidget->clear(); //我们先清空显示,然后显示“无法添加!” ui->listWidget->addItem(tr(“无法添加!”)); QFile file(“my.xml”); if (!file.open(QIODevice::ReadOnly)) return; QDomDocument doc; if (!doc.setContent(&file)) { file.close(); return; } file.close();
  • 431. QDomElement root = doc.documentElement(); QDomElement book = doc.createElement(tr(“图书”)); QDomAttr id = doc.createAttribute(tr(“编号”)); QDomElement title = doc.createElement(tr(“书名”)); QDomElement author = doc.createElement(tr(“作者”)); QDomText text; QString num = root.lastChild().toElement().attribute(tr(“编号”)); int count = num.toInt() +1; id.setValue(QString::number(count)); //我们获得了最后一个孩子结点的编号,然后加1,便是新的编号 book.setAttributeNode(id); text = doc.createTextNode(ui->lineEdit_2->text()); //注意:你那可能不是lineEdit_2。
  • 432. title.appendChild(text); text = doc.createTextNode(ui->lineEdit_3->text()); author.appendChild(text); book.appendChild(title); book.appendChild(author); root.appendChild(book); if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return ; QTextStream out(&file); doc.save(out,4);   //将文档保存到文件,4为子元素缩进字符数 file.close(); ui->listWidget->clear(); //最后更改显示为“添加成功!” ui->listWidget->addItem(tr(“添加成功!”)); } 这里先用只读方式打开XML文件,将其读入doc中,然后关闭。我们将新的节点 加入到最后面,并使其“编号”为以前的最后一个节点的编号加1。最后我们再用只 写的方式打开XML文件,将修改完的doc写入其中。运行程序,效果如下:
  • 433. 我们先添加一个节点,然后 再查看全部信息,发现确实 已经添加成功了。
  • 434. 查找,删除,更新操作。 因为这三个功能都要先利用“编号”进行查找,所以我们放在一起实现。我们先 在widget.h文件中添加一个函数声明: void doXml(const QString operate); 它有一个参数,用来传递所要进行的操作。 然后到widget.cpp文件中对它进行定义: void Widget::doXml(const QString operate) { ui->listWidget->clear(); ui->listWidget->addItem(tr(“没有找到相关内容!”)); QFile file(“my.xml”); if (!file.open(QIODevice::ReadOnly)) return ; QDomDocument doc; if (!doc.setContent(&file)) { file.close(); return ; } file.close(); QDomNodeList list = doc.elementsByTagName(tr(“图书”)); //以标签名进行查找
  • 435. for(int i=0; ilineEdit->text()) {  //如果元素的“编号”属性值与我们所查的相同 if(operate == “delete”)  //如果是删除操作 { QDomElement root = doc.documentElement();  //取出根节点 root.removeChild(list.at(i));  //从根节点上删除该节点 QFile file(“my.xml”);     //保存更改 if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return ; QTextStream out(&file); doc.save(out,4); file.close(); ui->listWidget->clear(); ui->listWidget->addItem(tr(“删除成功!”)); } else if(operate == “update”)  //如果是更新操作 { QDomNodeList child = list.at(i).childNodes();
  • 436. //找到它的所有子节点,就是“书名”和“作者” child.at(0).toElement().firstChild().setNodeValue(ui->lineEdit_2->text()); //将它子节点的首个子节点(就是文本节点)的内容更新 child.at(1).toElement().firstChild().setNodeValue(ui->lineEdit_3->text()); QFile file(“my.xml”);     //保存更改 if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return ; QTextStream out(&file); doc.save(out,4);   //将文档保存到文件,4为子元素缩进字符数 file.close(); ui->listWidget->clear(); ui->listWidget->addItem(tr(“更新成功!”)); } else if(operate == “find”)  //如果是查找操作 { ui->listWidget->clear(); ui->listWidget->addItem(e.tagName()+e.attribute(tr(“编号”))); QDomNodeList list = e.childNodes(); for(int i=0; ilistWidget->addItem(“   “+node.toElement().tagName() +” : “+node.toElement().text()); } } } } }
  • 437. 然后我们分别进入“查找”,“删除”,“更新”三个按键的单击事件槽函数,更改如下: void Widget::on_pushButton_2_clicked()  //查找 { doXml(“find”); } void Widget::on_pushButton_3_clicked()  //删除 { doXml(“delete”); } void Widget::on_pushButton_4_clicked()  //更新 { doXml(“update”); }
  • 438. 这时,我们运行程序。在“图书编号”中输入1,然后点击“查找”按键,效果如下:
  • 439. 这时我们将下面的“书名”和“作者”进行更改,然后单击“更新 ”按键,效果如下:
  • 440. 再次点击“查找”按键:
  • 441. 然后点击“删除”按键:
  • 442. 再点击“查看全部信息”按键:
  • 443. 三十、Qt数据库(十)XML(三) 下面我们讲述另一种读取XML文档的方法,即SAX 。是的,如果你 只想读取并显示整个XML文档,那么SAX是很好的选择,因为它提 供了比DOM更简单的接口,并且它不需要将整个XML文档一次性读 入内存,这样便可以用来读取较大的文件。我们对SAX不再进行过 多的介绍,因为现在你就可以掌握我们下面要讲的内容了。如果你 对SAX有兴趣,可以到网上查找相关资料。 在Qt的QtXml模块中提供了一个QXmlSimpleReader的类,它便是基 于SAX的XML解析器。这个解析器是基于事件的,但这些事件由它自 身进行关联,我们并不需要进行设置。我们只需知道,当解析器解 析一个XML的元素时,就会执行相应的事件,我们只需要重写这些 事件处理函数,就能让它按照我们想法进行解析。
  • 444. 比如要解析下面的元素: Qt 解析器会依次调用如下事件处理函数:startElement(),characters(),endElement()。 我们可以在startElement()中获得元素名(如“title”)和 属性,在characters()中获得元素中的文本(如“Qt”),在 endElement()中进行一些结束读取该元素时想要进行的操作。 而所有的这些事件处理函数我们都可以通过继承 QXmlDefaultHandler类来重写。
  • 445. 下面我们先看一个简单的例子: 1.新建空工程Empty Qt4 Project ,工程名为xml03 。 2.我们在工程中添加C++ Class,Class name为MySAX ,Base class为QXmlDefaultHandler 3.我们再添加一个main.cpp文件。 4.然后我们在xml03.pro文件中加入一行代码:QT += xml 下面是几个文件的内容:
  • 446. main.cpp文件:
  • 447. mysax.h文件:
  • 448. mysax.cpp文件:
  • 449. (注:上面程序中的注释//开始读写元素,应为//开始读取元素)
  • 450. 我们先运行一下程序,然后将第一节我们建立的“my.xml”文件复 制到我们现在的工程文件夹的debug文件夹下。然后再运行程序, 效果如下:
  • 451. 可以看到文件的解析过程如下: QFile file(fileName); QXmlInputSource inputSource(&file);  //读取文件内容 QXmlSimpleReader reader;   //建立 QXmlSimpleReader对象 reader.setContentHandler(this);  //设置内容处理程序 reader.setErrorHandler(this);  //设置错误处理程序 reader.parse(inputSource);  //解析文件 这里,setContentHandler()就是设置了startElement(), characters(),endElement()等事件的处理程序。而且我们 一般都要设置错误处理程序setErrorHandler(),最后我们使 用parse()来对文件进行解析,在解析过程中会不停地调用事 件处理函数,当然整个调用过程是在内部进行的,不用我们去 进行设置。利用SAX读取XML文档是十分方便快速的。
  • 452. 三十一、Qt Quick Designer介绍 Qt Quick 是一种高级用户界面技术,使用它可轻松地创建供移动 和嵌入式设备使用的动态触摸式界面和轻量级应用程序。三种全新 的技术共同构成了 Qt Quick 用户界面创建工具包:一个改进的Qt Creator IDE、一种新增的简便易学的语言 (QML) 和一个新加入 Qt 库中名为 QtDeclarative 的模块,这些使得 Qt 更加便于不熟 悉 C++ 的开发人员和设计人员使用。
  • 453. 我们到官方网站下载相关软件 http://qt.nokia.com/developer/qt-qtcreator-prerelease 我们需要分别下载Qt及Qt Creator 下载完后,我们先安装qt-win-opensource-4.7.0-beta1-mingw, 安装开始时的设置全部使用默认设置即可。在最后会弹出如下警告 框,我们选择“是”即可。 然后安装qt-creator-win-opensource-2.2.0-beta1,全部保持默 认设置即可。
  • 454. 我们新建一个Qt QML Application,我这里的工程名为“helloWorld”。 熟悉Qt Quick Designer 界面 这是整个 Qt Quick Designer 界面,它由几个面板 组成,下面分别进行 介绍。
  • 455. 主设计面板,也就是我们下面所说的场景。这是我们的主设计区, 所有的项目都要放到这里,当程序执行时,就是显示的这个面板上 的内容。
  • 456. Navigator导航器面板。场景中所有的项目都在这里列出。在这里,我们可以选中一个特定的项目,那么场景中对应的项目也会被选中,我们也可以在这里拖拽项目来更改它们的从属关系,或者点击项目后面的“眼睛”图标来设置它是否可以显示。
  • 457. Properties属性面板。在这里我 们可以设置项目的属性。比如项 目名称,填充颜色,边框颜色, 项目大小和位置,以及项目的缩 放,旋转和不透明度等。
  • 458. Library库面板。在这里提供了 一些常用的部件,我们可以将它 们拖放到场景中来使用。在 Resources子面板中,我们可以 看到我们工程文件夹下的图片等 资源,我们也可以将它们拖拽到 场景中直接使用。当我们以后新 建了组件后,它也会出现在 Library中。
  • 459. 状态面板。这里可以为场景新建或删除一个状态。
  • 460. 设计模式和代码编辑模式的转换,我们可以点击“Edit”图标进入 代码编辑界面。
  • 461. 简单的使用。 1.我们从库面板中拖入一个 Rectangle到场景中,调整 它的大小。然后在属性面板 中更改其ID为“myButton”, 并更改其颜色。将其Radius 属性更改为10,这时它就会 变为圆角了。这时的属性面板 如下。
  • 462. 然后我们从库面板中拖入一个Mouse Area部件到“myButton”上, 注意,要使得Mouse Area部件成为“myButton”的子项目,在导航 器面板中可以看到它们的关系。
  • 463. 这时我们选中了这个Mouse Area部件,在属性面板中将其id改为 “myMouseArea”,然后在Geometry子面板中点击 图标,鼠标 区域填充整个“myButton”。
  • 464. 这时我们在状态面板中点击一下后面的带有加号的方块,新建一个 状态。如下图。
  • 465. 我们在State1状态下,更改场景中的“Hello World”,改变它的 字体大小,并更改颜色。如下图。
  • 466. 然后我们进入Advanced子面板,更改一下Opacity(不透明度)和 Rotation(旋转)的值。如下图。
  • 467. 我们下面点击“Edit”图标,进入代码编辑界面。 在这里我们找到state代码段,添加一行代码 when: myMouseArea.pressed , 如下: states: [ State { name: “State1″ when: myMouseArea.pressed 这里我们省略了其他代码。
  • 468. 此时我们运行程序,效果如下。
  • 469. 然后我们点击按钮,效果如下:
  • 470. 这样这个简单的例子就讲完了。我们可以注意到,当我们在设计器 中更改界面时,编辑器中的代码就自动改变了。其实,如果我们在 编辑器中更改了代码,对应的设计器中的界面也会相应改变的。这 样就实现了真正的所见即所得。在后面的章节中你会进一步体会到 Qt Quick实现一个炫酷界面是那么简单。
  • 471. 三十二、QML组件 在上一节中,我们简单讲述了Qt Quick Designer的使用。现在我们开始讲述程序中生成的代码,然后引出QML组件的概念。这一节也包含了对QML语言的简单讲解。 我们先来看一下上一次生成的代码。 首先看一下整个文件的框架:
  • 472. import Qt 4.6  //QML文件都要以import开头 Rectangle {   //这里定义了一个矩形,以及它包含的所有子项目 id: mainWindow //矩形的id属性,相当于C++中的对象名,需要用小写字母开头 width: 200  //矩形的宽和高 height: 200 //下面是矩形的三个子项目 Text { //这里定义了文本的内容 … } Rectangle { //这里定义了一个矩形,以及该矩形中的所有子项目 … } states: [ //这里是一系列状态 ... ] }
  • 473. 下面我们查看每一个子项目的内容: 文本项目: Text { id: text1 //id属性 x: 66   //在父项目(mainWindow)中的位置 y: 93 text: “Hello World”  //文本内容 } 矩形项目: Rectangle { id: myButton  //属性 x: 50 y: 134 width: 100 height: 27 color: “#8d4848″ radius: 10 MouseArea {  //子项目 id: myMouseArea x: 12 y: 6 width: 100 height: 100 anchors.fill: parent  //填充整个父项目(myButton) anchors.bottomMargin: 0 anchors.topMargin: 0 anchors.leftMargin: 0 anchors.rightMargin: 0 } }
  • 474. 状态项目: states: [  State { name: "State1"   //新建的状态 when: myMouseArea.pressed   //当鼠标按下时启用 PropertyChanges {  //属性改变 target: text1  //目标是text1项目 x: 25       //下面是对text1各属性的改变 y: 48 width: 154 以上除了我们添加的那行代码外,其他的代码全部是自动生成的, 而且格式很规范。其实这些代码我们完全可以自己来写。下面我们 就重写上面的代码,再一点一点得实现整个程序的功能。height: 57 color: "#38883e" rotation: 30 scale: 1 opacity: 0.5 font.bold: false verticalAlignment: "AlignVCenter" horizontalAlignment: "AlignHCenter" font.pointSize: 20 } } ]
  • 475. 1.添加主窗口。 import Qt 4.6 Rectangle{ id:mainWindow; width:200; height:200 } 在QML中,必须有一个根元素,第一个元素即是根元素,它将做为所有项目的父项目。对于项目的属性,可以一个属性写一行,也可以像上面这样,所有的属性都写在一行,不过中间要用英文的“;”分号隔开。对于id属性,它和我们接触过的变量名一样,必须用小写字母或下划线开头,且不能包含字母,数字和下划线以外的字符。
  • 476. 这时我们进入Design界面,其内容如下:
  • 477. 添加文本项目。 因为其是“mainWindow”的子项目,所以Text{}要写在“mainWindow”的两个{}号里面。 我们在下面输入“Tex”,这时会自动弹出待选列表,我们发现列表中第一个就是“Text”,所有我们直接按下回车键,让系统自动补全输入。
  • 478. 可以看到系统自动生成了“id”和“text”两个属性值。我们只需 更改其内容即可。然后我们再添加文本的位置属性。其内容如下: Text { id: myText text: “Hello World!” anchors.centerIn:parent } 这里的anchors是一个布局管理器,它可以固定项目的位置,我们 这里的anchors.centerIn:parent就是表示“myText”要在其父项 目的中心。我们这时再查看Design界面,效果如下:
  • 479. (本页无文本内容)
  • 480. 添加矩形项目。 我们添加一个矩形,作为按钮。它也是“mainWindow”的子项目。而在按钮中还有一个MouseArea子项目,它填充整个按钮,因为只有添加了这个MouseArea,按钮才能接受鼠标点击事件。又因为,MouseArea只是填充了按钮,所以只有在按钮上点击,才会触发鼠标点击事件,而点击其他地方,是没有效果的。代码如下: Rectangle{ id:myButton; width:50; height:30 x:75; y:136; radius:10; color:”blue” MouseArea{id:myMouseArea; anchors.fill:parent} }
  • 481. 这里有个radius属性,它是矩形的半径值,简单的说就是它可以使 矩形的四个角变为弧线。现在我们在看一下Design界面,效果如下:
  • 482. 状态项目。 说是状态项目,更确切的说是“mainWindow”的状态属性,然后在其状态属性中又包含状态项目。 states: [    State {         name: "state1"         PropertyChanges {             target:myText             font.pointSize:20; color:"red"  rotation:30;pacity:0.5        }         when:myMouseArea.pressed    }     ]
  • 483. 这里我们使用了“[ ]”括号,它表示列表属性,也就是说,在方括号中间可以写多个项目作为其属性值,它们需要用英文“,”隔开。例如: [ State{}, State{} ] 因为我们这里只有一个项目,所以也可以省去方括号。在State{}中,我们又使用了PropertyChanges{}项目,它用来改变特定项目的属性值。这里我们先指定了要改变的项目是“myText”,然后再更改其属性值。这里的opacity属性,是不透明度,当为0时表示全透明,为1时表示完全不透明,所有项目的该属性默认值都是1。 下面一句when:myMouseArea.pressed ,表示当鼠标区域被点击时,进入该状态。这里需要说明的是,因为默认的开始状态,就是不做改变时的状态,所以我们要指定什么时候进入我们新建的状态。
  • 484. 其实我们也可以将“state1”作为默认的状态,我们可以在“mainWindow”中添加一行代码:state: “state1″  这样就表明了使“state1”作为初始状态。但这时我们要将when:myMouseArea.pressed一行代码删去。 此时我们再进入Design界面,效果如下:
  • 485. 这时整个程序的代码如下: import Qt 4.6 Rectangle{ id:mainWindow; width:200; height:200 Text { id: myText text: “Hello World!” anchors.centerIn:parent } Rectangle{ id:myButton; width:50; height:30 x:75; y:136; radius:10; color:”blue” MouseArea{id:myMouseArea; anchors.fill:parent} } states: [ State { name: "state1" PropertyChanges { target: myText font.pointSize:20; color:"red" rotation:30; opacity:0.5 } when:myMouseArea.pressed } ] }
  • 486. 运行程序,效果如下:
  • 487. 上面我们一步一步重写了整个程序,并在每一步完成时都查看了设 计器中的界面。就像前一节所说的那样,Qt Quick 实现了真正的 可视化编程,代码与界面完全同步,让用户可以直观的看到执行结 果。这个程序很小可以这样实现,但是如果稍大点的程序,所有的 项目的定义都写在一个文件中,就会显得很繁琐。而且,像按钮一 样的部件,我们可能需要多次使用,我们也不愿意重复编写代码。 所以,我们需要将子项目单独来写,这样就形成了组件。
  • 488. QML组件 1.我们在工程中新建Qt QML File,命名为“Button”。 注意:组件的名称的首字母一定要大写。 2.然后我们将Button.qml文件中的内容更改如下: import Qt 4.7 Rectangle { id:myButton; width:50; height:30 radius:10; color:”blue” signal clicked() MouseArea{ id:myMouseArea; anchors.fill:parent onClicked: myButton.clicked() } }
  • 489. 这里我们新建了一个signal信号函数clicked()。当执行该函数时,Button组件就会发出clicked()信号。我们在MouseArea中添加了一行代码: onClicked: myButton.clicked() 其中onClicked关联到MouseArea的clicked()信号,它相当于槽函数,这行代码的效果是,当MouseArea被按下时,就执行“myButton”的clicked()函数。
  • 490. 我们在helloWorld.qml中删除以下代码 Rectangle{ id:myButton; width:50; height:30 x:75; y:136; radius:10; color:”blue” MouseArea{id:myMouseArea; anchors.fill:parent} } 和when:myMouseArea.pressed
  • 491. 我们进入helloWorld.qml的设计器界面。 可以看到,在库面板,已经有Button元件了,我们将一个Button拖入场景中。如下图。
  • 492. 我们回到helloWorld.qml文件中,在新增的代码中添加一行代码。 Button { id: button1 x: 75 y: 135 onClicked:mainWindow.state = “state1″ } 这个onClicked就是对应按钮的clicked信号。当按钮被按下时,我 们让主窗口进入新建的状态。
  • 493. 此时执行程序,效果如下。 我们可以看到,QML组件是可以直接在其他文件中使用的, 我们不需要进行声明,或者包含头文件。而工程中的一个 组件,会自动添加到Library库中供我们使用。我们也看到 了QML中的信号和槽也十分简单。
  • 494. 三十三、QML项目之Image和BorderImage 在这一节里,我们主要介绍QML Items中的Image 和 BorderImage 两个项目。 1.Image图片: 我们新建Qt QML Application,工程名为“myImage”。 2.我们在库面板中拖入一个Image到场景中,在属性面板中我们可以选择一张图片的路径。 其实,更好的方法是, 我们将图片放到工程文 件夹中,这样在库面板 的资源栏中我们就可以 直接看到该图片了,而 且该图片也会显示在工 程文件列表中。
  • 495. 我们将两张图片放到工程文件夹下。如下图。
  • 496. 然后在库面板的资源页面查看添加的图片。我们可以将图片直接拖 入场景。如下图。
  • 497. 在工程文件列表中也会显示这两个图片。
  • 498. 图片平铺方式。 在属性面板中我们可以设置图片的平铺方式Fill Mode。如下图
  • 499. Stretch:默认选择的是Stretch一项,表示拉伸图片。就是说当将图片缩放时会拉伸图片。 其效果如下:
  • 500. 当图片缩放或者旋转时我们也可以选中属性面板中Smooth一项,使 图片变得平滑。效果如下:PreserveAspectFit:拉伸时缩放图片, 总是显示完整图片。
  • 501. PreserveAspectCrop:拉伸时缩放图片,但是可能对图片进行裁剪Tile:平铺图片
  • 502. TileVertically:竖直平铺图片。TileHorizontally:水平平铺图片。
  • 503. 使用网络上的图片。 我们也可以使用网络上的图片,直接在属性面板上更改图片路径为图片的地址即可。
  • 504. 这时,如果你的电脑连接着网络,那么图片会自动下载并显示出 来,效果如下:
  • 505. 但是有时候从网上下载图片是很慢的,所有我们希望在没有下载完图片时,图片区域可以有些提示, 所以我们利用Image的status属性,在Edit代码编辑界面,更改Image段代码如下: Image {         id: image1         Text{id:text1}  //用于显示信息         width: 200         height: 200         fillMode: “Tile”         source: “http://j.imagehost.org/0317/linux.jpg”         states: [         State {   //没有下载完图片时的状态             name: "loading"             when: image1.status != Image.Ready             PropertyChanges {                 target: text1                 text:"loading..."             }         }         ] }
  • 506. 这时运行程序效果如下
  • 507. 2、BorderImage边界图片 边界图片,顾名思义,就是将一张图片作为窗口的边界。它主要的 特点就是,在这里用四条线将一张图片分成了9部分。如下图 这四条线分别用它们到图片各边界的像素值来表示,上下左右依次 是top,bottom,left和right。比如top = 50 就是说离图片上边 界50像素的地方就是上边界线。这样,将图片分为9个区域后,它 们各自就有了不同的平铺规定。下面我们先看例子,再进行总结。
  • 508. 我们在库面板中选中Border Image,拖入场景。 然后在属性面板中输入图片的路径,并设置其各边界线的值均为30。如下图。
  • 509. 这时我们拉伸图片,查看效果。可以看到,图片四个角没有变化,其他区域都被拉伸了。这时我 们再将各边界线的值改为60,然后查看效果。
  • 510. 可以看到,被边界线分到四个角的图片是不被拉伸的。
  • 511. 平铺方式。 在这里我们可以分别指定水平方向的平铺方式(horizontalTileMode属性)和竖直方向的平铺方式(verticalTileMode属性)。平铺方式有以下三种: BorderIamge.Stretch :缩放图像以适合拉伸。(默认值) BorderImage.Repeat :平铺图像,当空间不够时,最后一个图像可能会被裁剪。 BorderImage.Round :平铺图像,但是缩放所有平铺的图像,确保最后一个图像不会被裁剪。 我们上面就是使用的Stretch方式。下面我们改用其他方式,查看 效果。
  • 512. 我们先更改代码如下: BorderImage {         id: borderimage1         horizontalTileMode:BorderImage.Repeat         verticalTileMode:BorderImage.Repeat         border.bottom:30;border.top:30;border.right:30;border.left:30         anchors.fill:parent         source: “colors.png” }
  • 513. 这时运行程序,效果如下:
  • 514. 我们更改平铺方式: horizontalTileMode:BorderImage.Round verticalTileMode:BorderImage.Round 再次运行程序,效果如下:
  • 515. 我们再次更改平铺方式: horizontalTileMode:BorderImage.Round verticalTileMode:BorderImage.Repeat 效果如下:
  • 516. 结论 使用边界图片,我们需要先指定四条边界线,然后指定水平和竖直方向的平铺方式。 图片被四条边界线分为9个区域: 区域5会通过horizontalTileMode和verticalTileMode进行平铺。 区域2,8会通过horizontalTileMode进行平铺。 区域4,6会通过verticalTileMode进行平铺。 区域1,3,7,9的图像不会变化。
  • 517. 三十四、Flipable、Flickable和状态与动画 这一节中我们再次讲解一下QML中状态和动画的知识,然后讲解两 个特效:Flipable翻转效果和Flickable弹动效果。 我们先新建一个Qt QML Application工程,命名为myAnimation。 一、状态与动画 在QML中提供了多个实用的动画元素。其 列表如图
  • 518. 下面我们进行简单的讲解。 1.PropertyAnimation 属性动画。 列表中的NumberAnimation 数值动画,ColorAnimation颜色动画和RotationAnimation旋转动画都继承自PropertyAnimation。 例如将程序代码更改如下: import Qt 4.6 Rectangle { width: 300;height: 200 Rectangle{ id:page; width:50; height:50 x:0; y:100; color:”red” PropertyAnimation on x{ to:100; duration:1000 } } }
  • 519. 其中的属性动画的代码可以用数值动画来代替: NumberAnimation on x{ to:100; duration:1000} 顾名思义,数值动画,就是只能对类型为real的属性进行动画设置。例如上面对x属性,使其在1000ms即一秒的时间里由以前的 0 变为100。效果如下:
  • 520. 我们再将属性动画改为: PropertyAnimation on color{ to:”blue”; duration:1000} 它可以用颜色动画来代替,相当于: ColorAnimation on color{ to:”blue”; duration:1000} 颜色动画只能用于类型是 color的属性。效果如下:
  • 521. 缓冲曲线 我们很多时候不想让动画只是线性的变化,例如实现一些皮球落地,刹车等特殊动画效果,我们就可以在动画中使用缓冲曲线。 例如: NumberAnimation on x{ to:100; duration:1000 easing.type: “InOutElastic”} 这里的曲线类型有很多种,我们可以查看QML PropertyAnimation Element Reference 关键字,在这个帮助文件中列出了所有的曲线 类型。
  • 522. (本页无文本内容)
  • 523. (本页无文本内容)
  • 524. (本页无文本内容)
  • 525. (本页无文本内容)
  • 526. (本页无文本内容)
  • 527. (本页无文本内容)
  • 528. (本页无文本内容)
  • 529. (本页无文本内容)
  • 530. (本页无文本内容)
  • 531. (本页无文本内容)
  • 532. (本页无文本内容)
  • 533. (本页无文本内容)
  • 534. (本页无文本内容)
  • 535. (本页无文本内容)
  • 536. (本页无文本内容)