优秀的编程知识分享平台

网站首页 > 技术文章 正文

Python入坑系列-pyside6桌面开发之QmainWindow自定义标题栏

nanyue 2024-12-24 14:49:56 技术文章 3 ℃

通过本文章,可以掌握以下内容:

  1. QMainWindow布局
  2. QMainWindow常用属性和方法介绍
  3. QMainWindow图标和背景设置
  4. QMainWindow无边框设计实现

1、QMainWindow布局

主窗口提供了用于构建应用程序的用户界面的框架,其主要布局如下

  • Menu Bar:菜单栏,通过添加QMenuBar
# 实例化QMenuBar
self.menuBarInstance = QMenuBar(self)
# 创建菜单
fileMenu = self.menuBarInstance.addMenu("文件")
# 创建动作
exitAction = QAction("退出", self)
exitAction.triggered.connect(self.close)
# 将动作添加到菜单
fileMenu.addAction(exitAction)
  • Toolbars:工具栏,通过往窗口添加QToolBar
# 实例化QToolBar
toolBar = QToolBar("我的工具栏", self)
self.addToolBar(toolBar)
# 创建动作
exitAction = QAction("退出", self)
exitAction.triggered.connect(self.close)
# 将动作添加到菜单
fileMenu.addAction(exitAction)
  • Dock Widgets:停靠部件,比如浮窗的拖动部件,通过往窗口添加QDockWidgets
# 创建一个QDockWidget
dock = QDockWidget("菜单", self)
self.addDockWidget(Qt.LeftDockWidgetArea, dock)
# 创建一个列表作为菜单项容器
menuList = QListWidget()
dock.setWidget(menuList)
  • Central Widget:占据主窗口中心区域的部件,通常是标准 Qt 小部,通过添加Widget部件
# 实例化一个QWidget作为中心部件
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
# 创建一个布局
layout = QVBoxLayout()
# 添加一些控件到布局中
layout.addWidget(QLabel("这是一个标签"))
# 将布局设置给中心部件
centralWidget.setLayout(layout)
  • Status Bar:状态栏显示,通过添加QStatusBar
# 实例化一个QStatusBar
statusBar = QStatusBar()
# 将状态栏设置为主窗口的状态栏
self.setStatusBar(statusBar)
# 在状态栏中添加一些信息
statusBar.addWidget(QLabel("这是状态栏信息"))

2、QMainWindow常用属性和方法

属性

  • windowTitle::窗口的标题。
  • menuBar:窗口的菜单栏。
  • statusBar:窗口的状态栏。
  • centralWidget:窗口的中心部件。
  • toolBars:窗口的工具栏列表。
  • dockWidgets:窗口的停靠窗口列表。
  • animated:操作停靠小部件和工具栏是否有动画
  • dockNestingEnabled:停靠部件是否可以嵌套
  • dockOptions:QMainWindow的停靠行为
  • documentMode:选项卡式停靠小部件的选项卡栏是否设置为文档模式
  • iconSize:此主窗口中工具栏图标的大小
  • tabShape:用于选项卡式停靠小部件的选项卡形状
  • toolButtonStyle:主窗口中工具栏按钮的样式
  • unifiedTitleAndToolBarOnMac:窗口是否在macOS上使用统一的标题和工具栏外观

方法

  • setWindowFlags(Qt.WindowFlags flags):用于指定窗口的各种属性和行为。例如,你可以设置窗口为无边框、始终在顶层显示、不显示在任务栏等

窗口装饰标志

Qt.MSWindowsFixedSizeDialogHint

Windows平台上,使对话框窗口的大小固定,不能被用户调整。

Qt.X11BypassWindowManagerHint

在X11平台(Linux/Unix)上,使窗口绕过窗口管理器,直接与系统交互。这通常用于需要完全控制窗口显示和行为的特殊窗口

Qt.FramelessWindowHint

创建一个无边框窗口。这通常用于自定义窗口的外观。

Qt.NoDropShadowWindowHint

禁用窗口的阴影效果。这在自定义窗口绘制时可能有用,特别是在需要精确控制窗口外观的情况下。

Qt.WindowTitleHint

窗口将包含一个标题栏。这是大多数窗口的默认行为。

Qt.WindowSystemMenuHint

窗口将包含一个系统菜单,通常位于标题栏的左上角。

Qt.WindowMinimizeButtonHint

窗口将包含一个最小化按钮。

Qt.WindowMaximizeButtonHint

窗口将包含一个最大化按钮。

Qt.WindowCloseButtonHint

窗口将包含一个关闭按钮。

Qt.WindowContextHelpButtonHint

窗口将包含一个上下文帮助按钮,在用户点击时通常会显示帮助信息。

Qt.WindowStaysOnTopHint

窗口将始终保持在其他窗口之上。

