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
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