前言:跨越语言的桥梁
在前四篇中,我们分别学习了QML语法基础、响应式UI设计、动画系统以及组件化架构。现在,我们来到了本系列最核心、最强大的部分------Python与QML的深度融合。真正的全栈GUI开发,不仅仅是让界面看起来漂亮,更重要的是如何将强大的Python后端逻辑与灵活的QML前端界面无缝结合。

在现代企业级应用中,数据是核心,交互是灵魂。Python凭借其丰富的数据处理库和成熟的生态系统,成为数据处理的不二选择;而QML则以其声明式语法和流畅的动画效果,为用户提供卓越的交互体验。两者的结合,就像大脑与感官的完美协作。
通过本篇学习,你将掌握如何构建真正的数据驱动应用,实现前后端的无缝通信,打造既美观又强大的桌面应用程序。
本篇学习目标
完成本篇学习后,你将能够:
-
深入理解PySide6与QML的完整通信机制
-
掌握多种数据绑定策略及其适用场景
-
实现Python与QML之间的双向实时数据同步
-
构建复杂的Model-View架构数据处理系统
-
处理异步操作和多线程环境下的数据交互
-
设计可扩展的前后端分离架构
-
优化大数据量场景下的性能表现
-
构建完整的企业级数据管理系统
-
实现错误处理和调试机制
-
掌握组件间复杂的数据流管理
知识地图:Python-QML集成全景

1. 深度集成架构解析
1.1 集成架构总览
Python与QML的集成不是简单的函数调用,而是一个完整的系统架构。让我们先通过架构图理解整个集成体系:

1.2 通信机制对比
在深入代码之前,让我们先理解Python与QML之间的各种通信方式及其适用场景:

通信机制详细对比表:

2. 核心集成技术详解
2.1 上下文属性:全局数据共享
上下文属性是最基础的集成方式,适合传递全局配置和共享数据。让我们通过一个完整的示例来理解:
python
# backend.py - Python后端实现
import sys
import os
from datetime import datetime
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field, asdict
from enum import Enum
import json
from PySide6.QtCore import (
QObject,
Slot,
Property,
Signal,
QUrl,
QTimer,
QThread,
QThreadPool,
QRunnable,
QMetaObject,
Qt
)
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, QmlElement
# 注册QML类型
QML_IMPORT_NAME = "Dashboard.Backend"
QML_IMPORT_MAJOR_VERSION = 1
@dataclass
class UserProfile:
"""用户信息数据类"""
username: str
email: str
role: str
avatar_url: str = ""
last_login: Optional[datetime] = None
preferences: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
if self.last_login:
data['last_login'] = self.last_login.isoformat()
return data
class ThemeMode(Enum):
"""主题模式枚举"""
LIGHT = "light"
DARK = "dark"
AUTO = "auto"
@QmlElement
class ApplicationConfig(QObject):
"""应用配置管理器"""
# 信号定义
themeChanged = Signal(str)
languageChanged = Signal(str)
configChanged = Signal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self._theme = ThemeMode.AUTO.value
self._language = "zh_CN"
self._config = {
"auto_save": True,
"check_updates": True,
"notifications": True,
"animation_enabled": True,
"font_size": 14
}
# 主题属性
@Property(str, notify=themeChanged)
def theme(self) -> str:
return self._theme
@theme.setter
def theme(self, value: str):
if self._theme != value and value in [t.value for t in ThemeMode]:
self._theme = value
self.themeChanged.emit(value)
self._save_config()
# 语言属性
@Property(str, notify=languageChanged)
def language(self) -> str:
return self._language
@language.setter
def language(self, value: str):
if self._language != value:
self._language = value
self.languageChanged.emit(value)
self._save_config()
# 配置属性
@Property(dict, notify=configChanged)
def config(self) -> Dict[str, Any]:
return self._config.copy()
@config.setter
def config(self, value: Dict[str, Any]):
if self._config != value:
self._config.update(value)
self.configChanged.emit(self._config)
self._save_config()
@Slot(str, result=bool)
def get_config_value(self, key: str) -> bool:
"""获取配置值"""
return self._config.get(key, False)
@Slot(str, bool)
def set_config_value(self, key: str, value: bool):
"""设置配置值"""
if self._config.get(key) != value:
self._config[key] = value
self.configChanged.emit(self._config)
self._save_config()
def _save_config(self):
"""保存配置到文件(模拟)"""
config_data = {
"theme": self._theme,
"language": self._language,
"config": self._config
}
# 实际应用中这里应该保存到文件
print(f"配置已保存: {config_data}")
@QmlElement
class UserManager(QObject):
"""用户管理器"""
# 信号定义
userLoggedIn = Signal(dict)
userLoggedOut = Signal()
profileUpdated = Signal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self._current_user: Optional[UserProfile] = None
self._is_logged_in = False
# 当前用户属性
@Property(bool, notify=userLoggedIn)
def isLoggedIn(self) -> bool:
return self._is_logged_in
@Property(dict, notify=userLoggedIn)
def currentUser(self) -> Dict[str, Any]:
if self._current_user:
return self._current_user.to_dict()
return {}
@Slot(str, str, result=bool)
def login(self, username: str, password: str) -> bool:
"""用户登录"""
# 模拟登录验证
if username and password:
self._current_user = UserProfile(
username=username,
email=f"{username}@example.com",
role="user",
avatar_url=f"https://api.dicebear.com/7.x/avataaars/svg?seed={username}",
last_login=datetime.now(),
preferences={"theme": "auto", "language": "zh_CN"}
)
self._is_logged_in = True
self.userLoggedIn.emit(self._current_user.to_dict())
return True
return False
@Slot()
def logout(self):
"""用户退出"""
self._current_user = None
self._is_logged_in = False
self.userLoggedOut.emit()
@Slot(dict)
def update_profile(self, profile_data: Dict[str, Any]):
"""更新用户资料"""
if self._current_user:
for key, value in profile_data.items():
if hasattr(self._current_user, key):
setattr(self._current_user, key, value)
self.profileUpdated.emit(self._current_user.to_dict())
上下文属性注册架构:

对应的QML前端代码:
javascript
// Main.qml - 主界面
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Dashboard.Backend 1.0
ApplicationWindow {
id: window
width: 1200
height: 800
visible: true
title: "企业数据管理系统"
// 通过上下文属性访问Python对象
property var config: Backend.config
property var userManager: Backend.userManager
// 主题管理
property color backgroundColor: config.theme === "dark" ? "#1a1a2e" : "#f5f5f5"
property color textColor: config.theme === "dark" ? "#ffffff" : "#333333"
// 监听主题变化
Connections {
target: config
function onThemeChanged(theme) {
console.log("主题已切换:", theme)
updateTheme()
}
}
// 监听登录状态
Connections {
target: userManager
function onUserLoggedIn(userData) {
console.log("用户登录:", userData.username)
showMainContent()
}
function onUserLoggedOut() {
console.log("用户退出")
showLoginForm()
}
}
// 主布局
StackLayout {
id: mainStack
anchors.fill: parent
currentIndex: userManager.isLoggedIn ? 1 : 0
// 登录界面
LoginPage {
config: window.config
userManager: window.userManager
}
// 主内容界面
MainContent {
config: window.config
userManager: window.userManager
}
}
function updateTheme() {
// 更新主题相关样式
window.color = backgroundColor
}
function showMainContent() {
mainStack.currentIndex = 1
}
function showLoginForm() {
mainStack.currentIndex = 0
}
Component.onCompleted: {
console.log("应用启动完成")
updateTheme()
}
}
2.2 双向数据绑定:响应式数据流
双向数据绑定是构建响应式应用的核心。让我们深入理解其工作原理:

