神秘资源的背后:当常规途径失效时,他竟然这样做了…!? PyQt、PySide动态可编辑标签页QTabWidget实现。
文章目录
前言
由于兴趣爱好,小才喜欢做一些PyQt5的简单小工具。虽然没有经历过正式的软件开发训练,但小才通过网络上的资源,一步步摸索学习,最终能够根据别人的示例代码进行优化修改,以达到自己想要的效果。从完全不懂到逐渐掌握,这个过程让小才感到十分愉快。
PyQt、PySide动态可编辑标签页QTabWidget实现效果:(温馨提示:代码部分较多,最底部领取无套路、免费打包好项目,直接运行!)
正文
需求描述
最近,小才想实现一个功能:能够打开多张图片并在界面上显示。每当打开一张图片时,就会显示一个标签,并且删除标签时可以同步删除对应的图片。这种效果类似于Element UI中的Tag组件,这让小才感到非常羡慕。
初始思路
在尝试实现这个功能时,小才第一时间想到了使用QTabWidget
。经过研究发现,QTabWidget
确实能够满足需求,只需要在每个Tab
中添加一个包含删除按钮的QWidget
即可。但是,这样做会导致每次新增一个Tab
时,内容也随之增加,即需要为每个Tab
准备一个QLabel
来显示图片。
面临的问题
然而,小才的需求其实很简单:只需要显示一张图片,当选择不同的Tab
时,更新同一个QLabel
的内容即可。这样可以避免重复创建多个QLabel
,减少资源浪费。
解决方案
为了达到这个目的,小才决定只使用一个QLabel
来显示图片。具体实现步骤如下:
- 当用户选择不同的
Tab
时,更新唯一的QLabel
来显示相应的图片。 - 当用户点击删除按钮时,移除该
Tab
以及对应的图片。
通过这种方法,小才既能实现多张图片的标签管理,又能避免重复创建多个QLabel
,从而提高效率并简化代码结构。
代码实现
废话不多说直接上代码:
使用的虽然是pyside6,但是pyqt5的控件类名一致的,很容易平替。
TabWidget.ui和TabWidgetUI.py
一个一个的Tab控件
TabWidget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabWidget</class>
<widget class="QWidget" name="TabWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>152</width>
<height>51</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="tab_btn">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
font: 12px "Microsoft YaHei UI";
background-color:rgb(255, 255, 255);
color: rgb(0, 0, 0);
border:none;
padding-left:5px;
padding-right:5px;
}
QPushButton:hover{
background-color:rgb(64, 158, 255);
color: rgb(255, 255, 255);
/*padding-bottom: 5px;*/
}
</string>
</property>
<property name="text">
<string>111试测试测试测试1</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget{
background-color:rgb(0, 64, 115);
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="delete_btn">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
font: 13px "Microsoft YaHei UI";
background-color:rgb(0, 64, 115);
color: rgb(255, 255, 255);
padding-bottom:1px;
border-radius: 10px;
}
/**
QPushButton:pressed{
padding-top:5px;
padding-left:5px;
}**/
QPushButton:hover{
background-color:rgb(255, 0,0);
}
QPushButton:disabled{
color:rgb(155,155,155);
}
</string>
</property>
<property name="text">
<string>X</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
TabWidgetUI.py
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'TabWidget.ui'
##
## Created by: Qt User Interface Compiler version 6.7.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QMetaObject, QSize)
from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QSizePolicy,
QWidget)
class Ui_TabWidget(object):
def setupUi(self, TabWidget):
if not TabWidget.objectName():
TabWidget.setObjectName(u"TabWidget")
TabWidget.resize(152, 51)
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(TabWidget.sizePolicy().hasHeightForWidth())
TabWidget.setSizePolicy(sizePolicy)
TabWidget.setMaximumSize(QSize(300, 16777215))
TabWidget.setStyleSheet(u"")
self.horizontalLayout = QHBoxLayout(TabWidget)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.tab_btn = QPushButton(TabWidget)
self.tab_btn.setObjectName(u"tab_btn")
sizePolicy.setHeightForWidth(self.tab_btn.sizePolicy().hasHeightForWidth())
self.tab_btn.setSizePolicy(sizePolicy)
self.tab_btn.setStyleSheet(u"QPushButton{\n"
" font: 12px \"Microsoft YaHei UI\";\n"
" background-color:rgb(255, 255, 255);\n"
" color: rgb(0, 0, 0);\n"
" border:none;\n"
" padding-left:5px;\n"
" padding-right:5px;\n"
"}\n"
"\n"
"QPushButton:hover{\n"
" background-color:rgb(64, 158, 255);\n"
" color: rgb(255, 255, 255);\n"
" /*padding-bottom: 5px;*/\n"
"}\n"
"")
self.horizontalLayout.addWidget(self.tab_btn)
self.widget = QWidget(TabWidget)
self.widget.setObjectName(u"widget")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.widget.setSizePolicy(sizePolicy1)
self.widget.setMinimumSize(QSize(20, 0))
self.widget.setMaximumSize(QSize(20, 16777215))
self.widget.setStyleSheet(u"QWidget{\n"
" background-color:rgb(0, 64, 115);\n"
" border-top-right-radius: 10px; \n"
" border-bottom-right-radius: 10px; \n"
"}\n"
"")
self.horizontalLayout_2 = QHBoxLayout(self.widget)
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.delete_btn = QPushButton(self.widget)
self.delete_btn.setObjectName(u"delete_btn")
sizePolicy.setHeightForWidth(self.delete_btn.sizePolicy().hasHeightForWidth())
self.delete_btn.setSizePolicy(sizePolicy)
self.delete_btn.setMinimumSize(QSize(20, 20))
self.delete_btn.setMaximumSize(QSize(20, 20))
self.delete_btn.setStyleSheet(u"QPushButton{\n"
" font: 13px \"Microsoft YaHei UI\";\n"
" background-color:rgb(0, 64, 115);\n"
" color: rgb(255, 255, 255);\n"
" padding-bottom:1px; \n"
" border-radius: 10px; \n"
"}\n"
"/**\n"
"QPushButton:pressed{\n"
" padding-top:5px;\n"
" padding-left:5px;\n"
"}**/\n"
"\n"
"\n"
"QPushButton:hover{\n"
" background-color:rgb(255, 0,0);\n"
"}\n"
"\n"
"QPushButton:disabled{\n"
" color:rgb(155,155,155);\n"
"}\n"
"")
self.horizontalLayout_2.addWidget(self.delete_btn)
self.horizontalLayout.addWidget(self.widget)
self.retranslateUi(TabWidget)
QMetaObject.connectSlotsByName(TabWidget)
# setupUi
def retranslateUi(self, TabWidget):
TabWidget.setWindowTitle(QCoreApplication.translate("TabWidget", u"Form", None))
self.tab_btn.setText(
QCoreApplication.translate("TabWidget", u"111\u8bd5\u6d4b\u8bd5\u6d4b\u8bd5\u6d4b\u8bd51", None))
self.delete_btn.setText(QCoreApplication.translate("TabWidget", u"X", None))
# retranslateUi
EditableTab.py
继承TabWidgetUI定义Tab的选择和未选择的样式。
from PySide6.QtWidgets import QWidget # 导入PySide6的QWidget模块
from TabWidgetUI import Ui_TabWidget # 导入自定义的UI类
# 定义被选中状态下的按钮样式
SELECT_STYLE = '''
QPushButton{
font: 13px "Microsoft YaHei UI"; /* 设置字体大小和字体类型 */
/*background-color:rgb(64, 158, 255);*/ /* 评论掉的背景颜色设置 */
background-color:rgb(0, 64, 115); /* 设置新的背景颜色 */
color: rgb(255, 255, 255); /* 设置文本颜色为白色 */
border-top-left-radius: 10px; /* 设置左上角圆角半径 */
border-bottom-left-radius: 10px; /* 设置左下角圆角半径 */
padding-left:5px; /* 设置左边内边距 */
}
QPushButton:disabled { /* 按钮禁用状态下的样式 */
color:rgb(155,155,155); /* 设置禁用状态下的文本颜色 */
}
QPushButton:pressed { /* 按钮被按下时的样式 */
padding-top:0px; /* 设置顶部内边距为0 */
padding-left:0px; /* 设置左边内边距为0 */
}
'''
# 定义未被选中状态下的按钮样式
UNSELECT_STYLE = '''
QPushButton{
font: 12px "Microsoft YaHei UI"; /* 设置字体大小和字体类型 */
background-color:rgb(255, 255, 255); /* 设置背景颜色为白色 */
color: rgb(0, 0, 0); /* 设置文本颜色为黑色 */
border-top-left-radius: 10px; /* 设置左上角圆角半径 */
border-bottom-left-radius: 10px; /* 设置左下角圆角半径 */
padding-left:5px; /* 设置左边内边距 */
}
QPushButton:hover { /* 按钮悬停时的样式 */
/*background-color:rgb(64, 158, 255);*/ /* 评论掉的背景颜色设置 */
color: rgb(0, 170, 255); /* 设置悬停状态下的文本颜色 */
/*padding-bottom: 5px;*/ /* 评论掉的底部内边距设置 */
}
QPushButton:pressed { /* 按钮被按下时的样式 */
padding-top:0px; /* 设置顶部内边距为0 */
padding-left:0px; /* 设置左边内边距为0 */
}
'''
class EditableTab(QWidget, Ui_TabWidget): # 定义一个可编辑的Tab类
def __init__(self, *args, **kwargs): # 初始化方法
super(EditableTab, self).__init__(*args, **kwargs) # 调用基类初始化方法
self.setupUi(self) # 设置UI
# 初始设置为未选择状态
self.setUnselected()
# 被选择状态的方法
def setSelected(self):
self.tab_btn.setStyleSheet(SELECT_STYLE) # 设置按钮样式为选中状态
# 未被选择状态的方法
def setUnselected(self):
self.tab_btn.setStyleSheet(UNSELECT_STYLE) # 设置按钮样式为未选中状态
# 获取Tab名称的方法
def getTabName(self):
return self.tab_btn.text() # 返回按钮的文本内容
EditableTabWidget.py
可编辑Tab的控件实现,内包新增、选中、删除Tab的方法。
from PySide6.QtCore import QSize, Signal
from PySide6.QtWidgets import QWidget, QSizePolicy, QHBoxLayout, QSpacerItem
from EditableTab import EditableTab # 导入自定义的EditableTab类
class EditableTabWidget(QWidget):
# 定义信号
# 当某个tab被选中时发出的信号,参数为选中的tab的索引
selected_signal = Signal(int)
# 当某个tab被取消选中时发出的信号,参数为取消选中的tab的索引
unselected_signal = Signal(int)
# 当某个tab被删除时发出的信号,参数为删除的tab的索引
delete_signal = Signal(int)
def __init__(self, parent):
super(EditableTabWidget, self).__init__(parent)
# 设置大小策略
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(sizePolicy)
# 设置最小和最大尺寸
self.setMinimumSize(QSize(0, 0))
self.setMaximumSize(QSize(16777215, 16777215)) # 最大尺寸为整数的最大值
# 创建水平布局
self.horizontalLayout = QHBoxLayout(self)
self.horizontalLayout.setSpacing(2)
self.horizontalLayout.setObjectName("horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 3, 0, 3) # 设置布局的边缘距离
# 添加一个水平间隔项
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
# 当前选中的tab
self.current_tab: EditableTab = None
# 大小策略
self.size_policy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.size_policy.setHorizontalStretch(0)
self.size_policy.setVerticalStretch(0)
self.size_policy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.size_policy2.setHorizontalStretch(0)
self.size_policy2.setVerticalStretch(0)
def addTab(self, tab_name, tooltip=None, choose_new=False):
'''
添加一个新的tab
:param tab_name: tab的名称
:param tooltip: tab的提示信息
:param choose_new: 是否自动选中新创建的tab
:return: 新创建的tab对象
'''
tabWidget = EditableTab(self)
self.size_policy.setHeightForWidth(tabWidget.sizePolicy().hasHeightForWidth())
tabWidget.setSizePolicy(self.size_policy)
# 绑定删除按钮的点击事件
tabWidget.delete_btn.clicked.connect(lambda: self.deleteTab(tabWidget))
# 设置tab的文本
tabWidget.tab_btn.setText(tab_name)
# 设置tab的提示信息
if tooltip is not None:
tabWidget.tab_btn.setToolTip(tooltip)
# 绑定tab按钮的点击事件
tabWidget.tab_btn.clicked.connect(lambda: self.selectTab(tabWidget))
# 在布局中插入新的tab
self.horizontalLayout.insertWidget(self.horizontalLayout.count() - 1, tabWidget)
# 如果需要自动选中新tab,则调用selectTab方法
if choose_new:
self.selectTab(tabWidget, False)
return tabWidget
def clearTab(self):
'''
清空所有tab
'''
for _ in range(self.getTabSize()):
self.deleteTab(self.horizontalLayout.itemAt(0).widget())
def getTabSize(self):
'''
获取tab的数量
'''
return self.horizontalLayout.count() - 1 # 减去一个水平间隔项
def getCurrentTab(self):
'''
获取当前选中的tab及其索引
'''
if self.current_tab is None:
return -1, None
return self.horizontalLayout.indexOf(self.current_tab), self.current_tab
def deleteTab(self, tabWidget: EditableTab):
'''
删除指定的tab
:param tabWidget: 要删除的tab对象
'''
# 发送删除事件
self.delete_signal.emit(self.horizontalLayout.indexOf(tabWidget))
# 从布局中移除tab
self.horizontalLayout.removeWidget(tabWidget)
# 删除tab对象
tabWidget.deleteLater()
# 如果当前选中的tab是要删除的那个tab,则将其设为None
if self.current_tab == tabWidget:
self.current_tab = None
def selectTab(self, tabWidget: EditableTab, emit_signal=True):
'''
选中指定的tab
:param tabWidget: 要选中的tab对象
:param emit_signal: 是否发送选中/取消选中信号
'''
if self.current_tab == tabWidget:
return
# 获取要选中的tab的索引
idx = self.horizontalLayout.indexOf(tabWidget)
# 如果已经有选中的tab,则先取消选中
if self.current_tab is not None:
self.current_tab.setUnselected()
# 设置大小策略
self.size_policy.setHeightForWidth(self.current_tab.sizePolicy().hasHeightForWidth())
self.current_tab.setSizePolicy(self.size_policy)
# 发送取消选中信号
if emit_signal:
self.unselected_signal.emit(self.horizontalLayout.indexOf(self.current_tab))
# 将当前选中的tab设置为新的tab
self.current_tab = tabWidget
# 选中新的tab
tabWidget.setSelected()
# 设置大小策略
self.size_policy2.setHeightForWidth(tabWidget.sizePolicy().hasHeightForWidth())
tabWidget.setSizePolicy(self.size_policy2)
# 发送选中信号
if emit_signal:
self.selected_signal.emit(idx)
def select_first_tab(self):
'''
选中第一个tab
'''
if self.getTabSize() > 0:
self.selectTab(self.horizontalLayout.itemAt(0).widget())
主界面mainUI.py和main.ui(有注意事项!!!!)
因为界面上显示的是可以缩放、拖动的ImageLabel,所以在将main.ui生成mainUI.py后,需要替换self.label = QLabel(parent)
为self.label = ImageLabel(parent)
。ImageLabel实现源码请看小才另外一篇博文随窗口自适应、可缩放、拖动QLabel
main.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1134</width>
<height>576</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_3" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_4" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QWidget" name="widget_5" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>431</width>
<height>19</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>放大</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>缩小</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_4">
<property name="text">
<string>复原</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>430</width>
<height>19</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
mainUI.py
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'main.ui'
##
## Created by: Qt User Interface Compiler version 6.5.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel,
QMainWindow, QPushButton, QSizePolicy, QSpacerItem,
QVBoxLayout, QWidget)
from custom_label import ImageLabel
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1134, 576)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.widget = QWidget(self.centralwidget)
self.widget.setObjectName(u"widget")
self.verticalLayout = QVBoxLayout(self.widget)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.widget_3 = QWidget(self.widget)
self.widget_3.setObjectName(u"widget_3")
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget_3.sizePolicy().hasHeightForWidth())
self.widget_3.setSizePolicy(sizePolicy)
self.widget_3.setMinimumSize(QSize(0, 40))
self.widget_3.setMaximumSize(QSize(16777215, 40))
self.horizontalLayout = QHBoxLayout(self.widget_3)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.widget_4 = QWidget(self.widget_3)
self.widget_4.setObjectName(u"widget_4")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.widget_4.sizePolicy().hasHeightForWidth())
self.widget_4.setSizePolicy(sizePolicy1)
self.widget_4.setMaximumSize(QSize(16777215, 16777215))
self.horizontalLayout_2 = QHBoxLayout(self.widget_4)
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.addWidget(self.widget_4)
self.pushButton = QPushButton(self.widget_3)
self.pushButton.setObjectName(u"pushButton")
sizePolicy1.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth())
self.pushButton.setSizePolicy(sizePolicy1)
self.pushButton.setMinimumSize(QSize(30, 0))
self.pushButton.setMaximumSize(QSize(30, 16777215))
self.horizontalLayout.addWidget(self.pushButton)
self.verticalLayout.addWidget(self.widget_3)
self.widget_2 = QWidget(self.widget)
self.widget_2.setObjectName(u"widget_2")
sizePolicy1.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth())
self.widget_2.setSizePolicy(sizePolicy1)
self.gridLayout_2 = QGridLayout(self.widget_2)
self.gridLayout_2.setSpacing(0)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.label = ImageLabel(self.widget_2)
self.label.setObjectName(u"label")
self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
self.widget_5 = QWidget(self.widget_2)
self.widget_5.setObjectName(u"widget_5")
sizePolicy1.setHeightForWidth(self.widget_5.sizePolicy().hasHeightForWidth())
self.widget_5.setSizePolicy(sizePolicy1)
self.widget_5.setMinimumSize(QSize(0, 40))
self.widget_5.setMaximumSize(QSize(16777215, 40))
self.horizontalLayout_3 = QHBoxLayout(self.widget_5)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.horizontalSpacer = QSpacerItem(431, 19, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(self.horizontalSpacer)
self.pushButton_2 = QPushButton(self.widget_5)
self.pushButton_2.setObjectName(u"pushButton_2")
self.horizontalLayout_3.addWidget(self.pushButton_2)
self.pushButton_3 = QPushButton(self.widget_5)
self.pushButton_3.setObjectName(u"pushButton_3")
self.horizontalLayout_3.addWidget(self.pushButton_3)
self.pushButton_4 = QPushButton(self.widget_5)
self.pushButton_4.setObjectName(u"pushButton_4")
self.horizontalLayout_3.addWidget(self.pushButton_4)
self.horizontalSpacer_2 = QSpacerItem(430, 19, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(self.horizontalSpacer_2)
self.gridLayout_2.addWidget(self.widget_5, 1, 0, 1, 1)
self.verticalLayout.addWidget(self.widget_2)
self.gridLayout.addWidget(self.widget, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"+", None))
self.label.setText("")
self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"\u653e\u5927", None))
self.pushButton_3.setText(QCoreApplication.translate("MainWindow", u"\u7f29\u5c0f", None))
self.pushButton_4.setText(QCoreApplication.translate("MainWindow", u"\u590d\u539f", None))
# retranslateUi
启动运行
import os.path
import sys
from typing import List
import cv2
import numpy as np
from PySide6.QtWidgets import QMainWindow, QApplication, QFileDialog
from check_except import check_except
from mainUI import Ui_MainWindow
from EditableTabWidget import EditableTabWidget
IMG_EXTENSIONS = (".bmp", ".jpg", ".jpeg", ".png", ".ppm", ".pgm", ".tif", ".tiff", ".webp")
def cv_imread(file_path):
#imdedcode读取的图像 中文路径
cv_img = cv2.imdecode(np.fromfile(file_path,dtype=np.uint8), cv2.IMREAD_COLOR)
return cv_img
class ImageInfo:
def __init__(self, image_name, image_path, cv_image):
'''
image_name: 图片tab名称
image_path: 图片路径
cv_image: opencv格式的图片
'''
self.image_path = image_path
self.image_name = image_name
self.cv_image = cv_image
class MainUI(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainUI, self).__init__(parent)
self.setupUi(self)
self.tabWidget = EditableTabWidget(self.centralwidget)
# 布局内加入自定义的可编辑标签页 充满布局
self.horizontalLayout_2.addWidget(self.tabWidget)
# 绑定tab删除事件
self.tabWidget.delete_signal.connect(self.delete_tab)
# 绑定tab选择事件
self.tabWidget.selected_signal.connect(self.select_tab)
# 记录显示图片列表
self.img_info_list:List[ImageInfo] = []
# 记录当前显示图片
self.current_img_info:ImageInfo = None
# 上次打开图片路径
self.last_img_path = "."
# 按键槽函数设置
# 新增图片tab按钮
self.pushButton.clicked.connect(self.add_new_tab)
# 放大
self.pushButton_2.clicked.connect(self.label.zoom_in)
# 缩小
self.pushButton_3.clicked.connect(self.label.zoom_out)
# 复原
self.pushButton_4.clicked.connect(self.label.reset_all)
@check_except()
def add_new_tab(self):
fileName, fileType = QFileDialog.getOpenFileName(self, f"请选择图片", self.last_img_path,
" ".join(list(map(lambda x: "*" + x, IMG_EXTENSIONS))))
if fileName == "":
# 取消选择则退出
return
self.last_img_path = os.path.dirname(fileName)
# 打开图片 可以读取中文路径
img = cv_imread(fileName)
# 创建标签页并选择新的标签页 设置标签名为图片名 悬浮时提示为图片路径
tab_widget = self.tabWidget.addTab(os.path.basename(fileName), fileName, choose_new=True)
# 显示图片
self.label.set_cv_image(img)
# 记录图片信息
image_info = ImageInfo(os.path.basename(fileName), fileName, img)
self.img_info_list.append(image_info)
self.current_img_info = image_info
# 删除tab时回调
@check_except()
def delete_tab(self, idx):
# 删除图片信息
# 如果当前图片是删除的图片
if self.current_img_info == self.img_info_list[idx]:
self.current_img_info = None
self.label.clear_image()
# 选中至第一个tab
self.tabWidget.select_first_tab()
self.img_info_list.pop(idx)
# 选择tab时回调
@check_except()
def select_tab(self, idx):
# 设置当前显示图片
self.current_img_info = self.img_info_list[idx]
self.label.set_cv_image(self.current_img_info.cv_image)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainUI()
win.show()
sys.exit(app.exec())
方法上的装饰器
check_except
是为了方便异常捕捉,小才不想因为显示报错而导致页面卡住然后退出。实现请看小才得另一篇文章python
程序运行异常与计算耗时@装饰器,不想看的话注释掉@check_except()
即可。
资源获取
感谢您的支持和鼓励! 😊🙏
如果大家对相关文章感兴趣,可以搜索并关注公众号"人才兄呐",查看和领取打包资源,完全免费哒!无套路直接领取。如果资源不存在可以直接联系小才我给你私发!更多有用资源持续手动更新中~~
感谢
“点赞+评论,让我知道你在关注我,感谢每一个支持我的人!”
“不要忘记关注我,点赞收藏,我会为你带来更多优质内容!”
“你的关注是我前进的动力,你的点赞是我创作的源泉。”
“点个赞吧!让我知道你在支持我,我会继续努力的!”
“关注我,点赞收藏,让我有更多的动力去创作更好的内容!”
“你的每一个点赞,都是我创作的动力,感谢你的关注和支持!”
“希望你喜欢我的内容,记得关注我哦!我会继续为大家带来更好的作品!”