美文网首页python基础开发
pyQt5(QScrollArea+QGridLayout):图

pyQt5(QScrollArea+QGridLayout):图

作者: 瞄芽 | 来源:发表于2021-10-29 10:04 被阅读0次

第一用pyQt5做了图片合成gif动图的小功能,将知识点做个记录。界面使用pyQt5的designer.exe 应该能好看些,但我全部敲代码,所以,布局很乱,界面很糙。虽然糙,但也是第一次用pyQt5的成果,先看下效果功能


QQ图片20211029094025.png

添加图片后,点击合成gif,将这些图片合成成gif动图。
代码分两部分整理,界面部分和事件部分

界面部分

模块整理

使用了pyQt5三个模块:

  • QtWidgets:包含创造经典桌面风格的用户界面提供了一套UI元素的类。
  • QtGui:包含类窗口系统集成、事件集成、二维图形、基本成像、字体和文本。
  • QtCore:包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。

以下为该界面用到的模块方法

QtWidgets:

QApplication

用于管理图形用户界面应用程序的控制流和主要设置。
获取屏幕大小

# 屏幕大小
self.desktop = QApplication.desktop()
self.height = self.desktop.height()
self.width = self.desktop.width()
QDesktopWidget

提供屏幕的有关信息。
窗口居中

qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()  # desktop的availableGeometry()获取可用位置和尺寸(除去任务栏等)
qr.moveCenter(cp)
self.move(qr.topLeft())
QFileDialog

打开和保存文件的标准对话框

方法 描述
getOpenFileNames() 多选文件
getOpenFileName() 单选文件
getExistingDirectory() 返回用户选择的文件路径
getOpenFileName() 返回用户选择的文件名
QGridLayout

布局管理器,以网格的形式对组件进行定位

QHBoxLayout

水平布局

方法 描述
setSpacing() 布局中控件间距
addWidget() 添加控件
addStretch() 添加伸缩空间,在第一个控件之前添加,所有控件达到居右的效果、最后一个控件后添加,所有控件居左、第一个控件之前,最后一个控件之后分别添加则居中
QLable

文本标签

方法 描述
setPixmap() 设置图片,使用QPixmap设置,如:setPixmap(QPixmap(':/pic/add.png'))
QLineEdit

文本输入框

方法 描述
setVisible() 设置可见
setText() 设置显示内容
text() 返回显示内容
setValidator() 设置文本框校验器
QMainWindow

因为要用到状态栏,所以我选择继承QMainWindow。

方法 描述
setWindowTitle() 设置窗口标题
setWindowIcon() 设置窗口图标
resize() 设置窗口大小
setStatusBar() 设置状态栏
QProgressBar

进度条

方法 描述
setRange() 设置进度条范围
setValue() 进度条值
QPushButton
方法 描述
setMaximumSize() 设置大小,使用QSize()设置,如:setMaximumSize(QSize(100, 50))
setIcon() 添加图片,使用QIcon设置,如:setIcon(QIcon(':/pic/add.png'))
setStyleSheet() 设置按钮样式
setCursor() 设置鼠标手型
setEnabled() 设置可用
QRadioButton
方法 描述
setChecked() 设置选中
QScrollArea

滚动区域。

方法 描述
setAutoFillBackground() 填充背景色
setWidgetResizable() 自动调整窗口小部件的大小,以避免在可以避免滚动条的地方使用滚动条。在该例子中,我使用了改变窗口的大小,所以该方法没效果。
setGeometry() 设置所在位置和大小,(x, y, w, h) 即 (X坐标、Y坐标、宽、高),其中x,y坐标是参照它所在的继承的窗口,非屏幕坐标,单位px
QStatusBar

窗口状态栏

方法 描述
setStyleSheet() 设置样式
showMessage() 状态栏显示信息
QWidget

基础窗口控件。

QtGui:

QDoubleValidator

校验器。