Qt.WindowStaysOnBottomHint

窗口将始终保持在其他窗口之下。

Qt.CustomizeWindowHint

允许窗口被自定义,通常与其他窗口标志一起使用,以提供特定的窗口外观和行为。

常用窗口标志

Qt.Widget

默认值,指示对象是一个控件

Qt.Window

使控件成为一个窗口,这会给它提供窗口装饰

Qt.Dialog

示窗口是一个对话框

Qt.Sheet

指示窗口是一个Mac风格的表单(仅在Mac OS X中有效)

Qt.Drawer

指示窗口是一个Mac风格的抽屉(仅在Mac OS X中有效)

Qt.Popup

指示窗口是一个弹出窗口

Qt.Tool

指示窗口是一个工具窗口。工具窗口是一个小窗口,通常用于提供模式或非模式对话框

Qt.ToolTip

指示窗口是一个工具提示

Qt.SplashScreen

指示窗口是一个启动画面

Qt.Desktop

指示窗口是桌面。这是一个特殊的窗口,不能创建

Qt.SubWindow

指示窗口是一个子窗口

  • addDockWidget(area, dockwidget, orientation): 在指定区域和方向添加一个停靠窗口。以下是停靠的枚举值

常用的停靠窗口标志

QMainWindow.AnimatedDocks

与属性相同animated。

QMainWindow.AllowNestedDocks

与属性相同dockNestingEnabled。

QMainWindow.AllowTabbedDocks

用户可以将一个停靠小部件放置在另一个停靠小部件的“顶部”。这两个小部件堆叠在一起,并出现一个选项卡栏,用于选择哪个小部件可见。

QMainWindow.ForceTabbedDocks

每个停靠区域都包含一堆选项卡式停靠小部件。换句话说,停靠小部件不能在停靠区域中彼此相邻放置。如果设置此选项,AllowNestedDocks 不起作用。

QMainWindow.VerticalTabs

主窗口两侧的两个垂直停靠区域垂直显示其选项卡。如果未设置此选项,所有停靠区域都会在底部显示其选项卡。暗示AllowTabbedDocks。也可以看看setTabPosition()。

QMainWindow.GroupedDragging

拖动扩展坞的标题栏时,所有与其关联的选项卡都将被拖动。暗示AllowTabbedDocks。如果某些 QDockWidget 在允许的区域有限制,则效果不佳。(这个枚举值是在 Qt 5.6 中添加的。)

  • addDockWidget(area, dockwidget): 在指定区域添加一个停靠窗口。
  • addToolBar(area, toolbar): 在指定区域添加一个工具栏。
  • addToolBar(title): 创建一个新的工具栏,使用提供的标题。
  • addToolBar(toolbar): 添加一个预先创建的工具栏。
  • addToolBarBreak([area=Qt.TopToolBarArea]): 在指定区域添加一个工具栏断点。
  • centralWidget(): 返回当前设置为中心部件的QWidget对象。
  • corner(corner): 返回指定角落的停靠区域。
  • dockOptions(): 返回当前的停靠选项。
  • dockWidgetArea(dockwidget): 返回指定停靠窗口所在的区域。
  • documentMode(): 返回文档模式的启用状态。
  • iconSize(): 返回工具栏图标的大小。
  • insertToolBar(before, toolbar): 在指定的工具栏之前插入一个工具栏。
  • insertToolBarBreak(before): 在指定的工具栏之前插入一个工具栏断点。
  • isAnimated(): 返回窗口是否启用动画。
  • isDockNestingEnabled(): 返回是否允许停靠窗口嵌套。
  • isSeparator(pos): 检查指定位置是否为分隔符。
  • menuBar(): 返回窗口的菜单栏。
  • menuWidget(): 返回设置为菜单栏的自定义QWidget对象。
  • removeDockWidget(dockwidget): 移除指定的停靠窗口。
  • removeToolBar(toolbar): 移除指定的工具栏。
  • removeToolBarBreak(before): 移除指定位置的工具栏断点。
  • resizeDocks(docks, sizes, orientation): 调整一组停靠窗口的大小。
  • restoreDockWidget(dockwidget): 恢复停靠窗口的状态。
  • restoreState(state[, version=0]): 恢复窗口的状态。
  • saveState([version=0]): 保存窗口的当前状态。
  • setCentralWidget(widget): 设置中心部件。
  • setCorner(corner, area): 设置窗口角落的停靠区域。
  • setDockOptions(options): 设置停靠选项。
  • setDocumentMode(enabled): 启用或禁用文档模式。
  • setIconSize(iconSize): 设置工具栏图标的大小。
  • setMenuBar(menubar): 设置窗口的菜单栏。
  • setMenuWidget(menubar): 设置自定义的菜单栏部件。
  • setStatusBar(statusbar): 设置窗口的状态栏。
  • setTabPosition(areas, tabPosition): 设置停靠窗口的标签位置。
  • setTabShape(tabShape): 设置停靠窗口的标签形状。
  • setToolButtonStyle(toolButtonStyle): 设置工具按钮的样式。
  • splitDockWidget(after, dockwidget, orientation): 将一个停靠窗口分割为两个。
  • statusBar(): 返回窗口的状态栏。
  • tabPosition(area): 返回指定区域的标签位置。
  • tabShape(): 返回停靠窗口的标签形状。
  • tabifiedDockWidgets(dockwidget): 返回与指定停靠窗口标签化的所有停靠窗口。
  • tabifyDockWidget(first, second): 将两个停靠窗口标签化。
  • takeCentralWidget(): 移除并返回中心部件。
  • toolBarArea(toolbar): 返回指定工具栏所在的区域
  • toolBarBreak(toolbar): 检查指定工具栏之前是否有工具栏断点。
  • toolButtonStyle(): 返回工具按钮的样式。
  • unifiedTitleAndToolBarOnMac(): 返回是否在Mac OS上使用统一的标题栏和工具栏样式。
  • createPopupMenu(): 创建一个包含所有可停靠窗口和工具栏的可见性控制项的弹出菜单。这个方法通常用于提供一个上下文菜单,允许用户选择哪些工具栏或停靠窗口是可见的。