双向绑定示例代码:
python
# data_binding.py - 双向绑定示例
from PySide6.QtCore import QObject, Property, Signal, Slot
from typing import Any, List, Dict
import json
@QmlElement
class DataModel(QObject):
"""数据模型 - 演示双向绑定"""
# 信号定义
dataChanged = Signal(dict)
itemsChanged = Signal(list)
filterChanged = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._data = {
"name": "示例数据",
"value": 100,
"enabled": True,
"options": ["选项1", "选项2", "选项3"]
}
self._items = []
self._filter_text = ""
self._selected_item = None
# 初始化数据
self._load_initial_data()
# ----- 基本数据绑定 -----
@Property(str, notify=dataChanged)
def name(self) -> str:
return self._data.get("name", "")
@name.setter
def name(self, value: str):
if self._data.get("name") != value:
self._data["name"] = value
self.dataChanged.emit(self._data)
@Property(int, notify=dataChanged)
def value(self) -> int:
return self._data.get("value", 0)
@value.setter
def value(self, value: int):
if self._data.get("value") != value:
self._data["value"] = value
self.dataChanged.emit(self._data)
@Property(bool, notify=dataChanged)
def enabled(self) -> bool:
return self._data.get("enabled", False)
@enabled.setter
def enabled(self, value: bool):
if self._data.get("enabled") != value:
self._data["enabled"] = value
self.dataChanged.emit(self._data)
# ----- 列表数据绑定 -----
@Property(list, notify=itemsChanged)
def items(self) -> List[Dict[str, Any]]:
return self._items
@items.setter
def items(self, value: List[Dict[str, Any]]):
if self._items != value:
self._items = value
self.itemsChanged.emit(self._items)
# ----- 计算属性 -----
@Property(int, notify=itemsChanged)
def itemCount(self) -> int:
return len(self._items)
@Property(int, notify=dataChanged)
def doubleValue(self) -> int:
return self._data.get("value", 0) * 2
# ----- 过滤功能 -----
@Property(str, notify=filterChanged)
def filterText(self) -> str:
return self._filter_text
@filterText.setter
def filterText(self, value: str):
if self._filter_text != value:
self._filter_text = value
self.filterChanged.emit(value)
self._apply_filter()
@Property(list, notify=itemsChanged)
def filteredItems(self) -> List[Dict[str, Any]]:
if not self._filter_text:
return self._items
return [
item for item in self._items
if self._filter_text.lower() in json.dumps(item).lower()
]
# ----- 公共方法 -----
@Slot(result=bool)
def validate_data(self) -> bool:
"""验证数据"""
if not self._data.get("name", "").strip():
return False
if self._data.get("value", 0) < 0:
return False
return True
@Slot(str, int, bool)
def update_data(self, name: str, value: int, enabled: bool):
"""批量更新数据"""
changed = False
if self._data.get("name") != name:
self._data["name"] = name
changed = True
if self._data.get("value") != value:
self._data["value"] = value
changed = True
if self._data.get("enabled") != enabled:
self._data["enabled"] = enabled
changed = True
if changed:
self.dataChanged.emit(self._data)
@Slot(dict)
def add_item(self, item: Dict[str, Any]):
"""添加项目"""
self._items.append(item)
self.itemsChanged.emit(self._items)
@Slot(int)
def remove_item(self, index: int):
"""删除项目"""
if 0 <= index < len(self._items):
self._items.pop(index)
self.itemsChanged.emit(self._items)
# ----- 私有方法 -----
def _load_initial_data(self):
"""加载初始数据"""
self._items = [
{"id": 1, "name": "项目A", "value": 100, "status": "active"},
{"id": 2, "name": "项目B", "value": 200, "status": "pending"},
{"id": 3, "name": "项目C", "value": 300, "status": "completed"},
{"id": 4, "name": "项目D", "value": 400, "status": "active"},
{"id": 5, "name": "项目E", "value": 500, "status": "cancelled"},
]
def _apply_filter(self):
"""应用过滤条件"""
# 过滤逻辑已在filteredItems属性中实现
pass
双向绑定QML界面:
javascript
// DataBindingDemo.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Dashboard.Backend 1.0
ApplicationWindow {
id: window
width: 1000
height: 700
visible: true
title: "双向数据绑定演示"
// 数据模型
property var dataModel: Backend.dataModel
// 主布局
SplitView {
anchors.fill: parent
orientation: Qt.Horizontal
// 左侧:数据编辑区
ScrollView {
SplitView.preferredWidth: 400
ColumnLayout {
width: parent.width
spacing: 20
padding: 20
// 标题
Label {
text: "数据编辑"
font.pixelSize: 24
font.bold: true
Layout.fillWidth: true
}
// 名称输入
GroupBox {
title: "基本属性"
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 10
// 名称 - 双向绑定
RowLayout {
Label { text: "名称:"; Layout.preferredWidth: 80 }
TextField {
id: nameField
text: dataModel.name
placeholderText: "输入名称"
Layout.fillWidth: true
// 绑定到Python属性
onTextChanged: {
if (activeFocus) {
dataModel.name = text
}
}
}
}
// 数值 - 双向绑定
RowLayout {
Label { text: "数值:"; Layout.preferredWidth: 80 }
Slider {
id: valueSlider
from: 0
to: 200
value: dataModel.value
stepSize: 1
Layout.fillWidth: true
onValueChanged: {
if (activeFocus) {
dataModel.value = value
}
}
}
Label {
text: dataModel.value
font.bold: true
Layout.preferredWidth: 40
}
}
// 启用状态 - 双向绑定
RowLayout {
Label { text: "启用:"; Layout.preferredWidth: 80 }
Switch {
id: enabledSwitch
checked: dataModel.enabled
onCheckedChanged: {
if (activeFocus) {
dataModel.enabled = checked
}
}
}
}
// 计算属性显示
RowLayout {
Label {
text: "双倍数值:"
Layout.preferredWidth: 80
}
Label {
text: dataModel.doubleValue
font.bold: true
color: "green"
}
}
}
}
// 批量操作
GroupBox {
title: "批量操作"
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 10
Button {
text: "验证数据"
onClicked: {
var valid = dataModel.validate_data()
validationLabel.text = valid ? "✓ 数据有效" : "✗ 数据无效"
validationLabel.color = valid ? "green" : "red"
}
}
Label {
id: validationLabel
text: ""
}
Button {
text: "重置数据"
onClicked: {
dataModel.update_data("默认名称", 100, true)
}
}
}
}
Item { Layout.fillHeight: true }
}
}
// 右侧:数据显示区
ScrollView {
SplitView.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 20
padding: 20
// 标题
Label {
text: "数据展示"
font.pixelSize: 24
font.bold: true
Layout.fillWidth: true
}
// 当前数据展示
GroupBox {
title: "当前数据"
Layout.fillWidth: true
GridLayout {
columns: 2
rowSpacing: 10
columnSpacing: 20
width: parent.width
Label { text: "名称:"; font.bold: true }
Label { text: dataModel.name }
Label { text: "数值:"; font.bold: true }
Label { text: dataModel.value }
Label { text: "启用状态:"; font.bold: true }
Label {
text: dataModel.enabled ? "已启用" : "已禁用"
color: dataModel.enabled ? "green" : "red"
}
Label { text: "双倍数值:"; font.bold: true }
Label {
text: dataModel.doubleValue
color: "blue"
font.bold: true
}
}
}
// 数据列表
GroupBox {
title: "数据列表 (共" + dataModel.itemCount + "项)"
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 10
// 搜索过滤
RowLayout {
TextField {
id: searchField
placeholderText: "搜索..."
Layout.fillWidth: true
// 绑定到过滤属性
text: dataModel.filterText
onTextChanged: {
if (activeFocus) {
dataModel.filterText = text
}
}
}
Label {
text: "找到 " + dataModel.filteredItems.length + " 项"
color: "#666"
}
}
// 列表视图
ListView {
id: listView
model: dataModel.filteredItems
clip: true
implicitHeight: 300
Layout.fillWidth: true
delegate: Rectangle {
width: ListView.view.width
height: 60
color: index % 2 ? "#f5f5f5" : "#ffffff"
border.color: "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 20
ColumnLayout {
spacing: 5
Layout.fillWidth: true
Text {
text: modelData.name || "未命名"
font.bold: true
font.pixelSize: 16
}
Text {
text: "ID: " + modelData.id + " | 值: " + modelData.value
color: "#666"
font.pixelSize: 12
}
}
// 状态标签
Rectangle {
radius: 10
color: {
switch(modelData.status) {
case "active": return "#4CAF50"
case "pending": return "#FF9800"
case "completed": return "#2196F3"
case "cancelled": return "#F44336"
default: return "#9E9E9E"
}
}
width: 80
height: 24
Text {
text: {
switch(modelData.status) {
case "active": return "活跃"
case "pending": return "待处理"
case "completed": return "已完成"
case "cancelled": return "已取消"
default: return "未知"
}
}
color: "white"
font.bold: true
font.pixelSize: 12
anchors.centerIn: parent
}
}
// 删除按钮
Button {
text: "删除"
highlighted: true
onClicked: dataModel.remove_item(index)
}
}
}
// 空状态
Label {
visible: listView.count === 0
text: "没有数据"
anchors.centerIn: parent
color: "#999"
}
}
// 添加新项目
RowLayout {
Button {
text: "添加项目"
icon.source: "qrc:/icons/add.svg"
onClicked: {
var newItem = {
"id": dataModel.itemCount + 1,
"name": "新项目" + (dataModel.itemCount + 1),
"value": Math.floor(Math.random() * 1000),
"status": ["active", "pending", "completed", "cancelled"][Math.floor(Math.random() * 4)]
}
dataModel.add_item(newItem)
}
}
Item { Layout.fillWidth: true }
}
}
}
Item { Layout.fillHeight: true }
}
}
}
// 状态监听
Connections {
target: dataModel
function onDataChanged(newData) {
console.log("数据已更新:", JSON.stringify(newData))
}
function onItemsChanged(newItems) {
console.log("项目列表已更新,共", newItems.length, "项")
}
function onFilterChanged(filterText) {
console.log("过滤条件:", filterText)
}
}
}
2.3 模型视图架构:高效数据展示
对于列表、表格等大量数据的展示,模型视图架构是最佳选择。让我们深入理解其工作原理:

