命名规范
类名:首字母大写,单词和单词之间首字面大写。
函数名,变量名称:首字母小写,单词和单词之间首字母大写。
快捷键
注释:ctrl + /
运行:ctrl + r
编译:ctrl + b
字体缩放:ctrl + 鼠标滑轮
查找:ctrl + f
整行移动:ctrl + shift + 上箭头或下箭头
自动对齐:ctrl + i
同名之间的.h和.cpp文件切换:F4
帮助文档第一种方式:F1
帮助文档第二种方式:左侧按键
帮助文档第三种方式:在安装路径下找
例如我们使用了QUdpSocket类,那么就需要引入QUdpSocket头文件,还需要在.pro项目文件中加入network模块。
在Qt中当创建的对象在堆区时,如果指定的父亲是QObject派生下来的类或者是QObject的子类派生下来的类,可以不用管理释放的操作,因为会将对象放到对象树中,这样做在一定程度上简化了内存回收机制。
下面我们创建一个自定义button类。
将自定义button类继承QPushButton类。这样自定义button就有了QPushButton类的一些功能接口函数。
下面我们在自定义button的构造函数和析构函数中打印信息。
然后我们在myWidget窗口中创建一个自定义button。
我们看到当运行程序时,调用了自定义button的构造函数,当我们点击X将窗口关闭时,程序调用了自定义button的析构函数,但是我们在程序中并没有使用delete,这就是对象树为我们完成了内存回收。即一个父类对象中添加的子类对象都会进入到这个父类对象的child列表中,当这个父类对象析构之前,会先将自己的child列表中的子类对象都先析构完,然后再调用自己的析构函数,这样就保证了父类对象中的资源都释放了。所以qt中有时候不需要手动释放内存。前提是这个父类需要为QObject的派生类。
下面我们验证对象树中先调用子类析构函数,再调用父类析构函数。我们在myWidget类的析构函数中也打印信息。
我们看到打印的结果为先打印myWidget类的析构函数中的内容,然后打印自定义button类的析构函数中的内容。这是因为在执行myWidget对象的析构函数时,会先将析构函数中的代码执行了,然后此时不进行最后一步释放该对象的内容,而是检查是否还有没有释放的子类对象,如果有的话就调用这个子类对象的析构函数,执行这个子类对象的析构函数时也会先将析构函数内的代码先执行,然后不释放子类对象的内容,再次检查子类对象是否还有没有释放的子类对象。直到当前子类对象没有子类了,然后再释放当前子类的内存,然后继续向上一层层释放父类对象的内存。
信号的发送者和信号的接收者通过connect函数建立关联。
信号槽的优点:松散耦合,信号发送端和信号接收端本身是没有关联的,通过connect连接将两端耦合在一起。
connect(信号的发送者,发送的具体信号,信号的接收者,信号的处理(槽函数))
我们可以在QPushButton的父类中查看到产生信号的函数。
我们可以在myWidget的父类QWidget类中找到它的槽函数,通过槽函数可以对当前窗口myWidget进行对应的操作。
下面我们给自定义的按钮添加一个功能,当点击这个按钮时,关闭myWidget窗口。需要注意的是使用MyPushButton::clicked和QPushButton::clicked都一样,因为MyPushButton中继承了QPushButton中的clicked函数,而QPushButton中的clicked函数又是从它的父类中继承的。对于槽函数也类似。
下面我们来自定义信号和槽函数。
然后我们创建一个Teacher类和一个Student类。
然后我们在Teacher类中自定义一个信号,信号只需要声明即可,不需要实现。
下面我们在Student类中声明一个槽函数,需要注意的是槽函数需要实现。
然后我们在Widget窗口中添加Teacher对象和Student对象。并且让Widget中的classIsOver函数中触发老师的hungry信号。然后我们让Teacher的hungry信号和Student的treat槽函数进行连接,这样当检测到hungry信号时,就会调用treat槽函数。需要注意的是可以使用emit关键字来触发信号。
我们看到成功触发了hungry信号,并且成功调用了treat槽函数。
下面我们在Teacher类中添加一个hungry信号的重载版本,即可以传入一个QString对象的参数。然后在Student类中添加一个treat槽函数的重载版本,重载版本中也可以传入一个QString对象的参数。需要注意的是信号的参数一定要和对应的槽函数的参数一致,因为当触发信号后会将信号的参数传递给槽函数。
然后我们让Widget类的classIsOver成员函数中触发Teacher的带参数的hungry信号。
可以看出将带参数的信号和带参数的槽函数进行连接时,需要多做一步处理。这样编译器才可以分清楚是将哪个重载版本的信号和对应的槽函数进行连接了。
我们在上面的结果中看到 “鱼香肉丝” 被打上了双引号,这是因为它为QString对象,想要将这个QString对象转换为char * 类型的字符串,可以使用下面的方法。
下面我们将程序中添加一个按钮控件,当点击按钮时触发老师的hungry信号,然后才会调用对应的treat槽函数。
我们看到确实实现了功能,即当点击按钮时会产生clicked信号,然后调用该信号连接的classIsOver槽函数。在classIsOver槽函数中又会触发hungry信号,然后调用该信号连接的treat槽函数。
上面的方法中使用的是信号和槽函数连接。下面我们使用信号和信号连接的方式来完成上面的操作。
利用disconnect可以断开信号与槽函数,或者信号与信号之间的连接。
下面我们来看一下Qt5版本以前的信号和槽函数的连接方式。我们看到Qt4版本的connect的缺点是不会做类型检测,所以当信号和槽函数的参数类型不是一一对应时,编译器也检查不出来。下面的SIGNAL和SLOT都为宏。
lambda表达式为c++11的新特性,所以当老的版本的项目想要使用lambda表达式时,需要在项目的配置文件中加上CONFIG += c++11。新版本的Qt一般都会自动加上。
lambda表达式的使用和c++11中的一致,所以可以看前面学习的c++11中的语法。需要注意的是lambda中的mutable关键字,如果lambda表达式的捕获列表中使用值捕获,那么在lambda的函数体中也不可以修改捕获的变量的值,因为默认这个值捕获的变量为const修饰的,所以想要改变这个拷贝的变量的值,就需要加上mutable关键字。加上mutable关键字只是让拷贝的变量的值可以修改了,而外面的变量本体的值还是没有改变。即如果不加mutable关键字修饰,那么在lambda函数体中拷贝的变量的值都无法修改。
下面我们使用lambda表达式触发hungry信号。
下面我们在lambda表达式形式的槽函数中执行关闭窗口的操作。我们看到槽函数中可以执行多个操作。
如果connect函数的第三个参数为this,第四个参数为lambda表达式。那么第三个参数可以省略。还有需要注意的是在lambda的捕获列表中建议使用值捕获,因为使用&捕获可能会存在bug的情况,即可能出现锁的问题导致捕获的变量无法改变值。
实现两个按钮,一个按钮为open,一个按钮为close。点击open按钮会弹出来一个新的窗口,点击close按钮会将新窗口关闭。
需要注意的是菜单栏只能创建一个。我们可以看到菜单栏创建后并不需要绑定父对象,因为menuBar()函数底层自动做了绑定父对象MainWindow的处理。
工具栏可以有多个。
这些方法都可以在Qt助手中进行查看和学习。
下面我们给工具栏中添加内容。我们将创建的菜单项放到工具栏中。
浮动窗口可以有多个。
我们看到虽然设置时将浮动窗口默认在最下面,但是浮动窗口在上面了,这是因为浮动窗口是根据中心部件来定位的,当前窗口中没有中心部件,所以此时浮动窗口其实就在最下面。
下面我们给窗口中添加一个中心部件。需要注意的是中心部件只能有一个。
此时我们看到浮动窗口就在中心部件的下方了。
然后我们设置浮动窗口只允许在窗口的上下区域停靠。
下面我们给菜单项添加图标,我们需要先将图标文件添加到Qt项目中。
我们需要将包含图标的文件放到当前项目的文件目录下。
然后我们再添加一个Qt资源文件。
然后我们就可以看到目录中多了一个资源文件,但是这个资源文件双击是不能进入编辑状态的,需要点击右键,然后点击Open in Editor选项才能进入编辑状态。
然后我们添加文件时先添加前缀,因为有时候文件比较多,需要用前缀来归类。我们当前图标比较少,直接将前缀为/即可。
然后我们点击添加文件,选择当前项目目录下的image文件夹中的图标进行添加。
然后我们就可以看到图标图片都被添加到项目中了。
下面我们就可以使用这些图片了。
模态对话框在打开期间不可以对其它窗口进行操作,而非模态对话框在打开期间可以对其它窗口进行操作。
下面我们使用设计来创建一个菜单和菜单项,当点击新建菜单项时,弹出一个模态对话框。模态对话框,会阻塞在lambda的函数体中的exec函数内容,在关闭之前不可以对其它窗口做操作。当对话框较小时,编译器就会出现警告。
下面我们再创建一个非模态对话框。我们看到非模态对话框就是不调用exec函数让对话框阻塞,而是调用show函数让对话框显示出来。像下面这样直接创建一个dlg2对象,此时对话框会出现后马上销毁,因为当dlg2对象出了lambda函数体的作用域后就销毁了,所以dlg2对象也会销毁。
所以我们需要将dlg2使用new在堆上创建。此时非模态对话框可以正常显示了。并且可以操作其它窗口。
但是上面的这样new在堆上创建dlg2对象会造成内存泄漏,即只要大窗口不关闭,那么每次点击新建菜单项都会在堆区中创建一个dlg2对象,并且将小窗口关闭后dlg2对象也不会销毁,这样就造成了内存泄漏。所以我们需要使用setAttribute函数的Qt::WA_DeleteOnClose选项,这样当关闭小窗口时,会将dlg2在堆区的内存释放,这样就不会造成内存泄漏了。
很多类都有setAttribute函数函数,而且setAttribute函数的选项也很多,我们可以通过Qt助手来进行查看。
所谓标准对话框,是Qt内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这样一个对话框。
Qt的内置对话框大致分为以下几类:
QColorDialog:选择颜色
QFileDialog:选择文件或者目录
QFontDialog:选择字体
QInputDialog:允许用户输入一个值,并将其值返回
QMessageBox:模态对话框,用于显示信息,询问问题等
QPageSetupDialog:为打印机提供纸张相关的选项
QPrintDialog:打印机配置
QPrintPreviewDialog:打印预览
QProgressDialog:显示操作过程
下面我们来看一下消息对话框。消息对话框大致分为下面的几类。
下面我们创建一个错误对话框。
下面我们创建一个提问对话框。
我们看到提问对话框的两个按钮默认为yes和no,我们可以通过question函数的第四个参数来设置这两个按钮。我们还可以通过第五个参数来设置默认关联回车按键,即蓝框的按钮。
我们看到这几个函数的返回值都是StandardButton类型。这些函数其实会将用户点击的按钮返回,所以我们就可以通过函数的返回值来判断用户点击了哪一个按钮。
下面我们再来简单使用一下其它的标准对话框。
颜色对话框
getColor函数会创建一个颜色对话框,然后返回一个QColor对象,该对象中记录了用户选择的颜色。
文件对话框
我们看到默认为我们打开的是桌面,并且只显示了pdf文件。只会过滤文件,并不会过滤文件夹。
我们看到QFileDialog::getOpenFileName函数的返回值是一个QString对象,该对象中记录了用户选择的文件的路径。
字体对话框
QFontDialog::getFont函数返回一个QFont对象,该对象中记录了用户在字体对话框中选择的字体的一些信息。
在Qt中通常使用设计来进行界面的布局,而底层逻辑使用代码实现。下面我们就来学习怎样使用设计来简单进行界面布局。
首先在创建项目时我们需要将创建界面选项勾选上。
然后我们双击.ui文件来进入设计模式。
然后我们就进入了设计窗口,因为我们选择创建的是QMainWindow,所以我们可以看到自动生成了菜单栏和工具栏和状态栏。如果不需要,我们就可以进行移除。
我们可以直接输入来建立菜单和菜单项,需要注意的是菜单项只能使用英语,因为编译器会自动为这个菜单项进行命名。不过我们可以根据菜单项对象的名称在代码中修改它的Text,也可以直接在这个菜单项的属性中直接修改text属性来改变内容。
下面我们重新创建一个项目,然后使用设计来布局一个登录窗口。登录窗口不需要菜单栏、工具栏等,所以我们选择QWidget类。
我们添加了组件形成了下面的窗口,但是下面的窗口并没有对齐。
我们可以使用Widget来包含这些组件,然后让Widget中采用水平布局,以此来将这些组件水平对齐。
我们也可以打破布局来重新进行布局。
下面我们让用户和密码等四个组件放在一个Widget中,然后将这个Widget使用栅格布局。这样这四个组件就平均了。
然后我们看窗口效果,可以看到当放大窗口时,登录组件的内容并不会跟着移动。
我们可以使用弹簧来让登录退出按钮永远保持在中间位置。如果想让中间弹簧的间距保持不变,可以更改中间弹簧的sizeType属性为Fixed,然后修改弹簧的长宽,弹簧就不会改变了。
可以通过修改Widget的这个属性来让Widget的高度变为和里面的组件高度相同。
我们可以通过windowTitle属性来修改窗口的名称。
我们还可以将窗口的最大值和最小值设置为一个定值并且一样,这样窗口大小就不可以缩放了。
可以通过这个属性来将文本框中的内容隐藏。
下面我们来学习其它控件。
我们可以通过下面的方式来给按钮添加图标。
还可以设置图标的大小。
一般PushButton显示文字,ToolButton显示图标。ToolButton按钮默认只显示图标,想要文字和图标都显示,需要进行属性修改。autoRaise属性为透明效果。
下面我们创建四个单选按钮,我们看到这四个按钮中只能有一个处于选中状态。如果我们想让两个为一组的话,那么我们可以使用Group Box来将这四个单选按钮分为两组。
下面我们使用代码设置单选按钮,让男默认选中。然后给表示女的单选按钮连接一个槽函数。
我们需要记住表示男的单选按钮和表示女的单选按钮的名称,当然这个名称自己也可以改。
下面我们再来看多选按钮。
多选按钮有个自己的信号,stateChanged信号。选中时参数为2,未选中时参数为0,而1是半选的状态。
List Widget为一个列表容器,我们可以向里面添加要显示的文字内容。
下面我们向List Widget中添加一行诗。
我们还可以一次添加多行到listWidget中。QStringList对象就等价于一个存放String对象的list容器。不过这个QStringList对象重载了<<流插入符,可以以下面的方式添加数据。这种写法不能将每个Item都设为水平居中,但是可以通过for循环来设置。
下面我们使用Tree Widget来实现一个英雄介绍的案例。
Table Widget控件类似于一个表格。
下面我们使用Table Widget来完成一个记录个人信息的表格。
Tool Box类似于QQ列表的分类。
可以通过右键来插入页。
在开发时我们可以通过Stacked Widget右上角的小箭头来切换页。
但是在实际形成的窗口中,这个小箭头就没有了。
在实际中需要使用按钮或鼠标等来切换页。下面我们设置三个按钮,用来切换这三个页。
然后我们就实现了通过点击按钮来切换页。
但是此时我们会发现这个首页每一次都会变化,并不是索引号为0的每次都默认显示。所以我们需要设置默认定位,即规定好哪一页放在首页。
Combo Box为下拉框。
下面我们添加一个按钮,当点击按钮时,就选中拖拉机。
Line Edit为单行的输入框。
Text Edit为多行输入框。
Plain Text Edit也为多行输入框,但是Plain Text Edit不可以设置文字的颜色,加粗等样式。
Spin Box为单精度。
Double Spin Box为双精度。
Time Edit为时间选择框。
Date Edit为日期选择框。
Date/Time Edit为日期和时间选择框。
Horizontal Scroll Bar 水平滚动条。
Vertical Scroll Bar 垂直滚动条。
Horizontal Slider 为水平滑条。一般可以和Spin Box组合使用。
Vertical Slider 为垂直滑条。一般可以和Spin Box组合使用。
Label标签可以显示文字,也可以显示图片。
下面我们利用Label显示图片。
Label还可以显示动图。下面我们使用Label显示动图。
下面我们来实现一个自定义控件。如果这个控件想要使用设计来布局,那么就可以添加一个Qt设计师界面类。
这样就又形成了一个控件。
然后我们在这个控件的界面放入一个Spin Box和一个Horizontal Slider滑条,并且使用Widget包含这两个控件,将这两个控件关联组成一个新的自定义控件。
然后我们回到主窗口的ui界面,添加一个Widget控件。然后将这个控件提升为SmallWidget控件。此时我们运行后就可以看到自定义控件被添加到了主窗口中。
下面我们来将Spin Box和Horizontal Slider进行关联。
下面我们再添加两个按钮,实现一些功能。
然后我们在自定义控件中实现两个接口函数。
然后将主窗口的两个按钮和自定义控件中的函数进行连接。
Qt中的事件有很多种。
下面我们再来自定义一个控件。如果我们不需要这个控件进入ui布局,那么就直接生成C++Class即可,而不需要生成ui文件。
因为下面我们需要将Label控件提升为我们自定义的myLabel控件,所以myLabel控件需要继承QLabel。
然后我们在窗口中添加一个Label控件,并且将这个控件提升为自定义控件。我们可以通过修改属性来使这个控件有边框。
下面我们重写myLabel控件中的一些事件函数。那么当在这个自定义控件中发生这些事件时,就会调用重写的事件函数了。
我们看到当鼠标进入和离开myLabel控件时,都调用了我们在myLabel类中重写的对应的事件函数。
上面我们重写的enterEvent函数和leaveEvent函数是myLabel继承QWidget的函数。而myLabel也继承了父类Label中的很多事件函数,下面我们再来重写这几个函数。
我们还可以获取鼠标事件的一些信息,这些信息都记录在事件函数的参数ev中。下面我们获取鼠标的坐标信息。x和y获得鼠标相对于控件的左上角的坐标,globalx和globaly获取鼠标相对于桌面的左上角的坐标。
下面我们判断是鼠标的左键还是右键按下。因为鼠标移动是一个过程性事件,所以我们需要使用&的形式来判断是左键按下移动还是右键按下移动。
我们发现只有在控件中按下鼠标按键后,鼠标移动才会捕获鼠标移动的坐标。如果我们想在控件内直接捕获鼠标的移动,而不需要再按键。我们可以设置控件的鼠标追踪为true,默认都为false。
下面我们重写定时器的事件。然后我们创建一个定时器,然后每隔我们设定的事件到时,都会执行一次定时器事件。
当我们想要创建两个定时器,并且让这两个定时器执行不同的代码时。我们此时就需要用到timerid来区分定时器。startTimer函数返回的就是定时器的id。
除了上面的使用方式,定时器还可以像下面这样使用。
下面我们再添加一个按钮,当点击这个暂停按钮时,Label4就停止增加。
步骤1.给控件安装事件过滤器。
步骤2.重写eventFilter事件。
通过上面的演示我们知道了事件分发器可以在控件中拦截事件,事件过滤器可以在窗口中拦截某个控件的某个事件。
我们可以通过重新pointEvent事件函数来在Widget窗口中进行绘图。
在画家画画之前还可以对画笔进行设置。画笔的样式等都可以进行调节。需要注意的是一定要先将画笔样式设置好,然后再给画家使用。
画家还可以使用画刷进行填充颜色,下面我们来设置一个画刷。画刷也可以设置自己想要的样式。
抗锯齿能力。可以让画出的画更精致,但是效率会变低。可以看到右边的圆更精致一些。
还可以让画家进行平移,即原来画家从左上角(0,0)位置开始画画,我们可以使用接口函数将画家的位置平移,即改变画家开始画画的位置。
我们看到使用代码设置的两个矩形的位置是一样的,但是画家画出来后两个矩形并没有重合,这就是因为我们移动了画家的位置。
我们看到保存了画家位置后,如果还原画家位置,那么就会让画家回到保存的位置进行作画。所以我们看到了只有两个矩形,因为第三次画家作画时回到了(100,0)位置作画,所以第二个矩形和第三个矩形重叠了。
我们还可以利用画家来画图片资源等。
下面我们添加一个按钮,当点击按钮时,我们让图片像右移动。并且我们设置了当图片向右移动超过窗口宽度后,就让图片重新返回到最左侧。
Qt的绘图系统实际上是使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令)。
绘图设备是指继承QPainterDevice的子类,Qt一共提供了四个这样的类,分别是QPixmap,QBitmap,QImage,QPicture。
下面我们使用QPixmap绘图设备向磁盘中进行画画,我们可以看到生成了一个.png图片。
下面我们使用QImage绘图设备向磁盘中进行画画。
下面我们利用QImage对图片的像素进行修改。我们看到图片中的像素被更改了。
下面我们在QPicture绘图设备上作画,然后记录绘图指令。
然后我们在paintEvent事件中重现绘图指令。我们看到通过pic.do文件中记录的绘图指令实现了在窗口中进行画画。
下面我们通过点击选取文件按钮来选择一个文件,并且将这个文件的内容显示到Text Edit中。
我们看到选中的文件中的内容都显示到了TextEdit中。
下面我们使用按行读取的方式将文件的内容进行读取。我们需要注意的是在每次打开文件后,都要记得将文件进行关闭。
下面我们来看Qt中向文件中的写操作。
我们看到选中的文件的后面追加了我们写入的数据。
通过QFileInfo可以进行文件信息获取。
通过调用下面的这些函数就可以获取对应文件的信息。
我们看到文件的信息被正确显示了。
下面我们打印文件的创建日期,我们看到显示的时间的格式可能并不是我们想要的。此时我们可以自己定义打印时间的格式。
这样我们就可以根据自己的需要来打印时间了。