3、QMainWindow图标和背景设置

设置logo图标

# 设置窗口图标
self.setWindowIcon(QIcon('path/to/your/icon.png'))

设置背景

  • 通过样式设置颜色
# 使用样式表设置背景颜色
self.setStyleSheet("background-color: lightblue;")
  • 通过样式设置图片
# 使用样式表设置背景图片
self.setStyleSheet("background-image: url('path/to/your/image.jpg');")
  • 通过QPainter绘制背景图片
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("通过paintEvent设置背景")
    def paintEvent(self, event):
        painter = QPainter(self)
        pixmap = QPixmap("path/to/your/image.jpg")
        P·ainter.drawPixmap(self.rect(), pixmap)
if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())

4、QMainWindow无边框设计实现

实现无边框设计,pyside6目前没有很好的解决方案。主要思路是设置无边框,通过自定义标题栏来实现窗口的基本属性,事件。下面是具体例子:

from PySide6.QtCore import QSize, Qt, QEvent
from PySide6.QtGui import QPalette, QRegion
from PySide6.QtWidgets import *

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.ColorRole.Highlight)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)

        self.title = QLabel("自定义工具栏", self)
        self.title.setStyleSheet(
            """font-weight: bold;
               border: 2px solid black;
               border-radius: 12px;
               margin: 2px;
            """
        )
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        if title := parent.windowTitle():
            self.title.setText(title)
        # 只设置自定义标题拖动
        self.title.mousePressEvent = parent.title_mousePressEvent
        self.title.mouseMoveEvent = parent.title_mouseMoveEvent
        self.title.mouseReleaseEvent = parent.title_mouseReleaseEvent
        title_bar_layout.addWidget(self.title)
        # Min button
        self.min_button = QToolButton(self)
        min_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMinButton
        )
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMaxButton
        )
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarCloseButton
        )
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarNormalButton
        )
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # Add buttons
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(28, 28))
            button.setStyleSheet(
                """QToolButton { border: 2px solid white;
                                 border-radius: 12px;
                                }
                """
            )
            title_bar_layout.addWidget(button)

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.setMinimumSize(600, 400)
        self.setWindowFlags(Qt.FramelessWindowHint)
        # 使用QSS设置边框样式为圆角
        self.setStyleSheet("MainWindow{border-radius: 1px;}")
        central_widget = QWidget()
        self.title_bar = CustomTitleBar(self)
        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        work_space_layout.addWidget(QLabel("Hello, World!", self))
        centra_widget_layout = QVBoxLayout()
        centra_widget_layout.setContentsMargins(0, 0, 0, 0)
        centra_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        centra_widget_layout.addWidget(self.title_bar)
        centra_widget_layout.addLayout(work_space_layout)
        central_widget.setLayout(centra_widget_layout)
        self.setCentralWidget(central_widget)

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()

    def window_state_changed(self, state):
        self.normal_button.setVisible(state == Qt.WindowState.WindowMaximized)
        self.max_button.setVisible(state != Qt.WindowState.WindowMaximized)

    def title_mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.position().toPoint()
        super().mousePressEvent(event)
        event.accept()

    def title_mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.position().toPoint() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()

    def title_mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

效果如下,可以实现放大,缩小,标题栏拖动功能:

上面例子没有实现窗口边框可拖动放大缩小的功能,是个遗憾。github上的有实现,可以参考:https://github.com/alexpdev/QtFrameless。

Tags:

最近发表
标签列表