mcpField
P
pyside6-qml-views

pyside6-qml-views

github.com/ds-codi设计与 UI49 次安装
代码仓库

关于 pyside6-qml-views

pyside6-qml-views 是采用 PySide6 与 QML 构建界面的设计技能,适合希望将界面与业务逻辑分离的开发者。它涵盖页面、可复用组件、主题、加载状态和资源组织,并要求副作用通过 Python bridge 处理。

主要功能

  1. 01以 QML 声明式定义界面
  2. 02页面绑定 Python bridge 属性
  3. 03复用按钮、搜索栏等组件
  4. 04使用主题单例统一样式
  5. 05通过 Loader 延迟加载内容
  6. 06通过资源系统加载图标

适用场景

  1. 01构建由多个页面组成的桌面界面
  2. 02创建可复用的 QML 控件
  3. 03将界面展示与 I/O 逻辑分离

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ds-codi/project-memory-mcp --skill pyside6-qml-views

安装后在 Claude Code 中输入/pyside6-qml-views确认已加载。

详细介绍

PySide6 QML 视图

此架构中的所有 UI 均以声明式方式定义在 .qml 文件中。QML 视图绑定到 Python 桥接属性,并调用桥接槽函数——其中不包含业务逻辑。

QML 文件组织

resources/
├── qml/
│   ├── main.qml                  # Root window / StackLayout host
│   ├── components/
│   │   ├── ActionButton.qml      # Reusable styled button
│   │   ├── StatusBadge.qml       # Status indicator
│   │   ├── SearchBar.qml         # Search input with debounce
│   │   ├── LoadingOverlay.qml    # Busy spinner overlay
│   │   └── ErrorBanner.qml       # Error message bar
│   ├── pages/
│   │   ├── JobListPage.qml       # Job listing with cards
│   │   ├── JobDetailPage.qml     # Single job detail view
│   │   ├── SettingsPage.qml      # App settings form
│   │   └── DashboardPage.qml     # Overview / landing page
│   ├── dialogs/
│   │   ├── CreateJobDialog.qml   # Modal dialog for new job
│   │   └── ConfirmDialog.qml     # Generic confirmation popup
│   └── styles/
│       ├── Theme.qml             # Colour palette, spacing, fonts
│       └── qmldir                # Module metadata for imports
├── icons/
│   ├── *.svg                     # Vector icons
│   └── *.png                     # Raster icons
└── qml.qrc                       # Qt resource file (optional)

根窗口(main.qml)

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

ApplicationWindow {
    id: root
    visible: true
    width: 1280
    height: 720
    title: "My Application"

    // Page navigation
    StackLayout {
        id: pageStack
        anchors.fill: parent
        currentIndex: 0

        JobListPage {}
        JobDetailPage {}
        SettingsPage {}
    }

    // Global toolbar
    header: ToolBar {
        RowLayout {
            anchors.fill: parent

            Label {
                text: "My App"
                font.bold: true
                Layout.leftMargin: 12
            }

            Item { Layout.fillWidth: true }

            ToolButton {
                text: "Jobs"
                onClicked: pageStack.currentIndex = 0
            }
            ToolButton {
                text: "Settings"
                onClicked: pageStack.currentIndex = 2
            }
        }
    }

    // Global error banner
    ErrorBanner {
        id: errorBanner
        anchors { top: parent.top; left: parent.left; right: parent.right }
        visible: jobBridge.errorMessage !== ""
        message: jobBridge.errorMessage
    }

    // Loading overlay
    LoadingOverlay {
        anchors.fill: parent
        visible: jobBridge.isBusy
    }
}

页面模式

每个页面都是一个独立的 QML 文件,可绑定到桥接属性:

// pages/JobListPage.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Page {
    id: jobListPage

    header: ToolBar {
        RowLayout {
            anchors.fill: parent
            SearchBar {
                id: searchBar
                Layout.fillWidth: true
                onSearchTriggered: jobBridge.searchJobs(query)
            }
            ActionButton {
                text: "New Job"
                icon.name: "add"
                onClicked: createJobDialog.open()
            }
        }
    }

    ListView {
        id: jobsListView
        anchors.fill: parent
        model: jobListModel
        spacing: 4
        clip: true

        delegate: ItemDelegate {
            width: jobsListView.width
            height: 64

            contentItem: RowLayout {
                spacing: 12
                Label {
                    text: model.jobNumber
                    font.bold: true
                    Layout.preferredWidth: 100
                }
                Label {
                    text: model.jobName
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                }
                StatusBadge {
                    status: model.status
                }
            }

            onClicked: {
                jobBridge.activateJob(model.jobNumber)
                pageStack.currentIndex = 1  // navigate to detail
            }
        }

        // Empty state
        Label {
            anchors.centerIn: parent
            visible: jobsListView.count === 0
            text: "No jobs found"
            opacity: 0.5
        }
    }

    CreateJobDialog {
        id: createJobDialog
    }
}

可复用组件模式