方法 描述
setRange() 值范围
setDecimals() 精度位数
QIcon

设置图标。

self.setWindowIcon(QIcon(':/pic/aehvi-0m7h6-001.ico'))
QPalette

对话框或控件调色板。

scroll_area_palette = self.scroll_area.palette()  # 获取已有的调色板
scroll_area_palette.setColor(QPalette.Window, Qt.white)  # 修改背景色为白色
self.scroll_area.setPalette(scroll_area_palette)  # 为组件设置调色板
self.scroll_area.setAutoFillBackground(True)  # 组件生效调色板

QtCore:

QSize

设置尺寸。

self.add_file_btn.setMaximumSize(QSize(113, 38))
QRect

设置左、上、宽、高。

代码整理

分两个类文件,界面类和事件类。

class Ui_Form(QMainWindow):
    def __init__(self):
        # 初始化继承父类(QMainWindow)
        super(Ui_Form, self).__init__()
        # 获取屏幕大小
        self.desktop = QApplication.desktop()
        self.height = self.desktop.height()
        self.width = self.desktop.width()

        # 设置窗口大小,并显示在屏幕中间
        self.resize(int(self.width * 0.5), int(self.height * 0.65))
        self.setWindowTitle("合成gif")
        self.setWindowIcon(QIcon(':/pic/aehvi-0m7h6-001.ico'))

        self.set_center()
        self.initUI()

窗口居中

    def set_center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()  # desktop的availableGeometry()获取可用位置和尺寸(除去任务栏等)
        qr.moveCenter(cp)
        self.move(qr.topLeft())

添加控件,主要是QScrollArea控件的滚动条,当区域内的内容超过滚动区域大小时显示滚动条。QScrollArea内部有个QGridLayout(QWidget子组件),因为QScrollArea只能添加widget,先实例一个QWidget,再把QGridLayout添加到QWidget中,就可以添加到QScrollArea里。计算好QGridLayout列数的大小不要超过QScrollArea的宽度,就不会显示横向滚动条,当图片加入QGridLayout的个数总长度超过QScrollArea的高度时,自动出现纵向滚动条。个人觉得这个方法最简单

滚动区域的大小,我按屏幕的大小比例计算,如果写死大小,如果程序打包出来,发给屏幕尺寸不一样的电脑,界面上控件位置就会惨不忍睹。我这个办法个人觉得很low,但是一时没找到更好的方法,先这么将就用了。

        # 滚动区域,存放图片
        self.scroll_area = QScrollArea(self)
        self.scroll_area_contentwidget = QWidget(self)
        self.gridLayout = QGridLayout(self.scroll_area_contentwidget)
        self.scroll_area.setWidget(self.scroll_area_contentwidget)
        # 设置滚动区域的位置和大小
        self.scroll_area.setGeometry(10, 10, int(self.width*0.5-20), int(self.height*0.6/3*2))  

设置滚动区域背景色,setAutoFillBackground 方法一定要设置为Ture,否则背景色不生效

        scroll_area_palette = self.scroll_area.palette()  # 获取已有的调色板
        scroll_area_palette.setColor(QPalette.Window, Qt.white)  # 修改背景色为白色
        self.scroll_area.setPalette(scroll_area_palette)  # 为组件设置调色板
        self.scroll_area.setAutoFillBackground(True)  # 组件生效调色板