QAbstractItemModel实现:
python
# table_model.py - 表格数据模型
from PySide6.QtCore import (
QObject,
QAbstractTableModel,
QModelIndex,
Qt,
Slot,
Signal
)
from PySide6.QtQml import QmlElement
from typing import Any, Dict, List, Optional
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
@QmlElement
class DataFrameModel(QAbstractTableModel):
"""DataFrame表格模型"""
# 信号定义
dataChanged = Signal()
sortRequested = Signal(int, int) # 列, 顺序
def __init__(self, parent=None):
super().__init__(parent)
self._dataframe = pd.DataFrame()
self._column_headers = []
self._sort_column = -1
self._sort_order = Qt.AscendingOrder
# ----- 必须实现的方法 -----
def rowCount(self, parent=QModelIndex()) -> int:
"""返回行数"""
return len(self._dataframe)
def columnCount(self, parent=QModelIndex()) -> int:
"""返回列数"""
return len(self._dataframe.columns)
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
"""返回数据"""
if not index.isValid():
return None
row = index.row()
col = index.column()
if role == Qt.DisplayRole:
# 显示数据
value = self._dataframe.iat[row, col]
# 处理特殊类型
if pd.isna(value):
return ""
elif isinstance(value, (datetime, pd.Timestamp)):
return value.strftime("%Y-%m-%d %H:%M")
elif isinstance(value, float):
return f"{value:.4f}"
else:
return str(value)
elif role == Qt.EditRole:
# 编辑数据
return self._dataframe.iat[row, col]
elif role == Qt.TextAlignmentRole:
# 文本对齐
value = self._dataframe.iat[row, col]
if isinstance(value, (int, float, np.number)):
return Qt.AlignRight | Qt.AlignVCenter
else:
return Qt.AlignLeft | Qt.AlignVCenter
elif role == Qt.BackgroundRole:
# 背景色
value = self._dataframe.iat[row, col]
if isinstance(value, (int, float)) and value < 0:
return "#FFEBEE" # 红色背景表示负数
return None
elif role == Qt.ForegroundRole:
# 文字颜色
value = self._dataframe.iat[row, col]
if isinstance(value, (int, float)) and value < 0:
return "#C62828" # 红色文字表示负数
return None
elif role == Qt.FontRole:
# 字体
if col == 0: # 第一列加粗
from PySide6.QtGui import QFont
font = QFont()
font.setBold(True)
return font
return None
elif role == Qt.ToolTipRole:
# 工具提示
value = self._dataframe.iat[row, col]
col_name = self._dataframe.columns[col]
return f"{col_name}: {value}"
return None
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any:
"""返回表头数据"""
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
# 列标题
if section < len(self._dataframe.columns):
return str(self._dataframe.columns[section])
else:
# 行号
return str(section + 1)
return None
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
"""返回项目标志"""
if not index.isValid():
return Qt.NoItemFlags
flags = super().flags(index)
flags |= Qt.ItemIsEnabled
flags |= Qt.ItemIsSelectable
flags |= Qt.ItemIsEditable
return flags
def setData(self, index: QModelIndex, value: Any, role: int = Qt.EditRole) -> bool:
"""设置数据"""
if not index.isValid() or role != Qt.EditRole:
return False
try:
row = index.row()
col = index.column()
# 转换数据类型
current_type = type(self._dataframe.iat[row, col])
if current_type == int:
value = int(value)
elif current_type == float:
value = float(value)
elif current_type == bool:
value = bool(value)
# 更新数据
self._dataframe.iat[row, col] = value
# 发射数据变化信号
self.dataChanged.emit(index, index, [role])
self.dataChanged.emit()
return True
except Exception as e:
print(f"设置数据失败: {e}")
return False
# ----- 自定义方法 -----
@Slot(str)
def load_csv(self, filepath: str):
"""从CSV文件加载数据"""
try:
self.beginResetModel()
# 读取CSV文件
self._dataframe = pd.read_csv(filepath)
# 清理列名
self._column_headers = [str(col) for col in self._dataframe.columns]
self.endResetModel()
self.dataChanged.emit()
print(f"成功加载CSV文件: {filepath}, 形状: {self._dataframe.shape}")
return True
except Exception as e:
print(f"加载CSV失败: {e}")
return False
@Slot()
def generate_sample_data(self):
"""生成示例数据"""
self.beginResetModel()
# 创建示例数据
dates = pd.date_range(start='2024-01-01', periods=100, freq='D')
data = {
'日期': dates,
'收入': np.random.normal(1000, 200, 100).cumsum(),
'成本': np.random.normal(600, 150, 100).cumsum(),
'利润': np.random.normal(400, 100, 100).cumsum(),
'增长率': np.random.normal(0.05, 0.02, 100),
'完成率': np.random.uniform(0, 1, 100),
'状态': np.random.choice(['进行中', '已完成', '已取消', '待审核'], 100)
}
self._dataframe = pd.DataFrame(data)
self._column_headers = list(self._dataframe.columns)
self.endResetModel()
self.dataChanged.emit()
print(f"生成示例数据,形状: {self._dataframe.shape}")
@Slot(int, int)
def sort(self, column: int, order: int = Qt.AscendingOrder):
"""排序数据"""
if column < 0 or column >= len(self._dataframe.columns):
return
self.beginResetModel()
# 更新排序状态
self._sort_column = column
self._sort_order = order
# 执行排序
col_name = self._dataframe.columns[column]
ascending = order == Qt.AscendingOrder
self._dataframe = self._dataframe.sort_values(
by=col_name,
ascending=ascending,
na_position='last'
)
self.endResetModel()
self.sortRequested.emit(column, order)
self.dataChanged.emit()
@Slot(int, result=list)
def get_column_data(self, column: int) -> list:
"""获取列数据"""
if 0 <= column < len(self._dataframe.columns):
return self._dataframe.iloc[:, column].tolist()
return []
@Slot(result=dict)
def get_statistics(self) -> dict:
"""获取统计信息"""
if self._dataframe.empty:
return {}
stats = {}
# 数值列统计
numeric_cols = self._dataframe.select_dtypes(include=[np.number]).columns
for col in numeric_cols:
col_data = self._dataframe[col]
stats[str(col)] = {
'count': int(len(col_data)),
'mean': float(col_data.mean()),
'std': float(col_data.std()),
'min': float(col_data.min()),
'max': float(col_data.max()),
'sum': float(col_data.sum())
}
# 整体统计
stats['_overall'] = {
'rows': len(self._dataframe),
'columns': len(self._dataframe.columns),
'memory_usage': self._dataframe.memory_usage(deep=True).sum()
}
return stats
@Slot(str, result=list)
def filter_by_status(self, status: str) -> list:
"""按状态过滤"""
if '状态' in self._dataframe.columns:
filtered = self._dataframe[self._dataframe['状态'] == status]
return filtered.to_dict('records')
return []
# ----- 属性 -----
@property
def dataframe(self) -> pd.DataFrame:
"""获取DataFrame"""
return self._dataframe
@property
def shape(self) -> tuple:
"""获取形状"""
return self._dataframe.shape
@property
def columns(self) -> list:
"""获取列名"""
return self._column_headers
表格视图QML界面:
javascript
// TableViewDemo.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import Dashboard.Backend 1.0
ApplicationWindow {
id: window
width: 1400
height: 800
visible: true
title: "表格数据视图演示"
// 数据模型
property var tableModel: Backend.tableModel
// 主布局
ColumnLayout {
anchors.fill: parent
spacing: 0
// 工具栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 60
color: "#f5f5f5"
border.color: "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
// 标题
Label {
text: "数据表格"
font.pixelSize: 20
font.bold: true
Layout.fillWidth: true
}
// 数据操作按钮
Button {
text: "加载示例数据"
icon.source: "qrc:/icons/refresh.svg"
onClicked: tableModel.generate_sample_data()
}
Button {
text: "加载CSV..."
icon.source: "qrc:/icons/folder.svg"
onClicked: fileDialog.open()
}
// 统计信息
Button {
text: "显示统计"
icon.source: "qrc:/icons/stats.svg"
onClicked: statsPopup.open()
}
// 导出按钮
Button {
text: "导出数据"
icon.source: "qrc:/icons/download.svg"
onClicked: exportData()
}
}
}
// 过滤栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "#ffffff"
border.color: "#e0e0e0"
border.width: 1
visible: filterRow.visible
RowLayout {
id: filterRow
anchors.fill: parent
anchors.margins: 10
spacing: 10
visible: tableModel.shape[0] > 0
Label {
text: "快速过滤:"
font.bold: true
}
ComboBox {
id: statusFilter
model: ["全部", "进行中", "已完成", "已取消", "待审核"]
currentIndex: 0
onCurrentTextChanged: {
applyFilters()
}
}
TextField {
id: searchFilter
placeholderText: "搜索..."
Layout.fillWidth: true
onTextChanged: {
searchTimer.restart()
}
Timer {
id: searchTimer
interval: 500
onTriggered: applyFilters()
}
}
Button {
text: "清除过滤"
flat: true
onClicked: {
statusFilter.currentIndex = 0
searchFilter.text = ""
}
}
}
}
// 表格区域
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#ffffff"
ScrollView {
anchors.fill: parent
TableView {
id: tableView
anchors.fill: parent
clip: true
model: tableModel
// 列宽
columnWidthProvider: function(column) {
if (column === 0) return 120
if (column === 1) return 100
if (column === 2) return 100
if (column === 3) return 100
if (column === 4) return 80
if (column === 5) return 80
if (column === 6) return 100
return 120
}
// 行高
rowHeightProvider: function(row) {
return 40
}
// 代理
delegate: DelegateChooser {
DelegateChoice {
column: 0
TableDelegate {
required property var model
required property int row
required property int column
text: display || ""
// 双击编辑
onDoubleClicked: {
editDialog.openForEdit(row, column, text)
}
}
}
DelegateChoice {
column: 4
TableDelegate {
required property var model
required property int row
required property int column
text: {
var value = display || 0
return (value * 100).toFixed(1) + "%"
}
// 进度条背景
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 5
}
height: 4
radius: 2
color: "#e0e0e0"
Rectangle {
width: parent.width * (model.display || 0)
height: parent.height
radius: parent.radius
color: {
var value = model.display || 0
if (value < 0.3) return "#f44336"
if (value < 0.7) return "#ff9800"
return "#4caf50"
}
}
}
}
}
DelegateChoice {
column: 6
TableDelegate {
required property var model
required property int row
required property int column
text: display || ""
// 状态标签
Rectangle {
anchors.centerIn: parent
width: parent.width - 20
height: 24
radius: 12
color: {
var status = display || ""
switch(status) {
case "进行中": return "#2196F3"
case "已完成": return "#4CAF50"
case "已取消": return "#F44336"
case "待审核": return "#FF9800"
default: return "#9E9E9E"
}
}
Text {
text: parent.parent.text
color: "white"
font.bold: true
font.pixelSize: 12
anchors.centerIn: parent
}
}
}
}
// 默认代理
DelegateChoice {
TableDelegate {
required property var model
required property int row
required property int column
text: display || ""
// 数值列右对齐
horizontalAlignment: {
var value = model.display
if (typeof value === 'number' || !isNaN(value)) {
return Text.AlignRight
}
return Text.AlignLeft
}
}
}
}
// 表头
header: HorizontalHeaderView {
syncView: tableView
clip: true
delegate: Rectangle {
id: headerDelegate
implicitWidth: 120
implicitHeight: 40
color: "#f5f5f5"
border.color: "#e0e0e0"
border.width: 1
// 排序指示器
property bool isSorted: tableView.model.sort_column === column
property bool sortAscending: tableView.model.sort_order === Qt.AscendingOrder
RowLayout {
anchors.fill: parent
anchors.margins: 5
spacing: 5
Text {
text: display || ""
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
// 排序图标
Text {
text: {
if (!headerDelegate.isSorted) return "↕"
return headerDelegate.sortAscending ? "↑" : "↓"
}
visible: headerDelegate.isSorted
color: "#2196F3"
font.bold: true
}
}
// 点击排序
MouseArea {
anchors.fill: parent
onClicked: {
var newOrder = headerDelegate.isSorted && !headerDelegate.sortAscending ?
Qt.AscendingOrder : Qt.DescendingOrder
tableView.model.sort(column, newOrder)
}
}
}
}
// 空状态
Label {
visible: tableView.rows === 0
text: "没有数据\n点击'加载示例数据'或'加载CSV...'开始"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.centerIn: parent
color: "#999"
font.pixelSize: 16
}
}
}
}
// 状态栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 30
color: "#f5f5f5"
border.color: "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 5
spacing: 20
Label {
text: "总计: " + (tableModel.shape[0] || 0) + " 行 × " + (tableModel.shape[1] || 0) + " 列"
font.pixelSize: 12
color: "#666"
}
Item { Layout.fillWidth: true }
Label {
text: "排序: " + (tableModel.sort_column >= 0 ?
tableModel.columns[tableModel.sort_column] + " " +
(tableModel.sort_order === Qt.AscendingOrder ? "↑" : "↓") : "无")
font.pixelSize: 12
color: "#666"
}
}
}
}
// 文件对话框
FileDialog {
id: fileDialog
title: "选择CSV文件"
nameFilters: ["CSV文件 (*.csv)", "所有文件 (*)"]
onAccepted: {
var filepath = selectedFile.toString().replace("file:///", "")
tableModel.load_csv(filepath)
}
}
// 统计信息弹窗
Popup {
id: statsPopup
width: 600
height: 400
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
contentItem: Rectangle {
color: "white"
radius: 8
ColumnLayout {
anchors.fill: parent
spacing: 0
// 标题栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "#2196F3"
radius: 8
RowLayout {
anchors.fill: parent
anchors.margins: 15
Label {
text: "数据统计"
color: "white"
font.bold: true
font.pixelSize: 18
Layout.fillWidth: true
}
Button {
text: "关闭"
flat: true
onClicked: statsPopup.close()
}
}
}
// 统计内容
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
width: parent.width
spacing: 20
padding: 20
Repeater {
model: Object.keys(tableModel.get_statistics())
delegate: GroupBox {
title: modelData === "_overall" ? "整体统计" : modelData
Layout.fillWidth: true
GridLayout {
columns: 2
rowSpacing: 5
columnSpacing: 20
width: parent.width
Repeater {
model: Object.entries(tableModel.get_statistics()[modelData])
delegate: RowLayout {
Label {
text: modelData[0] + ":"
font.bold: true
Layout.preferredWidth: 80
}
Label {
text: typeof modelData[1] === 'number' ?
modelData[1].toLocaleString(Qt.locale(), 'f', 2) :
modelData[1]
color: "#2196F3"
}
}
}
}
}
}
}
}
}
}
}
// 编辑对话框
Popup {
id: editDialog
width: 400
height: 200
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
property int editRow: -1
property int editColumn: -1
property string originalValue: ""
function openForEdit(row, column, value) {
editRow = row
editColumn = column
originalValue = value
editField.text = value
editLabel.text = "编辑 [" + tableModel.columns[column] + "]"
open()
}
contentItem: Rectangle {
color: "white"
radius: 8
ColumnLayout {
anchors.fill: parent
spacing: 15
padding: 20
Label {
id: editLabel
text: "编辑"
font.bold: true
font.pixelSize: 18
}
TextField {
id: editField
Layout.fillWidth: true
placeholderText: "输入新值..."
}
RowLayout {
spacing: 10
Button {
text: "取消"
Layout.fillWidth: true
onClicked: editDialog.close()
}
Button {
text: "保存"
highlighted: true
Layout.fillWidth: true
onClicked: {
if (editDialog.editRow >= 0 && editDialog.editColumn >= 0) {
var index = tableModel.index(editDialog.editRow, editDialog.editColumn)
tableModel.setData(index, editField.text)
editDialog.close()
}
}
}
}
}
}
}
// 过滤函数
function applyFilters() {
// 这里可以实现复杂的过滤逻辑
var status = statusFilter.currentText
var search = searchFilter.text.toLowerCase()
if (status !== "全部") {
var filtered = tableModel.filter_by_status(status)
// 更新表格显示过滤后的数据
// 注意:这里需要实现过滤逻辑
}
}
// 导出函数
function exportData() {
// 导出数据逻辑
console.log("导出数据...")
}
// 初始化
Component.onCompleted: {
// 初始加载示例数据
tableModel.generate_sample_data()
}
}
3. 高级集成技术
3.1 异步操作与多线程
在GUI应用中,避免阻塞主线程至关重要。让我们看看如何使用Qt的线程机制来处理耗时操作:
python
# async_worker.py - 异步工作器
import time
import random
from datetime import datetime
from typing import Dict, Any, List
from enum import Enum
from PySide6.QtCore import (
QObject,
QRunnable,
QThreadPool,
Slot,
Signal,
QTimer,
QMutex,
QMutexLocker
)
from PySide6.QtQml import QmlElement
class TaskStatus(Enum):
"""任务状态枚举"""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
class WorkerSignals(QObject):
"""工作器信号"""
started = Signal(str) # 任务ID
progress = Signal(str, float, str) # 任务ID, 进度, 消息
result = Signal(str, object) # 任务ID, 结果
error = Signal(str, str) # 任务ID, 错误信息
finished = Signal(str) # 任务ID
@QmlElement
class AsyncTask(QRunnable):
"""异步任务基类"""
def __init__(self, task_id: str, task_data: Dict[str, Any]):
super().__init__()
self.task_id = task_id
self.task_data = task_data
self.signals = WorkerSignals()
self._is_cancelled = False
self._mutex = QMutex()
def run(self):
"""执行任务"""
try:
self.signals.started.emit(self.task_id)
result = self._execute()
if not self._is_cancelled:
self.signals.result.emit(self.task_id, result)
self.signals.finished.emit(self.task_id)
except Exception as e:
if not self._is_cancelled:
self.signals.error.emit(self.task_id, str(e))
self.signals.finished.emit(self.task_id)
def cancel(self):
"""取消任务"""
with QMutexLocker(self._mutex):
self._is_cancelled = True
def _execute(self):
"""具体执行逻辑,子类实现"""
raise NotImplementedError
class DataProcessingTask(AsyncTask):
"""数据处理任务"""
def _execute(self):
"""模拟数据处理"""
total_steps = random.randint(5, 10)
for i in range(total_steps):
# 检查是否取消
with QMutexLocker(self._mutex):
if self._is_cancelled:
return None
# 模拟处理
time.sleep(0.5)
# 更新进度
progress = (i + 1) / total_steps
message = f"处理步骤 {i + 1}/{total_steps}"
self.signals.progress.emit(self.task_id, progress, message)
# 返回结果
return {
"task_id": self.task_id,
"processed_at": datetime.now().isoformat(),
"data_size": len(str(self.task_data)),
"status": "success"
}
class NetworkRequestTask(AsyncTask):
"""网络请求任务"""
def _execute(self):
"""模拟网络请求"""
url = self.task_data.get("url", "")
method = self.task_data.get("method", "GET")
# 模拟延迟
time.sleep(random.uniform(1.0, 3.0))
# 检查是否取消
with QMutexLocker(self._mutex):
if self._is_cancelled:
return None
# 模拟进度更新
self.signals.progress.emit(self.task_id, 0.3, "正在连接服务器...")
time.sleep(0.5)
self.signals.progress.emit(self.task_id, 0.6, "正在下载数据...")
time.sleep(0.5)
self.signals.progress.emit(self.task_id, 0.9, "正在解析响应...")
time.sleep(0.2)
# 模拟响应
return {
"task_id": self.task_id,
"url": url,
"method": method,
"status_code": 200,
"data": {
"timestamp": datetime.now().isoformat(),
"content": f"来自 {url} 的模拟响应数据"
}
}
@QmlElement
class TaskManager(QObject):
"""任务管理器"""
# 信号
taskAdded = Signal(dict)
taskUpdated = Signal(dict)
taskRemoved = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.thread_pool = QThreadPool.globalInstance()
self.thread_pool.setMaxThreadCount(4) # 最大4个线程
self.tasks: Dict[str, Dict[str, Any]] = {}
self.active_tasks: Dict[str, AsyncTask] = {}
# 清理已完成任务的定时器
self.cleanup_timer = QTimer()
self.cleanup_timer.setInterval(30000) # 30秒清理一次
self.cleanup_timer.timeout.connect(self._cleanup_completed_tasks)
self.cleanup_timer.start()
@Slot(str, dict, result=str)
def start_data_processing(self, name: str, data: dict) -> str:
"""启动数据处理任务"""
task_id = f"data_{datetime.now().timestamp()}"
task = DataProcessingTask(task_id, data)
self._setup_task_signals(task, name)
self.tasks[task_id] = {
"id": task_id,
"name": name,
"type": "data_processing",
"status": TaskStatus.PENDING.value,
"progress": 0.0,
"created_at": datetime.now().isoformat()
}
self.thread_pool.start(task)
self.active_tasks[task_id] = task
self.taskAdded.emit(self.tasks[task_id])
return task_id
@Slot(str, dict, result=str)
def start_network_request(self, url: str, params: dict) -> str:
"""启动网络请求任务"""
task_id = f"net_{datetime.now().timestamp()}"
task_data = {"url": url, "params": params}
task = NetworkRequestTask(task_id, task_data)
self._setup_task_signals(task, f"请求: {url}")
self.tasks[task_id] = {
"id": task_id,
"name": f"请求: {url}",
"type": "network_request",
"status": TaskStatus.PENDING.value,
"progress": 0.0,
"created_at": datetime.now().isoformat()
}
self.thread_pool.start(task)
self.active_tasks[task_id] = task
self.taskAdded.emit(self.tasks[task_id])
return task_id
@Slot(str)
def cancel_task(self, task_id: str):
"""取消任务"""
if task_id in self.active_tasks:
task = self.active_tasks[task_id]
task.cancel()
if task_id in self.tasks:
self.tasks[task_id]["status"] = TaskStatus.CANCELLED.value
self.taskUpdated.emit(self.tasks[task_id])
@Slot(str, result=dict)
def get_task_info(self, task_id: str) -> dict:
"""获取任务信息"""
return self.tasks.get(task_id, {})
@Slot(result=list)
def get_all_tasks(self) -> list:
"""获取所有任务"""
return list(self.tasks.values())
@Slot(result=list)
def get_active_tasks(self) -> list:
"""获取活动任务"""
return [
task for task in self.tasks.values()
if task["status"] in [TaskStatus.PENDING.value, TaskStatus.RUNNING.value]
]
def _setup_task_signals(self, task: AsyncTask, task_name: str):
"""设置任务信号连接"""
@Slot(str)
def on_started(tid: str):
self.tasks[tid]["status"] = TaskStatus.RUNNING.value
self.tasks[tid]["started_at"] = datetime.now().isoformat()
self.taskUpdated.emit(self.tasks[tid])
@Slot(str, float, str)
def on_progress(tid: str, progress: float, message: str):
if tid in self.tasks:
self.tasks[tid]["progress"] = progress
self.tasks[tid]["message"] = message
self.taskUpdated.emit(self.tasks[tid])
@Slot(str, object)
def on_result(tid: str, result: object):
if tid in self.tasks:
self.tasks[tid]["status"] = TaskStatus.COMPLETED.value
self.tasks[tid]["result"] = result
self.tasks[tid]["completed_at"] = datetime.now().isoformat()
self.taskUpdated.emit(self.tasks[tid])
# 从活动任务中移除
if tid in self.active_tasks:
del self.active_tasks[tid]
@Slot(str, str)
def on_error(tid: str, error: str):
if tid in self.tasks:
self.tasks[tid]["status"] = TaskStatus.FAILED.value
self.tasks[tid]["error"] = error
self.tasks[tid]["completed_at"] = datetime.now().isoformat()
self.taskUpdated.emit(self.tasks[tid])
if tid in self.active_tasks:
del self.active_tasks[tid]
@Slot(str)
def on_finished(tid: str):
# 可以在任务完成时执行一些清理操作
pass
# 连接信号
task.signals.started.connect(on_started)
task.signals.progress.connect(on_progress)
task.signals.result.connect(on_result)
task.signals.error.connect(on_error)
task.signals.finished.connect(on_finished)
def _cleanup_completed_tasks(self):
"""清理已完成的任务"""
to_remove = []
for task_id, task in self.tasks.items():
if task["status"] in [TaskStatus.COMPLETED.value, TaskStatus.FAILED.value, TaskStatus.CANCELLED.value]:
# 检查是否已完成超过5分钟
completed_at = task.get("completed_at")
if completed_at:
try:
completed_time = datetime.fromisoformat(completed_at)
if (datetime.now() - completed_time).total_seconds() > 300: # 5分钟
to_remove.append(task_id)
except ValueError:
pass
for task_id in to_remove:
if task_id in self.tasks:
del self.tasks[task_id]
self.taskRemoved.emit(task_id)
异步任务执行流程:

