1 QTreeWidget
TreeWidget 控件的 ObjectName 是 tree
1.1 设置默认选项
在 TreeWidget 和 StackedWidget 互动的时候这是有必要的
也可以通过 QtDesigner 将 StackedWidget 某一页展示出来(翻到那一页就行,不过 TreeWidget 的节点不会高亮,依旧要设置)
没想到其他方法,暂时就这些
# 需要遍历整个 tree,根据 path (里面是 节点.text(0))找到你指定的节点
# 获取根节点数目 QTreeWidget.topLevelItem
# 该函数没被测试
def traverse_tree(self, path: str) -> None:
"""遍历树的节点"""
n = self.ui.tree.topLevelItemCount()
index = 0
for i in range(n):
item = self.ui.tree.topLevelItem(i)
if item.text(0) == path[index]:
nn = item.childCount()
index += 1
if count != 0:
for j in range(count):
if item.child(j).text(0) == path[index]:
self.ui.tree.setCurrentItem(item)
return
只会遍历两层(根和它相邻的子节点),建议就近原则,比如默认选第一个根节点
# 获取第一个根节点就完事了
local_item = self.ui.tree.topLevelItem(0)
self.ui.tree.setCurrentItem(local_item)
1.2 QTreeWidget 和 QStackedWidget 的互动
StackedWidget 控件的 ObjectName 是 stack
功能: 通过TreeWidget的节点来切换Stackedwidget页面
tree 的结构如下(表头就省略咯)
- 模型构建
- 结果预测
- 单项预测
- 多项预测
def signal_connect_slot(self):
"""信号与槽"""
# QTreeWidget 的点击事件
self.ui.tree.clicked.connect(self.tree_on_clicked)
def tree_on_clicked(self):
try:
item = self.ui.tree.currentItem() # 获取当前点击的节点
if item.text(0) == "模型构建":
# 通过指定页面下标设置 StackedWidget 控件所展示页面
self.ui.stack.setCurrentIndex(0)
return
elif item.text(0) == "结果预测":
return
parent = item.parent()
index = parent.indexOfChild(item)
parent_name = parent.text(0)
# 后面也许还要加节点,所以保险点
if index == 0 and parent_name == "结果预测":
self.ui.stack.setCurrentIndex(1)
return
if index == 1 and parent_name == "结果预测":
self.ui.stack.setCurrentIndex(2)
return
except:
# 我不知道将来会不会报错,但还是先这么写吧
pass
1.3 依旧是Tree和Stacked的互动(简易版)
差不多算
一行搞定了
tree.currentItemChanged
是QTreeWidget
的一个信号,当当前选中项发生变化时就会发出该信号。连接到这个信号的槽函数会接收两个参数:当前选中项和之前选中的项。在这个示例中,lambda
函数被用作槽函数,用于将当前选中项的索引传递给QStackedWidget
的setCurrentIndex()
方法以显示对应的窗口部件。
previous
参数是之前选中的项,可以用于在当前项变化时执行一些操作,例如清除之前选中项的一些状态等。
逻辑挺简单的, 我之前为什么要那么写呢? 之前还有节点之下还有节点, 当前方案, 只有点击顶级节点才有效果😢
# Connect the tree widget to the stacked widget
self.tree.currentItemChanged.connect(
lambda current, previous: stacked.setCurrentIndex(
tree.indexOfTopLevelItem(current)))
一个完整的案例
给界面添加了一个分割条
import sys
from PySide2.QtWidgets import (QApplication, QMainWindow,
QSplitter, QTreeWidget,
QTreeWidgetItem, QStackedWidget,
QLabel)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Create a QSplitter widget
splitter = QSplitter(self)
# Create a QTreeWidget widget
tree = QTreeWidget(splitter)
tree.setHeaderLabels(['Name', 'Description'])
# Add some items to the tree widget
item1 = QTreeWidgetItem(tree, ['Item 1', 'Description of item 1'])
item2 = QTreeWidgetItem(tree, ['Item 2', 'Description of item 2'])
item3 = QTreeWidgetItem(tree, ['Item 3', 'Description of item 3'])
# Create a QStackedWidget widget
stacked = QStackedWidget(splitter)
# Add some widgets to the stacked widget
widget1 = QLabel('This is widget 1')
widget2 = QLabel('This is widget 2')
widget3 = QLabel('This is widget 3')
stacked.addWidget(widget1)
stacked.addWidget(widget2)
stacked.addWidget(widget3)
# Connect the tree widget to the stacked widget
tree.currentItemChanged.connect(
lambda current, previous: stacked.setCurrentIndex(
tree.indexOfTopLevelItem(current)))
# Set the splitter as the central widget of the main window
self.setCentralWidget(splitter)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
2 去默认系统边框的窗口
去掉边框而且啥都不做,运行出来的窗口不能移动、缩放没动画、方方正正等等
在不同的Windows系统版本中,原始窗口看起来不一样,Win11是圆角 某些老版本好像是方角,而且我想要菜单栏和放大缩小按钮在一栏,还必须有不去边框有的一些样式(如缩放动画、亚克力,窗口阴影等等)
天不负有心人, 终于让我找到了
官方教程:Quick start — PyQt-Frameless-Window v0.2.1 documentation
官方博客园:之一Yo - 博客园 (cnblogs.com)
2.1 需要的部分包
For PyQt5
pip install PyQt5-Frameless-Window
For PyQt6
pip install PyQt6-Frameless-Window
For PySide2:
我使用的是这个
pip install PySide2-Frameless-Window
For PySide6:
pip install PySideSix-Frameless-Window
2.2 简易功能
教程里面的案例,我改了一点点
使用的时候继承一下Window
就行了
import win32con
from ctypes import cast
from ctypes.wintypes import LPRECT, MSG
from PySide2.QtCore import Qt, QEvent, QPoint
from PySide2.QtWidgets import QApplication
from PySide2.QtGui import (QColor, QIcon, QCursor, QMouseEvent, QScreen)
from qframelesswindow import FramelessWindow, StandardTitleBar
from qframelesswindow.titlebar.title_bar_buttons import TitleBarButtonState
from qframelesswindow.utils import win32_utils as win_utils
from qframelesswindow.utils.win32_utils import Taskbar
from qframelesswindow.windows.c_structures import LPNCCALCSIZE_PARAMS
class CustomTitleBar(StandardTitleBar):
""" Custom title bar """
def __init__(self, parent):
super().__init__(parent)
# customize the style of title bar button
self.minBtn.setHoverColor(Qt.white)
self.minBtn.setHoverBackgroundColor(QColor(0, 100, 182))
self.minBtn.setPressedColor(Qt.white)
self.minBtn.setPressedBackgroundColor(QColor(54, 57, 65))
# use qss to customize title bar button
self.maxBtn.setStyleSheet("""
TitleBarButton {
qproperty-hoverColor: white;
qproperty-hoverBackgroundColor: rgb(0, 100, 182);
qproperty-pressedColor: white;
qproperty-pressedBackgroundColor: rgb(54, 57, 65);
}
""")
class Window(FramelessWindow):
def __init__(self, parent=None):
super().__init__(parent=parent)
# change the default title bar if you like
self.setTitleBar(CustomTitleBar(self))
#---------------------- ADDED CODE --------------------------#
self.set_window_info()
self.init_widgets() # first
self.signal_connect_slot()
self.set_center()
#------------------------------------------------------------#
self.titleBar.raise_()
def signal_connect_slot(self):
"""关联信号与槽"""
pass
def init_widgets(self):
"""初始化窗口"""
pass
def set_window_info(self):
"""设置窗口信息(窗口图标、窗口标题)"""
self.setWindowIcon(QIcon("ui/img/logo.png"))
self.setWindowTitle("PyQt-Frameless-Window")
def set_center(self):
"""使窗口在屏幕中央"""
center = QScreen.availableGeometry(
QApplication.primaryScreen()).center()
geo = self.frameGeometry()
geo.moveCenter(center)
self.move(geo.topLeft())
def resizeEvent(self, e):
# don't forget to call the resizeEvent() of super class
super().resizeEvent(e)
def nativeEvent(self, eventType, message):
""" Handle the Windows message """
msg = MSG.from_address(message.__int__())
if not msg.hWnd:
return super().nativeEvent(eventType, message)
if msg.message == win32con.WM_NCHITTEST and self._isResizeEnabled:
pos = QCursor.pos()
xPos = pos.x() - self.x()
yPos = pos.y() - self.y()
w, h = self.width(), self.height()
lx = xPos < self.BORDER_WIDTH
rx = xPos > w - self.BORDER_WIDTH
ty = yPos < self.BORDER_WIDTH
by = yPos > h - self.BORDER_WIDTH
if lx and ty:
return True, win32con.HTTOPLEFT
elif rx and by:
return True, win32con.HTBOTTOMRIGHT
elif rx and ty:
return True, win32con.HTTOPRIGHT
elif lx and by:
return True, win32con.HTBOTTOMLEFT
elif ty:
return True, win32con.HTTOP
elif by:
return True, win32con.HTBOTTOM
elif lx:
return True, win32con.HTLEFT
elif rx:
return True, win32con.HTRIGHT
#------------------- ADDED CODE ---------------------#
elif self.titleBar.childAt(pos-self.geometry().topLeft()) is self.titleBar.maxBtn:
self.titleBar.maxBtn.setState(TitleBarButtonState.HOVER)
return True, win32con.HTMAXBUTTON
elif msg.message in [0x2A2, win32con.WM_MOUSELEAVE]:
self.titleBar.maxBtn.setState(TitleBarButtonState.NORMAL)
elif msg.message in [win32con.WM_NCLBUTTONDOWN, win32con.WM_NCLBUTTONDBLCLK]:
if self.titleBar.childAt(QCursor.pos()-self.geometry().topLeft()) is self.titleBar.maxBtn:
QApplication.sendEvent(self.titleBar.maxBtn, QMouseEvent(
QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier))
return True, 0
elif msg.message in [win32con.WM_NCLBUTTONUP, win32con.WM_NCRBUTTONUP]:
if self.titleBar.childAt(QCursor.pos()-self.geometry().topLeft()) is self.titleBar.maxBtn:
QApplication.sendEvent(self.titleBar.maxBtn, QMouseEvent(
QEvent.MouseButtonRelease, QPoint(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier))
#-----------------------------------------------------------#
elif msg.message == win32con.WM_NCCALCSIZE:
if msg.wParam:
rect = cast(msg.lParam, LPNCCALCSIZE_PARAMS).contents.rgrc[0]
else:
rect = cast(msg.lParam, LPRECT).contents
isMax = win_utils.isMaximized(msg.hWnd)
isFull = win_utils.isFullScreen(msg.hWnd)
# adjust the size of client rect
if isMax and not isFull:
thickness = win_utils.getResizeBorderThickness(msg.hWnd)
rect.top += thickness
rect.left += thickness
rect.right -= thickness
rect.bottom -= thickness
# handle the situation that an auto-hide taskbar is enabled
if (isMax or isFull) and Taskbar.isAutoHide():
position = Taskbar.getPosition(msg.hWnd)
if position == Taskbar.LEFT:
rect.top += Taskbar.AUTO_HIDE_THICKNESS
elif position == Taskbar.BOTTOM:
rect.bottom -= Taskbar.AUTO_HIDE_THICKNESS
elif position == Taskbar.LEFT:
rect.left += Taskbar.AUTO_HIDE_THICKNESS
elif position == Taskbar.RIGHT:
rect.right -= Taskbar.AUTO_HIDE_THICKNESS
result = 0 if not msg.wParam else win32con.WVR_REDRAW
return True, result
return super().nativeEvent(eventType, message)
一个简单使用案例
import sys
from ui.ui_window import Window
class PreDataWidget(Window):
def __init__(self, parent=None):
# 这里就别改了
super().__init__(parent=parent)
def init_widgets(self):
# 初始化代码放这
pass
def signal_connect_slot(self):
# 信号与槽关联放这
pass
## 这一部分代码是可选的, 功能是让运行出来显示的窗口看起来比在QtDesigner设计的更大
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
# enable dpi scale
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
##
app = QApplication(sys.argv)
demo = PreDataWidget()
demo.show()
sys.exit(app.exec_())