【PySide || PyQT】QTreeWidget和QSTackedWidget的互动

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.currentItemChangedQTreeWidget的一个信号,当当前选中项发生变化时就会发出该信号。连接到这个信号的槽函数会接收两个参数:当前选中项和之前选中的项。在这个示例中,lambda函数被用作槽函数,用于将当前选中项的索引传递给QStackedWidgetsetCurrentIndex()方法以显示对应的窗口部件。
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_())
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

维他命C++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值