异步任务QML界面:
javascript
// AsyncTaskDemo.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Dashboard.Backend 1.0
ApplicationWindow {
id: window
width: 1200
height: 800
visible: true
title: "异步任务管理"
// 任务管理器
property var taskManager: Backend.taskManager
// 主布局
ColumnLayout {
anchors.fill: parent
spacing: 0
// 工具栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 60
color: "#f5f5f5"
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
// 标题
Label {
text: "异步任务管理器"
font.pixelSize: 20
font.bold: true
Layout.fillWidth: true
}
// 控制按钮
Button {
text: "启动数据处理"
onClicked: {
var taskId = taskManager.start_data_processing(
"数据处理任务",
{"size": 1000, "complexity": "medium"}
)
console.log("启动数据处理任务:", taskId)
}
}
Button {
text: "启动网络请求"
onClicked: {
var taskId = taskManager.start_network_request(
"https://api.example.com/data",
{"timeout": 5000}
)
console.log("启动网络请求任务:", taskId)
}
}
Button {
text: "批量启动"
onClicked: startBatchTasks()
}
}
}
// 内容区域
SplitView {
Layout.fillWidth: true
Layout.fillHeight: true
orientation: Qt.Horizontal
// 左侧:任务控制面板
ScrollView {
SplitView.preferredWidth: 400
ColumnLayout {
width: parent.width
spacing: 20
padding: 20
// 任务统计
GroupBox {
title: "任务统计"
Layout.fillWidth: true
GridLayout {
columns: 2
rowSpacing: 10
columnSpacing: 20
width: parent.width
Label { text: "总任务数:"; font.bold: true }
Label {
text: taskRepeater.count
color: "#2196F3"
}
Label { text: "运行中:"; font.bold: true }
Label {
text: {
var running = 0
for (var i = 0; i < taskRepeater.count; i++) {
var task = taskRepeater.itemAt(i)
if (task && task.status === "running") {
running++
}
}
return running
}
color: "#4CAF50"
}
Label { text: "已完成:"; font.bold: true }
Label {
text: {
var completed = 0
for (var i = 0; i < taskRepeater.count; i++) {
var task = taskRepeater.itemAt(i)
if (task && task.status === "completed") {
completed++
}
}
return completed
}
color: "#9C27B0"
}
Label { text: "失败/取消:"; font.bold: true }
Label {
text: {
var failed = 0
for (var i = 0; i < taskRepeater.count; i++) {
var task = taskRepeater.itemAt(i)
if (task && (task.status === "failed" || task.status === "cancelled")) {
failed++
}
}
return failed
}
color: "#F44336"
}
}
}
// 控制面板
GroupBox {
title: "任务控制"
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 10
// 线程池设置
RowLayout {
Label {
text: "最大线程数:"
Layout.preferredWidth: 100
}
Slider {
id: threadSlider
from: 1
to: 8
value: 4
stepSize: 1
Layout.fillWidth: true
onValueChanged: {
// 这里应该设置线程池大小
// 注意:实际应用中需要更复杂的线程池管理
}
}
Label {
text: Math.round(threadSlider.value)
font.bold: true
}
}
// 批量操作
RowLayout {
Button {
text: "全部暂停"
onClicked: pauseAllTasks()
}
Button {
text: "全部取消"
onClicked: cancelAllTasks()
}
Button {
text: "清除完成"
onClicked: clearCompletedTasks()
}
}
}
}
// 系统信息
GroupBox {
title: "系统信息"
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 5
Label {
text: "线程池状态:"
font.bold: true
}
Label {
text: "活动线程: " + taskManager.threadPool.activeThreadCount
color: "#666"
}
Label {
text: "排队任务: " + taskManager.threadPool.queueSize
color: "#666"
}
Label {
text: "内存使用: " + (Math.random() * 100).toFixed(1) + " MB"
color: "#666"
}
}
}
Item { Layout.fillHeight: true }
}
}
// 右侧:任务列表
ScrollView {
SplitView.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 10
padding: 20
// 搜索栏
RowLayout {
TextField {
id: searchField
placeholderText: "搜索任务..."
Layout.fillWidth: true
}
ComboBox {
id: filterCombo
model: ["全部", "运行中", "已完成", "失败", "已取消"]
currentIndex: 0
}
}
// 任务列表
Repeater {
id: taskRepeater
model: taskManager.allTasks
delegate: TaskItem {
taskId: modelData.id
taskName: modelData.name
taskType: modelData.type
status: modelData.status
progress: modelData.progress || 0
message: modelData.message || ""
createdTime: modelData.created_at
Layout.fillWidth: true
Layout.preferredHeight: 100
visible: {
if (!searchField.text) {
if (filterCombo.currentIndex === 0) return true
if (filterCombo.currentIndex === 1) return status === "running"
if (filterCombo.currentIndex === 2) return status === "completed"
if (filterCombo.currentIndex === 3) return status === "failed"
if (filterCombo.currentIndex === 4) return status === "cancelled"
} else {
var searchText = searchField.text.toLowerCase()
return taskName.toLowerCase().includes(searchText) ||
taskId.toLowerCase().includes(searchText)
}
return true
}
onCancelClicked: taskManager.cancelTask(taskId)
}
}
// 空状态
Label {
visible: taskRepeater.count === 0
text: "暂无任务\n点击上方按钮开始新任务"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
color: "#999"
font.pixelSize: 16
}
}
}
}
}
// 任务项组件
component TaskItem: Rectangle {
id: taskRoot
// 属性
property string taskId: ""
property string taskName: ""
property string taskType: ""
property string status: "pending"
property real progress: 0
property string message: ""
property string createdTime: ""
signal cancelClicked(string taskId)
radius: 8
border.color: "#e0e0e0"
border.width: 1
// 根据状态设置颜色
property color statusColor: {
switch(status) {
case "running": return "#2196F3"
case "completed": return "#4CAF50"
case "failed": return "#F44336"
case "cancelled": return "#FF9800"
default: return "#9E9E9E"
}
}
// 状态文本
property string statusText: {
switch(status) {
case "pending": return "等待中"
case "running": return "运行中"
case "completed": return "已完成"
case "failed": return "失败"
case "cancelled": return "已取消"
default: return "未知"
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 8
// 标题行
RowLayout {
Layout.fillWidth: true
// 任务图标
Rectangle {
width: 36
height: 36
radius: 18
color: Qt.lighter(statusColor, 1.8)
Text {
text: {
switch(taskType) {
case "data_processing": return "🔢"
case "network_request": return "🌐"
default: return "📄"
}
}
anchors.centerIn: parent
font.pixelSize: 16
}
}
// 任务信息
ColumnLayout {
spacing: 2
Layout.fillWidth: true
Text {
text: taskName
font.bold: true
font.pixelSize: 16
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: "ID: " + taskId.substring(0, 8) + "..."
color: "#666"
font.pixelSize: 12
}
}
// 状态标签
Rectangle {
radius: 10
color: statusColor
width: 80
height: 24
Text {
text: statusText
color: "white"
font.bold: true
font.pixelSize: 12
anchors.centerIn: parent
}
}
// 取消按钮
Button {
text: "取消"
visible: status === "running"
onClicked: taskRoot.cancelClicked(taskId)
}
}
// 进度条
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 6
radius: 3
color: "#e0e0e0"
visible: status === "running"
Rectangle {
width: parent.width * progress
height: parent.height
radius: parent.radius
color: statusColor
// 进度动画
Behavior on width {
NumberAnimation { duration: 300 }
}
}
}
// 进度文本
RowLayout {
Layout.fillWidth: true
visible: status === "running"
Text {
text: "进度: " + (progress * 100).toFixed(1) + "%"
color: "#666"
font.pixelSize: 12
}
Item { Layout.fillWidth: true }
Text {
text: message
color: "#666"
font.pixelSize: 12
}
}
// 时间信息
RowLayout {
Layout.fillWidth: true
Text {
text: "创建: " + formatTime(createdTime)
color: "#999"
font.pixelSize: 11
}
Item { Layout.fillWidth: true }
// 运行时间
Text {
text: {
if (status === "running") {
var created = new Date(createdTime)
var now = new Date()
var diff = Math.floor((now - created) / 1000)
return "运行: " + formatDuration(diff)
}
return ""
}
color: "#999"
font.pixelSize: 11
}
}
}
// 悬停效果
HoverHandler {
id: hoverHandler
}
states: State {
name: "hovered"
when: hoverHandler.hovered
PropertyChanges {
target: taskRoot
scale: 1.02
}
}
transitions: Transition {
NumberAnimation {
properties: "scale"
duration: 200
}
}
}
// 批量启动任务
function startBatchTasks() {
for (var i = 0; i < 5; i++) {
if (Math.random() > 0.5) {
taskManager.start_data_processing(
"批量处理任务 " + (i + 1),
{"index": i, "size": Math.random() * 1000}
)
} else {
taskManager.start_network_request(
"https://api.example.com/data/" + i,
{"index": i}
)
}
}
}
// 暂停所有任务
function pauseAllTasks() {
// 注意:实际暂停逻辑需要更复杂的实现
console.log("暂停所有任务")
}
// 取消所有任务
function cancelAllTasks() {
for (var i = 0; i < taskRepeater.count; i++) {
var task = taskRepeater.itemAt(i)
if (task && task.status === "running") {
taskManager.cancelTask(task.taskId)
}
}
}
// 清除已完成任务
function clearCompletedTasks() {
// 这里应该调用任务管理器的清理方法
console.log("清除已完成任务")
}
// 格式化时间
function formatTime(timeString) {
if (!timeString) return ""
var date = new Date(timeString)
return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})
}
// 格式化持续时间
function formatDuration(seconds) {
var hours = Math.floor(seconds / 3600)
var minutes = Math.floor((seconds % 3600) / 60)
var secs = seconds % 60
if (hours > 0) {
return hours + "h " + minutes + "m"
} else if (minutes > 0) {
return minutes + "m " + secs + "s"
} else {
return secs + "s"
}
}
// 监听任务变化
Connections {
target: taskManager
function onTaskAdded(task) {
console.log("任务已添加:", task.id, task.name)
}
function onTaskUpdated(task) {
console.log("任务已更新:", task.id, task.status, task.progress)
}
function onTaskRemoved(taskId) {
console.log("任务已移除:", taskId)
}
}
}
3.2 错误处理与日志系统
健壮的应用程序需要完善的错误处理和日志系统:
python
# error_handler.py - 错误处理与日志
import sys
import traceback
import logging
from datetime import datetime
from typing import Dict, Any, Optional, List
from enum import Enum
from PySide6.QtCore import QObject, Slot, Signal, Property, QTimer
from PySide6.QtQml import QmlElement
class ErrorLevel(Enum):
"""错误级别"""
DEBUG = 0
INFO = 1
WARNING = 2
ERROR = 3
CRITICAL = 4
class LogEntry:
"""日志条目"""
def __init__(self, level: ErrorLevel, message: str, details: str = "",
timestamp: datetime = None, source: str = ""):
self.level = level
self.message = message
self.details = details
self.timestamp = timestamp or datetime.now()
self.source = source
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"level": self.level.value,
"level_name": self.level.name,
"message": self.message,
"details": self.details,
"timestamp": self.timestamp.isoformat(),
"source": self.source
}
@QmlElement
class ErrorHandler(QObject):
"""错误处理器"""
# 信号
errorOccurred = Signal(dict) # 错误信息
warningOccurred = Signal(dict) # 警告信息
infoMessage = Signal(dict) # 信息消息
logUpdated = Signal(list) # 日志更新
def __init__(self, parent=None):
super().__init__(parent)
self._logs: List[LogEntry] = []
self._max_logs = 1000
# 设置Python日志
self._setup_logging()
# 自动清理日志的定时器
self.cleanup_timer = QTimer()
self.cleanup_timer.setInterval(60000) # 1分钟清理一次
self.cleanup_timer.timeout.connect(self._cleanup_old_logs)
self.cleanup_timer.start()
def _setup_logging(self):
"""设置Python日志系统"""
# 创建格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 创建处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
# 获取根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(console_handler)
# 连接到我们的处理器
logging.getLogger().addHandler(self)
@Slot(str, str, result=bool)
def handle_error(self, message: str, details: str = "") -> bool:
"""处理错误"""
try:
entry = LogEntry(
level=ErrorLevel.ERROR,
message=message,
details=details,
source="QML"
)
self._add_log(entry)
self.errorOccurred.emit(entry.to_dict())
# 记录到文件
self._write_to_file(entry)
return True
except Exception as e:
print(f"处理错误时发生异常: {e}")
return False
@Slot(str, str)
def handle_warning(self, message: str, details: str = ""):
"""处理警告"""
entry = LogEntry(
level=ErrorLevel.WARNING,
message=message,
details=details,
source="QML"
)
self._add_log(entry)
self.warningOccurred.emit(entry.to_dict())
self._write_to_file(entry)
@Slot(str, str)
def log_info(self, message: str, details: str = ""):
"""记录信息"""
entry = LogEntry(
level=ErrorLevel.INFO,
message=message,
details=details,
source="QML"
)
self._add_log(entry)
self.infoMessage.emit(entry.to_dict())
self._write_to_file(entry)
@Slot(str, str, str)
def log_debug(self, message: str, details: str = "", source: str = ""):
"""记录调试信息"""
entry = LogEntry(
level=ErrorLevel.DEBUG,
message=message,
details=details,
source=source or "QML"
)
self._add_log(entry)
self._write_to_file(entry)
@Slot(Exception, str)
def handle_exception(self, exception: Exception, context: str = ""):
"""处理异常"""
try:
# 获取异常信息
exc_type = type(exception).__name__
exc_message = str(exception)
exc_traceback = traceback.format_exc()
# 创建详细消息
details = f"类型: {exc_type}\n消息: {exc_message}\n上下文: {context}\n\n堆栈跟踪:\n{exc_traceback}"
entry = LogEntry(
level=ErrorLevel.ERROR,
message=f"未处理的异常: {exc_message}",
details=details,
source="Python"
)
self._add_log(entry)
self.errorOccurred.emit(entry.to_dict())
self._write_to_file(entry)
except Exception as e:
print(f"处理异常时发生错误: {e}")
@Slot(result=list)
def get_logs(self, level: int = 0, limit: int = 100) -> List[Dict[str, Any]]:
"""获取日志"""
filtered = [log for log in self._logs if log.level.value >= level]
limited = filtered[-limit:] if limit > 0 else filtered
return [log.to_dict() for log in limited]
@Slot(int, result=list)
def get_logs_by_level(self, level: int) -> List[Dict[str, Any]]:
"""按级别获取日志"""
filtered = [log for log in self._logs if log.level.value == level]
return [log.to_dict() for log in filtered]
@Slot()
def clear_logs(self):
"""清空日志"""
self._logs.clear()
self.logUpdated.emit([])
@Slot(str, result=str)
def export_logs(self, filepath: str) -> str:
"""导出日志到文件"""
try:
with open(filepath, 'w', encoding='utf-8') as f:
for log in self._logs:
f.write(f"[{log.timestamp.isoformat()}] {log.level.name}: {log.message}\n")
if log.details:
f.write(f"详细信息: {log.details}\n")
f.write("-" * 50 + "\n")
return "success"
except Exception as e:
return f"导出失败: {str(e)}"
def _add_log(self, entry: LogEntry):
"""添加日志条目"""
self._logs.append(entry)
# 限制日志数量
if len(self._logs) > self._max_logs:
self._logs = self._logs[-self._max_logs:]
# 发送更新信号
self.logUpdated.emit(self.get_logs(limit=50))
def _write_to_file(self, entry: LogEntry):
"""写入日志文件"""
try:
with open("application.log", "a", encoding="utf-8") as f:
f.write(f"[{entry.timestamp.isoformat()}] {entry.level.name}: {entry.message}\n")
if entry.details:
f.write(f"详细信息: {entry.details}\n")
except Exception as e:
print(f"写入日志文件失败: {e}")
def _cleanup_old_logs(self):
"""清理旧日志"""
# 保留最近24小时的日志
cutoff_time = datetime.now().timestamp() - 24 * 3600
self._logs = [
log for log in self._logs
if log.timestamp.timestamp() > cutoff_time
]
# Python日志处理器接口
def emit(self, record):
"""处理Python日志记录"""
level_map = {
logging.DEBUG: ErrorLevel.DEBUG,
logging.INFO: ErrorLevel.INFO,
logging.WARNING: ErrorLevel.WARNING,
logging.ERROR: ErrorLevel.ERROR,
logging.CRITICAL: ErrorLevel.CRITICAL
}
entry = LogEntry(
level=level_map.get(record.levelno, ErrorLevel.INFO),
message=record.getMessage(),
details="",
timestamp=datetime.fromtimestamp(record.created),
source=record.name
)
self._add_log(entry)
错误处理QML界面:
javascript
// ErrorHandlerDemo.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Dashboard.Backend 1.0
ApplicationWindow {
id: window
width: 1200
height: 800
visible: true
title: "错误处理与日志系统"
// 错误处理器
property var errorHandler: Backend.errorHandler
// 错误级别颜色
property var levelColors: {
"DEBUG": "#9E9E9E",
"INFO": "#2196F3",
"WARNING": "#FF9800",
"ERROR": "#F44336",
"CRITICAL": "#D32F2F"
}
// 主布局
ColumnLayout {
anchors.fill: parent
spacing: 0
// 工具栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 60
color: "#f5f5f5"
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
// 标题
Label {
text: "错误处理与日志系统"
font.pixelSize: 20
font.bold: true
Layout.fillWidth: true
}
// 控制按钮
Button {
text: "测试错误"
onClicked: testError()
}
Button {
text: "测试警告"
onClicked: testWarning()
}
Button {
text: "测试异常"
onClicked: testException()
}
Button {
text: "清空日志"
onClicked: errorHandler.clear_logs()
}
Button {
text: "导出日志"
onClicked: exportLogs()
}
}
}
// 过滤栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "#ffffff"
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Label { text: "过滤级别:" }
ComboBox {
id: levelFilter
model: ["全部", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
currentIndex: 2
onCurrentTextChanged: refreshLogs()
}
TextField {
id: searchFilter
placeholderText: "搜索日志..."
Layout.fillWidth: true
onTextChanged: searchTimer.restart()
Timer {
id: searchTimer
interval: 500
onTriggered: refreshLogs()
}
}
Button {
text: "自动滚动"
checkable: true
checked: true
}
}
}
// 日志列表
ScrollView {
id: logScrollView
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: logListView
model: ListModel {}
spacing: 1
delegate: Rectangle {
width: ListView.view.width
height: logContent.height + 20
color: index % 2 ? "#fafafa" : "#ffffff"
// 左侧颜色条
Rectangle {
width: 5
height: parent.height
color: levelColors[level_name] || "#9E9E9E"
}
// 日志内容
ColumnLayout {
id: logContent
anchors {
left: parent.left
right: parent.right
top: parent.top
margins: 10
}
spacing: 5
// 标题行
RowLayout {
Layout.fillWidth: true
// 级别标签
Rectangle {
radius: 3
color: levelColors[level_name] || "#9E9E9E"
width: 70
height: 20
Text {
text: level_name
color: "white"
font.bold: true
font.pixelSize: 11
anchors.centerIn: parent
}
}
// 时间
Text {
text: formatTime(timestamp)
color: "#666"
font.pixelSize: 12
}
Item { Layout.fillWidth: true }
// 来源
Text {
text: source || "未知"
color: "#999"
font.pixelSize: 11
font.italic: true
}
}
// 消息
Text {
text: message
font.pixelSize: 14
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
// 详细信息
Text {
text: details || ""
visible: details && details.length > 0
color: "#666"
font.pixelSize: 12
wrapMode: Text.WordWrap
Layout.fillWidth: true
// 更多/收起按钮
MouseArea {
anchors.fill: parent
onClicked: {
if (parent.maximumLineCount === 1) {
parent.maximumLineCount = 1000
} else {
parent.maximumLineCount = 1
}
}
}
}
}
// 分隔线
Rectangle {
width: parent.width
height: 1
color: "#e0e0e0"
anchors.bottom: parent.bottom
}
}
// 空状态
Label {
visible: logListView.count === 0
text: "暂无日志"
anchors.centerIn: parent
color: "#999"
font.pixelSize: 16
}
}
}
// 状态栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 30
color: "#f5f5f5"
RowLayout {
anchors.fill: parent
anchors.margins: 5
Label {
text: "日志数量: " + logListView.count
color: "#666"
font.pixelSize: 12
}
Item { Layout.fillWidth: true }
Label {
text: "最后更新: " + formatTime(new Date())
color: "#666"
font.pixelSize: 12
}
}
}
}
// 测试函数
function testError() {
var messages = [
"数据库连接失败",
"网络请求超时",
"文件不存在",
"权限不足",
"数据格式错误"
]
var details = [
"无法连接到数据库服务器 127.0.0.1:5432",
"请求 https://api.example.com/data 在5000ms后超时",
"文件 /path/to/data.json 不存在",
"用户没有写入 /var/log 目录的权限",
"JSON解析失败: Unexpected token ' in JSON at position 42"
]
var index = Math.floor(Math.random() * messages.length)
errorHandler.handle_error(messages[index], details[index])
}
function testWarning() {
var messages = [
"内存使用过高",
"磁盘空间不足",
"网络延迟较高",
"缓存即将过期",
"API调用频率接近限制"
]
var details = [
"当前内存使用率: 85%,建议优化",
"磁盘剩余空间: 2.3GB,建议清理",
"平均网络延迟: 250ms,可能影响性能",
"缓存将在5分钟后过期,建议刷新",
"API调用: 95/100,接近限制"
]
var index = Math.floor(Math.random() * messages.length)
errorHandler.handle_warning(messages[index], details[index])
}
function testException() {
try {
// 故意抛出异常
throw new Error("测试异常: 除零错误")
} catch (e) {
errorHandler.handle_exception(e, "测试异常处理")
}
}
// 导出日志
function exportLogs() {
var dialog = Qt.createComponent("FileDialog.qml").createObject(window)
dialog.selectExisting = false
dialog.nameFilters = ["日志文件 (*.log)", "文本文件 (*.txt)", "所有文件 (*)"]
dialog.onAccepted.connect(function() {
var result = errorHandler.export_logs(dialog.fileUrl.toString().replace("file:///", ""))
if (result === "success") {
showMessage("日志导出成功")
} else {
showMessage("导出失败: " + result)
}
})
dialog.open()
}
// 刷新日志
function refreshLogs() {
logListView.model.clear()
var logs = []
if (levelFilter.currentText === "全部") {
logs = errorHandler.get_logs(0, 100)
} else {
var levelMap = {
"DEBUG": 0, "INFO": 1, "WARNING": 2,
"ERROR": 3, "CRITICAL": 4
}
var level = levelMap[levelFilter.currentText] || 1
logs = errorHandler.get_logs(level, 100)
}
// 应用搜索过滤
var searchText = searchFilter.text.toLowerCase()
var filteredLogs = logs.filter(function(log) {
if (!searchText) return true
return log.message.toLowerCase().includes(searchText) ||
log.details.toLowerCase().includes(searchText) ||
log.source.toLowerCase().includes(searchText)
})
// 添加到列表
for (var i = 0; i < filteredLogs.length; i++) {
logListView.model.append(filteredLogs[i])
}
// 自动滚动到底部
if (logScrollView.ScrollBar.vertical.position > 0.9) {
logListView.positionViewAtEnd()
}
}
// 格式化时间
function formatTime(dateString) {
var date = new Date(dateString)
return date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
// 显示消息
function showMessage(text) {
var popup = Qt.createComponent("Popup.qml").createObject(window)
popup.text = text
popup.open()
}
// 监听日志更新
Connections {
target: errorHandler
function onLogUpdated(logs) {
refreshLogs()
}
function onErrorOccurred(error) {
console.log("错误发生:", error.message)
}
function onWarningOccurred(warning) {
console.log("警告发生:", warning.message)
}
}
// 初始化
Component.onCompleted: {
// 添加一些测试日志
errorHandler.log_info("应用程序启动", "初始化完成")
errorHandler.log_debug("系统信息", "内存: 8GB, 磁盘: 256GB", "System")
// 初始加载日志
refreshLogs()
}
}
4. 实战项目:企业数据管理系统
现在让我们将所有技术整合到一个完整的实战项目中:
4.1 系统架构

