Commit df6e421f by 吴斌

init

parent e5091817
__pycache__
.idea
#demo
\ No newline at end of file
# 项目介绍
一个用于学习和熟悉 PyQt 组件的例子。
通过这个 demo 可以学习到:
1. 熟悉 PyQt5 的基础组件。
2. 熟悉 PyQt5 的布局。
3. 熟悉 PyQt5 的事件处理。
4. 熟悉 PyQt5 的信号与槽。
5. 熟悉 PyQt5 的自定义组件。
6. 熟悉 PyQt5 的 qss 样式。
# 需要了解的一些东西
## python 推荐编码规范
![](assets/img/code_rule.png)
## iconfont
https://www.iconfont.cn/
本项目中使用,iconfont 的字体图标库。便于对图标进行简单的修改。
![iconfont](assets/img/iconfont.png)
## 项目结构介绍
- assets: 资源文件夹。
- kit: 通用部分。
- component: 通用组件/自定义组件。
- pages: 页面。
- style: 样式代码。主要是 qss 文件。
\ No newline at end of file
import os
import sass
from PyQt5.QtCore import QObject
class Config(QObject):
DARK = "dark"
LIGHT = "light"
def __init__(self):
super().__init__()
# 工程根目录
self.root_path = ""
self.theme = Config.LIGHT
self.getRootPath()
def getRootPath(self):
# 获取当前文件所在的绝对路径
current_path = os.path.abspath(__file__)
# 获取当前文件所在的目录
self.root_path = os.path.dirname(current_path)
def init_qss(self):
current_theme = self.theme
theme_dir = self.root_path + "\\style\\"
theme_list = os.listdir(theme_dir)
qss = ""
for file in theme_list:
if file.endswith(".qss"):
with open(theme_dir + file, "r", encoding="utf-8") as f:
qss += f.read()
return qss
config = Config()
import sys
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFontDatabase, QIcon
from PyQt5.QtWidgets import QPushButton, QGraphicsDropShadowEffect, QVBoxLayout, QWidget, QApplication, QSizePolicy
import config
from kit.component.kit_icon import KitIcon
class KitButton(QPushButton):
# Button Type
OutLined = 'outlined'
Text = 'text'
# Button Shape
Round = 'round'
Square = 'square'
def __init__(self, text: str = "", icon: str = None, parent=None):
super(KitButton, self).__init__(parent=parent)
self._type = None
self._shape = None
self._style = None
self.setText(text)
if icon is not None:
self.setIcon(KitIcon(icon).toIcon())
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setFixedHeight(40)
self.setIconSize(QSize(20, 20))
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setType(self, button_type: str):
self._type = button_type
self.setProperty('type', button_type)
self.style().polish(self)
def setShape(self, button_shape: str):
self._shape = button_shape
self.setProperty('shape', button_shape)
self.style().polish(self)
def setIcon(self, icon: [QIcon, str]):
if icon is None:
return
if type(icon) is QIcon:
new_icon = icon
elif type(icon) is str:
new_icon = KitIcon(icon).toIcon()
else:
raise TypeError("Icon must be a QIcon or a str")
super().setIcon(new_icon)
def setShadow(self, if_shadow: bool = True):
# creating a QGraphicsDropShadowEffect object
if if_shadow:
shadow = QGraphicsDropShadowEffect()
shadow.setXOffset(0)
shadow.setYOffset(2)
# setting blur radius
shadow.setBlurRadius(4)
shadow.setColor(Qt.gray)
# adding shadow to the button
self.setGraphicsEffect(shadow)
else:
self.setGraphicsEffect(None)
def sizeHint(self):
return QSize(100, 40)
class KitIconButton(KitButton):
def __init__(self, icon_str: str = None, parent=None):
super(KitIconButton, self).__init__(parent=parent)
self.setText("")
self.setIcon(icon_str)
self.__init_widget()
def __init_widget(self):
self.setFixedSize(40, 40)
from PyQt5.QtCore import QPropertyAnimation, QParallelAnimationGroup, QAbstractAnimation, QSize, Qt
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import QWidget, QStyleOption, QStyle, QVBoxLayout, QApplication, QLabel, QGraphicsDropShadowEffect
from config import config
from kit.component.kit_button import KitButton
from kit.component.kit_overlay import KitOverlay
class KitDrawer(QWidget):
"""
侧边栏抽屉
"""
Left = 0
Top = 1
Right = 2
Bottom = 3
CloseOnClicked = 0
CloseOnEscape = 1
def __init__(self, orientation=Left):
super(KitDrawer, self).__init__()
# 内部使用的变量
self._parent = None
self._width = 300
self._height = 300
self._orientation = orientation
self.close_policy = KitDrawer.CloseOnClicked
self.overlay = KitOverlay()
self.__init_widget()
self.__init_slot()
self.__init_qss()
def setWidth(self, width):
if self._orientation == self.Left or self._orientation == self.Right:
self._width = width
def setHeight(self, height):
if self._orientation == self.Top or self._orientation == self.Bottom:
self._height = height
def setClosePolicy(self, policy: int):
self.close_policy = policy
self.overlay.setClosePolicy(policy)
def __init_widget(self):
self.hide()
self.overlay.setClosePolicy(KitOverlay.CloseOnClicked)
def __init_slot(self):
self.overlay.clicked.connect(lambda: self.close() if self.close_policy == KitDrawer.CloseOnClicked else None)
self.overlay.resized.connect(lambda: self.__init_size())
self.overlay.resized.connect(lambda: self.__fresh_position())
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
shadow = QGraphicsDropShadowEffect(self)
shadow.setOffset(0, 0)
shadow.setBlurRadius(10)
shadow.setColor(QColor(0, 0, 0, 80))
self.setGraphicsEffect(shadow)
def __init_size(self):
"""
初始化大小
"""
if self._orientation == self.Left or self._orientation == self.Right:
self.setFixedSize(self._width, self._parent.height())
elif self._orientation == self.Top or self._orientation == self.Bottom:
self.setFixedSize(self._parent.width(), self._height)
def __init_parent(self):
"""
初始化父类
"""
window = QApplication.activeWindow()
if window is not None:
self._parent = window
self.setParent(self._parent)
else:
self._parent = None
def __init_position(self):
"""
初始化位置
"""
if self._orientation == self.Left:
self.move(0 - self._width, 0)
elif self._orientation == self.Top:
self.move(0, 0 - self._height)
elif self._orientation == self.Right:
self.move(self._parent.width(), 0)
elif self._orientation == self.Bottom:
self.move(0, self._parent.height())
def __fresh_position(self):
if self.isHidden():
return
if self._orientation == self.Left:
self.move(0, 0)
elif self._orientation == self.Top:
self.move(0, 0)
elif self._orientation == self.Right:
self.move(self._parent.width() - self._width, 0)
elif self._orientation == self.Bottom:
self.move(0, self._parent.height() - self._height)
def __init_animation(self):
"""
初始化动画
"""
# 滑出动画
self.__sizeAnimation = None
if self._orientation == KitDrawer.Left:
self.__sizeAnimation = QPropertyAnimation(self, b"x")
self.__sizeAnimation.setStartValue(0)
self.__sizeAnimation.setEndValue(self._width)
self.__sizeAnimation.valueChanged.connect(lambda value: self.move(value - self._width, 0))
elif self._orientation == KitDrawer.Top:
self.__sizeAnimation = QPropertyAnimation(self, b"y")
self.__sizeAnimation.setStartValue(0)
self.__sizeAnimation.setEndValue(self._height)
self.__sizeAnimation.valueChanged.connect(lambda value: self.move(0, value - self._height))
elif self._orientation == KitDrawer.Right:
self.__sizeAnimation = QPropertyAnimation(self, b"x")
self.__sizeAnimation.setStartValue(0)
self.__sizeAnimation.setEndValue(self._width)
self.__sizeAnimation.valueChanged.connect(lambda value: self.move(self._parent.width() - value, 0))
elif self._orientation == KitDrawer.Bottom:
self.__sizeAnimation = QPropertyAnimation(self, b"y")
self.__sizeAnimation.setStartValue(0)
self.__sizeAnimation.setEndValue(self._height)
self.__sizeAnimation.valueChanged.connect(lambda value: self.move(0, self._parent.height() - value))
# 动画合集
self.__animation_group = QParallelAnimationGroup()
self.__animation_group.addAnimation(self.__sizeAnimation)
def open(self):
"""
打开抽屉
"""
self.__init_parent()
self.__init_animation()
self.__animation_group.setDirection(QAbstractAnimation.Forward)
self.overlay.show()
self.setHidden(False)
self.raise_()
self.__animation_group.start()
def close(self):
"""
关闭抽屉
"""
self.overlay.close()
self.__animation_group.setDirection(QAbstractAnimation.Backward)
self.__animation_group.start()
self.__animation_group.finished.connect(self.hide)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
qss = config.init_qss()
app.setStyleSheet(qss)
top_main = QWidget()
top_main.setObjectName('top_main')
top_main.resize(800, 800)
top_main_layout = QVBoxLayout()
top_main.setLayout(top_main_layout)
main = QWidget()
main.setObjectName('main')
main.resize(400, 400)
main_layout = QVBoxLayout()
main.setLayout(main_layout)
top_main_layout.addWidget(main)
drawer_left = KitDrawer(orientation=KitDrawer.Left)
drawer_left.setWidth(600)
drawer_left.setClosePolicy(KitDrawer.CloseOnEscape)
btn = KitButton('left')
btn.clicked.connect(lambda: drawer_left.open())
drawer_left.setLayout(QVBoxLayout())
close_left = KitButton('close')
close_left.clicked.connect(lambda: drawer_left.close())
drawer_left.layout().addWidget(close_left)
drawer_right = KitDrawer(orientation=KitDrawer.Right)
btn2 = KitButton('right')
btn2.clicked.connect(lambda: drawer_right.open())
drawer_right.setLayout(QVBoxLayout())
close_right = KitButton('close')
close_right.clicked.connect(lambda: drawer_right.close())
drawer_right.layout().addWidget(close_right)
drawer_top = KitDrawer(orientation=KitDrawer.Top)
btn3 = KitButton('top')
btn3.clicked.connect(lambda: drawer_top.open())
drawer_top.setLayout(QVBoxLayout())
close_top = KitButton('close')
close_top.clicked.connect(lambda: drawer_top.close())
drawer_top.layout().addWidget(close_top)
drawer_bottom = KitDrawer(orientation=KitDrawer.Bottom)
btn4 = KitButton('bottom')
btn4.clicked.connect(lambda: drawer_bottom.open())
drawer_bottom.setLayout(QVBoxLayout())
close_bottom = KitButton('close')
close_bottom.clicked.connect(lambda: drawer_bottom.close())
drawer_bottom.layout().addWidget(close_bottom)
main_layout.addWidget(btn)
main_layout.addWidget(btn2)
main_layout.addWidget(btn3)
main_layout.addWidget(btn4)
top_main.show()
sys.exit(app.exec_())
import sys
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFontDatabase, QPixmap, QIcon
from PyQt5.QtWidgets import QLabel, QApplication, QWidget, QVBoxLayout, QSizePolicy
import config
class KitIcon(QLabel):
def __init__(self, icon_str: str = None, parent=None):
super().__init__(parent=parent)
self.icon = icon_str
self.setAlignment(Qt.AlignCenter)
self.setContentsMargins(0, 0, 0, 0)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setIcon(self.icon)
# 转换为QIcon
def toIcon(self) -> QIcon:
img = self.grab().toImage()
pixmap = QPixmap.fromImage(img)
return QIcon(pixmap)
def toPixmap(self) -> QPixmap:
img = self.grab().toImage()
pixmap = QPixmap.fromImage(img)
return pixmap
def setIcon(self, icon_str):
self.icon = icon_str
self.setText(icon_str)
def sizeHint(self) -> QSize:
return QSize(20, 20)
if __name__ == "__main__":
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
qss = config.init_qss()
app.setStyleSheet(qss)
fontId = QFontDatabase.addApplicationFont("assets/font/iconfont.ttf")
fontName = QFontDatabase.applicationFontFamilies(fontId)[0]
main = QWidget()
main.setLayout(QVBoxLayout())
icon = KitIcon("\ue6b8")
icon.setProperty('type', 'primary')
main.layout().addWidget(icon)
label = QLabel("123")
main.layout().addWidget(label)
main.show()
sys.exit(app.exec_())
from PyQt5.QtCore import pyqtSignal, Qt, QEvent
from PyQt5.QtGui import QPalette, QPainter, QBrush, QColor, QPen
from PyQt5.QtWidgets import QWidget, QApplication
from kit.component.kit_button import KitButton
class KitOverlay(QWidget):
"""
遮罩层
show() 打开遮罩层,同时置于顶层,如果需要其他组件在最上面,记得调用该组件的raise_()函数
close() 关闭遮罩层
setClosePolicy() 设置关闭策略
"""
CloseOnClicked = 0
CloseOnEscape = 1
"""
关闭策略
CloseOnClicked: 点击遮罩层关闭
CloseOnEscape: 必须调用close函数才能关闭
"""
clicked = pyqtSignal()
resized = pyqtSignal()
def __init__(self, parent=None):
super(KitOverlay, self).__init__(parent)
self._parent = parent
self.close_policy = KitOverlay.CloseOnEscape
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setMouseTracking(False)
self.installEventFilter(self)
self.hide()
def __init_slot(self):
self.clicked.connect(lambda: self.close() if self.close_policy == KitOverlay.CloseOnClicked else None)
def __init_qss(self):
palette = QPalette(self.palette())
palette.setColor(palette.Background, Qt.transparent)
self.setPalette(palette)
def __init_parent(self):
# 用于获取当前应用的最上层的组件,可以理解为主窗口
widget = QApplication.activeWindow()
if widget is None:
widget = self.window()
if self._parent == widget:
return
self._parent = widget
self.setParent(self._parent)
def setClosePolicy(self, policy: CloseOnClicked or CloseOnEscape):
self.close_policy = policy
def paintEvent(self, event):
if self._parent is not None:
self.resize(self._parent.size())
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.fillRect(event.rect(), QBrush(QColor(0, 0, 0, 127)))
painter.setPen(QPen(Qt.NoPen))
def eventFilter(self, obj, e):
if e.type() == QEvent.MouseButtonPress:
self.clicked.emit()
elif e.type() == QEvent.MouseButtonDblClick:
return True
elif e.type() == QEvent.MouseMove and e.buttons() == Qt.LeftButton:
return True
return super().eventFilter(obj, e)
def show(self):
self.focusWidget()
self.__init_parent()
self.raise_()
super().show()
def resizeEvent(self, a0) -> None:
super().resizeEvent(a0)
self.resized.emit()
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from PyQt5.QtGui import QFontDatabase
from config import config
import sys
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
qss = config.init_qss()
app.setStyleSheet(qss)
fontId = QFontDatabase.addApplicationFont("assets/font/iconfont.ttf")
fontName = QFontDatabase.applicationFontFamilies(fontId)[0]
main = QWidget()
layout = QVBoxLayout()
main.setLayout(layout)
btn = KitButton('open overlay')
overlay = KitOverlay()
overlay.setClosePolicy(KitOverlay.CloseOnClicked)
layout.addWidget(btn)
btn.clicked.connect(lambda: [overlay.show(), btn.raise_()])
main.show()
sys.exit(app.exec_())
\ No newline at end of file
from PyQt5.QtCore import pyqtSignal, Qt, QSize
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QLayout, QGraphicsDropShadowEffect, QApplication
from config import config
from kit.component.kit_button import KitIconButton, KitButton
from kit.component.kit_overlay import KitOverlay
class KitPopupBase(QWidget):
"""
弹窗要使用show打开
"""
def __init__(self):
super(KitPopupBase, self).__init__()
self._parent = None
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
shadow = QGraphicsDropShadowEffect()
shadow.setXOffset(0)
shadow.setYOffset(0)
shadow.setBlurRadius(4)
shadow.setColor(Qt.black)
self.setGraphicsEffect(shadow)
self.hide()
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def init_parent(self):
self.focusWidget()
window = QApplication.activeWindow()
if window is not None:
self._parent = window
self.setParent(window)
def show(self):
self.init_parent()
super(KitPopupBase, self).show()
class KitPopup(KitPopupBase):
CloseOnClicked = 0
CloseOnEscape = 1
def __init__(self):
super(KitPopup, self).__init__()
self.close_policy = KitPopup.CloseOnClicked
self.overlay = KitOverlay()
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
pass
def __init_slot(self):
self.overlay.clicked.connect(lambda: self.close() if self.close_policy == KitPopup.CloseOnClicked else None)
self.overlay.resized.connect(self.__fresh_position)
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def __fresh_position(self):
self.move((self.overlay.width() - self.width()) // 2, (self.overlay.height() - self.height()) // 2)
self.update()
def setClosePolicy(self, policy: int):
self.close_policy = policy
self.overlay.setClosePolicy(policy)
def show(self):
self.overlay.show()
self.raise_()
super().show()
def close(self):
super().close()
self.overlay.close()
class KitModal(KitPopup):
"""
可以用来做一个单例。
"""
confirm = pyqtSignal()
cancel = pyqtSignal()
def __init__(self, title: str = None, content_layout: QLayout = None, bottom_buttons_layout: QLayout = None):
super(KitModal, self).__init__()
self.title = title
self.header_layout = QHBoxLayout()
self.content_layout = QVBoxLayout()
self.bottom_buttons_layout = QHBoxLayout()
self.__init_widget()
self.__init_slot()
self.__init_qss()
self.setTitle(self.title)
self.setContentLayout(content_layout)
self.setBottomButtonLayout(bottom_buttons_layout)
def __init_widget(self):
self.setClosePolicy(KitPopup.CloseOnEscape)
self.title_label = QLabel()
self.title_label.setObjectName('modal_title')
self.header_widget = QWidget()
self.header_widget.setObjectName('modal_header')
self.header_widget.setFixedHeight(40)
self.header_widget.setLayout(self.header_layout)
self.header_layout.addWidget(self.title_label, alignment=Qt.AlignHCenter)
self.content_widget = QWidget()
self.content_widget.setObjectName('modal_content')
self.content_widget.setLayout(self.content_layout)
self.bottom_button_widget = QWidget()
self.bottom_button_widget.setObjectName('modal_bottom')
self.bottom_button_widget.setLayout(self.bottom_buttons_layout)
self.v_layout = QVBoxLayout()
self.v_layout.addWidget(self.header_widget)
self.v_layout.addWidget(self.content_widget, 1)
self.v_layout.addWidget(self.bottom_button_widget)
self.close_button = KitIconButton('\ue634')
self.close_button.setObjectName('modal_close_btn')
self.close_button.setShape(KitButton.Round)
self.close_button.setType(KitButton.Text)
self.close_button.setShadow(False)
self.close_button.setParent(self.header_widget)
self.default_button_layout = QHBoxLayout()
self.confirm_btn = KitButton("确定")
self.cancel_btn = KitButton("取消")
self.cancel_btn.setType(KitButton.Text)
self.default_button_layout.addStretch(1)
self.default_button_layout.addWidget(self.cancel_btn)
self.default_button_layout.addWidget(self.confirm_btn)
self.setLayout(self.v_layout)
def __init_slot(self):
self.close_button.clicked.connect(self.close)
self.confirm_btn.clicked.connect(self.confirm.emit)
self.cancel_btn.clicked.connect(self.cancel.emit)
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def __fresh_close_btn_position(self):
self.close_button.move(self.header_widget.rect().right() - self.close_button.width(), 0)
def setTitle(self, title: str):
self.title = title
self.title_label.setText(title)
def setContent(self, content: str):
layout = QVBoxLayout()
label = QLabel(content)
label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
layout.addWidget(label)
self.setContentLayout(layout)
def setHeaderLayout(self, layout):
while self.header_layout.count() > 0:
self.header_layout.removeItem(self.header_layout.takeAt(0))
if layout is None:
layout = QHBoxLayout()
self.header_layout.addLayout(layout)
def setContentLayout(self, layout):
while self.content_layout.count() > 0:
self.content_layout.removeItem(self.content_layout.takeAt(0))
if layout is None:
layout = QVBoxLayout()
self.content_layout.addLayout(layout)
def setDefaultBottomButton(self):
self.setNoBottomButton()
if not self.cancel_btn.isVisible():
self.cancel_btn.setParent(self.bottom_button_widget)
self.cancel_btn.setVisible(True)
self.bottom_buttons_layout.addLayout(self.default_button_layout)
def setBottomButtonLayout(self, layout):
self.setNoBottomButton()
self.bottom_buttons_layout.addLayout(layout)
def setNoCloseButton(self):
self.close_button.hide()
def setNoBottomButton(self):
while self.bottom_buttons_layout.count() > 0:
self.bottom_buttons_layout.removeItem(self.bottom_buttons_layout.takeAt(0))
def setOneBottomButton(self):
self.cancel_btn.setVisible(False)
def resizeEvent(self, a0):
super().resizeEvent(a0)
self.__fresh_close_btn_position()
@classmethod
def setModalNotice(cls, title: str = None, content: str = None):
modal_notice = cls(title)
modal_notice.setContent(content)
modal_notice.setNoBottomButton()
modal_notice.adjustSize()
modal_notice.show()
return modal_notice
@classmethod
def setModalDialog(cls, title: str = None, content: str = None, confirm=None, cancel=None):
modal_dialog = cls(title)
modal_dialog.setContent(content)
modal_dialog.confirm.connect(confirm)
modal_dialog.cancel.connect(cancel)
modal_dialog.setDefaultBottomButton()
modal_dialog.adjustSize()
modal_dialog.show()
return modal_dialog
if __name__ == "__main__":
import sys
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
fontId = QFontDatabase.addApplicationFont("assets/font/iconfont.ttf")
fontName = QFontDatabase.applicationFontFamilies(fontId)[0]
qss = config.init_qss()
app.setStyleSheet(qss)
main = QWidget()
main_layout = QVBoxLayout()
main.resize(600, 800)
main.setLayout(main_layout)
modal = KitModal("123")
modal.confirm.connect(lambda: print("confirm"))
modal.cancel.connect(lambda: modal.close())
modal.setOneBottomButton()
modal.setNoBottomButton()
modal.setDefaultBottomButton()
btn = KitButton('show modal', parent=main)
btn.clicked.connect(lambda: modal.show())
main_layout.addWidget(btn)
content_layout = QVBoxLayout()
content_label = QLabel("这是弹窗内容")
content_layout.addWidget(content_label)
btn2 = KitButton('change modal content')
btn2.clicked.connect(lambda: modal.setContentLayout(content_layout))
main_layout.addWidget(btn2)
btn3 = KitButton('show modal notice')
btn3.clicked.connect(lambda: KitModal.setModalNotice("提示", "这是一个提示"))
main_layout.addWidget(btn3)
btn4 = KitButton('show modal dialog')
btn4.clicked.connect(lambda: KitModal.setModalDialog("提示", "这是一个提示", lambda: print("confirm"), lambda: print("cancel")))
main_layout.addWidget(btn4)
main.show()
sys.exit(app.exec_())
# Table组件
## 1. 功能
- [x] 自定义内部组件
- [x] 自定义行背景色
- [x] 自定义交替颜色
- [x] 自定义列宽,行高
- 指定 > 自适应 > 100
- [x] 自定义列对齐方式
- [x] 冻结效果
- [x] 分页效果
- [x] 表头固定效果
- [x] 表头双击排序,python 内置 sort 通过 Unicode来排序的。
- [ ] 还需添加排序提示
- [x] 内容省略,并悬浮提示
- [x] 多行显示 '_wrap': True
- [x] 无数据显示
## 2. API
### setTableColumnProperty(list)
设置表格列属性
``` python
table.setTableColumnProperty([
{'key': 'name', 'display': '名称', 'width': 80},
{'key': 'name2', 'display': '名称2'},
{'key': 'name3', 'display': '名称3'},
{'key': 'norad', 'display': '编号', 'width': 300},
{'key': 'btn', 'display': '操作', 'cell': type(CustomCell()), 'width': 100}
])
```
key:列索引,用于表格显示和数据内容的值对应
display: 表头显示的名称
width: 表格列宽
cell: 自定义单元格,必须继承 TableWidgetCell 类,并且重写 setValue(self, index_data, row_data) 函数。
> 默认情况下表格每列平分整个宽度,不足100的为100。但是通过 width 指定的宽度优先级最高。
### setTableData(list)
设置表格数据
``` python
table.setTableData([
{'name': '张三', 'name2': '张三2222', 'name3': '张三3333333333333', 'norad': 23942, 'btn': '1'},
{'name': '李四', 'name2': '李四2', 'name3': '李四3', 'norad': 12902, 'btn': '2'},
{'name': '王五', 'name2': '王五2', 'name3': '王五3', '_bg': 'lightgreen', 'norad': 12802, 'btn': '3'},
{'name': '赵六', 'name2': '赵六2', 'name3': '赵六3', 'norad': 12802, 'btn': '12354'},
{'name': '张三', 'name2': '张三2', 'name3': '张三3', 'norad': 23942, 'btn': '5'},
{'name': '李四', 'name2': '李四2', 'name3': '李四3', 'norad': 12902, 'btn': '6'},
])
```
json 数据,key 和 setTableColumnProperty 中的 key 对应值,将会放到对应列下。
> 可以使用 '_bg' 来控制行的颜色,优先级高于 斑马纹交替色, 低于 鼠标点击选中颜色。
> 可以使用 '_checked' 来控制改行的 checkbox 是否为勾选。
### setTableCheck(bool)
设置表格第一列是否为 CheckBox。
设置了的话,数据每列索引需要 +1。
``` python
table.setTableCheck(True)
```
### setTableFreezeLeft(int)
设置表格冻结列数量
``` python
table.setTableFreezeLeft(2)
```
### setTablePagination(bool)
是否开启表格分页功能,默认开启。
``` python
table.setTablePagination(True)
```
### setTableCurrentPage(int)
设置当前页码
``` python
table.setTableCurrentPage(1)
```
### setTablePageSum(int)
设置每页的条目数
``` python
table.setTablePageSum(10)
```
### setHeaderRowHeight(int)
设置表格表头高度
``` python
table.setHeaderRowHeight(60)
```
### setBodyRowHeight(int)
设置表格内容行高度
只能设置所有行高度,目前还不持支单独设置某一行。
``` python
table.setBodyRowHeight(60)
```
### getCheckList()
获取选中的行数据
``` python
table.getCheckList()
```
\ No newline at end of file
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QEvent
from PyQt5.QtGui import QResizeEvent, QFontDatabase
from PyQt5.QtWidgets import QLabel, QPushButton, QTableWidget, QAbstractItemView, QWidget, QCheckBox, QVBoxLayout
from kit.component.kit_button import KitButton
from kit.component.table.table_page import TablePagination
class TableCellWidget(QLabel):
def __init__(self, parent=None):
super(TableCellWidget, self).__init__(parent=parent)
self.bg_color = None
self.elide_mode = Qt.ElideRight
self.wrap_mode = False
self.index_data = None
self.row_data = None
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setContentsMargins(4, 0, 4, 0)
self.setAlignment(Qt.AlignCenter)
def __init_slot(self):
pass
def __init_qss(self):
self.setAutoFillBackground(True)
self.setAttribute(Qt.WA_StyledBackground, True)
def initCellValue(self, index_data, row_data):
self.index_data = index_data
self.row_data = row_data
self.setValue(self.index_data, self.row_data)
def setValue(self, index_data, row_data):
self.setWordWrap(self.wrap_mode)
font = self.fontMetrics()
if font.width(str(self.index_data)) > self.width()-8:
self.setToolTip(str(self.index_data))
else:
self.setToolTip(None)
if not self.wrap_mode:
text = font.elidedText(str(self.index_data), self.elide_mode, self.width()-8)
else:
text = str(self.index_data)
self.setText(text)
def setElideMode(self, elide_mode: Qt.TextElideMode):
self.elide_mode = elide_mode
self.update()
def setBgColor(self, color: str):
self.bg_color = color
self.update()
def sizeHint(self):
return QSize(100, 40)
def mousePressEvent(self, ev):
super().mousePressEvent(ev)
print(self.index_data, self.row_data)
def paintEvent(self, a0):
super().paintEvent(a0)
self.setValue(self.index_data, self.row_data)
if self.bg_color is not None:
self.setStyleSheet(self.styleSheet() + f"background-color: {self.bg_color};")
class TableCellCheck(TableCellWidget):
stateChanged = pyqtSignal(int)
def __init__(self, tristate=False, parent=None):
super(TableCellCheck, self).__init__(parent=parent)
self.check_state = Qt.Unchecked
self.check = QCheckBox()
self.layout = QVBoxLayout()
self._clicked = False
if tristate:
self.check.setTristate(True)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def setValue(self, index_data, row_data):
if index_data is None:
return
self.setCheckState(index_data)
def __init_widget(self):
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.layout.addWidget(self.check, alignment=Qt.AlignHCenter)
self.check.installEventFilter(self)
def __init_slot(self):
self.check.stateChanged.connect(lambda: self.setCheckState(self.check.checkState()))
self.stateChanged.connect(lambda i: self.check.setCheckState(i))
self.check.clicked.connect(lambda i: print(self.check.checkState()))
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setCheckState(self, state: Qt.CheckState):
if self.check_state == state:
return
if self._clicked and self.check.checkState() == Qt.PartiallyChecked:
self.check_state = Qt.Checked
else:
self.check_state = state
self._clicked = False
self.stateChanged.emit(self.check_state)
self.update()
def checkState(self):
return self.check_state
def eventFilter(self, a0, a1) -> bool:
if (a1.type() == QEvent.MouseButtonPress or a1.type() == QEvent.MouseButtonDblClick) and self.check.isTristate():
self._clicked = True
return super().eventFilter(a0, a1)
class TableBase(QTableWidget):
tableColumnPropertyChanged = pyqtSignal(list)
tableDataChanged = pyqtSignal(list)
tableShowChecked = pyqtSignal(bool)
def __init__(self, parent=None):
super(TableBase, self).__init__(parent=parent)
self.table_column_property = []
self.row_height = 40
self.check_cell_width = 40
self.column_hint_width = 100
self.table_data: [dict] = []
self.show_check = False
self.if_fit_width = True
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.horizontalHeader().setVisible(False)
self.verticalHeader().setVisible(False)
self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
self.setShowGrid(False)
self.setAutoScroll(False)
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setTableColumnProperty(self, table_column_property: list):
if self.table_column_property != table_column_property:
self.table_column_property = table_column_property
self.tableColumnPropertyChanged.emit(table_column_property)
def setTableData(self, table_data: list):
if self.table_data != table_data:
self.table_data = table_data
self.tableDataChanged.emit(table_data)
def setShowCheck(self, show: bool):
if self.show_check != show:
self.show_check = show
self.tableShowChecked.emit(show)
def setRowChecked(self, row):
if self.show_check and 0 <= row < len(self.table_data) and isinstance(self.table_data[row], dict):
self.cellWidget(row, 0).setCheckState(Qt.Checked)
self.table_data[row]['_checked'] = True
def setRowUnChecked(self, row):
if self.show_check and 0 <= row < len(self.table_data) and isinstance(self.table_data[row], dict):
self.cellWidget(row, 0).setCheckState(Qt.Unchecked)
self.table_data[row]['_checked'] = False
def checkAll(self):
if self.show_check:
for row in range(self.rowCount()):
self.setRowChecked(row)
def uncheckAll(self):
if self.show_check:
for row in range(self.rowCount()):
self.setRowUnChecked(row)
def __fresh_column_width(self):
share_width_column = len(self.table_column_property)
view_width = self.viewport().width()
column_width = [0] * share_width_column
if self.show_check:
view_width -= self.check_cell_width
for column in range(len(self.table_column_property)):
if self.table_column_property[column].get('width') is not None:
column_width[column] = int(self.table_column_property[column].get('width'))
view_width -= column_width[column]
share_width_column -= 1
for column in range(len(self.table_column_property)):
if column_width[column] == 0:
column_width[column] = view_width // share_width_column \
if view_width // share_width_column > self.column_hint_width else self.column_hint_width
if self.show_check:
column_width.insert(0, 40)
for column in range(self.columnCount()):
self.setColumnWidth(column, column_width[column])
def __fresh_row_height(self):
for row in range(self.rowCount()):
self.setRowHeight(row, self.row_height)
def __fresh_cell_size(self):
# 调整单元格大小 TableCellWidget
for column in range(self.columnCount()):
for row in range(self.rowCount()):
item = self.cellWidget(row, column)
if isinstance(item, TableCellWidget):
item.setFixedSize(self.columnWidth(column), self.rowHeight(row))
def resizeEvent(self, e) -> None:
super().resizeEvent(e)
if self.if_fit_width:
self.__fresh_column_width()
self.__fresh_row_height()
self.__fresh_cell_size()
class TableHeader(TableBase):
headerCheckChanged = pyqtSignal(int)
def __init__(self, parent=None):
super(TableHeader, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.verticalScrollBar().setVisible(False)
def __init_slot(self):
self.tableDataChanged.connect(self.__fresh_table_header)
self.tableColumnPropertyChanged.connect(self.__fresh_table_header)
self.tableShowChecked.connect(self.__fresh_table_header)
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setHeaderRowHeight(self, height: int):
self.row_height = height
self.__fresh_table_header()
def setHeaderCheckState(self, state):
if self.show_check:
widget = self.cellWidget(0, 0)
if type(widget) == TableCellCheck:
widget.setCheckState(state)
def __fresh_table_header(self):
self.setRowCount(1)
self.setFixedHeight(self.row_height)
offset = 1 if self.show_check else 0
self.setColumnCount(len(self.table_column_property) + offset)
for column in range(self.columnCount()):
property_column = column - offset
if property_column == -1:
table_cell_widget = TableCellCheck(True, self)
table_cell_widget.stateChanged.connect(self.headerCheckChanged.emit)
else:
table_cell_widget = TableCellWidget(self)
table_cell_widget.initCellValue(self.table_column_property[property_column]["display"], self.table_column_property[property_column])
self.setCellWidget(0, column, table_cell_widget)
self.resizeEvent(QResizeEvent(self.size(), self.size()))
class TableBody(TableBase):
rowCheckedChanged = pyqtSignal(int, bool)
def __init__(self, parent=None):
super(TableBody, self).__init__(parent=parent)
self.current_row = -1
self.alternate_row_color = True
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
def __init_slot(self):
self.tableColumnPropertyChanged.connect(self.__fresh_table_body)
self.tableDataChanged.connect(self.__fresh_table_body)
self.tableShowChecked.connect(self.__fresh_table_body)
self.currentCellChanged.connect(lambda current_row, current_column, previous_row, previous_column:
self.__handle_current_row_changed(current_row, previous_row))
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setTableAlternateRowColor(self, alternate: bool):
self.alternate_row_color = alternate
self.__fresh_table_body()
def setBodyRowHeight(self, height: int):
self.row_height = height
self.__fresh_table_body()
def __fresh_table_body(self):
if self.table_data is None or len(self.table_data) == 0:
self.clear()
self.setRowCount(2)
column = len(self.table_column_property)
if self.show_check:
column += 1
self.setColumnCount(column)
cell = QLabel()
cell.setObjectName('table_none')
cell.resize(self.viewport().width(), self.row_height * 2)
cell.setStyleSheet('border: 1px solid red')
cell.setText('暂无数据')
cell.setAlignment(Qt.AlignCenter)
self.setCellWidget(0, 0, cell)
self.setSpan(0, 0, 2, column)
return
self.clearSpans()
offset = 1 if self.show_check else 0
self.setColumnCount(len(self.table_column_property) + offset)
self.setRowCount(len(self.table_data))
for row in range(self.rowCount()):
for column in range(self.columnCount()):
property_column = column - offset
if property_column == -1:
table_cell_widget = TableCellCheck()
if self.table_data[row].get('_checked'):
table_cell_widget.setCheckState(Qt.Checked)
else:
table_cell_widget.setCheckState(Qt.Unchecked)
table_cell_widget.stateChanged.connect(lambda state, r=row: self.__handle_row_checked(r, state))
else:
if self.table_column_property[property_column].get("cell") is None:
table_cell_widget = TableCellWidget()
else:
table_cell_widget = self.table_column_property[property_column].get('cell')()
if self.table_column_property[property_column].get("_wrap"):
table_cell_widget.wrap_mode = True
table_cell_widget.initCellValue(self.table_data[row].get(self.table_column_property[property_column]["key"]), self.table_data[row])
self.__init_cell_color(row, table_cell_widget)
self.setCellWidget(row, column, table_cell_widget)
self.resizeEvent(QResizeEvent(self.size(), self.size()))
self.setCurrentCell(self.current_row, self.currentColumn())
def __init_cell_color(self, row, widget):
if (row < 0 or row >= self.rowCount()
or widget is None
or self.table_data is None
or len(self.table_data) == 0
):
return
widget.setStyleSheet('')
# 当前行被选中的颜色
if row == self.current_row:
widget.setProperty('row', 'current')
widget.style().polish(widget)
return widget
# 表格指定行变色
if self.table_data[row].get('_bg') is not None and self.table_data[row].get('_bg') != '':
widget.setStyleSheet(
self.styleSheet() + f'TableCellWidget{{background-color:{self.table_data[row].get("_bg")}}};')
return widget
# 表格交替行变色
if self.alternate_row_color:
if row % 2 == 0:
widget.setProperty('row', 'even')
else:
widget.setProperty('row', 'odd')
else:
widget.setProperty('row', None)
widget.style().polish(widget)
return widget
def __handle_current_row_changed(self, current_row, previous_row):
self.current_row = current_row
for column in range(self.columnCount()):
widget = self.cellWidget(self.current_row, column)
self.__init_cell_color(self.current_row, widget)
previous_widget = self.cellWidget(previous_row, column)
self.__init_cell_color(previous_row, previous_widget)
def __handle_row_checked(self, row, check):
if check == Qt.Checked:
self.setRowChecked(row)
if check == Qt.Unchecked:
self.setRowUnChecked(row)
self.rowCheckedChanged.emit(row, check)
class TableMain(TableBase):
def __init__(self, parent=None):
super(TableMain, self).__init__(parent=parent)
self.table_header = TableHeader()
self.table_body = TableBody()
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
self.layout.addWidget(self.table_header)
self.layout.addWidget(self.table_body)
def __init_slot(self):
self.table_body.horizontalScrollBar().valueChanged.connect(self.table_header.horizontalScrollBar().setValue)
self.table_header.headerCheckChanged.connect(self.__handle_header_check)
self.table_body.rowCheckedChanged.connect(self.__handle_body_check)
self.table_header.cellDoubleClicked.connect(lambda row, column: self.sortByColumnUnicode(column))
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setTableData(self, table_data: list):
super().setTableData(table_data)
self.table_body.setTableData(table_data)
self.table_header.setTableData(table_data)
self.__handle_body_check()
def setTableColumnProperty(self, table_column_property: list):
super().setTableColumnProperty(table_column_property)
self.table_body.setTableColumnProperty(table_column_property)
self.table_header.setTableColumnProperty(table_column_property)
def sortByColumnUnicode(self, column):
if self.show_check:
column -= 1
order = 'up' if self.table_column_property[column].get('_sort') == 'down' else 'down'
self.table_column_property[column]['_sort'] = order
if self.table_data is not None and len(self.table_data) > 0:
index_data = self.table_data[0].get(self.table_column_property[column]['key'])
if isinstance(index_data, dict):
return self.table_data
self.table_data.sort(key=lambda x: x[self.table_column_property[column]['key']], reverse=order == 'down')
self.setTableData(self.table_data)
return self.table_data
def setShowCheck(self, show: bool):
super().setShowCheck(show)
self.table_body.setShowCheck(show)
self.table_header.setShowCheck(show)
def __handle_header_check(self, state):
if state == Qt.Checked:
self.table_body.checkAll()
self.table_header.setHeaderCheckState(Qt.Checked)
elif state == Qt.Unchecked:
self.table_body.uncheckAll()
self.table_header.setHeaderCheckState(Qt.Unchecked)
def __handle_body_check(self):
state = 0
for item in self.table_data:
if item.get('_checked'):
state += 1
if state == len(self.table_data) and len(self.table_data) > 0:
self.table_header.setHeaderCheckState(Qt.Checked)
elif state > 0:
self.table_header.setHeaderCheckState(Qt.PartiallyChecked)
else:
self.table_header.setHeaderCheckState(Qt.Unchecked)
def getCurrentRow(self):
return self.table_body.current_row
def getCurrentRowData(self):
return self.table_data[self.get_current_row()]
def setHeaderRowHeight(self, height):
self.table_header.setHeaderRowHeight(height)
def setBodyRowHeight(self, height):
self.table_body.setBodyRowHeight(height)
def setColumnWidth(self, column: int, width: int) -> None:
self.table_body.setColumnWidth(column, width)
self.table_header.setColumnWidth(column, width)
self.table_header.resizeEvent(QResizeEvent(
QSize(self.table_body.columnWidth(column), self.table_header.height()),
QSize(self.table_body.columnWidth(column), self.table_header.height())
))
self.table_body.resizeEvent(QResizeEvent(
QSize(self.table_body.columnWidth(column), self.table_body.height()),
QSize(self.table_body.columnWidth(column), self.table_body.height())
))
def columnWidth(self, column: int) -> int:
self.table_body.columnWidth(column)
def resizeEvent(self, e) -> None:
super().resizeEvent(e)
self.table_header.resizeEvent(e)
self.table_body.resizeEvent(e)
class TableFreeze(TableMain):
def __init__(self, parent=None):
super(TableFreeze, self).__init__(parent=parent)
self.freeze_column_left = 0
self.table_freeze = TableMain(self)
self.table_freeze.setObjectName('table_freeze')
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
# 冻结部分
self.table_freeze.table_body.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.table_freeze.table_body.verticalScrollBar().setVisible(False)
self.table_freeze.table_body.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.table_freeze.table_body.horizontalScrollBar().setVisible(False)
self.table_freeze.table_header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.table_freeze.table_header.verticalScrollBar().setVisible(False)
self.table_freeze.table_header.if_fit_width = False
self.table_freeze.table_body.if_fit_width = False
# 非冻结部分
self.table_body.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
def __init_slot(self):
# 垂直方向同步滚动
self.table_body.verticalScrollBar().valueChanged.connect(self.table_freeze.table_body.verticalScrollBar().setValue)
self.table_freeze.table_body.verticalScrollBar().valueChanged.connect(self.table_body.verticalScrollBar().setValue)
# 当前单元格同步
self.table_body.currentCellChanged.connect(self.table_freeze.table_body.setCurrentCell)
self.table_freeze.table_body.currentCellChanged.connect(self.table_body.setCurrentCell)
# 同步排序
self.table_freeze.table_header.cellDoubleClicked.connect(lambda: self.setTableData(self.table_freeze.table_data))
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setLeftFreeze(self, freeze_column_left: int):
self.freeze_column_left = freeze_column_left
if self.freeze_column_left > 0:
self.table_body.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
else:
self.table_body.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.__fresh_freeze_table_size()
def setTableColumnProperty(self, table_column_property: list):
super().setTableColumnProperty(table_column_property)
self.table_freeze.setTableColumnProperty(table_column_property)
def setTableData(self, table_data: list):
super().setTableData(table_data)
self.table_freeze.setTableData(table_data)
self.table_freeze.setVisible(True)
if self.table_data is None or len(self.table_data) == 0:
self.table_freeze.setVisible(False)
self.__fresh_freeze_table_size()
def setShowCheck(self, show: bool):
super().setShowCheck(show)
self.table_freeze.setShowCheck(show)
def setHeaderRowHeight(self, height):
super().setHeaderRowHeight(height)
self.table_freeze.setHeaderRowHeight(height)
def setBodyRowHeight(self, height):
super().setBodyRowHeight(height)
self.table_freeze.setBodyRowHeight(height)
def __fresh_freeze_table_size(self):
self.table_body.update()
width = 0
for column in range(self.freeze_column_left):
width += self.table_body.columnWidth(column)
self.table_freeze.resize(width, self.height()-self.table_body.horizontalScrollBar().height())
for column in range(self.table_freeze.columnCount()):
self.table_freeze.setColumnHidden(column, True)
for left in range(self.freeze_column_left):
self.table_freeze.setColumnHidden(left, False)
self.table_freeze.setColumnWidth(left, self.table_body.columnWidth(left))
def resizeEvent(self, e) -> None:
super().resizeEvent(e)
self.__fresh_freeze_table_size()
class KitTable(QWidget):
def __init__(self, parent=None):
super(KitTable, self).__init__(parent=parent)
self.table_data = []
self.table_current_page_data = []
self.table_column_property = []
self.freeze_table = TableFreeze()
self.show_pagination = True
self.page = TablePagination()
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.layout.addWidget(self.freeze_table, 1)
self.layout.addWidget(self.page)
self.setTableData([])
def __init_slot(self):
self.page.currentPageChanged.connect(lambda page, page_data: self.__fresh_current_page_data(page_data))
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def resizeEvent(self, a0) -> None:
super().resizeEvent(a0)
self.freeze_table.resizeEvent(a0)
def setTableColumnProperty(self, table_column_property):
self.table_column_property = table_column_property
self.freeze_table.setTableColumnProperty(table_column_property)
def setTableData(self, table_data: list):
self.table_data = table_data
self.freeze_table.setTableData(table_data)
self.__fresh_page_table_data()
def __fresh_page_table_data(self):
if self.show_pagination:
self.page.setAllData(self.table_data)
def __fresh_current_page_data(self, data: list):
self.table_current_page_data = data
self.freeze_table.setTableData(data)
def setTableShowCheck(self, check: bool):
self.freeze_table.setShowCheck(check)
def setTableLeftFreeze(self, freeze_column_left: int):
self.freeze_table.setLeftFreeze(freeze_column_left)
def setTablePagination(self, show: bool):
self.show_pagination = show
self.page.setVisible(show)
self.setTableData(self.table_data)
def setTableCurrentPage(self, page):
self.page.setCurrentPage(page)
def setTablePageSum(self, page_sum):
self.page.setPageSum(page_sum)
def getCheckList(self):
return [row_data for row_data in self.freeze_table.table_data if row_data.get('_checked')]
def setHeaderRowHeight(self, height):
self.freeze_table.setHeaderRowHeight(height)
def setBodyRowHeight(self, height):
self.freeze_table.setBodyRowHeight(height)
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton, QHBoxLayout, QLabel, QLineEdit
from config import config
from kit.component.kit_button import KitIconButton, KitButton
class TablePagination(QWidget):
currentPageChanged = pyqtSignal(int, list)
def __init__(self, parent=None):
super(TablePagination, self).__init__(parent=parent)
self.all_page = None
self.all_data = None
self.page_data = None
self.current_page = None
self.page_sum = 20
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setAlignment(Qt.AlignVCenter)
self.setLayout(self.layout)
self.all_data_label = QLabel()
self.all_page_label = QLabel()
self.page_input = QLineEdit()
self.page_input.setAlignment(Qt.AlignCenter)
self.page_input.setTextMargins(0, 0, 0, 0)
self.page_input.setFixedSize(40, 20)
self.previous_btn = KitIconButton('\ue603')
self.previous_btn.setObjectName('page_icon')
self.previous_btn.setFixedSize(20, 20)
self.previous_btn.setType(KitButton.Text)
self.next_btn = KitIconButton('\ue61f')
self.next_btn.setObjectName('page_icon')
self.next_btn.setFixedSize(20, 20)
self.next_btn.setType(KitButton.Text)
self.layout.addStretch(1)
self.layout.addWidget(self.all_data_label)
self.layout.addWidget(self.previous_btn)
self.layout.addWidget(self.page_input)
self.layout.addWidget(self.next_btn)
self.layout.addWidget(self.all_page_label)
self.layout.addStretch(1)
def __init_slot(self):
self.previous_btn.clicked.connect(self.previousPage)
self.next_btn.clicked.connect(self.nextPage)
self.page_input.editingFinished.connect(self.__page_input_finished)
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def setAllData(self, all_data):
self.all_data = all_data
self.__fresh_page_info()
def setPageSum(self, page_sum):
if self.page_sum <= 0:
raise ValueError('pageSum must be greater than 0')
self.page_sum = page_sum
self.__fresh_page_info()
def setCurrentPage(self, current_page):
if self.current_page == current_page:
return
self.current_page = current_page
self.__fresh_page_info()
def __fresh_page_info(self):
"""
根据all_data和page_sum计算出 总页数 和 总条目数
:return:
"""
if self.all_data is None or self.page_sum is None:
return
if len(self.all_data) == 0:
self.all_data_label.setText('共0条')
self.all_page_label.setText('共1页')
self.pageSum = 1
self.page_input.setText('1')
self.previous_btn.setEnabled(False)
self.next_btn.setEnabled(False)
return
all_data_count = len(self.all_data)
self.all_data_label.setText(f'共{all_data_count}条')
all_page = all_data_count // self.page_sum
if all_data_count % self.page_sum != 0:
all_page += 1
self.all_page = all_page
self.all_page_label.setText(f'共{all_page}页')
if self.current_page is None or self.current_page < 1:
self.current_page = 1
else:
self.setCurrentPage(self.current_page)
self.__fresh_current_page()
def nextPage(self):
self.setCurrentPage(int(self.current_page) + 1)
def previousPage(self):
self.setCurrentPage(int(self.current_page) - 1)
def __page_input_finished(self):
self.setCurrentPage(int(self.page_input.text()))
def __fresh_current_page(self):
self.previous_btn.setEnabled(True)
self.next_btn.setEnabled(True)
if self.current_page >= self.all_page:
self.current_page = self.all_page
self.next_btn.setEnabled(False)
if self.current_page <= 1:
self.current_page = 1
self.previous_btn.setEnabled(False)
self.page_data = self.all_data[(self.current_page - 1) * self.page_sum: self.current_page * self.page_sum]
self.page_input.setText(str(self.current_page))
self.currentPageChanged.emit(self.current_page, self.page_data)
if __name__ == "__main__":
app = QApplication(sys.argv)
qss = config.init_qss()
app.setStyleSheet(qss)
print(qss)
main = QWidget()
main.setLayout(QVBoxLayout())
fontId = QFontDatabase.addApplicationFont("assets/font/iconfont.ttf")
fontName = QFontDatabase.applicationFontFamilies(fontId)[0]
page = TablePagination()
page.setAllData([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
page.setPageSum(3)
main.layout().addWidget(page)
btn = QPushButton()
main.layout().addWidget(btn)
main.show()
sys.exit(app.exec_())
\ No newline at end of file
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtWidgets import QApplication
from config import config
from pages.window import Window
if __name__ == "__main__":
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
fontId = QFontDatabase.addApplicationFont("assets/iconfont.ttf")
qss = config.init_qss()
app.setStyleSheet(qss)
main = Window()
main.resize(800, 600)
main.show()
sys.exit(app.exec_())
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QTabWidget
from pages.widget_example.basic_example import BasicExample
from pages.widget_example.custom_example import CustomExample
from pages.widget_example.table_example import TableExample
class Index(QTabWidget):
def __init__(self, parent=None):
super(Index, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.addTab(BasicExample(), "基础组件")
self.addTab(TableExample(), "表格")
self.addTab(CustomExample(), "自定义组件")
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
\ No newline at end of file
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QScrollArea, QWidget, QGridLayout, QLabel, QCheckBox, QComboBox, QSpinBox, QDoubleSpinBox, \
QLineEdit, QRadioButton, QSlider, QProgressBar
from kit.component.kit_button import KitButton
from kit.component.kit_icon import KitIcon
class BasicExample(QScrollArea):
def __init__(self, parent=None):
super(BasicExample, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.main_widget = QWidget()
self.setWidget(self.main_widget)
self.layout = QGridLayout()
self.layout.setSpacing(16)
self.main_widget.setLayout(self.layout)
# 按钮
label1 = QLabel("按钮")
label1.setObjectName("title")
self.layout.addWidget(label1, 0, 0)
button1 = KitButton("默认")
self.layout.addWidget(button1, 1, 0)
button2 = KitButton("圆角")
button2.setShape(KitButton.Round)
self.layout.addWidget(button2, 1, 1)
button3 = KitButton("边框")
button3.setType(KitButton.OutLined)
self.layout.addWidget(button3, 1, 2)
button4 = KitButton("文字")
button4.setType(KitButton.Text)
self.layout.addWidget(button4, 1, 3)
button_icon = KitButton("下载", "\ue60c")
self.layout.addWidget(button_icon, 1, 4)
button_round = KitButton("", "\ue60c")
button_round.setShape(KitButton.Round)
button_round.setFixedWidth(40)
self.layout.addWidget(button_round, 1, 5)
# 图标
label2 = QLabel("图标")
label2.setObjectName("title")
self.layout.addWidget(label2, 2, 0)
icon_1 = KitIcon("\ue669")
self.layout.addWidget(icon_1, 3, 0)
icon_2 = KitIcon("\ue608")
self.layout.addWidget(icon_2, 3, 1)
icon_3 = KitIcon("\ue657")
self.layout.addWidget(icon_3, 3, 2)
icon_4 = KitIcon("\ue659")
self.layout.addWidget(icon_4, 3, 3)
icon_5 = KitIcon("\ue60c")
self.layout.addWidget(icon_5, 3, 4)
# 勾选框
label3 = QLabel("勾选框")
label3.setObjectName("title")
self.layout.addWidget(label3, 4, 0)
check_box_1 = QCheckBox("默认")
self.layout.addWidget(check_box_1, 5, 0)
check_box_2 = QCheckBox("三态")
check_box_2.setTristate(True)
self.layout.addWidget(check_box_2, 5, 1)
# 选择器
label4 = QLabel("选择器")
label4.setObjectName("title")
self.layout.addWidget(label4, 6, 0)
combo_1 = QComboBox()
combo_1.addItem("选项1")
combo_1.addItem("选项2")
combo_1.addItem("选项3")
self.layout.addWidget(combo_1, 7, 0)
combo_2 = QComboBox()
combo_2.addItems(["选项1", "选项2", "选项3"])
self.layout.addWidget(combo_2, 7, 1)
# 步进器
label5 = QLabel("步进器")
label5.setObjectName("title")
self.layout.addWidget(label5, 8, 0)
spinbox_1 = QSpinBox()
spinbox_1.setRange(0, 10)
self.layout.addWidget(spinbox_1, 9, 0)
spinbox_2 = QDoubleSpinBox()
spinbox_2.setRange(0, 5.00)
spinbox_2.setSingleStep(0.5)
self.layout.addWidget(spinbox_2, 9, 1)
# 输入框
label6 = QLabel("输入框")
label6.setObjectName("title")
self.layout.addWidget(label6, 10, 0)
line_edit_1 = QLineEdit()
self.layout.addWidget(line_edit_1, 11, 0)
line_label = QLabel()
self.layout.addWidget(line_label, 11, 1)
line_edit_1.textChanged.connect(lambda text: line_label.setText(text))
# 单选
label7 = QLabel("单选")
label7.setObjectName("title")
self.layout.addWidget(label7, 12, 0)
radio_1 = QRadioButton("选项1")
self.layout.addWidget(radio_1, 13, 0)
radio_2 = QRadioButton("选项2")
self.layout.addWidget(radio_2, 13, 1)
radio_3 = QRadioButton("选项3")
self.layout.addWidget(radio_3, 13, 2)
# 滑块
label8 = QLabel("滑块")
label8.setObjectName("title")
self.layout.addWidget(label8, 14, 0)
slider_1 = QSlider(Qt.Horizontal)
slider_1.setRange(0, 100)
self.layout.addWidget(slider_1, 15, 0)
slider_2 = QSlider(Qt.Vertical)
slider_2.setRange(0, 100)
self.layout.addWidget(slider_2, 15, 1)
# 进度条
label9 = QLabel("进度条")
label9.setObjectName("title")
self.layout.addWidget(label9, 16, 0)
progress_1 = QProgressBar()
progress_1.setValue(50)
self.layout.addWidget(progress_1, 17, 0)
progress_2 = QProgressBar()
progress_2.setRange(0, 0)
self.layout.addWidget(progress_2, 17, 1)
self.main_widget.adjustSize()
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QScrollArea, QWidget, QGridLayout, QLabel
from kit.component.kit_button import KitButton
from kit.component.kit_drawer import KitDrawer
from kit.component.kit_popup import KitModal
class CustomExample(QScrollArea):
def __init__(self, parent=None):
super(CustomExample, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.main_widget = QWidget()
self.setWidget(self.main_widget)
self.layout = QGridLayout()
self.main_widget.setLayout(self.layout)
# 弹窗
label1 = QLabel("弹窗")
label1.setObjectName("title")
self.layout.addWidget(label1, 0, 0)
modal_notice = KitButton("提示弹窗")
self.layout.addWidget(modal_notice, 1, 0)
modal_notice.clicked.connect(lambda: KitModal.setModalNotice("提示", "这是一个提示弹窗"))
modal_dialog = KitButton("对话弹窗")
self.layout.addWidget(modal_dialog, 1, 1)
modal_dialog.clicked.connect(lambda: KitModal.setModalDialog("对话", "这是一个对话弹窗", lambda: print("点击了确定按钮"), lambda: print("点击了取消按钮")))
# 抽屉
label2 = QLabel("抽屉")
label2.setObjectName("title")
self.layout.addWidget(label2, 2, 0)
button_1 = KitButton("左侧抽屉")
drawer_left = KitDrawer(KitDrawer.Left)
button_1.clicked.connect(lambda: drawer_left.open())
self.layout.addWidget(button_1, 3, 0)
button_2 = KitButton("右侧抽屉")
drawer_right = KitDrawer(KitDrawer.Right)
button_2.clicked.connect(lambda: drawer_right.open())
self.layout.addWidget(button_2, 3, 1)
button_3 = KitButton("顶部抽屉")
drawer_top = KitDrawer(KitDrawer.Top)
button_3.clicked.connect(lambda: drawer_top.open())
self.layout.addWidget(button_3, 3, 2)
button_4 = KitButton("底部抽屉")
drawer_bottom = KitDrawer(KitDrawer.Bottom)
button_4.clicked.connect(lambda: drawer_bottom.open())
self.layout.addWidget(button_4, 3, 3)
self.main_widget.adjustSize()
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QScrollArea, QVBoxLayout
from kit.component.kit_button import KitButton, KitIconButton
from kit.component.table.kit_table import KitTable, TableCellWidget
class CustomCell(TableCellWidget):
def __init__(self):
self.btn = KitButton()
self.btn.clicked.connect(lambda: print(self.index_data, self.row_data))
super().__init__()
self.__init_widget()
def __init_widget(self):
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.btn)
self.setLayout(self.layout)
def setValue(self, index_data, row_data):
self.btn.setText(str(index_data))
class TableExample(QScrollArea):
def __init__(self, parent=None):
super(TableExample, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.main_widget = QWidget()
self.setWidget(self.main_widget)
layout = QVBoxLayout()
self.main_widget.setLayout(layout)
table = KitTable()
table.setTableColumnProperty([
{"display": "序号", "key": "id"},
{"display": "姓名", "key": "name", 'width': 200, '_wrap': True},
{"display": "年龄", "key": "age"},
{"display": "身高", "key": "height", 'cell': type(CustomCell())},
])
table_data = [
{"id": 1, "name": "张三1289375091 27305781230975091", "age": 18, "height": 180},
{"id": 2, "name": "李四", "age": 19, "height": 170},
{"id": 3, "name": "王五", "age": 20, "height": 160},
{"id": 4, "name": "赵六", "age": 21, "height": 150},
{"id": 5, "name": "田七", "age": 22, "height": 140},
]
table_data_2 = []
for i in range(100):
table_data_2.append({"id": i, "name": "张三" + str(i), "age": i, "height": 180})
table_data_3 = []
for i in range(80):
table_data_3.append({"id": i * 2, "name": "李四" + str(i), "age": i * 2, "height": 170})
table.setTableShowCheck(True)
# table.setTablePageSum(2)
table.setTableLeftFreeze(2)
table.setBodyRowHeight(60)
table.setTableData([])
layout.addWidget(table)
clear_btn = KitButton('清空')
clear_btn.clicked.connect(lambda: table.setTableData([]))
layout.addWidget(clear_btn)
btn = KitButton('数据1')
btn.clicked.connect(lambda: table.setTableData(table_data_2))
layout.addWidget(btn)
btn2 = KitButton('数据2')
btn2.clicked.connect(lambda: table.setTableData(table_data_3))
layout.addWidget(btn2)
print_all = KitButton('打印全部数据')
print_all.clicked.connect(lambda: print(table.table_data))
layout.addWidget(print_all)
table.freeze_table.table_body.setRowCount(1)
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
def resizeEvent(self, a0) -> None:
self.main_widget.resize(self.size())
\ No newline at end of file
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow
from pages.index import Index
class Window(QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent=parent)
self.__init_widget()
self.__init_slot()
self.__init_qss()
def __init_widget(self):
self.setCentralWidget(Index())
def __init_slot(self):
pass
def __init_qss(self):
self.setAttribute(Qt.WA_StyledBackground, True)
\ No newline at end of file
* {
font-size: 16px;
}
/* KitButton */
KitButton {
background-color: #386a20;
border-radius: 4px; }
KitButton:hover {
background-color: #335026; }
KitButton:pressed {
background-color: #1D450A; }
KitButton:disabled {
background-color: #d1d5db; }
KitButton[shape="round"] {
border-radius: 20px; }
KitButton[shape="square"] {
border-radius: 0px; }
KitButton[type="outlined"] {
background-color: transparent;
border: 1px solid black; }
KitButton[type="outlined"]:hover {
background-color: #1d450a;
border: 1px solid #1d450a; }
KitButton[type="outlined"]:pressed {
background-color: #162a0d;
border: 1px solid black; }
KitButton[type="text"] {
background-color: transparent; }
KitButton[type="text"]:hover {
background-color: #1d450a; }
KitButton[type="text"]:pressed {
background-color: #162a0d; }
KitDrawer {
background-color: white; }
KitIcon {
font-family: "iconfont";
background-color: transparent;
font-size: 16px;
color: black; }
KitModal {
background-color: #3b82f6;
border: 1px solid black;
border-radius: 4px; }
KitModal #modal_close_btn:hover {
background-color: rgba(75, 85, 99, 0.2); }
KitModal #modal_close_btn:pressed {
background-color: rgba(75, 85, 99, 0.7); }
KitModal #modal_title {
font-size: 20px; }
TableHeader {
background-color: transparent;
border: none; }
QTableWidget {
border: none;
background-color: transparent; }
TableHeader TableCellWidget {
border-top: 2px solid black;
border-bottom: 2px solid black;
background-color: #0c4a6e;
font-weight: 900; }
TableBody {
border: none;
background-color: transparent;
outline: none; }
TableBody:item:select {
border: none; }
TableBody TableCellWidget {
background-color: white; }
TableBody TableCellWidget[row='even'] {
background-color: white; }
TableBody TableCellWidget[row='odd'] {
background-color: gray; }
TableBody TableCellWidget[row='current'] {
background-color: lightblue; }
#table_freeze {
border-right-width: 6px;
border-right-style: solid;
border-right-color: qlineargradient(x1:0, x2:1, stop:0 rgba(0, 0, 0, 50), stop:1 transparent);
}
#table_freeze QScrollBar:vertical {
width: 0px;
}
TablePagination {
background-color: transparent;
}
TablePagination LineEdit {
border: 1px solid #d1d5db; }
TablePagination #page_icon {
background-color: transparent; }
TablePagination #page_icon:hover {
background-color: #9ca3af; }
TablePagination #page_icon:pressed {
background-color: #6b7280; }
QLabel {
color: #171717; }
#title{
font-size:20px;
}
QLable[type="main"] {
color: white; }
QLabel[type='info'] {
color: #9ca3af; }
QLabel[type='warning'] {
color: #fbbf24; }
QLabel[type='error'] {
color: #ef4444; }
QLabel[type='primary'] {
color: #60a5fa; }
QLabel[type='success'] {
color: #22c55e; }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment