未完整Qt是什么Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立图形界面所需的所有功能。它是完全面向对象的很容易扩展并且允许真正的组件编程。Qt的发展史1991年 Qt最早由芬兰奇趣科技开发1996年 进入商业领域它也是目前流行的Linux桌面环境KDE的基础2008年 奇趣科技被诺基亚公司收购Qt称为诺基亚旗下的编程基础2012年 Qt又被Digia公司芬兰一家软件公司收购2014年4月 跨平台的集成开发环境Qt Creator3.1.0发布同年5月20日配发了Qt5.3正式版至此Qt实现了对iOS、Android、WP等各平台的全面支持。Qt的优势跨平台几乎支持所有的平台接口简单容易上手学习QT框架对学习其他框架有参考意义。一定程度上简化了内存回收机制开发效率高能够快速的构建应用程序。有很好的社区氛围市场份额在缓慢上升。可以进行嵌入式开发。Qt版本Qt按照不同的版本发行分为商业版和开源版商业版为商业软件提供开发他们提供传统商业软件发行版并且提供在商业有效期内的免费升级和技术支持服务。开源的LGPL协议版本为了开发自有而设计的开放源码软件它提供了和商业版本同样的功能在GNU通用公共许可下它是免费的。目前我们学习上使用的就是这个版本。可以到官网下载最新版本Index of /archive/qthttp://download.qt.io/archive/qt/成功案例Linux桌面环境KDEK Desktop EnvironmentWPS Office 办公软件Skype 网络电话Google Earth 谷歌地球VLC多媒体播放器VirtualBox虚拟机软件等等一个最简单的Qt应用程序main函数#include widget.h #include QApplication int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }Qt系统提供的类头文件没有.h后缀Qt一个类对应一个头文件类名和头文件名一致QApplication应用程序类管理图形用户界面应用程序的控制流和主要设置。是Qt生命一个程序要确保一直运行就肯定至少得有一个循环这就是Qt主消息循环在其中完成来自窗口系统和其它资源的所有事件消息处理和调度。它也处理应用程序的初始化和结束并且提供对话管理。对于任何一个使用Qt的图形用户界面应用程序都正好存在一个QApplication 对象不论这个应用程序在同一时刻有多少个窗口。程序进入消息循环等待对用户输入进行响应。这里main()把控制权转交给QtQt完成事件处理工作当应用程序退出的时候exec()的值就会返回。在exec()中Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。类头文件#include QWidget class MyWidget : public QWidget { //引入Qt信号和槽机制的一个宏 Q_OBJECT public: //构造函数中parent是指父窗口 //如果parent是0那么窗口就是一个顶层的窗口 MyWidget (QWidget *parent 0); ~ MyWidget (); };.pro文件.pro就是工程文件(project)它是qmake自动生成的用于生产makefile的配置文件。类似于VS中的.sln 和vsproj文件。以下是.pro文件的一个案例#引入Qt的模块core gui QT core gui #如果qt版本大于4那么引入widgets模块 greaterThan(QT_MAJOR_VERSION, 4): QT widgets #生成最终文件的文件名可执行文件exe TARGET 01_MyWidget #项目类型生成什么类型的文件可执行程序还是库文件 TEMPLATE app #要编译的源文件列表 SOURCES \ main.cpp \ mywidget.cpp #要编译的头文件列表 HEADERS \ mywidget.h.pro文件的规则1.注释从“#”开始到这一行结束。2.模块引入QT 模块名表示当前项目引入Qt哪些模块。引入模块的意思就简单理解为引入C/C头文件搜索路径如果没引入对应模块就使用该头文件的话会报错说找不到该头文件。当然不必要的模块还是别引入因为引入模块不仅仅是引入头文件搜索路径那么简单还包括引入连接的库等一系列操作会让程序变臃肿。3. 模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择TEMPLATE appapp -建立一个应用程序的makefile。这是默认值所以如果模板没有被指定这个将被使用。lib - 建立一个库的makefile。vcapp - 建立一个应用程序的VisualStudio项目文件。vclib - 建立一个库的VisualStudio项目文件。subdirs -这是一个特殊的模板它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。4.指定生成的应用程序名TARGET QtDemo5.工程中包含的头文件HEADERS include/painter.h6.工程中包含的.ui设计文件FORMS forms/painter.ui7.工程中包含的源文件SOURCES sources/main.cpp sources8.工程中包含的资源文件RESOURCES qrc/painter.qrc9.greaterThan(QT_MAJOR_VERSION, 4): QT widgets这条语句的含义是如果QT_MAJOR_VERSION大于4也就是当前使用的Qt5及更高版本需要增加widgets模块。如果项目仅需支持Qt5也可以直接添加“QT widgets”一句。不过为了保持代码兼容最好还是按照QtCreator生成的语句编写。10.配置信息CONFIG用来告诉qmake关于应用程序的配置信息。CONFIG c11 //使用c11的特性qt5.6以上版本默认使用C11在这里使用“”是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“”那样替换已经指定的所有选项更安全。命名规范类名单词首字母大写单词和单词之间直接连接无需连接字符 。MyClassQPushButton class MainWindowQt中内置的类型头文件和类命名同名。#include QString QSring str; #include QWidget QWidget w;函数名字变量名首字母小写之后每个单词首字母大写单词和单词之间直接连接无需连接字符void connectTheSignal();类的成员变量设置函数用使用 set成员变量名获取成员变量的函数直接用成员变量名如果是bool类型有可能会用一些表示状态的术语如isVisilblehasFocus//普通成员变量设置和获取 void setText(QString text); QString text()const; //bool的成员变量设置和获取 void setEnabled(bool enabled); bool isEnabled()const;QtCreator常用快捷键运行 ctrl R编译 ctrl B帮助文档 F1 点击F1两次跳到帮助界面跳到符号定义 F2 或者ctrl 鼠标点击注释 ctrl/字体缩放 ctrl 鼠标滚轮整行移动代码 ctrl shift ↑或↓自动对齐 ctrl i同名之间的.h和.cpp文件跳转 F4Qt按钮小程序按钮的创建和父子关系在Qt程序中最常用的控件之一就是按钮了首先我们来看下如何创建一个按钮#include QPushButton QPushButton * btn new QPushButton; //设置父亲 btn-setParent(this); //设置文字 btn-setText(德玛西亚); //移动位置 btn-move(100,100); //第二种创建 QPushButton * btn2 new QPushButton(孙悟空,this); //重新指定窗口大小 this-resize(600,400); //设置窗口标题 this-setWindowTitle(第一个项目); //限制窗口大小 this-setFixedSize(600,400);一个按钮其实就是一个QPushButton类的对象如果只是创建出对象是无法显示到窗口中的所以我们需要依赖一个父窗口也就是指定一个父亲利用setParent函数或者按钮创建的时候通过构造函数传参此时我们称两个窗口建立了父子关系。在有父窗口的情况下窗口调用show会显示在父窗口中如果没有父窗口那么窗口调用show显示的会是一个顶层的窗口顶层窗口是能够在任务栏中找到的不依赖于任何一个窗口而独立存在按钮也是继承于QWidget也属于窗口。如果想设置按钮上显示的文字可以用setText移动按钮位置用move。对于窗口而言我们可以修改左上角窗口的标题setWindowTitle重新指定窗口大小resize或者设置固定的窗口大小setFixedSize。Qt窗口坐标体系以左上角为原点0,0以向右的方向为x轴的正方向以向下方向为y轴的正方向。对于嵌套窗口其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。对象树模型QObject是Qt里边绝大部分类的根类1.QObject对象之间是以对象树的形式组织起来的。当两个QObject或子类的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children孩子的list列表中。当父对象析构的时候这个列表中的所有对象也会被析构。注意这里是说父对象和子对象不要理解成父类和子类2.QWidget是能够在屏幕上显示的一切组件的父类QWidget继承自QObject因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。我们向某个窗口中添加了一个按钮或者其他控件建立父子关系当用户关闭这个窗口的时候该窗口就会被析构之前添加到他上边的按钮和其他控件也会被一同析构。这个结果也是我们开发人员所期望的。当然我们也可以手动删除子对象。当子对象析构的时候会发出一个信号destroyed父对象收到这个信号之后就会从children列表中将它剔除。比如当我们删除了一个按钮时其所在的主窗口会自动将该按钮从其子对象列表children中删除并且自动调整屏幕显示按钮在屏幕上消失。当这个窗口析构的时候children列表里边已经没有这个按钮子对象所以我们手动删除也不会引起程序错误。Qt 引入对象树的概念在一定程度上解决了内存问题。3.对象树中对象的顺序是没有定义的。这意味着销毁这些对象的顺序也是未定义的。4.任何对象树中的 QObject对象 delete 的时候如果这个对象有 parent则自动将其从 parent 的children()列表中删除如果有孩子则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次这是由析构顺序决定的。如果QObject在栈上创建Qt 保持同样的行为。正常情况下这也不会发生什么问题。来看下下面的代码片段{ QWidget window; QPushButton quit(Quit, window); }作为父组件的 window 和作为子组件的 quit 都是QObject的子类事实上它们都是QWidget的子类而QWidget是QObject的子类。这段代码是正确的quit 的析构函数不会被调用两次因为标准 C要求局部对象的析构顺序应该按照其创建顺序的相反过程。因此这段代码在超出作用域时会先调用 quit 的析构函数将其从父对象 window 的子对象列表中删除然后才会再调用 window 的析构函数。但是如果我们使用下面的代码{ QPushButton quit(Quit); QWidget window; quit.setParent(window); }情况又有所不同析构顺序就有了问题。我们看到在上面的代码中作为父对象的 window 会首先被析构因为它是最后一个创建的对象。在析构过程中它会调用子对象列表中每一个对象的析构函数也就是说 quit 此时就被析构了。然后代码继续执行在 window 析构之后quit 也会被析构因为 quit 也是一个局部变量在超出作用域的时候当然也需要析构。但是这时候已经是第二次调用 quit 的析构函数了C 不允许调用两次析构函数因此程序崩溃了。由此我们看到Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下所以我们最好从开始就养成良好习惯在 Qt 中尽量在构造的时候就指定 parent 对象并且大胆在堆上创建。信号与槽机制信号各种事件槽 响应信号的动作当某个事件发生后如某个按钮被点击了一下它就会发出一个被点击的信号signal。某个对象接收到这个信号之后就会做一些相关的处理动作称为槽slot。但是Qt对象不会无故收到某个信号要想让一个对象收到另一个对象发出的信号这时候需要建立连接connect系统自带的信号和槽下面我们完成一个小功能上面我们已经学习了按钮的创建但是还没有体现出按钮的功能按钮最大的功能也就是点击后触发一些事情比如我们点击按钮就把当前的窗口给关闭掉那么在Qt中这样的功能如何实现呢QPushButton * quitBtn new QPushButton(关闭窗口,this); connect(quitBtn,QPushButton::clicked,this,MyWidget::close);第一行是创建一个关闭按钮这个之前已经学过第二行就是核心了也就是信号槽的使用方式connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数connect(sender, signal, receiver, slot);1)sender信号发送者 2)signal信号 3)receiver信号接收者 4)slot接收对象在接收到信号之后所需要调用的函数槽函数这里要注意的是connect的四个参数都是指针信号和槽是函数指针。系统自带的信号和槽如何查找呢这个就需要利用帮助文档了在帮助文档中比如我们上面的按钮的点击信号在帮助文档中输入QPushButton首先我们可以在Contents中寻找关键字 signals信号的意思但是我们发现并没有找到这时候我们应该想到也许这个信号的被父类继承下来的因此我们去他的父类QAbstractButton中就可以找到该关键字点击signals索引到系统自带的信号有如下几个这里的clicked就是我们要找到槽函数的寻找方式和信号一样只不过他的关键字是slot。 4.2 自定义信号和槽自定义信号和槽Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求比如说点击某个按钮让另一个按钮的文字改变这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。自定义信号使用条件声明在类的signals域下没有返回值void类型的函数只有函数声明没有定义可以有参数可以重载通过emit关键字来触发信号形式emit object-sig(参数);自定义槽函数使用条件qt4 必须声明在 private/public/protected slots域下面qt5之后可以声明public下同时还可以是静态的成员函数全局函数lambda表达式没有返回值void类型的函数不仅有声明还得要有实现可以有参数可以重载使用自定义信号和槽定义场景下课了老师跟同学说肚子饿了信号学生请老师吃饭槽 首先定义一个学生类和老师类老师类中声明信号 饿了hungrysignals: void hungry();学生类中声明槽 请客treatpublic slots: void treat();在窗口中声明一个公共方法下课这个方法的调用会触发老师饿了这个信号而响应槽函数学生请客void MyWidget::ClassIsOver() { //发送信号 emit teacher-hungry(); }学生响应了槽函数并且打印信息//自定义槽函数 实现void Student::treat() { qDebug() Student treat teacher; }在窗口中连接信号槽teacher new Teacher(this); student new Student(this); connect(teacher,Teacher::hungury,student,Student::treat);并且调用下课函数测试打印出相应log 自定义的信号 hungry带参数需要提供重载的自定义信号和 自定义槽void hungry(QString name); 自定义信号 void treat(QString name ); 自定义槽 但是由于有两个重名的自定义信号和自定义的槽直接连接会报错所以需要利用函数指针来指向函数地址 然后在做连接 void (Teacher:: * teacherSingal)(QString) Teacher:: hangry; void (Student:: * studentSlot)(QString) Student::treat; connect(teacher,teacherSingal,student,studentSlot); 也可以使用static_cast静态转换挑选我们要的函数 connect( teacher, static_castvoid(Teacher:: *)(QString)(Teacher:: hangry), student, static_castvoid(Student:: *)(QString)( Student::treat));信号和槽的扩展1.一个信号可以和多个槽相连如果是这种情况这些槽会一个接一个的被调用但是槽函数调用顺序是不确定的。像上面的例子可以将一个按钮点击信号连接到关闭窗口的槽函数同时也连接到学生请吃饭的槽函数点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。2. 多个信号可以连接到一个槽只要任意一个信号发出这个槽就会被调用。如一个窗口多个按钮都可以关闭这个窗口。3.一个信号可以连接到另外的一个信号当第一个信号发出时第二个信号被发出。除此之外这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子可以新建一个按钮btn。connect(btn,QPushButton::clicked,teacher,Teacher::hungry);4.信号和槽可以断开连接可以使用disconnect函数当初建立连接时connect参数怎么填的disconnect里边4个参数也就怎么填。这种情况并不经常出现因为当一个对象delete之后Qt自动取消所有连接到这个对象上面的槽。5.信号和槽函数参数类型和个数必须同时满足两个条件1信号函数的参数个数必须大于等于槽函数的参数个数2信号函数的参数类型和槽函数的参数类型必须一一对应Lambda 表达式C11中的Lambda表达式用于定义匿名的函数对象以简化编程工作。首先看一下Lambda表达式的基本构成分为四个部分[局部变量捕获列表]、函数参数、函数额外属性设置opt、函数返回值-retype、{函数主体}[capture](parameters) opt -retType { ……; }局部变量引入方式[ ]标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表[] 表示lambda表达式不能访问外部函数体的任何局部变量[a] 在函数体内部使用值传递的方式访问a变量[b] 在函数体内部使用引用传递的方式访问b变量[] 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本[] 引用的方式使用lambda表达式外部的所有变量[, foo] foo使用引用方式, 其余是值传递的方式[,foo] foo使用值传递方式, 其余是引用传递的方式[this] 在函数内部可以使用类的成员函数和成员变量和形式也都会默认引入由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。所以在无特殊情况下建议使用[](){}的形式函数参数(params)表示lambda函数对象接收的参数类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值如(int a,int b)和按引用如(int a,int b)两种方式进行传递。函数参数部分可以省略省略后相当于无参的函数。选项 OptOpt 部分是可选项最常用的是mutable声明这部分可以省略。外部函数局部变量通过值传递引进来时其默认是const所以不能修改这个局部变量的拷贝加上mutable就可以int a 10 ; []() { a20;//编译报错a引进来是const } []()mutable { a20;//编译成功 };函数返回值 -retType-retType标识lambda函数返回值的类型。这部分可以省略但是省略了并不代表函数没有返回值编译器会自动根据函数体内的return语句判断返回值类型但是如果有多条return语句而且返回的类型都不一样编译会报错如[]()mutable { int b 20; float c 30.0; if(a0) return b; else return c;//编译报错两条return语句返回类型不一致 };是函数主体{}{}标识函数的实现这部分不能省略但函数体可以为空。槽函数使用 Lambda 表达式以QPushButton点击事件为例connect(btn,QPushButton::clicked,[](){ qDebug()Clicked; });这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候clicked信号被触发lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数这样也就能够接收到信号函数传递过来的参数了。由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来也就是: [](){ }的形式QMainWindowQMainWindow是一个为用户提供主窗口程序的类包含一个菜单栏menu bar、多个工具栏(tool bars)、多个停靠部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget)是许多应用程序的基础如文本编辑器图片编辑器等。菜单栏一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。1.通过QMainWindow类的menubar函数获取主窗口菜单栏指针如果当前窗口没有菜单栏该函数会自动创建一个。QMenuBar * menuBar() const;2.创建菜单调用QMenu的成员函数addMenu来添加菜单QAction* addMenu(QMenu * menu); QMenu* addMenu(const QString title); QMenu* addMenu(const QIcon icon, const QString title);3.创建菜单项调用QMenu的成员函数addAction来添加菜单项QAction* activeAction() const; QAction* addAction(const QString text); QAction* addAction(const QIcon icon, const QString text); QAction* addAction(const QString text, const QObject * receiver, const char * member, const QKeySequence shortcut 0); QAction* addAction(const QIcon icon, const QString text, const QObject * receiver, const char * member, const QKeySequence shortcut 0);Qt 并没有专门的菜单项类只是使用一个QAction类抽象出公共的动作。当我们把QAction对象添加到菜单就显示成一个菜单项添加到工具栏就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。工具栏主窗口的工具栏上可以有多个工具条通常采用一个菜单对应一个工具条的的方式也可根据需要进行工具条的划分。1.调用QMainWindowd对象的成员函数addToolBar,该函数每次调用都会创建一个新的工具栏并且返回该工具栏的指针。2.插入属于工具条的项这时工具条上添加项也是用QAction。通过QToolBar类的addAction函数添加。3.工具条是一个可移动的窗口它的停靠区域由QToolBar的allowAreas决定包括以下值可以通过查帮助文档allowAreas来索引到1Qt::LeftToolBarArea 停靠在左侧2Qt::RightToolBarArea 停靠在右侧3Qt::TopToolBarArea 停靠在顶部4Qt::BottomToolBarArea 停靠在底部5Qt::AllToolBarAreas 以上四个位置都可停靠状态栏添加和删除状态栏的方法和添加删除菜单栏方法一样。 状态栏添加左侧控件、右侧控件只能通过代码来添加。 5.6.5 停靠部件停靠部件核心部件UI窗口中默认核心部件就是一个widgetUI 文件管理UI 文件下使用信号和槽转到槽信号槽编辑器资源文件QDialog 对话框布局常用控件QLabel 控件使用QLineEdit其他控件自定义控件Qt消息事件机制绘图事件和绘图设备文件操作