4.2 核心模块实现
python
# enterprise_system.py - 企业数据管理系统核心
import sys
import os
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass, field, asdict
from enum import Enum
import json
import csv
import sqlite3
from pathlib import Path
from PySide6.QtCore import (
QObject,
Slot,
Property,
Signal,
QUrl,
QTimer,
QThreadPool,
QSettings,
QStandardPaths
)
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, QmlElement
class DataSourceType(Enum):
"""数据源类型"""
DATABASE = "database"
CSV_FILE = "csv_file"
EXCEL_FILE = "excel_file"
JSON_FILE = "json_file"
API = "api"
CUSTOM = "custom"
class DataPermission(Enum):
"""数据权限"""
READ = "read"
WRITE = "write"
DELETE = "delete"
ADMIN = "admin"
@dataclass
class DataSource:
"""数据源定义"""
id: str
name: str
type: DataSourceType
connection_string: str
description: str = ""
enabled: bool = True
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
config: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
data['type'] = self.type.value
data['created_at'] = self.created_at.isoformat()
data['updated_at'] = self.updated_at.isoformat()
return data
@dataclass
class DataSet:
"""数据集定义"""
id: str
name: str
source_id: str
query: str
description: str = ""
columns: List[Dict[str, Any]] = field(default_factory=list)
row_count: int = 0
last_refresh: Optional[datetime] = None
refresh_interval: int = 0 # 秒
enabled: bool = True
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
if self.last_refresh:
data['last_refresh'] = self.last_refresh.isoformat()
return data
@QmlElement
class DataSourceManager(QObject):
"""数据源管理器"""
# 信号
sourceAdded = Signal(dict)
sourceUpdated = Signal(dict)
sourceRemoved = Signal(str)
sourceConnected = Signal(str, bool, str) # id, success, message
dataRefreshed = Signal(str, list) # dataset_id, data
def __init__(self, parent=None):
super().__init__(parent)
self.sources: Dict[str, DataSource] = {}
self.datasets: Dict[str, DataSet] = {}
self.connections: Dict[str, Any] = {} # 数据库连接等
# 加载配置
self._load_config()
# 自动刷新定时器
self.refresh_timer = QTimer()
self.refresh_timer.setInterval(60000) # 1分钟
self.refresh_timer.timeout.connect(self._auto_refresh)
self.refresh_timer.start()
@Slot(str, str, str, str, result=str)
def add_source(self, name: str, type_str: str, connection: str, description: str = "") -> str:
"""添加数据源"""
try:
source_type = DataSourceType(type_str)
source_id = f"src_{datetime.now().timestamp()}"
source = DataSource(
id=source_id,
name=name,
type=source_type,
connection_string=connection,
description=description
)
self.sources[source_id] = source
self.sourceAdded.emit(source.to_dict())
self._save_config()
return source_id
except Exception as e:
print(f"添加数据源失败: {e}")
return ""
@Slot(str, result=bool)
def connect_source(self, source_id: str) -> bool:
"""连接数据源"""
if source_id not in self.sources:
self.sourceConnected.emit(source_id, False, "数据源不存在")
return False
source = self.sources[source_id]
try:
if source.type == DataSourceType.DATABASE:
# 连接数据库
conn = sqlite3.connect(source.connection_string)
self.connections[source_id] = conn
self.sourceConnected.emit(source_id, True, "连接成功")
return True
elif source.type == DataSourceType.CSV_FILE:
# 验证CSV文件
if not os.path.exists(source.connection_string):
self.sourceConnected.emit(source_id, False, "文件不存在")
return False
self.sourceConnected.emit(source_id, True, "文件验证成功")
return True
else:
# 其他类型
self.sourceConnected.emit(source_id, True, "连接成功")
return True
except Exception as e:
error_msg = f"连接失败: {str(e)}"
self.sourceConnected.emit(source_id, False, error_msg)
return False
@Slot(str, str, str, str, result=str)
def add_dataset(self, name: str, source_id: str, query: str, description: str = "") -> str:
"""添加数据集"""
if source_id not in self.sources:
return ""
dataset_id = f"ds_{datetime.now().timestamp()}"
dataset = DataSet(
id=dataset_id,
name=name,
source_id=source_id,
query=query,
description=description
)
self.datasets[dataset_id] = dataset
self._save_config()
# 初始加载数据
self.refresh_dataset(dataset_id)
return dataset_id
@Slot(str, result=list)
def refresh_dataset(self, dataset_id: str) -> list:
"""刷新数据集"""
if dataset_id not in self.datasets:
return []
dataset = self.datasets[dataset_id]
source = self.sources.get(dataset.source_id)
if not source:
return []
try:
data = []
if source.type == DataSourceType.DATABASE:
# 从数据库查询
if dataset.source_id in self.connections:
conn = self.connections[dataset.source_id]
cursor = conn.cursor()
cursor.execute(dataset.query)
# 获取列名
columns = [desc[0] for desc in cursor.description]
dataset.columns = [{"name": col, "type": "string"} for col in columns]
# 获取数据
rows = cursor.fetchall()
for row in rows:
item = {}
for i, col in enumerate(columns):
item[col] = row[i]
data.append(item)
dataset.row_count = len(data)
elif source.type == DataSourceType.CSV_FILE:
# 从CSV文件读取
with open(source.connection_string, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
# 获取列名
if reader.fieldnames:
dataset.columns = [{"name": col, "type": "string"} for col in reader.fieldnames]
# 读取数据
for row in reader:
data.append(row)
dataset.row_count = len(data)
# 更新数据集信息
dataset.last_refresh = datetime.now()
# 发射信号
self.dataRefreshed.emit(dataset_id, data)
return data
except Exception as e:
print(f"刷新数据集失败: {e}")
return []
@Slot(str, result=list)
def get_dataset_data(self, dataset_id: str) -> list:
"""获取数据集数据"""
# 这里应该实现数据缓存逻辑
return []
@Slot(str, result=list)
def query(self, query_str: str) -> list:
"""执行查询"""
# 这里可以实现一个简单的查询引擎
return []
def _auto_refresh(self):
"""自动刷新数据集"""
now = datetime.now()
for dataset in self.datasets.values():
if dataset.enabled and dataset.refresh_interval > 0:
if not dataset.last_refresh or \
(now - dataset.last_refresh).total_seconds() >= dataset.refresh_interval:
self.refresh_dataset(dataset.id)
def _load_config(self):
"""加载配置"""
config_path = self._get_config_path()
if config_path.exists():
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
# 加载数据源
for source_data in config.get('sources', []):
source = DataSource(
id=source_data['id'],
name=source_data['name'],
type=DataSourceType(source_data['type']),
connection_string=source_data['connection_string'],
description=source_data.get('description', ''),
enabled=source_data.get('enabled', True)
)
self.sources[source.id] = source
# 加载数据集
for dataset_data in config.get('datasets', []):
dataset = DataSet(
id=dataset_data['id'],
name=dataset_data['name'],
source_id=dataset_data['source_id'],
query=dataset_data['query'],
description=dataset_data.get('description', ''),
refresh_interval=dataset_data.get('refresh_interval', 0),
enabled=dataset_data.get('enabled', True)
)
self.datasets[dataset.id] = dataset
except Exception as e:
print(f"加载配置失败: {e}")
def _save_config(self):
"""保存配置"""
config = {
'sources': [source.to_dict() for source in self.sources.values()],
'datasets': [dataset.to_dict() for dataset in self.datasets.values()]
}
config_path = self._get_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
def _get_config_path(self) -> Path:
"""获取配置文件路径"""
app_data = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
return Path(app_data) / "enterprise_system" / "config.json"
由于篇幅限制,这里只展示了企业数据管理系统的核心部分。完整的系统应该包括:
-
用户管理模块 - 用户认证、权限控制
-
报表生成模块 - 数据报表、图表导出
-
工作流引擎 - 业务流程自动化
-
通知系统 - 邮件、桌面通知
-
API网关 - 与其他系统集成
-
监控系统 - 性能监控、告警
5. 性能优化与调试
5.1 性能优化策略

5.2 调试技巧
-
使用Qt Creator调试器
-
添加详细的日志记录
-
使用性能分析工具
-
内存泄漏检测
-
QML控制台输出
总结
通过本篇的学习,你已经掌握了Python与QML深度集成的核心技术:
-
上下文属性 - 全局数据共享
-
双向数据绑定 - 响应式数据流
-
模型视图架构 - 高效数据展示
-
异步编程 - 多线程处理
-
错误处理 - 健壮性保障
-
实战项目 - 企业级应用开发
这些技术是构建现代桌面应用程序的基础。在实际开发中,还需要结合具体业务需求,灵活运用这些技术。
记住,良好的架构设计和代码组织是成功的关键。