关于 pyside6-qml-views
pyside6-qml-views 是采用 PySide6 与 QML 构建界面的设计技能,适合希望将界面与业务逻辑分离的开发者。它涵盖页面、可复用组件、主题、加载状态和资源组织,并要求副作用通过 Python bridge 处理。
主要功能
- 01以 QML 声明式定义界面
- 02页面绑定 Python bridge 属性
- 03复用按钮、搜索栏等组件
- 04使用主题单例统一样式
- 05通过 Loader 延迟加载内容
- 06通过资源系统加载图标
适用场景
- 01构建由多个页面组成的桌面界面
- 02创建可复用的 QML 控件
- 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