组件文件结构
// components/ActionButton.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Button {
    id: control

    // Custom properties
    property color accentColor: "#1976D2"
    property bool loading: false

    enabled: !loading
    opacity: enabled ? 1.0 : 0.5

    contentItem: Row {
        spacing: 8
        BusyIndicator {
            running: control.loading
            visible: control.loading
            width: 16; height: 16
        }
        Label {
            text: control.text
            color: "white"
            verticalAlignment: Text.AlignVCenter
        }
    }

    background: Rectangle {
        radius: 4
        color: control.down ? Qt.darker(accentColor, 1.2)
             : control.hovered ? Qt.lighter(accentColor, 1.1)
             : accentColor
    }
}
带自定义信号的组件
// components/SearchBar.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

TextField {
    id: searchField

    signal searchTriggered(string query)

    placeholderText: "Search..."
    selectByMouse: true

    // Debounced search
    Timer {
        id: debounceTimer
        interval: 300
        onTriggered: searchField.searchTriggered(searchField.text)
    }

    onTextChanged: debounceTimer.restart()
    onAccepted: {
        debounceTimer.stop()
        searchTriggered(text)
    }
}

对话框模式

// dialogs/CreateJobDialog.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Dialog {
    id: dialog
    title: "Create New Job"
    modal: true
    anchors.centerIn: Overlay.overlay
    width: 400
    standardButtons: Dialog.Ok | Dialog.Cancel

    onAccepted: {
        if (jobNumberInput.text.trim() !== "") {
            jobBridge.createJob(jobNumberInput.text.trim())
        }
    }
    onRejected: dialog.close()

    // Reset on open
    onOpened: {
        jobNumberInput.text = ""
        jobNumberInput.forceActiveFocus()
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 12

        Label { text: "Job Number" }
        TextField {
            id: jobNumberInput
            Layout.fillWidth: true
            placeholderText: "e.g. 1234567"
            validator: RegularExpressionValidator {
                regularExpression: /^\d{5,8}[A-Z]?$/
            }
        }

        Label {
            text: "Enter a valid job number (5-8 digits, optional letter suffix)"
            font.pixelSize: 11
            opacity: 0.6
        }
    }
}

主题 / 样式

主题单例
// styles/Theme.qml
pragma Singleton
import QtQuick 2.15

QtObject {
    // Colours
    readonly property color primary: "#1976D2"
    readonly property color primaryDark: "#1565C0"
    readonly property color accent: "#FF9800"
    readonly property color background: "#FAFAFA"
    readonly property color surface: "#FFFFFF"
    readonly property color error: "#D32F2F"
    readonly property color textPrimary: "#212121"
    readonly property color textSecondary: "#757575"

    // Spacing
    readonly property int spacingSmall: 4
    readonly property int spacingMedium: 8
    readonly property int spacingLarge: 16
    readonly property int spacingXLarge: 24

    // Typography
    readonly property int fontSizeSmall: 12
    readonly property int fontSizeMedium: 14
    readonly property int fontSizeLarge: 18
    readonly property int fontSizeTitle: 24

    // Elevation / Radii
    readonly property int borderRadius: 4
    readonly property int cardRadius: 8
}
qmldir(模块注册)
// styles/qmldir
module Styles
singleton Theme 1.0 Theme.qml
用法
import "styles" as Styles

Rectangle {
    color: Styles.Theme.surface
    radius: Styles.Theme.cardRadius

    Label {
        color: Styles.Theme.textPrimary
        font.pixelSize: Styles.Theme.fontSizeMedium
    }
}

加载状态

// components/LoadingOverlay.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    id: overlay
    color: "#80000000"  // semi-transparent black
    visible: false
    z: 999

    BusyIndicator {
        anchors.centerIn: parent
        running: overlay.visible
        width: 48; height: 48
    }

    MouseArea {
        anchors.fill: parent
        // Block clicks through overlay
    }
}

状态指示器

// components/StatusBadge.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    id: badge
    property string status: ""

    width: statusLabel.implicitWidth + 16
    height: 24
    radius: 12
    color: {
        switch (status.toLowerCase()) {
            case "active":   return "#4CAF50";
            case "complete": return "#2196F3";
            case "on_hold":  return "#FF9800";
            case "archived": return "#9E9E9E";
            default:         return "#BDBDBD";
        }
    }

    Label {
        id: statusLabel
        anchors.centerIn: parent
        text: badge.status
        color: "white"
        font.pixelSize: 11
        font.bold: true
    }
}

QML 最佳实践

规则原因
将组件控制在 150 行以内便于维护;提取子组件
每个文件只包含一个根项符合 QML 约定
仅在需要引用时使用 id避免不必要的标识
优先使用属性绑定,而非命令式 JS声明式更新,减少错误
对较重或有条件显示的内容使用 Loader延迟实例化可节省内存
切勿在 QML JS 中嵌入 SQL、HTTP 或文件 I/O所有副作用都通过桥接槽函数处理
限定属性访问范围(root.width 而非 width)避免嵌套项中的名称遮蔽
使用锚点或布局,而非手动设置 x/y响应式且易于维护

资源加载

从文件系统加载图标
Image {
    source: "file:///" + Qt.resolvedUrl("../../icons/logo.svg")
}
通过 Qt 资源系统加载图标
Image {
    source: "qrc:/icons/logo.svg"
}
Qt 资源文件(qml.qrc)
<RCC>
    <qresource prefix="/">
        <file>qml/main.qml</file>
        <file>qml/components/ActionButton.qml</file>
        <file>icons/logo.svg</file>
    </qresource>
</RCC>

参考资料

评论

支持 Markdown