最近需要写一个训练数据清洗筛选的辅助小工具,之前一直习惯用tkinter来写些小工具,没有接触过PyQt。但是看需求感觉用tkinter不是很好实现(应该是我太菜了),想到以前同事推荐过我用QT,所以这是一篇PyQt5新手小白三天来学习的备忘录。
首先,PyQt5的安装。直接pip安装的方式给我后面打包成exe挖了一个坑。因为版本的原因,打包出来的exe会爆找不到PyQt5模块,后来回滚到5.10版本就没出现这个错误了。可能最新版的PyQt导入包的时候不能这样写吧,要是有路过的大佬可能解释一下这个报错,不胜感激
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys, os, time, shutil
import json
我们先创建一个类叫Example,因为后面我要用到状态栏,所以我选择继承自QMainWindow,然后创建一个initUI方法用于绘制界面
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()##界面绘制
def initUI(self):
'''
do something
'''
if __name__ == '__main__':
##创建应用对象
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
获取屏幕大小
self.desktop = QApplication.desktop()
self.height = self.desktop.height()
self.width = self.desktop.width()
设置窗体大小并显示在屏幕中间
self.resize(int(self.width*0.85), int(self.height*0.8))
self.center()
##窗体居中函数
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
添加按键,状态栏等等控件。这里利用QVBoxlaayout控制界面垂直布局,其中最主要的就是QScrollArea滚动条组件。QScrollArea内部有一个网络布局分布(QGridLayout)的QWidget子组件,用以显示文件夹下的所有图片。
##创建状态栏和文本编辑栏
self.status = self.statusBar()
self.textbox = QLineEdit(self)
##不显示QLineEdit的边缘,设置位置,字体样式,不可编辑
self.textbox.setStyleSheet("background:transparent;border-width:0;border-style:outset")
self.textbox.move(70+int(self.width*0.7), 50)
self.textbox.resize(int(self.width*0.1), 100)
self.textbox.setFocusPolicy(Qt.NoFocus)
self.textbox.setFont(QFont("Arial", 20))
self.textbox.setText(" NOP:")
##创建滚动条
self.scroll_area_images = QScrollArea(self)
self.scroll_area_images.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget(self)
self.scrollAreaWidgetContents.setObjectName('scrollAreaWidgetContends')
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scroll_area_images.setWidget(self.scrollAreaWidgetContents)
self.scroll_area_images.setGeometry(50, 50, int(self.width*0.7), int(self.height*0.7))
self.vertocall = QVBoxLayout()
##添加按键
##选择文件夹按键
self.open_file_pushbutton = QPushButton(self)
self.open_file_pushbutton.setGeometry(120+int(self.width*0.7), 250, 100, 30)
self.open_file_pushbutton.setObjectName('open_pushbutton')
self.open_file_pushbutton.setText('选择根文件夹')
self.open_file_pushbutton.clicked.connect(self.open)##关联函数
##上一个文件夹按键
self.before_pushbutton = QPushButton(self)
self.before_pushbutton.setGeometry(120+int(self.width*0.7), 350, 100, 30)
self.before_pushbutton.setObjectName('before_pushbutton')
self.before_pushbutton.setText('上一个文件夹')
self.before_pushbutton.clicked.connect(self.before)
##下一个文件夹按键
self.next_pushbutton = QPushButton(self)
self.next_pushbutton.setGeometry(120+int(self.width*0.7), 450, 100, 30)
self.next_pushbutton.setObjectName('next_pushbutton')
self.next_pushbutton.setText('下一个文件夹')
self.next_pushbutton.clicked.connect(self.next)
self.vertocall.addWidget(self.scroll_area_images)
界面效果如下:
----------------------------------------分割线,以上是界面层,下面是逻辑层。---------------------------------------------------------------------------
通过按键选择跟文件夹
def open(self):
self.file_path = QFileDialog.getExistingDirectory(self, '选择文文件夹', '/')
每次进入新的文件夹,清空图像
#初始化滚动栏
def clear_layout(self):
for i in range(self.gridLayout.count()):
self.gridLayout.itemAt(i).widget().deleteLater()
加载图片
def start_img_viewer(self):
if self.initial_path:
photo_list = self.sort_photo([os.path.join(self.initial_path[self.num], photo) for photo in os.listdir(self.initial_path[self.num]) if os.path.splitext(photo)[-1].lower()=='.jpg'])##获取文件夹里的所有jpg图片,并对每张图片的相似频率进行排序
photo_num = len(photo_list)
if photo_num != 0:
for i in range(photo_num):
image_id = photo_list[i]
pixmap = QPixmap(image_id)
self.addImage(pixmap, image_id)
QApplication.processEvents()##实时加载,可能图片加载数量比较多
else:
QMessageBox.information(self, '提示', '该文件夹图片为空')
else:
QMessageBox.information(self, '提示', '请先选择根文件夹')
def get_nr_of_image_columns(self):
#展示图片的区域,计算每排显示图片数。返回的列数-1是因为我不想频率拖动左右滚动条,影响数据筛选效率
scroll_area_images_width = int(0.68*self.width)
if scroll_area_images_width > self.display_image_size:
pic_of_columns = scroll_area_images_width // self.display_image_size #计算出一行几列;
else:
pic_of_columns = 1
return pic_of_columns-1
def addImage(self, pixmap, image_id):
##获取图片列数
nr_of_columns = self.get_nr_of_image_columns()
nr_of_widgets = self.gridLayout.count()
self.max_columns = nr_of_columns
if self.col < self.max_columns:
self.col += 1
else:
self.col = 0
self.row += 1
clickable_image = QClickableImage(self.display_image_size, self.display_image_size, pixmap, image_id)
clickable_image.clicked.connect(self.on_left_clicked)
clickable_image.rightClicked.connect(self.on_right_clicked)
self.gridLayout.addWidget(clickable_image, self.row, self.col)
图片显示的模块封装成了一个类QClickableImage,其作用是生成一个Qwidget。里面上面一个Qlabel()显示图片,下面一个Qlabel()显示图片名。图片显示利用的是Qpixmap控件,对图片进行尺寸调整
class QClickableImage(QWidget):
image_id =''
def __init__(self,width =0,height =0,pixmap =None,image_id = ''):
QWidget.__init__(self)
self.width =width
self.height = height
self.pixmap =pixmap
self.layout =QVBoxLayout(self)
self.lable2 =QLabel()
self.lable2.setObjectName('label2')
if self.width and self.height:
self.resize(self.width,self.height)
if self.pixmap and image_id:
pixmap = self.pixmap.scaled(QSize(self.width,self.height),Qt.KeepAspectRatio,Qt.SmoothTransformation)
self.label1 = MyLabel(pixmap, image_id)
self.label1.setObjectName('label1')
#self.label1.connect(self.mouseressevent())
self.layout.addWidget(self.label1)
if image_id:
self.image_id =image_id
self.lable2.setText(image_id.split('\\')[-1])
self.lable2.setAlignment(Qt.AlignCenter)
###让文字自适应大小
self.lable2.adjustSize()
self.layout.addWidget(self.lable2)
self.setLayout(self.layout)
clicked = pyqtSignal(object)
rightClicked = pyqtSignal(object)
def imageId(self):
return self.image_id
因为我需要实现右键菜单触发不同操作的功能,所以QClickableImage里显示图片的Qlabel()我又封成了一个类MyLabel()
class MyLabel(QLabel):
global NOP_value, NOP_dict
def __init__(self, pixmap =None, image_id = None):
QLabel.__init__(self)
self.pixmap = pixmap
self.image_id = image_id
self.setPixmap(pixmap)
self.setAlignment(Qt.AlignCenter)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.rightMenuShow) # 开放右键策略
def rightMenuShow(self, point):
# 添加右键菜单
self.popMenu = QMenu()
ch = QAction(u'撤回', self)
sc = QAction(u'删除', self)
xs = QAction(u'显示相似频率', self)
self.popMenu.addAction(ch)
self.popMenu.addAction(sc)
self.popMenu.addAction(xs)
# 绑定事件
ch.triggered.connect(self.reback)
sc.triggered.connect(self.delete)
xs.triggered.connect(self.rshow)
self.showContextMenu(QCursor.pos())
def rshow(self):
'''
do something
'''
def delete(self):
'''
do something
'''
def reback(self):
'''
do something
'''
def showContextMenu(self, pos):
# 调整位置
'''''
右键点击时调用的函数
'''
# 菜单显示前,将它移动到鼠标点击的位置
self.popMenu.move(pos)
self.popMenu.show()
def menuSlot(self, act):
print(act.text())
写到这里和PyQt相关的内容就算写完了,最后效果大概这样
参考文章: