目录
1. 前言
上一篇已经完成了所有的准备工作,下面开始实现基本功能
2. QtDesigner的基本组件
下面将介绍一些组件基本的使用方法,有需要的老哥也可以去官网查看,网站是英文的。
在上一篇中,我们复制了一个ui文件到项目里,现在在designer中打开这个文件
2.1 Push Button(按钮)
qt中的组件也有很多种类型,我们先看按钮类型的。
按钮的意义相信不用多说。这里我们先新建一个Push Button,在右边的buttons菜单栏可以找到它,直接双击可对他的名称进行修改,回车确定,这里我将其改为’confirm’,如图:
利用组件的objectName,我们也可以直接在代码里对其进行操作,我这里将其改为button_confirm。这样我们就定义好一个简单的按钮了
注意:一旦修改了UI,想要他在代码里生效必须先保存,然后通过PyUic重新生成py文件,生成的新py文件会直接覆盖之前的
有了按钮自然是要让他触发某些事件的,例如这里如果我要让上面Label的内容发生改变,只需要在init_ui里增加一行关键的代码:
self.ui.button_confirm.clicked.connect(self.click_confirm)
如图所示:
这里使用到的是qt中信号和槽的相关概念,在这里self.ui.button_confirm是我们刚刚定义的button对象,clicked是点击事件,点击button就等同于发出了一次信号,connect(self.click_confirm)是让这个button的点击事件绑定到click_confirm这个函数上,这个click_confirm就是槽函数,当然这里我们还没有实现它,于是我们还需实现click_confirm函数
如下图所示:
这里是将Label中的文本改为"hi Earth!",此处的label就是之前定义的Label组件的objectName
def click_confirm(self):
self.ui.label.setText("hi Earth!")
运行py文件,点击confirm,可以看到label中的文本发生了改变。
2.2 Label(文本和图片标签)
label是非常常用的组件,一般用来显示固定的文本或者图片信息,不支持用户手动在界面上输入内容(这里的界面是指qt项目启动时的界面,相当于成品的界面,不是designer设计界面)
上面我们已经知道了设置Label显示的文本信息使用label.setText()方法,如果要获取label的文本信息,则可以使用label.text()。各个组件的方法可以自行查询。
接下来介绍一下如何在Label中显示本地图片,首先我们需要准备一张常用格式的图片,我这里使用png格式,将图片放到项目中(放到项目中是因为方便,只要知道图片的路径就行),如下图:
我新建了一个static文件夹并在里面放置了两张图片,接下来我们就显示’xlsx.png’。
在designer中新建Label,objectName命名为label_img,调整到合适的大小,不输入任何文字,完成后编译ui文件。如下图
我们想要在这个Label中显示xlsx.png,需要用到QPixmap。代码如下:
icon = QPixmap('static/xlsx.png')
self.ui.label_img.setPixmap(icon)
使用QPixmap需要引入相关的包
from PyQt5.QtGui import QPixmap
这里的’static/xlsx.png’根据图片位置修改,可以是相对路径也可以是绝对路径,完整代码如下:
现在启动项目,应当能看到图片显示了
2.3 Line Edit(支持输入的文本组件)
在上面的例子中我们可以通过事件获取和改变的Label的值,但是Label不允许用户手动输入,此时我们可以使用Line Edit组件。新建编译步骤之后就不再叙述了,获取和设置Line Edit的文本显示内容也和Label别无二致
# 获取文本
self.ui.lineEdit.text()
# 设置文本
self.ui.lineEdit.setText("xxxx")
值得一提的是像这种可以允许用户输入的组件一般都会有readOnly属性,代表组件是否允许写入,如下图所示:
readOnly属性可以直接在代码里动态改变
self.lineEdit.setReadOnly(True or False)
到了这个地方,如果我们需要通过pyqt来实现一个简单计算器之类的demo应该不是什么问题了
2.4 menuBar(菜单栏)
一个好的工具怎么能没有菜单栏呢,哪怕仅仅是一个Exit…………
在designer中设置菜单栏十分方便,如图:
输入后需要按回车健生效,我在此定义了一个File菜单,还可以继续定义File的子菜单,或是在File右边继续定义File的同级菜单
多个子菜单中间可以插入分割符,右键选择移除动作可以删除子菜单或是分隔符,子菜单还可以继续向右添加下级菜单,如图:
设置好菜单之后,就要设置菜单的触发事件了。使用如下代码,此处的actionOpen是我设置的菜单的ObjectName
self.ui.actionOpen.triggered.connect(self.menu_click_success)
menu_click_success函数我让他将之前显示图片的Label中的图片换成另外一张。我们还可以给这个菜单设置快捷键,这里我设置成Ctrl+O
self.ui.actionOpen.setShortcut('Ctrl+O')
效果如下:
这里使用快捷键能够达到相同的效果,代码如下:
2.5 Stacked Widget(多页面组件)
了解了基本组件后,设计出一个简单的页面已经不是什么问题了,但是我们平时使用的工具一般都是有多个页面的,这时候就需要Stacked Widget组件了,它可以让我们在画布上翻页,并且每页的组件都是独立的。
Stacked Widget的使用也很简单,直接拖动到画布上,拉住边框伸展成需要的大小,然后把每页的组件放上去就行了。如下图所示:
我这里先把第一页的组件全部选中剪切掉,把Stacked Widget页面铺开后再粘贴回去,最后使用方向键调整组件的位置。如果要设计新页面也可以直接像这样把第一页的组件复制过去,只修改需要修改的部分。
需要注意的是复制过去的组件objectName会发生改变,不同页面的组件是独立的,并且页面的大小完全是随意的,假如你每个页面都有一些固定且效果相同的区域,那么可以把这部分放到页面之外。
2.5.1 增加/删除页面
刚刚我们创建了一个Stacked Widget,你会发现它只有两页,通过右上角的对象查看器可以查看层级关系,如下图:
我们也可以使用对象查看器来增加或删减页面,在StackedWidget上单机鼠标右键,如图:
页面的从上到下,这里菜单显示的是页1还是页2是根据画布上Stacked Widget的页面来决定的。在我这里page_3就是第一页,此时我们选择下面的插入,就能在page_3前或后新增页面。想要删除页面可以在“2的页1”子菜单中删除当前页面
2.5.2 翻页
刚才演示了在画布上通过小按钮翻页,如下:
然而真正使用起来一般是通过某个按钮(是不是马上就想到了各种安装包上的祖传“下一步”)来实现的,为此我们新建两个用来翻页的按钮,放置到合适的位置,如图:
这里我在第二页之后增加了一个页面,在第二页上增加了用来往上和往下翻页的按钮,这时我们需要给这两个按钮绑定翻页事件了。
首先每个页面都有对应的下标,从0开始,设置Stacked Widget组件当前的下标就能改变显示的页面,代码如下:
self.ui.stackedWidget.setCurrentIndex(这里是要设置的下标值)
因此我们要回到上一页或者进到下一页只需要获取到当前页面的下标,然后对其进行加减再调用上面的代码设置就行了,获取当前页面下标的代码如下:
self.ui.stackedWidget.currentIndex()
如果我们需要获取Stacked Widget的总页数可以使用下面的代码:
self.ui.stackedWidget.count()
我的下一页函数如下:
def click_next(self):
last_index = self.ui.stackedWidget.count() - 1
# 判断如果是最后一页就不允许往后翻页
if self.ui.stackedWidget.currentIndex() == last_index:
return
target_index = self.ui.stackedWidget.currentIndex() + 1
self.ui.stackedWidget.setCurrentIndex(target_index)
上一页函数:
def click_previous(self):
# 判断如果是第一页就不允许往前翻页
if self.ui.stackedWidget.currentIndex() == 0:
return
target_index = self.ui.stackedWidget.currentIndex() - 1
self.ui.stackedWidget.setCurrentIndex(target_index)
应当能实现如下效果:
2.6 QFileDialog(文件窗口)
在第一篇中的演示中可以看到我在工具中选中并读取了本地的excel文件,如果有这种功能需求就需要用到QFileDialog了,直接给出代码:
file_name = QFileDialog.getOpenFileName(self, "选择excel文件", os.getcwd(), "Xlsx files(*.xlsx);;Xls files(*.xls)")
QFileDialog需要import。可以看到这里有四个参数,除了self之外,第二个就是文件窗口的标题,第三个是打开的默认路径,我这里使用os.getcwd()确保在任何主机上打开路径都不会出问题,最后一个参数是限制用户可选择的文件类型,可以有多个,使用两个分号分隔。
我这里将之前创建的Open菜单绑定打开并选取文件的事件,代码如下:
def menu_click_success(self):
file_name = QFileDialog.getOpenFileName(self, "选择excel文件", os.getcwd(), "Xlsx files(*.xlsx);;Xls files(*.xls)")
print(file_name)
menu_click_success是菜单绑定的槽函数,用户选择的文件信息会被绑定到file_name上,这个file_name是一个长度为2的元组,分别储存了文件的绝对路径和文件的类型信息,这里我们打印看下
打印的信息如下:
('C:/Users/xxxx/Desktop/test1.xlsx', 'Xlsx files(*.xlsx)')
此时我们要读取用户选择的文件只需要用file_name[0]就行了
如果我们读取并处理了用户选择的文件,处理完毕需要让用户选择保存的位置应该怎么办呢,此时用如下的代码即可:
file_path = QFileDialog.getSaveFileName(self, "保存文件", os.getcwd() + "/未命名", "xlsx files (*.xlsx)")
参数跟之前的方法大致相同,但是第三个参数我们在os.getcwd()后面拼接"未命名",就会自动在文件命名的给出“未命名”作为文件默认名,如下图所示:
3 关于业务逻辑
我的业务逻辑还算比较简单,大致就是读取朋友日常所需要处理的工作表,工作表的格式可能不是固定的,所以我设计了一个页面可以让用户手动输入工作表的一些特征,根据特征来读取工作表,按照某些列来分组筛选最后导出,功能比较单一,使用openpyxl就能搞定。这篇就先不写这部分了,毕竟也不是重点
over
基本组件大致就是这样,下一篇会记录一些关于多线程和信号任务处理,进度条的使用和一些比较麻烦的点,可能还需要最后一篇记录一下把写好的工具打包成exe的相关过程