{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/python-qt-overview/",
  "atlas": "https://www.pystone.net/?node=python-qt-overview#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/python-qt-overview.md",
  "context": "https://www.pystone.net/notes/python-qt-overview.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:python-qt-overview",
  "slug": "python-qt-overview",
  "title": "Python Qt",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "Python Qt QT: https://doc.qt.io/qt 6/portingguide.html QT: https://wiki.qt.io/Main https://wiki.qt.io/Qt for Python PySide6: https://doc.qt.io/qtforpython 6/quickstart.html",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "3ba372cd196d5049aa9860a6461d3db2c80d9bd8993a7248f66c955081ed2d3b",
  "assets": [
    {
      "reference": "assets/image-20231110180950213.png",
      "url": "/media/bbc81808eab946ef289f.png",
      "mediaType": "image/png",
      "contentHash": "bbc81808eab946ef289fcdd06db6cb880ea78eb73ca46685e470e6a321581d9e",
      "byteLength": 18824,
      "width": 474,
      "height": 341
    },
    {
      "reference": "assets/image-20231115162013069.png",
      "url": "/media/5c9541f30ac6617a4b13.png",
      "mediaType": "image/png",
      "contentHash": "5c9541f30ac6617a4b137c75a30012b7cea06adce43c8ddfd29a1449b030286a",
      "byteLength": 34008,
      "width": 651,
      "height": 320
    },
    {
      "reference": "assets/image-20231115162029989.png",
      "url": "/media/493f8f88d2181f2e182e.png",
      "mediaType": "image/png",
      "contentHash": "493f8f88d2181f2e182e05ed2425174dbcde400abc7ac9a1c4c7c0bd9f64bae8",
      "byteLength": 22081,
      "width": 735,
      "height": 337
    },
    {
      "reference": "assets/image-20231115162640822.png",
      "url": "/media/adebad97c506f6c3b92a.png",
      "mediaType": "image/png",
      "contentHash": "adebad97c506f6c3b92a861a28535566d35ed8d54b58fff5b0bd0fd725cc761e",
      "byteLength": 29709,
      "width": 628,
      "height": 361
    },
    {
      "reference": "assets/image-20231128151300120.png",
      "url": "/media/b3edbcb094f82965fa0e.png",
      "mediaType": "image/png",
      "contentHash": "b3edbcb094f82965fa0e535c4c2e1e876ff94572999159b52fb59fd1d11f1a23",
      "byteLength": 52925,
      "width": 1026,
      "height": 804
    },
    {
      "reference": "assets/image-20231128151446641.png",
      "url": "/media/8406557cc64fc1fc1103.png",
      "mediaType": "image/png",
      "contentHash": "8406557cc64fc1fc1103b0828939367a613733a11e6cfd11f4ad3b8237394db4",
      "byteLength": 38958,
      "width": 712,
      "height": 934
    },
    {
      "reference": "assets/image-20231128150928996.png",
      "url": "/media/ef3568f7f5e22e773a14.png",
      "mediaType": "image/png",
      "contentHash": "ef3568f7f5e22e773a140baac6a39ab22f88ee7da21dac5c6e6616641c86406e",
      "byteLength": 9403,
      "width": 332,
      "height": 205
    },
    {
      "reference": "assets/image-20231128151640508.png",
      "url": "/media/fcc02ad533d8bafbc724.png",
      "mediaType": "image/png",
      "contentHash": "fcc02ad533d8bafbc724c741e94bcb2c67343a30234136c9e7c7d722eefb1421",
      "byteLength": 23365,
      "width": 1029,
      "height": 822
    }
  ],
  "headings": [
    {
      "depth": 1,
      "text": "Python Qt",
      "anchor": "python-qt",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#python-qt"
    },
    {
      "depth": 2,
      "text": "教程或示例资料",
      "anchor": "教程或示例资料",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%95%99%E7%A8%8B%E6%88%96%E7%A4%BA%E4%BE%8B%E8%B5%84%E6%96%99"
    },
    {
      "depth": 2,
      "text": "工具",
      "anchor": "工具",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%B7%A5%E5%85%B7"
    },
    {
      "depth": 2,
      "text": "参考项目",
      "anchor": "参考项目",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%8F%82%E8%80%83%E9%A1%B9%E7%9B%AE"
    },
    {
      "depth": 2,
      "text": "开发环境与流程",
      "anchor": "开发环境与流程",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E4%B8%8E%E6%B5%81%E7%A8%8B"
    },
    {
      "depth": 2,
      "text": "编程知识",
      "anchor": "编程知识",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%BC%96%E7%A8%8B%E7%9F%A5%E8%AF%86"
    },
    {
      "depth": 2,
      "text": "框架特征",
      "anchor": "框架特征",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%A1%86%E6%9E%B6%E7%89%B9%E5%BE%81"
    },
    {
      "depth": 2,
      "text": "编程规范",
      "anchor": "编程规范",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%BC%96%E7%A8%8B%E8%A7%84%E8%8C%83"
    },
    {
      "depth": 2,
      "text": "接口用法",
      "anchor": "接口用法",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%8E%A5%E5%8F%A3%E7%94%A8%E6%B3%95"
    },
    {
      "depth": 3,
      "text": "事件循环",
      "anchor": "事件循环",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF"
    },
    {
      "depth": 3,
      "text": "绘制相关",
      "anchor": "绘制相关",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%BB%98%E5%88%B6%E7%9B%B8%E5%85%B3"
    },
    {
      "depth": 3,
      "text": "常用控件",
      "anchor": "常用控件",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%B8%B8%E7%94%A8%E6%8E%A7%E4%BB%B6"
    },
    {
      "depth": 4,
      "text": "MDI 多个子窗口",
      "anchor": "mdi-多个子窗口",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#mdi-%E5%A4%9A%E4%B8%AA%E5%AD%90%E7%AA%97%E5%8F%A3"
    },
    {
      "depth": 4,
      "text": "树控件",
      "anchor": "树控件",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%A0%91%E6%8E%A7%E4%BB%B6"
    },
    {
      "depth": 2,
      "text": "界面布局",
      "anchor": "界面布局",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%95%8C%E9%9D%A2%E5%B8%83%E5%B1%80"
    },
    {
      "depth": 2,
      "text": "多线程问题处理",
      "anchor": "多线程问题处理",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%97%AE%E9%A2%98%E5%A4%84%E7%90%86"
    },
    {
      "depth": 2,
      "text": "QSS样式",
      "anchor": "qss样式",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#qss%E6%A0%B7%E5%BC%8F"
    },
    {
      "depth": 2,
      "text": "QSS样式基本语法",
      "anchor": "qss样式基本语法",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#qss%E6%A0%B7%E5%BC%8F%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95"
    },
    {
      "depth": 2,
      "text": "QSS样式在工程中的用法与开发流程",
      "anchor": "qss样式在工程中的用法与开发流程",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#qss%E6%A0%B7%E5%BC%8F%E5%9C%A8%E5%B7%A5%E7%A8%8B%E4%B8%AD%E7%9A%84%E7%94%A8%E6%B3%95%E4%B8%8E%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B"
    },
    {
      "depth": 2,
      "text": "组合方式",
      "anchor": "组合方式",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%BB%84%E5%90%88%E6%96%B9%E5%BC%8F"
    },
    {
      "depth": 3,
      "text": "QApplication 引用.qss 文件",
      "anchor": "qapplication-引用qss-文件",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#qapplication-%E5%BC%95%E7%94%A8qss-%E6%96%87%E4%BB%B6"
    },
    {
      "depth": 2,
      "text": "结合Python数据可视化",
      "anchor": "结合python数据可视化",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E7%BB%93%E5%90%88python%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96"
    },
    {
      "depth": 2,
      "text": "Matplotlib",
      "anchor": "matplotlib",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#matplotlib"
    },
    {
      "depth": 2,
      "text": "PyQtGraph",
      "anchor": "pyqtgraph",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#pyqtgraph"
    },
    {
      "depth": 2,
      "text": "工程架构",
      "anchor": "工程架构",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%B7%A5%E7%A8%8B%E6%9E%B6%E6%9E%84"
    },
    {
      "depth": 2,
      "text": "模块拆解合并工作流",
      "anchor": "模块拆解合并工作流",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%A8%A1%E5%9D%97%E6%8B%86%E8%A7%A3%E5%90%88%E5%B9%B6%E5%B7%A5%E4%BD%9C%E6%B5%81"
    },
    {
      "depth": 2,
      "text": "单例模式用法",
      "anchor": "单例模式用法",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%94%A8%E6%B3%95"
    },
    {
      "depth": 2,
      "text": "循环引用问题处理",
      "anchor": "循环引用问题处理",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E9%97%AE%E9%A2%98%E5%A4%84%E7%90%86"
    },
    {
      "depth": 3,
      "text": "from import和import",
      "anchor": "from-import和import",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#from-import%E5%92%8Cimport"
    },
    {
      "depth": 3,
      "text": "UI和Data模块互相引用的解决方案",
      "anchor": "ui和data模块互相引用的解决方案",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#ui%E5%92%8Cdata%E6%A8%A1%E5%9D%97%E4%BA%92%E7%9B%B8%E5%BC%95%E7%94%A8%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"
    },
    {
      "depth": 2,
      "text": "踩坑集",
      "anchor": "踩坑集",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E8%B8%A9%E5%9D%91%E9%9B%86"
    },
    {
      "depth": 2,
      "text": "感谢",
      "anchor": "感谢",
      "citation": "https://www.pystone.net/notes/python-qt-overview/#%E6%84%9F%E8%B0%A2"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:application-and-cloud-engineering",
      "title": "应用开发与云端工程",
      "url": "https://www.pystone.net/notes/application-and-cloud-engineering/",
      "atlas": "https://www.pystone.net/?node=application-and-cloud-engineering#knowledge-atlas",
      "label": "应用开发与云端工程",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "桌面应用开发中的“Python Qt”导航项",
      "citation": "https://www.pystone.net/notes/application-and-cloud-engineering/#%E6%A1%8C%E9%9D%A2%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91"
    }
  ],
  "contentMarkdown": "# Python Qt\nQT: https://doc.qt.io/qt-6/portingguide.html\nQT: https://wiki.qt.io/Main\nhttps://wiki.qt.io/Qt_for_Python\nPySide6: https://doc.qt.io/qtforpython-6/quickstart.html\n\n参考教程：\n白月黑羽：https://www.byhy.net/tut/py/gui/qt_01/\n基于PySide6的GUI程序开发全流程：https://cloud.tencent.com/developer/article/2334289 (计算器程序示例)\n\n简言之博主的博客：https://jwt1399.top/posts/41724.html#toc-heading-1\n\n---\n\n## 教程或示例资料\nCode Editor Example：https://doc.qt.io/qtforpython-6.2/examples/example_widgets__codeeditor.html#:~:text=,codeeditor.py\nYoloSide： https://github.com/Jai-wei/YOLOv8-PySide6-GUI\nPySide代码式教程：https://github.com/muziing/PySide6-Code-Tutorial\nPySide2 入门教程：https://github.com/se7enXF/pyside2\nPyQt各种测试和例子：https://github.com/PyQt5/PyQt\nPyQt-Fluent-Widgets：https://github.com/zhiyiYo/PyQt-Fluent-Widgets\n\nTop 19 Python desktop-app Projects:https://www.libhunt.com/l/python/topic/desktop-app\n\n- [Qt-Material](https://qt-material.readthedocs.io/en/latest/index.html#qt-material)：PySide6、PySide2和PyQt5的 Material Design 风格样式表\n- [QDarkStyleSheet](https://github.com/ColinDuquesnoy/QDarkStyleSheet) ： 一个黑暗风格的样式表\n- [PyQt5_stylesheets](https://github.com/RedFalsh/PyQt5_stylesheets) ：PyQt5 风格样式表\n- [CustomWidgets](https://github.com/PyQt5/CustomWidgets)：PyQt Custom Widgets - PyQt 自定义控件\n- [PyQt Examples](https://github.com/PyQt5/PyQt)：PyQt各种测试和例子\n- [3rd-Apps](https://github.com/PyQt5/3rd-Apps)：Collecting 3rd App\n\n来源: 简言之\n文章作者: 简简\n文章链接: [https://jwt1399.top/posts/41724.html#toc-heading-171](https://jwt1399.top/posts/41724.html#toc-heading-171)\n本文章著作权归作者所有，任何形式的转载都请注明出处。\n## 工具\nQtAwesome https://github.com/spyder-ide/qtawesome\n**[PySide6-Code-Tutorial](https://github.com/muziing/PySide6-Code-Tutorial)**\n\nhttps://github.com/5yutan5/PyQtDarkTheme\nhttps://github.com/KhamisiKibet/QT-PyQt-PySide-Custom-Widgets\n## 参考项目\nhttps://github.com/pyQode/pyQode\nhttps://github.com/TenderOwl/Frog/tree/master\nhttps://github.com/reflex-dev/reflex/tree/main\n\nSpyderIDE(专为科学开发和数据分析设计的强大 Python IDE): https://github.com/spyder-ide/spyder\nOpenShot(多平台的视频编辑软件): https://github.com/OpenShot/openshot-qt\nOrange(基于组件的数据可视化和分析工具): https://github.com/biolab/orange3\n\nhttps://github.com/CadQuery/CQ-editor\nhttps://github.com/gmarull/qtmodern\nhttps://github.com/Javacr/PyQt5-YOLOv5\nhttps://github.com/Jai-wei/YOLOv8-PySide6-GUI\nhttps://github.com/Wanderson-Magalhaes/Modern_GUI_PyDracula_PySide6_or_PyQt6\n## 开发环境与流程\n\n\n* 配置虚拟环境\n* 安装PySide6 (6.3.2)\n* 配置 open qt designer菜单项\n![](assets/image-20231110180950213.png)\n\n* 配置rcc与uic菜单项\n\n```cpp\nProgram -->​​D:\\...\\venv\\Scripts\\pyside6-uic.exe​​\nArguments --> $FileName$ -o $FileNameWithoutAllExtensions$.py\n```\n\n```cpp\nProgram -->​​ ​​D:\\...\\venv\\Scripts\\pyside6-rcc.exe​​\nArguments --> $FileName$ -o $FileNameWithoutAllExtensions$_rc.py\n```\n\n* 打包exe\n```python\npip install pyinstaller\npip install auto-py-to-exe\n```\n\nauto-py-to-exe实际上是对pyinstaller的封装.\n选择入口文件即可.\npyinstall教程:https://www.byhy.net/tut/py/etc/toexe/\n\n## 编程知识\n\n## 框架特征\n在 Qt 系统中，控件（widget）是 `层层嵌套` 的，除了最顶层的控件，其他的控件都有父控件。\n\n## 编程规范\n\n代码模块化: 通常应该把 一个窗口和其包含的控件，对应的代码 全部封装到类中.\n## 接口用法\n### 事件循环\n事件循环 - 使应用程序能够响应用户的交互和系统事件，如鼠标点击、键盘输入、窗口关闭等。\n\n`QApplication`的成员函数exec  用于启动 PyQt 应用程序的事件循环,并等待直到应用退出。\n\n当界面上一个控件被操作时，就会发出 `信号` ，英文叫 `signal`，表明一个事件（比如被点击、被输入文本）发生了。\n\n处理 signal 的函数 叫做 `slot`\n\n```py\n//把 button 被 点击（clicked） 的信号（signal）， 连接（connect）到了 handleCalc 这样的一个 slot上\nbutton.clicked.connect(handleCalc)\n```\n\n### 绘制相关\nmove决定这个控件相对于父窗口的左上角的位置\n\n\n### 常用控件\n\n菜单栏:\n```cpp\n菜单栏（QMenuBar） ->  菜单（QMenu）->  子菜单（QMenu） -> QAction\n```\n\n\n#### MDI 多个子窗口\n\n#### 树控件\n\n## 界面布局\n\n布局经验（via. 白月黑羽）\n- 先不使用任何Layout，把所有控件 按位置 摆放在界面上\n- 然后先从 最内层开始 进行控件的 Layout 设定\n- 逐步拓展到外层 进行控件的 Layout设定\n- 最后调整 layout中控件的大小比例， 优先使用 Layout的 layoutStrentch 属性来控制\n\n\n## 多线程问题处理\nQt建议：\n* 只在主线程中操作界面。在子线程操作界面可能会有意外问题，如输出显示不全，甚至程序崩溃。\n* 使用信号处理子线程的事件。\n\n信号在多线程问题中的使用：\n* 自定义一个Qt 的 QObject类，里面封装一些自定义的 Signal信号。\n\t一种信号定义为 该类的 一个 静态属性，值为Signal 实例对象即可。\n\tSignal实例对象的初始化参数指定的类型，就是 发出信号对象时，传递的参数数据类型。\n\t可以定义 `多个` Signal静态属性，对应这种类型的对象可以发出的 `多种` 信号。\n\n* 定义主线程执行的函数处理Signal信号（通过connect方法）\n* 在新线程需要操作界面的时候，就通过自定义对象 发出 信号\n* 主线程信号处理函数，被触发执行，获取Signal里面的参数，执行必要的更新界面操作。\n\n\n```python\nfrom PySide6.QtWidgets import QApplication, QTextBrowser\nfrom PySide6.QtUiTools import QUiLoader\nfrom threading import Thread\nimport time\nimport sys\n\nfrom PySide6.QtCore import Signal, QObject\n\nclass MySignals(QObject):\n    text_print = Signal(str, str)\n    update_table = Signal(str)\n\n## 实例化\nglobal_ms = MySignals()\n\nclass Stats:\n    def __init__(self):\n        self.ui = QUiLoader().load('ui/multi_thread.ui')\n        self.ui.infoBox1.setText(\"初始文本\\n\")\n        self.ui.infoBox2.setText(\"初始文本\\n\")\n        global_ms.text_print.connect(self.printToGui)\n\n    def printToGui(self, infoBoxName, text):\n        infoBox = getattr(self.ui, infoBoxName)\n        print(\"printToGui called: \" + str(text))\n        infoBox.append(str(text))\n        infoBox.append(' --infobox-append--')\n        infoBox.ensureCursorVisible()\n\n    def task1(self):\n        def threadFunc():\n            time.sleep(2)\n            # 通过Signal的emit触发执行主线程里面的处理函数\n            print(\"线程1触发输出\")\n            global_ms.text_print.emit(\"infoBox1\", '输出内容1')\n\n        thread = Thread(target = threadFunc)\n        thread.start()\n\n    def task2(self):\n        def threadFunc():\n            time.sleep(4)\n            print(\"线程2触发输出\")\n            global_ms.text_print.emit(\"infoBox2\", '输出内容2')\n\n        thread = Thread(target = threadFunc)\n        thread.start()\n\napp = QApplication()\nsts = Stats()\nsts.ui.show()\nsts.task1()\nsts.task2()\napp.exec_()\n```\n\n## QSS样式\n\n## QSS样式基本语法\n文档：https://doc.qt.io/qt-5/stylesheet-syntax.html\n\n更详细的笔记见：\n[Python Qt QSS样式用法](obsidian://open?vault=ObsidianVault&file=4%20%E4%BF%A1%E6%81%AF%E6%8A%80%E6%9C%AF%E9%A2%86%E5%9F%9F%2F%E6%A1%8C%E9%9D%A2%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%2FPython%20Qt%20QSS%E6%A0%B7%E5%BC%8F%E7%94%A8%E6%B3%95)\n\n**选择器**用于选择一个或多个需要应用样式的 Qt 部件\n\n![](assets/image-20231115162013069.png)\n\n\n![](assets/image-20231115162029989.png)\n\n```css\n\nQTextEdit { background-color: yellow }\nQTextEdit { background-color: #e7d8d8 }\nQTextEdit { background-image: url(gg03.png); }\nQTextEdit { margin:10px 11px 12px 13px }\n\n/*\nsolid 实线 dashed 虚线 dotted 点\n*/\n*[myclass=bar2btn]:hover{ border:1px solid #1d649c; }\n\n/*字体、大小、颜色*/\n*{ font-family:微软雅黑; font-size:15px; color: #1d649c; }\n\nQPushButton { width:50px; height:20px; }\n\n```\n\n![](assets/image-20231115162640822.png)\n图片来源：https://www.byhy.net/tut/py/gui/qt_09/\n\n\n## QSS样式在工程中的用法与开发流程\n## 组合方式\n### QApplication 引用.qss 文件\n\nQSS选择器\n```python\napp = QApplication(sys.argv)\nform = QSSSelector()\n## 选择器\n#指定按钮\nqssStyle = '''\n\tQPushButton[name=\"btn2\"] {\n\t\tbackground-color:red;\n\t\tcolor:yellow;\n\t\theight:120;\n\t\tfont-size:60px;\n\t}\n\tQPushButton[name=\"btn3\"] {\n\t\tbackground-color:blue;\n\t\tcolor:yellow;\n\t\theight:60;\n\t\tfont-size:30px;\n\t}\n'''\nform.setStyleSheet(qssStyle)\nform.show()\nsys.exit(app.exec_())\n```\n\nQSS子控件选择器\n```python\napp = QApplication(sys.argv)\nform = QSSSubControl()\n'''\n通过名字来引用,#myComboBox相当于web里通过id来引用\ndrop-down是下拉子控件\n'''\nqssStyle = '''\n   QComboBox#myComboBox::drop-down {\n\t   image:url(../picture/icon/first.png)\n   }\n'''\nform.setStyleSheet(qssStyle)\nform.show()\nsys.exit(app.exec_())\n```\n\n\n## 结合Python数据可视化\n\n## Matplotlib\n\n```python\nimport sys\nfrom PySide6.QtWidgets import QApplication, QVBoxLayout, QMainWindow, QWidget\nfrom matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas\nfrom matplotlib.figure import Figure\nimport numpy as np\n\nclass MplWidget(QWidget):\n    def __init__(self, parent=None):\n        super(MplWidget, self).__init__(parent)\n\n        # 创建一个新的图标Figure\n        # 创建一个Matplotlib图标的画布FigureCanvas。  FigureCanvas是将这个图表转换为可以在PySide6中显示的小部件\n        self.canvas = FigureCanvas(Figure())\n\n        vertical_layout = QVBoxLayout()\n        vertical_layout.addWidget(self.canvas)\n\n        # 在画布上创建一个1x1网格的第1个子图\n        self.canvas.axes = self.canvas.figure.add_subplot(111)\n        self.setLayout(vertical_layout)\n\nclass MainWindow(QMainWindow):\n    def __init__(self):\n        super().__init__()\n\n        self.central_widget = QWidget()\n        self.setCentralWidget(self.central_widget)\n        layout = QVBoxLayout(self.central_widget)\n\n        self.mpl_widget = MplWidget(self.central_widget)\n        layout.addWidget(self.mpl_widget)\n\n        self.plot()\n\n    def plot(self):\n        mu, sigma = 100, 15\n        print( str(np.random.randn(10000)))\n        x = mu + sigma * np.random.randn(10000)\n        self.mpl_widget.canvas.axes.clear()\n\n        # x: 这是直方图将要展示的数据集。在这个例子中，x应该是一个包含数据点的数组或列表。\n        # 50: 表示直方图的条形（bins）的数量。在这个例子中，数据将被分成50个条形来展示。\n        # density=1: 当设置为1时，表示直方图的纵轴将显示密度而不是计数。这意味着直方图下的总面积（或总高度）等于1。\n        self.mpl_widget.canvas.axes.hist(x, 50, density=1, facecolor='g', alpha=0.75)\n        self.mpl_widget.canvas.axes.set_xlabel('Smarts') # X轴标签\n        self.mpl_widget.canvas.axes.set_ylabel('Probability')\n        self.mpl_widget.canvas.axes.set_title('Histogram of IQ') # 标题\n        self.mpl_widget.canvas.axes.text(60, .025, r'$\\mu=100,\\ \\sigma=15$') # 在指定位置添加文本\n        self.mpl_widget.canvas.axes.axis([40, 160, 0, 0.03]) # 设置坐标轴范围\n        self.mpl_widget.canvas.axes.grid(True) # 显示网格\n        self.mpl_widget.canvas.draw() # 更新画布上的绘图，这样更改才会显示在GUI上。\n\napp = QApplication(sys.argv)\nmain = MainWindow()\nmain.show()\nsys.exit(app.exec_())\n```\n\n## PyQtGraph\n优点： 大数据量的作图性能高于 Matplotlib， 动态更新图的性能也比Matplotlib高。并且和Qt图形界面框架完美融合。\n缺点： 作图功能没有Matplotlib多，开发社区没有Matplotlib大。\n\n## 工程架构\n需要将代码进行模块化，使其易于维护和扩展，有两种方案：\n\n（1）将所有UI绘制在一个`main_window.ui`文件中（将不同部分分发给不同的类）\n**优点**：UI设计集中，易于统一管理和调整。\n**缺点**：代码中分发逻辑可能变得复杂。\n\n1. **定义接口或回调**：在主窗口类中定义接口或回调函数，以便不同的部分可以与主窗口通信。\n2. **创建子组件类**：为UI中的每个独立部分创建单独的Python类。\n3. **在主窗口类中实例化**：在加载了`main_window.ui`之后，根据UI中的不同部分实例化相应的子组件类，并将相关UI元素传递给它们。\n```python\nclass PartA:\n    def __init__(self, ui_component):\n        self.ui_component = ui_component\n        # 初始化PartA\n\nclass MainWindow:\n    def __init__(self):\n        self.ui = load_ui('main_window.ui')\n        self.part_a = PartA(self.ui.someComponent)\n\n```\n\n（2）**多个UI文件（每个部分一个UI文件）**：\n- **优点**：模块化程度高，易于维护和扩展。\n- **缺点**：需要更多的文件组织和管理。\n\n**对于较大、复杂的项目，第一种方案可能更优**；而对于小型或中等规模的项目，第二种方案可能更为合适。\n\n## 模块拆解合并工作流\n\n在一个UI文件当中绘制。\n![](assets/image-20231128151300120.png)\n\n绘制好后，可将各个部分拆到不同的UI文件当中。\n![](assets/image-20231128151446641.png)\n\n每个UI文件编写对应的类，在运行时对UI进行初始化。\n![](assets/image-20231128150928996.png)\n\n类中的代码如下：\n```python\nfrom PySide6.QtUiTools import QUiLoader\n\nclass ControlArea():\n    def __init__(self):\n        super(ControlArea, self).__init__()\n        loader = QUiLoader()\n        self.ui = loader.load(\"ui/frm_control_area.ui\")\n```\n\n用一个.ui文件和.py文件作为主窗口的布局和总体的管理：\n![](assets/image-20231128151640508.png)\n在MainWindow类中逐个对子模块进行实例化和绑定。\n```python\nclass MainWindow(QObject):\n    def __init__(self):\n        QObject.__init__(self)\n        print(\"init main window\")\n        self.ui = QUiLoader().load('ui/main_window_framework.ui')\n        print(\"load main window ui done\")\n        ## PRINT ==> SYSTEM\n        print('System: ' + platform.system())\n        print('Version: ' +platform.release())\n\n        # 实例化子部分\n        self.actionMenuArea = ActionMenuArea()\n        self.controlArea = ControlArea()\n        self.dataArea = DataArea()\n\n        # 将子部分添加到主窗口\n        self.ui.frmLeft.layout().addWidget(self.controlArea.ui)\n        self.ui.frmLeft.layout().addWidget(self.actionMenuArea.ui)\n        self.ui.frmMiddle.layout().addWidget(self.dataArea.ui)\n```\n\n## 单例模式用法\n\n\n```python\nfrom functools import wraps\n\ndef Singleton(orig_cls):\n    orig_new = orig_cls.__new__\n    instance = None\n\n    @wraps(orig_cls.__new__)\n    def __new__(cls, *args, **kwargs):\n        nonlocal instance\n        if instance is None:\n            instance = orig_new(cls, *args, **kwargs)\n        return instance\n    orig_cls.__new__ = __new__\n    return orig_cls\n\n```\n\n```python\n## import common utils\nfrom utils.common_utils import *\n\n@Singleton\nclass Recorder:\n    def __init__(self):\n        self.isRecording = False\n\n    def Start(self):\n        pass\n\n    def Stop(self):\n        pass\n\n\n## 使用\nobj1 = Recorder()\nobj2 = Recorder()\n## obj1 和 obj2 是相同的实例\nprint(obj1 is obj2) # 输出 True\n```\n\n注意事项\n- 确保单例的创建和使用不会导致循环依赖。\n- 在单例类中，避免在`__init__`方法中编写复杂的初始化逻辑，因为它只会在第一次实例化时执行。\n- 考虑线程安全问题，尤其是在多线程环境中使用单例。\n\n\n## 循环引用问题处理\n循环引用：两个或多个模块相互导入对方，导致Python无法正确初始化这些模块。\n\n解决方案：\n1. 重新组织代码结构：调整代码结构，避免模块间的直接循环依赖。\n* 将相关的函数或类重构到一个新的模块中\n* 创建一个新的模块作为共同依赖。\n* 通过使用观察者模式、信号槽机制或回调函数来减少模块间的直接依赖。\n\n2. 使用`import`而不是`from ... import ...`。\n\n2. 延迟导入：在函数或方法内部进行导入，而不是在模块顶部。这种“延迟导入”可以避免初始化时的循环依赖问题。\n\n\n```python\ndef my_function():\n    from data.action_data_manager import ActionDataManager\n    # 使用 ActionDataManager\n\n```\n\n### from import和import\n`from ... import ...`\n1. **导入特定对象**：使用`from module import name`语句可以直接从模块中导入一个或多个特定的对象（如函数、类或变量）。\n2. **名称空间**：导入的对象直接进入当前名称空间，无需使用模块名称作为前缀。\n3. **循环引用问题**：如果两个模块互相`from ... import ...`对方，Python在导入过程中需要立即解析这些对象。如果被导入的对象尚未定义（因为相互依赖尚未解决），就会导致错误。\n\n`import ...`\n1. **导入整个模块**：使用`import module`语句会导入整个模块。\n2. **名称空间**：必须通过模块名称来访问模块内的对象，例如`module.name`。\n3. **循环引用问题**：`import`在处理循环引用时通常更为宽容。这是因为`import`语句导入模块本身，而不是其中的特定对象。当Python执行`import`语句时，它只是加载模块并使其可用，而不需要立即解析模块内的所有内容。因此，即使存在循环引用，只要避免在模块级别执行依赖于彼此的代码，通常不会出现问题。\n\n### UI和Data模块互相引用的解决方案\n\n1. 使用事件或信号槽机制（QT框架原生做法）\n这是避免循环依赖的一种优雅方式。\n\n```python\nclass ActionDataManager:\n    data_changed = Signal()  # 使用适当的信号库\n\n    def addActionItem(self, action):\n        self.actions.append(action)\n        self.data_changed.emit()  # 触发信号\n\n```\n\n```python\nclass DataArea(QWidget):\n    def __init__(self, data_manager):\n        super(DataArea, self).__init__()\n        data_manager.data_changed.connect(self.updateUI)\n\n    def updateUI(self):\n        actions = ActionDataManager().getActions()\n        # 更新UI逻辑\n```\n\n2. 使用回调函数\n```python\nclass ActionDataManager:\n    def __init__(self):\n        self.on_data_changed = None\n\n    def addActionItem(self, action):\n        self.actions.append(action)\n        if self.on_data_changed:\n            self.on_data_changed()\n\n```\n\n```python\nclass DataArea(QWidget):\n    def __init__(self, data_manager):\n        super(DataArea, self).__init__()\n        data_manager.on_data_changed = self.updateUI\n\n```\n\n## 踩坑集\n\nQ：QUiLoader.load ui文件时，报错栈溢出：\nProcess finished with exit code -1073740791 (0xC0000409)\nA: 需要先实例化QApplication([])，再加载UI文件。\n\n\nQ: QUiLoader().load的方式加载UI，使用Pycharm编写代码的时候，会出现编辑器报Cannot find reference或者Unresolved attribute reference的情况，如何解决?\n\n\n## 感谢\nref: https://www.byhy.net/tut/py/gui/qt_02/\nref: https://blog.51cto.com/u_12072082/5569244\nref: https://blog.csdn.net/weixin_44593822/article/details/113834208\nhttps://qtdebug.com/qtbook-qss-selector/\nhttps://jwt1399.top/posts/41724.html#toc-heading-171\n"
}