添加按钮,加入水平布局

        # 添加两个按钮
        self.btnwidget = QWidget(self)
        self.btnwidget.setGeometry(QRect(10, int(self.height*0.6/3*2)+20, 211, 50))  # 按钮控件布局及大小

        self.hlayout_btn = QHBoxLayout(self.btnwidget)  # 加水平布局
        self.hlayout_btn.setSpacing(10)  # 水平布局中控件的间隔
        
        # 添加文件按钮
        self.add_file_btn = QPushButton('添加文件', self.btnwidget)
        self.add_file_btn.setMaximumSize(QSize(113, 38))  # 设置按钮大小
        self.add_file_btn.setIcon(QIcon(':/pic/add.png')) # 按钮添加图片
        self.add_file_btn.setStyleSheet("border-radius:10px;border:1px groove gray;")  # 设置按钮样式
        self.add_file_btn.setCursor(Qt.PointingHandCursor)  # 鼠标移入显示手型

        self.empty_btn = QPushButton('清空列表', self.btnwidget)
        self.empty_btn.setMaximumSize(QSize(113, 38))
        self.empty_btn.setIcon(QIcon(':/pic/cal.png'))
        self.empty_btn.setStyleSheet("border-radius:10px;border:1px groove gray;")
        self.empty_btn.setCursor(Qt.PointingHandCursor)
        self.empty_btn.setEnabled(False)  # 设置按钮不可用
        
        # 水平布局加入按钮
        self.hlayout_btn.addWidget(self.add_file_btn) 
        self.hlayout_btn.addWidget(self.empty_btn)

输出信息块的代码,与按钮添加类似,主要是水平布局在最后添加的控件添加拉伸,使各控件局左,否则各个控件间就会隔的很开

        self.hlayout_out.addWidget(self.label_out)
        self.hlayout_out.addWidget(self.radio_btn1)
        self.hlayout_out.addWidget(self.radio_btn2)
        self.hlayout_out.addWidget(self.custom_edit)
        self.hlayout_out.addWidget(self.custom_btn)
        self.hlayout_out.addStretch()  # 设置拉伸

设置时间间隔的文本输入框,使用校验器

        self.hlayout_duration = QHBoxLayout(self.switchDuration)    
        pDoubleValidator = QDoubleValidator() # 数值校验器,只能输入数字
        pDoubleValidator.setRange(0, 100) # 输入数值范围
        pDoubleValidator.setNotation(QDoubleValidator.StandardNotation)
        pDoubleValidator.setDecimals(1)  # 输入精度位
        self.duration_edit.setValidator(pDoubleValidator)  # 文本输入框加入校验器

合成按钮,设置按钮样式

        self.com_btn = QPushButton('合成gif', self)
        self.com_btn.setStyleSheet("background-color: rgb(170, 170, 255);color: white;"
                                   "border-radius: 10px;  border: 2px groove gray;")

到此界面部分就完成!


事件部分

事件处理,新定义了一个类文件,继承界面类

class Ui_Event(Ui_Form):
    def __init__(self):
        super(Ui_Event, self).__init__()
        self.initUI()
        self.radio_btn1.toggled.connect(lambda: self.radio_btn_sign(self.radio_btn1)) # 输入信息单选框事件
        self.radio_btn2.toggled.connect(lambda: self.radio_btn_sign(self.radio_btn1))
        self.add_file_btn.clicked.connect(self.open)  # 添加文件按钮事件
        self.empty_btn.clicked.connect(self.empty)    # 清空图片列表事件
        self.custom_btn.clicked.connect(self.custom)  # 自定义输出按钮事件
        self.com_btn.clicked.connect(self.complex)    # 合成按钮事件

        self.col = -1
        self.row = 0
        self.gif_file = './result.gif'
        self.img_list = []

主要是添加用户选中的图片到QGridLayout里,这边用到的是先将图片设置到100px*100px的大小(这样方便计算出QGridLayout的一行的大小,QGridLayout不超过QScrollArea的宽度,就不会出现横向的滚动条),存到QLable中,然后把QLable添加到QGridLayout的一个个网格里。

    def open(self):
        imgfiles, _ = QFileDialog.getOpenFileNames(self, 'Open file', '.', 'Image files (*.jpg *.gif *.png *.jpeg)') # 打开文件目录,设置可选择的文件类型
        photo_num = len(imgfiles)
        if photo_num != 0:
            for i in range(photo_num):
                image_id = imgfiles[i]
                pixmap = QPixmap(image_id)
                self.img_list.append(image_id)
                pixmap_change = pixmap.scaled(100, 100, Qt.KeepAspectRatio)  # 改变展示图片的大小,保持等比例缩放
                label2 = QLabel()
                label2.setPixmap(QPixmap(pixmap_change))  # 图片加到lable上
                self.addImage(label2)
            self.empty_btn.setEnabled(True)
        else:
            QMessageBox.information(self, '提示', '未选择图片')

添加图片到QGridLayout,这里还是得注意计算QGridLayout各列总宽度不要超过QScrollArea的跨度。总列数*100px(我们前面设置的一张图片的大小)

    def get_nr_of_image_columns(self):
        # 展示图片的区域,计算每排显示图片数
        scroll_area_images_width = int(self.width*0.5-20)
        return scroll_area_images_width // 100 - 2  # 除100后可能有余数,可能会超过QScrollArea的宽度,-2 尽量减少列数,保证总列数不超过QScrollArea的宽度

    def addImage(self, label2):
        nr_of_columns = self.get_nr_of_image_columns()
        self.max_columns = nr_of_columns
        if self.col < self.max_columns:
            self.col += 1
        else:
            self.col = 0
            self.row += 1
        self.gridLayout.addWidget(label2, self.row, self.col)  # 添加图片的lable到gridLayout具体行列上

清空列表,清空列表要把图片img_list也一起清空了,img_list定义的是全局变量,否则再下次合成后,会把上次加入的图片也合成进去。

    def empty(self):
        print(self.gridLayout.count())
        for i in reversed(range(self.gridLayout.count())):
            move_widget = self.gridLayout.itemAt(i).widget()
            move_widget.setParent(None)
            move_widget.deleteLater()
        self.empty_btn.setEnabled(False)
        self.col = -1
        self.row = 0
        self.img_list.clear()
        self.label_bar1.setText('准备合成:')
        self.label_bar2.setText('准备合成:')
        self.pbar.setValue(0)

合成方法就很简单了,imageio模块imread,mimsave方法搞定,注意状态栏进度条的处理。

    def create_gif(self, image_list, gif_name, duration=0.35):
        frames = []
        image_num = len(image_list)
        step_size = 500//image_num + 1
        for i, image_name in enumerate(image_list):
            frames.append(imageio.imread(image_name))  # 加入合成图片
            step_sum = step_size*(i+1)  # 进度条值计算
            max = step_sum if step_sum<500 else 500
            self.pbar.setValue(max)  # 设置进度条值
        imageio.mimsave(gif_name, frames, 'GIF', duration=duration) # 合成gif
        return

    def complex(self):
        com_duration = self.duration_edit.text()
        if self.gridLayout.count() == 0:
            QMessageBox.information(self, '提示', '未添加合成的图片')
        elif not com_duration:
            QMessageBox.information(self, '提示', '切换间隔未填写')
        else:
            self.label_bar1.setText('开始合成')
            self.label_bar2.setText('正在合成:')
            if self.radio_btn2.isChecked() == True:
                self.gif_file = self.custom_edit.text() + '/result.gif'
            if not os.path.exists(os.path.dirname(self.gif_file)):
                QMessageBox.information(self, '提示', '输出目录不存在')
            else:
                self.create_gif(self.img_list, self.gif_file, com_duration)
                self.label_bar1.setText('完成合成')
                self.label_bar2.setText('完成合成:')

整体功能完全,界面优化的地方还有很多,比如窗口最大化,控件不会自适应、界面布局太丑、输入的文件名用户自己定义……

打算后续用designer.exe重新再做过。代码可打包成exe,如何打包exe后续再整理。

相关文章

网友评论

    本文标题:pyQt5(QScrollArea+QGridLayout):图

    本文链接:https://www.haomeiwen.com/subject/uthialtx.html