diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index e60ca202..d702b3ce 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,7 @@
 # These are supported funding model platforms
 
-custom: https://github.com/PyQt5/PyQt#donate-打赏
+liberapay: Irony
+custom: 
+  - https://github.com/PyQt5/PyQt#donate-打赏
+  - https://github.com/PyQt5/PyQt/blob/master/Donate/zhifubao.png
+  - https://github.com/PyQt5/PyQt/blob/master/Donate/weixin.png
diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml
new file mode 100644
index 00000000..e3084824
--- /dev/null
+++ b/.github/workflows/mirror.yml
@@ -0,0 +1,15 @@
+name: 'GitHub Actions Mirror'
+
+on: [push, delete]
+
+jobs:
+  mirror_to_gitee:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - uses: pixta-dev/repository-mirroring-action@v1
+        with:
+          target_repo_url:
+            git@gitee.com:PyQt5/PyQt.git
+          ssh_private_key:
+            ${{ secrets.GIT_KEY }}
diff --git a/.gitignore b/.gitignore
index fb1435fc..6688f9a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,763 @@
+# Created by https://www.toptal.com/developers/gitignore/api/python,qt,qtcreator,visualstudiocode,visualstudio,macos,linux,windows,cmake
+# Edit at https://www.toptal.com/developers/gitignore?templates=python,qt,qtcreator,visualstudiocode,visualstudio,macos,linux,windows,cmake
+
+### CMake ###
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+
+### CMake Patch ###
+# External projects
+*-prefix/
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+### Qt ###
+# C++ objects and libs
+*.slo
+*.lo
+*.o
+*.a
+*.la
+*.lai
+*.so.*
+*.dll
+*.dylib
+
+# Qt-es
+object_script.*.Release
+object_script.*.Debug
+*_plugin_import.cpp
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+moc_*.cpp
+moc_*.h
+qrc_*.cpp
+ui_*.h
+*.qmlc
+*.jsc
+Makefile*
+*build-*
+*.qm
+*.prl
+
+# Qt unit tests
+target_wrapper.*
+
+# QtCreator
+*.autosave
+
+# QtCreator Qml
+*.qmlproject.user
+*.qmlproject.user.*
+
+# QtCreator CMake
+CMakeLists.txt.user*
+
+# QtCreator 4.8< compilation database
+
+# QtCreator local machine specific files for imported projects
+*creator.user*
+
+*_qmlcache.qrc
+
+### QtCreator ###
+# gitignore for Qt Creator like IDE for pure C/C++ project without Qt
+#
+# Reference: http://doc.qt.io/qtcreator/creator-project-generic.html
+
+
+
+# Qt Creator autogenerated files
+
+
+# A listing of all the files included in the project
+*.files
+
+# Include directories
+*.includes
+
+# Project configuration settings like predefined Macros
+*.config
+
+# Qt Creator settings
+*.creator
+
+# User project settings
+*.creator.user*
+
+# Qt Creator backups
+
+# Flags for Clang Code Model
+*.cxxflags
+*.cflags
+
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+*.code-workspace
+
+# Local History for Visual Studio Code
+
+# Windows Installer files from build outputs
+
+# JetBrains Rider
+*.sln.iml
+
+### VisualStudio Patch ###
+# Additional files built by Visual Studio
+
+# End of https://www.toptal.com/developers/gitignore/api/python,qt,qtcreator,visualstudiocode,visualstudio,macos,linux,windows,cmake
+
 # Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[co]
@@ -24,8 +784,6 @@ dist/
 downloads/
 eggs/
 .eggs/
-lib/
-lib64/
 parts/
 sdist/
 var/
diff --git a/Demo/AutoRestart.py b/Demo/AutoRestart.py
index 70375954..024d61bb 100644
--- a/Demo/AutoRestart.py
+++ b/Demo/AutoRestart.py
@@ -1,21 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月31日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: AutoRestart
 @description: 
-'''
+"""
 
-from optparse import OptionParser
 import os
 import sys
+from optparse import OptionParser
 
-from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout
-
+try:
+    from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout
 
 canRestart = True
 
diff --git a/Demo/BubbleTips.py b/Demo/BubbleTips.py
index 8af85c76..6f842a3c 100644
--- a/Demo/BubbleTips.py
+++ b/Demo/BubbleTips.py
@@ -1,30 +1,31 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月27日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: BubbleTips
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtCore import QRectF, Qt, QPropertyAnimation, pyqtProperty, \
-    QPoint, QParallelAnimationGroup, QEasingCurve
-from PyQt5.QtGui import QPainter, QPainterPath, QColor, QPen
-from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication,\
-    QLineEdit, QPushButton
-
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QRectF, Qt, QPropertyAnimation, pyqtProperty, \
+        QPoint, QParallelAnimationGroup, QEasingCurve
+    from PyQt5.QtGui import QPainter, QPainterPath, QColor, QPen
+    from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication, \
+        QLineEdit, QPushButton
+except ImportError:
+    from PySide2.QtCore import QRectF, Qt, QPropertyAnimation, Property as pyqtProperty, \
+        QPoint, QParallelAnimationGroup, QEasingCurve
+    from PySide2.QtGui import QPainter, QPainterPath, QColor, QPen
+    from PySide2.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication, \
+        QLineEdit, QPushButton
 
 
 class BubbleLabel(QWidget):
-
     BackgroundColor = QColor(195, 195, 195)
     BorderColor = QColor(150, 150, 150)
 
@@ -133,10 +134,10 @@ def setWindowOpacity(self, opacity):
     opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)
 
 
-class TestWidget(QWidget):
+class Window(QWidget):
 
     def __init__(self, *args, **kwargs):
-        super(TestWidget, self).__init__(*args, **kwargs)
+        super(Window, self).__init__(*args, **kwargs)
         layout = QVBoxLayout(self)
         self.msgEdit = QLineEdit(self, returnPressed=self.onMsgShow)
         self.msgButton = QPushButton("显示内容", self, clicked=self.onMsgShow)
@@ -158,6 +159,6 @@ def onMsgShow(self):
 
 if __name__ == "__main__":
     app = QApplication(sys.argv)
-    w = TestWidget()
+    w = Window()
     w.show()
     sys.exit(app.exec_())
diff --git a/Demo/CallVirtualKeyboard.py b/Demo/CallVirtualKeyboard.py
index b44af512..4afe18ba 100644
--- a/Demo/CallVirtualKeyboard.py
+++ b/Demo/CallVirtualKeyboard.py
@@ -4,20 +4,19 @@
 """
 Created on 2019年5月22日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Demo.CallVirtualKeyboard
 @description: 调用系统虚拟键盘
 """
 import glob
 
-from PyQt5.QtCore import QProcess, QSysInfo
-from PyQt5.QtWidgets import QWidget, QTextEdit, QVBoxLayout, QPushButton
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QProcess, QSysInfo
+    from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout, QPushButton
+except ImportError:
+    from PySide2.QtCore import QProcess, QSysInfo
+    from PySide2.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout, QPushButton
 
 
 class Window(QWidget):
@@ -50,7 +49,7 @@ def _onOpenKeyboard(self):
                 self.resultEdit.append('start osk error: %s' % e)
         elif kernelType == 'darwin':
             pass
-#         elif kernelType=='linux':
+        #         elif kernelType=='linux':
         else:
             ret = QProcess.startDetached('florence')
             self.resultEdit.append('start florence: %s' % ret)
@@ -62,7 +61,7 @@ def _onOpenKeyboard(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/CircleLine.py b/Demo/CircleLine.py
index 11a92dc0..83102c7e 100644
--- a/Demo/CircleLine.py
+++ b/Demo/CircleLine.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年3月19日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CircleLine
 @description: 
@@ -14,13 +14,14 @@
 from random import random, randint
 from time import time
 
-from PyQt5.QtCore import QTimer, Qt
-from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen
-from PyQt5.QtWidgets import QWidget
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
+try:
+    from PyQt5.QtCore import QTimer, Qt
+    from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen
+    from PyQt5.QtWidgets import QWidget, QApplication
+except ImportError:
+    from PySide2.QtCore import QTimer, Qt
+    from PySide2.QtGui import QColor, QPainter, QPainterPath, QPen
+    from PySide2.QtWidgets import QWidget, QApplication
 
 # 最小和最大半径、半径阈值和填充圆的百分比
 radMin = 10
@@ -60,18 +61,21 @@
 circleExpSp = 0.00004
 circlePulse = False
 
+
 # 生成随机整数 a<=x<=b
 
 
 def randint(a, b):
     return floor(random() * (b - a + 1) + a)
 
+
 # 生成随机小数
 
 
 def randRange(a, b):
     return random() * (b - a) + a
 
+
 # 生成接近a的随机小数
 
 
@@ -88,7 +92,7 @@ def __init__(self, background, width, height):
         self.radius = hyperRange(radMin, radMax)
         self.filled = (False if randint(
             0, 100) > concentricCircle else 'full') if self.radius < radThreshold else (
-                False if randint(0, 100) > concentricCircle else 'concentric')
+            False if randint(0, 100) > concentricCircle else 'concentric')
         self.color = colors[randint(0, len(colors) - 1)]
         self.borderColor = colors[randint(0, len(colors) - 1)]
         self.opacity = 0.05
@@ -233,29 +237,29 @@ def renderPoints(self, painter, circles):
                 # otherwise we connect them only if the dist is < linkDist
                 if dist < self.linkDist:
                     xi = (1 if circles[i].x < circles[j].x else -
-                          1) * abs(circles[i].radius * deltax / dist)
+                    1) * abs(circles[i].radius * deltax / dist)
                     yi = (1 if circles[i].y < circles[j].y else -
-                          1) * abs(circles[i].radius * deltay / dist)
+                    1) * abs(circles[i].radius * deltay / dist)
                     xj = (-1 if circles[i].x < circles[j].x else 1) * \
-                        abs(circles[j].radius * deltax / dist)
+                         abs(circles[j].radius * deltax / dist)
                     yj = (-1 if circles[i].y < circles[j].y else 1) * \
-                        abs(circles[j].radius * deltay / dist)
+                         abs(circles[j].radius * deltay / dist)
                     path = QPainterPath()
                     path.moveTo(circles[i].x + xi, circles[i].y + yi)
                     path.lineTo(circles[j].x + xj, circles[j].y + yj)
-#                     samecolor = circles[i].color == circles[j].color
+                    #                     samecolor = circles[i].color == circles[j].color
                     c = QColor(circles[i].borderColor)
                     c.setAlphaF(min(circles[i].opacity, circles[j].opacity)
                                 * ((self.linkDist - dist) / self.linkDist))
                     painter.setPen(QPen(c, (
                         lineBorder * backgroundMlt if circles[i].background else lineBorder) * (
-                        (self.linkDist - dist) / self.linkDist)))
+                                                (self.linkDist - dist) / self.linkDist)))
                     painter.drawPath(path)
 
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = CircleLineWindow()
     w.resize(800, 600)
diff --git a/Demo/CustomProperties.py b/Demo/CustomProperties.py
index 8a053a2e..43eab24d 100644
--- a/Demo/CustomProperties.py
+++ b/Demo/CustomProperties.py
@@ -1,25 +1,25 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年4月12日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 自定义属性测试
 @description: 
-'''
+"""
 from random import randint
 
-from PyQt5.QtCore import pyqtProperty, pyqtSignal
-from PyQt5.QtWidgets import QPushButton
-
-
-__version__ = "0.0.1"
+try:
+    from PyQt5.QtCore import pyqtProperty, pyqtSignal
+    from PyQt5.QtWidgets import QPushButton, QApplication
+except ImportError:
+    from PySide2.QtCore import Property as pyqtProperty, Signal as pyqtSignal
+    from PySide2.QtWidgets import QPushButton, QApplication
 
 
 class Window(QPushButton):
-
     bgChanged = pyqtSignal(str, str)
 
     def __init__(self):
@@ -56,7 +56,7 @@ def setTextColor(self, c):
 
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.setStyleSheet(
diff --git a/Demo/Data/frameless.ui b/Demo/Data/frameless.ui
new file mode 100644
index 00000000..a7919b02
--- /dev/null
+++ b/Demo/Data/frameless.ui
@@ -0,0 +1,197 @@
+
+
+ FormFrameless 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   
+    0 
+    
+   
+    3 
+    
+   
+    3 
+    
+   
+    3 
+    
+   
+    3 
+    
+   - 
+    
+     
+      
+       Symbola +
 +
 +
+      
+       0 +
 +
+       0 +
 +
+       0 +
 +
+       0 +
 +
+       0 +
 +
- 
+       
+        
+         Qt::Horizontal +
 +
+         
+          253 +20 +
 +
 +
 +
+- 
+       
+        
+         
+          36 +36 +
 +
 +
+         
+          36 +36 +
 +
 +
+         
+          webdings +
 +
 +
+         Minimum +
 +
+         0 +
 +
 +
+- 
+       
+        
+         
+          36 +36 +
 +
 +
+         
+          36 +36 +
 +
 +
+         
+          webdings +
 +
 +
+         Maximum +
 +
+         1 +
 +
 +
+- 
+       
+        
+         
+          36 +36 +
 +
 +
+         
+          36 +36 +
 +
 +
+         
+          webdings +
 +
 +
+         Normal +
 +
+         2 +
 +
 +
+- 
+       
+        
+         
+          36 +36 +
 +
 +
+         
+          36 +36 +
 +
 +
+         
+          webdings +
 +
 +
+         Close +
 +
+         r +
 +
 +
+ +
 +
+- 
+    
+     
+      QFrame::NoFrame +
 +
+      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">frameless window with move and resize</p></body></html> +
 +
 +
+ 
+  
+  
diff --git a/Demo/Data/serializewidget.ui b/Demo/Data/serializewidget.ui
new file mode 100644
index 00000000..c165780e
--- /dev/null
+++ b/Demo/Data/serializewidget.ui
@@ -0,0 +1,268 @@
+
+
+ SerializeWidget 
+ 
+  
+   
+    0 
+    0 
+    800 
+    600 
+    
+   
+  
+   TestSerialize 
+   
+  
+   - 
+    
+     
+      Qt::Horizontal +
 +
+      false +
 +
+      
+       
+        500 +16777215 +
 +
 +
+       Json View +
 +
+       - 
+        + +
 +
+      
+       Widget View +
 +
+       - 
+        
+         
+          0 +
 +
+          
+           Input +
 +
+           - 
+            +- 
+            
+             
+              CheckBox +
 +
 +
+- 
+            
+             
+              RadioButton +
 +
 +
+- 
+            +- 
+            +- 
+            +- 
+            +- 
+            +- 
+            +- 
+            
+             
+              Qt::Horizontal +
 +
+              
+               40 +20 +
 +
 +
 +
+- 
+            
+             
+              Qt::Vertical +
 +
+              
+               20 +40 +
 +
 +
 +
+ +
 +
+          
+           Edit +
 +
+           - 
+            +- 
+            + +
 +
+          
+           Correlation +
 +
+           - 
+            
+             
+              Qt::Horizontal +
 +
 +
+- 
+            +- 
+            
+             
+              Qt::Vertical +
 +
+              
+               20 +40 +
 +
 +
 +
+- 
+            
+             
+              0 +
 +
+              Qt::Vertical +
 +
 +
+- 
+            
+             
+              Qt::Vertical +
 +
 +
+ +
 +
+          
+           View +
 +
+           - 
+            
+             
+              ListView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+- 
+            
+             
+              TreeView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+- 
+            
+             
+              TableView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+ +
 +
 +
+ +
 +
 +
+ 
+  
+  
diff --git a/Demo/EmbedWindow.py b/Demo/EmbedWindow.py
index c3360d76..b6ffe04f 100644
--- a/Demo/EmbedWindow.py
+++ b/Demo/EmbedWindow.py
@@ -4,22 +4,26 @@
 """
 Created on 2018年3月1日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: EmbedWindow
 @description: 嵌入外部窗口
 """
 
-__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com'
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
-from PyQt5.QtGui import QWindow
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget,\
-    QLabel
 import win32con
 import win32gui
 
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QWindow
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget, \
+        QLabel, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QWindow
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget, \
+        QLabel, QApplication
+
 
 class Window(QWidget):
 
@@ -32,16 +36,22 @@ def __init__(self, *args, **kwargs):
 
         layout.addWidget(QPushButton('获取所有可用、可视窗口', self,
                                      clicked=self._getWindowList, maximumHeight=30))
+        layout.addWidget(QPushButton('释放窗口', clicked=self.releaseWidget, maximumHeight=30))
         layout.addWidget(
             QLabel('双击列表中的项目则进行嵌入目标窗口到下方\n格式为:句柄|父句柄|标题|类名', self, maximumHeight=30))
         self.windowList = QListWidget(
             self, itemDoubleClicked=self.onItemDoubleClicked, maximumHeight=200)
         layout.addWidget(self.windowList)
 
+    def releaseWidget(self):
+        """释放窗口"""
+        if self.layout().count() == 5:
+            self.restore()
+            self._getWindowList()
+
     def closeEvent(self, event):
         """窗口关闭"""
-        if self.layout().count() == 4:
-            self.restore()
+        self.releaseWidget()
         super(Window, self).closeEvent(event)
 
     def _getWindowList(self):
@@ -55,39 +65,41 @@ def onItemDoubleClicked(self, item):
         self.windowList.takeItem(self.windowList.indexFromItem(item).row())
         hwnd, phwnd, _, _ = item.text().split('|')
         # 开始嵌入
-
-        if self.layout().count() == 4:
-            # 如果数量等于4说明之前已经嵌入了一个窗口,现在需要把它释放出来
-            self.restore()
+        self.releaseWidget()
         hwnd, phwnd = int(hwnd), int(phwnd)
         # 嵌入之前的属性
         style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
         exstyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
-        print('save', hwnd, style, exstyle)
+        wrect = win32gui.GetWindowRect(hwnd)[:2] + win32gui.GetClientRect(hwnd)[2:]
+        print('save', hwnd, style, exstyle, wrect)
 
         widget = QWidget.createWindowContainer(QWindow.fromWinId(hwnd))
         widget.hwnd = hwnd  # 窗口句柄
         widget.phwnd = phwnd  # 父窗口句柄
         widget.style = style  # 窗口样式
         widget.exstyle = exstyle  # 窗口额外样式
+        widget.wrect = wrect  # 窗口位置
         self.layout().addWidget(widget)
 
+        widget.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
+        win32gui.SetParent(hwnd, int(self.winId()))
+
     def restore(self):
         """归还窗口"""
         # 有bug,归还后窗口没有了WS_VISIBLE样式,不可见
-        widget = self.layout().itemAt(3).widget()
-        print('restore', widget.hwnd, widget.style, widget.exstyle)
-        win32gui.SetParent(widget.hwnd, widget.phwnd)  # 让它返回它的父窗口
-        win32gui.SetWindowLong(
-            widget.hwnd, win32con.GWL_STYLE, widget.style | win32con.WS_VISIBLE)  # 恢复样式
-        win32gui.SetWindowLong(
-            widget.hwnd, win32con.GWL_EXSTYLE, widget.exstyle)  # 恢复样式
-        win32gui.ShowWindow(
-            widget.hwnd, win32con.SW_SHOW)  # 显示窗口
+        widget = self.layout().itemAt(4).widget()
+        hwnd, phwnd, style, exstyle, wrect = widget.hwnd, widget.phwnd, widget.style, widget.exstyle, widget.wrect
+        print('restore', hwnd, phwnd, style, exstyle, wrect)
         widget.close()
         self.layout().removeWidget(widget)  # 从布局中移出
         widget.deleteLater()
 
+        win32gui.SetParent(hwnd, phwnd)  # 让它返回它的父窗口
+        win32gui.SetWindowLong(hwnd, win32con.GWL_STYLE, style | win32con.WS_VISIBLE)  # 恢复样式
+        win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, exstyle)  # 恢复样式
+        win32gui.ShowWindow(hwnd, win32con.SW_SHOW)  # 显示窗口
+        win32gui.SetWindowPos(hwnd, 0, wrect[0], wrect[1], wrect[2], wrect[3], win32con.SWP_NOACTIVATE)
+
     def _enumWindows(self, hwnd, _):
         """遍历回调函数"""
         if hwnd == self.myhwnd:
@@ -102,7 +114,10 @@ def _enumWindows(self, hwnd, _):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+    import cgitb
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/FacePoints.py b/Demo/FacePoints.py
index 7ebea2b8..98b6165c 100644
--- a/Demo/FacePoints.py
+++ b/Demo/FacePoints.py
@@ -1,31 +1,33 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月29日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FacePoints
 @description: 人脸特征点
-'''
-from bz2 import BZ2Decompressor
+"""
 import cgitb
 import os
 import sys
+from bz2 import BZ2Decompressor
 
-from PyQt5.QtCore import QTimer, QUrl, QFile, QIODevice
-from PyQt5.QtGui import QImage, QPixmap
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication
 import cv2  # @UnresolvedImport
 import dlib
 import numpy
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QTimer, QUrl, QFile, QIODevice
+    from PyQt5.QtGui import QImage, QPixmap
+    from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication
+except ImportError:
+    from PySide2.QtCore import QTimer, QUrl, QFile, QIODevice
+    from PySide2.QtGui import QImage, QPixmap
+    from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PySide2.QtWidgets import QLabel, QMessageBox, QApplication
 
 DOWNSCALE = 4
 URL = 'http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2'
@@ -163,7 +165,7 @@ def onCapture(self):
 
 
 if __name__ == "__main__":
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = OpencvWidget()
     w.show()
diff --git a/Demo/FollowWindow.py b/Demo/FollowWindow.py
index 5e54287c..41ad74c7 100644
--- a/Demo/FollowWindow.py
+++ b/Demo/FollowWindow.py
@@ -4,23 +4,21 @@
 """
 Created on 2018年10月22日
 @author: Irony
-@site: https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FollowWindow
 @description: 跟随外部窗口
 """
 import os
 
-from PyQt5.QtCore import QTimer
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
 import win32gui
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
 
 
 class Window(QWidget):
@@ -53,7 +51,7 @@ def checkWindow(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     # 先检测是否已有记事本打开
     hwnd = win32gui.FindWindow('Notepad', None)
     print('hwnd', hwnd)
diff --git a/Demo/FramelessDialog.py b/Demo/FramelessDialog.py
index 4f3d551a..b55bef3f 100644
--- a/Demo/FramelessDialog.py
+++ b/Demo/FramelessDialog.py
@@ -4,20 +4,22 @@
 """
 Created on 2019年4月19日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FramelessDialog
 @description: 无边框圆角对话框 
 """
-from PyQt5.QtCore import Qt, QSize, QTimer
-from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget,\
-    QGraphicsDropShadowEffect, QPushButton, QGridLayout, QSpacerItem,\
-    QSizePolicy
 
-
-__Author__ = "Irony"
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt, QSize, QTimer
+    from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget, \
+        QGraphicsDropShadowEffect, QPushButton, QGridLayout, QSpacerItem, \
+        QSizePolicy, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt, QSize, QTimer
+    from PySide2.QtWidgets import QDialog, QVBoxLayout, QWidget, \
+        QGraphicsDropShadowEffect, QPushButton, QGridLayout, QSpacerItem, \
+        QSizePolicy, QApplication
 
 Stylesheet = """
 #Custom_Widget {
@@ -77,7 +79,7 @@ def sizeHint(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Dialog()
     w.exec_()
diff --git a/Demo/FramelessWindow.py b/Demo/FramelessWindow.py
index aef6c261..7e1608e2 100644
--- a/Demo/FramelessWindow.py
+++ b/Demo/FramelessWindow.py
@@ -1,22 +1,24 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit
 
-from Lib.FramelessWindow import FramelessWindow  # @UnresolvedImport
 
+"""
+Created on 2018年4月30日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: Test
+@description:
+"""
+
+try:
+    from PyQt5.QtGui import QIcon
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QApplication
+except ImportError:
+    from PySide2.QtGui import QIcon
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QApplication
 
-# Created on 2018年4月30日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: Test
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+from Lib.FramelessWindow import FramelessWindow  # @UnresolvedImport
 
 
 class MainWindow(QWidget):
@@ -63,7 +65,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyleSheet(StyleSheet)
     w = FramelessWindow()
diff --git a/Demo/GifCursor.py b/Demo/GifCursor.py
index 2dbdc527..0b1ea8f1 100644
--- a/Demo/GifCursor.py
+++ b/Demo/GifCursor.py
@@ -4,19 +4,18 @@
 """
 Created on 2020年3月13日
 @author: Irony
-@site: https://pyqt.site https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Demo.GifCursor
 @description: 
 """
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
 
-from Demo.Lib.QCursorGif import QCursorGif
+try:
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
+except ImportError:
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2020'
-__Version__ = 1.0
+from Lib.QCursorGif import QCursorGif
 
 
 class Window(QWidget, QCursorGif):
@@ -38,8 +37,9 @@ def __init__(self, *args, **kwargs):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/IsSignalConnected.py b/Demo/IsSignalConnected.py
index 4dbb19e0..7d534f3f 100644
--- a/Demo/IsSignalConnected.py
+++ b/Demo/IsSignalConnected.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年2月24日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: IsSignalConnected
 @description: 判断信号是否连接
@@ -13,13 +13,6 @@
 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
-
-
 class Window(QWidget):
 
     def __init__(self, *args, **kwargs):
@@ -34,11 +27,13 @@ def __init__(self, *args, **kwargs):
 
     def doTest(self):
         self.retView.append("""
-        # button1 clicked 是否连接: %s
-        # button2 clicked 是否连接: %s
+        # button1 clicked 是否连接: %s, %s
+        # button2 clicked 是否连接: %s, %s
         """ % (
             self.isSignalConnected(self.button1, 'clicked()'),
-            self.isSignalConnected(self.button2, 'clicked()')
+            self.button1.receivers(self.button1.clicked) > 0,
+            self.isSignalConnected(self.button2, 'clicked()'),
+            self.button2.receivers(self.button2.clicked) > 0,
         ))
 
     def isSignalConnected(self, obj, name):
@@ -57,6 +52,7 @@ def isSignalConnected(self, obj, name):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/Lib/Application.py b/Demo/Lib/Application.py
index c1216004..eed9376a 100644
--- a/Demo/Lib/Application.py
+++ b/Demo/Lib/Application.py
@@ -1,35 +1,34 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 单实例应用.Application
 @description: 
-'''
+"""
+
 from PyQt5.QtCore import QSharedMemory, pyqtSignal, Qt
 from PyQt5.QtNetwork import QLocalSocket, QLocalServer
 from PyQt5.QtWidgets import QApplication
 
 
-__version__ = "0.0.1"
-
 class SharedApplication(QApplication):
-    
+
     def __init__(self, *args, **kwargs):
         super(SharedApplication, self).__init__(*args, **kwargs)
         self._running = False
-        key = "SharedApplication" + __version__
+        key = "SharedApplication"
         self._memory = QSharedMemory(key, self)
-        
+
         isAttached = self._memory.isAttached()
         print("isAttached", isAttached)
         if isAttached:  # 如果进程附加在共享内存上
             detach = self._memory.detach()  # 取消进程附加在共享内存上
             print("detach", detach)
-        
+
         if self._memory.create(1) and self._memory.error() != QSharedMemory.AlreadyExists:
             # 创建共享内存,如果创建失败,则说明已经创建,否则未创建
             print("create ok")
@@ -37,14 +36,14 @@ def __init__(self, *args, **kwargs):
             print("create failed")
             self._running = True
             del self._memory
-    
+
     def isRunning(self):
         return self._running
 
+
 class QSingleApplication(QApplication):
-    
     messageReceived = pyqtSignal(str)
-    
+
     def __init__(self, *args, **kwargs):
         super(QSingleApplication, self).__init__(*args, **kwargs)
         appid = QApplication.applicationFilePath().lower().split("/")[-1]
@@ -56,13 +55,13 @@ def __init__(self, *args, **kwargs):
         self._socketIn = None
         self._socketOut = None
         self._running = False
-        
+
         # 先尝试连接
         self._socketOut = QLocalSocket(self)
         self._socketOut.connectToServer(self._socketName)
         self._socketOut.error.connect(self.handleError)
         self._running = self._socketOut.waitForConnected()
-        
+
         if not self._running:  # 程序未运行
             self._socketOut.close()
             del self._socketOut
@@ -70,20 +69,20 @@ def __init__(self, *args, **kwargs):
             self._socketServer.listen(self._socketName)
             self._socketServer.newConnection.connect(self._onNewConnection)
             self.aboutToQuit.connect(self.removeServer)
-    
+
     def handleError(self, message):
         print("handleError message: ", message)
-    
+
     def isRunning(self):
         return self._running
-    
+
     def activationWindow(self):
         return self._activationWindow
-    
+
     def setActivationWindow(self, activationWindow, activateOnMessage=True):
         self._activationWindow = activationWindow
         self._activateOnMessage = activateOnMessage
-    
+
     def activateWindow(self):
         if not self._activationWindow:
             return
@@ -91,7 +90,7 @@ def activateWindow(self):
             self._activationWindow.windowState() & ~Qt.WindowMinimized)
         self._activationWindow.raise_()
         self._activationWindow.activateWindow()
-    
+
     def sendMessage(self, message, msecs=5000):
         if not self._socketOut:
             return False
@@ -99,10 +98,10 @@ def sendMessage(self, message, msecs=5000):
             message = str(message).encode()
         self._socketOut.write(message)
         if not self._socketOut.waitForBytesWritten(msecs):
-            raise RuntimeError("Bytes not written within %ss" % 
+            raise RuntimeError("Bytes not written within %ss" %
                                (msecs / 1000.))
         return True
-    
+
     def _onNewConnection(self):
         if self._socketIn:
             self._socketIn.readyRead.disconnect(self._onReadyRead)
@@ -112,7 +111,7 @@ def _onNewConnection(self):
         self._socketIn.readyRead.connect(self._onReadyRead)
         if self._activateOnMessage:
             self.activateWindow()
-    
+
     def _onReadyRead(self):
         while 1:
             message = self._socketIn.readLine()
@@ -123,4 +122,4 @@ def _onReadyRead(self):
 
     def removeServer(self):
         self._socketServer.close()
-        self._socketServer.removeServer(self._socketName)
\ No newline at end of file
+        self._socketServer.removeServer(self._socketName)
diff --git a/Demo/Lib/FramelessWindow.py b/Demo/Lib/FramelessWindow.py
index 04afccb9..b4fa3e0e 100644
--- a/Demo/Lib/FramelessWindow.py
+++ b/Demo/Lib/FramelessWindow.py
@@ -1,26 +1,29 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from PyQt5.QtCore import Qt, pyqtSignal, QPoint
-from PyQt5.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel,\
-    QSpacerItem, QSizePolicy, QPushButton
 
 
-# Created on 2018年4月30日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: FramelessWindow
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+"""
+Created on 2018年4月30日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: FramelessWindow
+@description:
+"""
 
+try:
+    from PyQt5.QtCore import Qt, pyqtSignal, QPoint
+    from PyQt5.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \
+        QSpacerItem, QSizePolicy, QPushButton
+except ImportError:
+    from PySide2.QtCore import Qt, Signal as pyqtSignal, QPoint
+    from PySide2.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \
+        QSpacerItem, QSizePolicy, QPushButton
 
-class TitleBar(QWidget):
 
+class TitleBar(QWidget):
     # 窗口最小化信号
     windowMinimumed = pyqtSignal()
     # 窗口最大化信号
@@ -48,7 +51,7 @@ def __init__(self, *args, **kwargs):
         layout.setContentsMargins(0, 0, 0, 0)
         # 窗口图标
         self.iconLabel = QLabel(self)
-#         self.iconLabel.setScaledContents(True)
+        #         self.iconLabel.setScaledContents(True)
         layout.addWidget(self.iconLabel)
         # 窗口标题
         self.titleLabel = QLabel(self)
@@ -138,7 +141,6 @@ def mouseMoveEvent(self, event):
 
 
 class FramelessWindow(QWidget):
-
     # 四周边距
     Margins = 5
 
diff --git a/Demo/Lib/QCursorGif.py b/Demo/Lib/QCursorGif.py
index 0ff9959c..707e7479 100644
--- a/Demo/Lib/QCursorGif.py
+++ b/Demo/Lib/QCursorGif.py
@@ -4,19 +4,20 @@
 """
 Created on 2020年3月13日
 @author: Irony
-@site: https://pyqt.site https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Demo.Lib.QCursorGif
 @description: 
 """
-from PyQt5.QtCore import QTimer, Qt
-from PyQt5.QtGui import QCursor, QPixmap
-from PyQt5.QtWidgets import QApplication
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2020'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QTimer, Qt
+    from PyQt5.QtGui import QCursor, QPixmap
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import QTimer, Qt
+    from PySide2.QtGui import QCursor, QPixmap
+    from PySide2.QtWidgets import QApplication
 
 
 class QCursorGif:
@@ -55,4 +56,4 @@ def setCursorTimeout(self, timeout):
 
     def setOldCursor(self, parent=None):
         self._oldCursor = (parent.cursor() or Qt.ArrowCursor) if parent else (
-            QApplication.instance().overrideCursor() or Qt.ArrowCursor)
+                QApplication.instance().overrideCursor() or Qt.ArrowCursor)
diff --git a/Demo/Lib/UiNotify.py b/Demo/Lib/UiNotify.py
index 20d26aa4..38f1ad92 100644
--- a/Demo/Lib/UiNotify.py
+++ b/Demo/Lib/UiNotify.py
@@ -6,46 +6,50 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore, QtGui, QtWidgets
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
 
 class Ui_NotifyForm(object):
     def setupUi(self, NotifyForm):
         NotifyForm.setObjectName("NotifyForm")
         NotifyForm.resize(300, 200)
         NotifyForm.setStyleSheet("QWidget#widgetTitle {\n"
-"    background-color: rgb(76, 169, 106);\n"
-"}\n"
-"QWidget#widgetBottom {\n"
-"    border-top-style: solid;\n"
-"    border-top-width: 2px;\n"
-"    border-top-color: rgb(185, 218, 201);\n"
-"}\n"
-"QLabel#labelTitle {\n"
-"    color: rgb(255, 255, 255);\n"
-"}\n"
-"QLabel#labelContent {\n"
-"    padding: 5px;\n"
-"}\n"
-"QPushButton {\n"
-"    border: none;\n"
-"    background: transparent;\n"
-"}\n"
-"QPushButton#buttonClose {\n"
-"    font-family: \"webdings\";\n"
-"    color: rgb(255, 255, 255);\n"
-"}\n"
-"QPushButton#buttonClose:hover {\n"
-"    background-color: rgb(212, 64, 39);\n"
-"}\n"
-"QPushButton#buttonView {\n"
-"    color: rgb(255, 255, 255);\n"
-"    border-radius: 5px;\n"
-"    border: solid 1px rgb(76, 169, 106);\n"
-"    background-color: rgb(76, 169, 106);\n"
-"}\n"
-"QPushButton#buttonView:hover {\n"
-"    color: rgb(0, 0, 0);\n"
-"}")
+                                 "    background-color: rgb(76, 169, 106);\n"
+                                 "}\n"
+                                 "QWidget#widgetBottom {\n"
+                                 "    border-top-style: solid;\n"
+                                 "    border-top-width: 2px;\n"
+                                 "    border-top-color: rgb(185, 218, 201);\n"
+                                 "}\n"
+                                 "QLabel#labelTitle {\n"
+                                 "    color: rgb(255, 255, 255);\n"
+                                 "}\n"
+                                 "QLabel#labelContent {\n"
+                                 "    padding: 5px;\n"
+                                 "}\n"
+                                 "QPushButton {\n"
+                                 "    border: none;\n"
+                                 "    background: transparent;\n"
+                                 "}\n"
+                                 "QPushButton#buttonClose {\n"
+                                 "    font-family: \"webdings\";\n"
+                                 "    color: rgb(255, 255, 255);\n"
+                                 "}\n"
+                                 "QPushButton#buttonClose:hover {\n"
+                                 "    background-color: rgb(212, 64, 39);\n"
+                                 "}\n"
+                                 "QPushButton#buttonView {\n"
+                                 "    color: rgb(255, 255, 255);\n"
+                                 "    border-radius: 5px;\n"
+                                 "    border: solid 1px rgb(76, 169, 106);\n"
+                                 "    background-color: rgb(76, 169, 106);\n"
+                                 "}\n"
+                                 "QPushButton#buttonView:hover {\n"
+                                 "    color: rgb(0, 0, 0);\n"
+                                 "}")
         self.verticalLayout = QtWidgets.QVBoxLayout(NotifyForm)
         self.verticalLayout.setContentsMargins(0, 0, 0, 0)
         self.verticalLayout.setSpacing(6)
@@ -61,7 +65,8 @@ def setupUi(self, NotifyForm):
         self.labelTitle.setText("")
         self.labelTitle.setObjectName("labelTitle")
         self.horizontalLayout_3.addWidget(self.labelTitle)
-        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding,
+                                           QtWidgets.QSizePolicy.Minimum)
         self.horizontalLayout_3.addItem(spacerItem)
         self.buttonClose = QtWidgets.QPushButton(self.widgetTitle)
         self.buttonClose.setMinimumSize(QtCore.QSize(26, 26))
@@ -80,7 +85,8 @@ def setupUi(self, NotifyForm):
         self.horizontalLayout.setContentsMargins(0, 5, 5, 5)
         self.horizontalLayout.setSpacing(0)
         self.horizontalLayout.setObjectName("horizontalLayout")
-        spacerItem1 = QtWidgets.QSpacerItem(170, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        spacerItem1 = QtWidgets.QSpacerItem(170, 20, QtWidgets.QSizePolicy.Expanding,
+                                            QtWidgets.QSizePolicy.Minimum)
         self.horizontalLayout.addItem(spacerItem1)
         self.buttonView = QtWidgets.QPushButton(self.widgetBottom)
         self.buttonView.setMinimumSize(QtCore.QSize(75, 25))
@@ -102,10 +108,10 @@ def retranslateUi(self, NotifyForm):
 
 if __name__ == "__main__":
     import sys
+
     app = QtWidgets.QApplication(sys.argv)
     NotifyForm = QtWidgets.QWidget()
     ui = Ui_NotifyForm()
     ui.setupUi(NotifyForm)
     NotifyForm.show()
     sys.exit(app.exec_())
-
diff --git a/Demo/Lib/qpropmapper.py b/Demo/Lib/qpropmapper.py
new file mode 100644
index 00000000..9064f108
--- /dev/null
+++ b/Demo/Lib/qpropmapper.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/05
+@file: qpropertymapper.py
+@description:
+"""
+
+from typing import Any, Union
+
+from box import Box
+
+try:
+    from PyQt5.QtCore import (
+        QDateTime,
+        QMetaProperty,
+        QObject,
+        Qt,
+    )
+    from PyQt5.QtCore import (
+        pyqtSignal as Signal,
+    )
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import (
+        QDateTime,
+        QMetaProperty,
+        QObject,
+        Qt,
+        Signal,
+    )
+    from PySide2.QtWidgets import QWidget
+
+
+class DictBox(Box):
+    def keys(self, dotted: bool = False):
+        if not dotted:
+            return super().keys()
+
+        if not self._box_config["box_dots"]:
+            raise Exception(
+                "Cannot return dotted keys as this Box does not have `box_dots` enabled"
+            )
+
+        keys = set()
+        for key, value in self.items():
+            added = False
+            if isinstance(key, str):
+                if isinstance(value, Box):
+                    for sub_key in value.keys(dotted=True):
+                        keys.add(f"{key}.{sub_key}")
+                        added = True
+                if not added:
+                    keys.add(key)
+        return sorted(keys, key=lambda x: str(x))
+
+
+class QPropertyMapper(QObject):
+    Verbose = False
+    propertyChanged = Signal(int, str, object)
+    Signal = (
+        "dateTimeChanged",
+        "currentTextChanged",
+        "valueChanged",
+        "toggled",
+        "textChanged",
+    )
+    Props = (
+        "dateTime",
+        "html",
+        "plainText",
+        "currentText",
+        "checked",
+        "value",
+        "text",
+    )
+
+    def __init__(self, *args, **kwargs):
+        data = kwargs.pop("data", {})
+        super().__init__(*args, **kwargs)
+        self._widgetKey = {}
+        self._keyWidget = {}
+        self.propertyChanged.connect(self.onPropertyChanged)
+        self.loadData(data, clear=False)
+
+    def __enter__(self):
+        self.__log("[__enter__]: block signals")
+        self.blockSignals(True)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.__log("[__exit__]: unblock signals")
+        self.blockSignals(False)
+
+    def event(self, ev) -> bool:
+        if ev.type() == ev.DynamicPropertyChange:
+            name = bytes(ev.propertyName()).decode()
+            value = self.property(name)
+            self.propertyChanged.emit(value is not None, name, value)
+        return super().event(ev)
+
+    def clear(self):
+        self.__log("[clear]: clear datas")
+        with self:
+            keys = self.dynamicPropertyNames().copy()
+            for key in keys:
+                self.setProperty(bytes(key).decode(), None)
+            # clear binds
+
+    def loadData(self, data: dict, clear: bool = False):
+        self.__log("[loadData]: load data")
+        if clear:
+            self.clear()
+        data = DictBox(data, default_box=True, box_dots=True)
+        for key, value in data.items(True):
+            self.setProperty(str(key), value)
+
+    def toDict(self, raw=True) -> dict:
+        data = DictBox(default_box=True, box_dots=True)
+        for key in self.dynamicPropertyNames():
+            key = bytes(key).decode()
+            try:
+                data[key] = self.property(key)
+            except Exception as e:
+                self.__log(f"[toDict]: {e}")
+        if raw:
+            return data.to_dict()
+        return data
+
+    def toJson(self, indent=None, **kwargs) -> str:
+        return self.toDict(False).to_json(indent=indent, **kwargs)
+
+    def getProperty(
+        self, widget: QWidget, prop: str = ""
+    ) -> Union[QMetaProperty, None]:
+        """获取控件的对应属性
+
+        Args:
+            widget (QWidget): 控件对象
+            prop (str, optional): 指定的属性. 默认为 ""
+
+        Returns:
+            Union[QMetaProperty, None]: 返回属性对象
+        """
+        qmo = widget.metaObject()
+        props = [prop] if prop else self.Props
+
+        for prop in props:
+            idx = qmo.indexOfProperty(prop)
+            if idx > -1:
+                p = qmo.property(idx)
+                if p.isReadable() and p.isWritable():
+                    self.__log(
+                        f"[getProperty]: get prop: {prop} of widget: {self.__widgetInfo(widget)}"
+                    )
+                    return p
+
+        return None
+
+    def bind(self, widget: QWidget, key: str, default: Any = None, prop: str = ""):
+        # 1. 记录widget和key
+        if widget not in self._widgetKey:
+            self.__log(
+                f"[bind]: bind key: {key} to widget: {self.__widgetInfo(widget)}"
+            )
+            self._widgetKey[widget] = {
+                "key": key,
+                "prop": self.getProperty(widget, prop),
+            }
+            # 2. 设置控件的默认值
+            with self:
+                self.__setValue(widget, key, default)
+
+        # 3. 记录所有关联的widget
+        if key not in self._keyWidget:
+            self._keyWidget[key] = set()
+        self._keyWidget[key].add(widget)
+
+        # 4. 绑定widget的相关信号
+        for signal in self.Signal:
+            signal = getattr(widget, signal, None)
+            if signal:
+                self.__log(
+                    f"[bind]: connect key: {key}, signal: {signal} of widget: {self.__widgetInfo(widget)}"
+                )
+                signal.connect(self.__setData)
+                break
+
+    def __log(self, *args):
+        if self.Verbose:
+            print(*args)
+
+    def __widgetInfo(self, widget: QWidget) -> Union[str, None]:
+        try:
+            return f"<{widget.__class__.__name__}(name={widget.objectName()}) {hex(id(widget))}>"
+        except Exception:
+            return None
+
+    def __getDefault(self, widget: QWidget, default: Any = None):
+        """获取控件的默认值
+
+        Args:
+            widget (QWidget): 控件对象
+            default (Any, optional): 默认值. 默认为None
+
+        Returns:
+            Any: 默认值
+        """
+        if default:
+            return default
+
+        # 1. 从记录表中获取窗口的属性对象
+        prop: Union[QMetaProperty, None] = self._widgetKey[widget]["prop"]
+        if prop is None:
+            return None
+
+        # 2. 获取属性值并进行一些转换
+        value = prop.read(widget)
+        if value is not None:
+            if prop.name() == "dateTime":
+                return value.toString("yyyy-MM-dd HH:mm:ss")
+            elif prop.name() == "html" and len(widget.property("plainText")) == 0:
+                return ""
+            return value
+
+        return None
+
+    def __setValue(
+        self,
+        widget: QWidget,
+        key: str,
+        default: Any = None,
+        updated: bool = False,
+    ):
+        value: Any = default
+
+        # 1. 首次同步widget属性值到mapper
+        if not updated:
+            value = self.__getDefault(widget, default)
+            self.__log(
+                f"[__setValue]: setProperty key: {key}, value: {value} of widget: {self.__widgetInfo(widget)}"
+            )
+            self.setProperty(key, value)
+
+        if value is None:
+            return
+
+        # 2. 设置widget的属性值
+        prop: Union[QMetaProperty, None] = self._widgetKey[widget]["prop"]
+        if prop is not None:
+            if prop.name() == "dateTime" and isinstance(value, str):
+                value = QDateTime.fromString(value, Qt.ISODate)
+            self.__log(
+                f"[__setValue]: update key: {key}, value: {value} of widget: {self.__widgetInfo(widget)}"
+            )
+            prop.write(widget, value)
+
+    def __setData(self, *args, **kwargs):
+        """控件值发生变化时,更新关联控件以及设置mapper数据"""
+
+        # 1. 获取发送者widget关联的key和prop
+        sender = self.sender()
+        info = self._widgetKey.get(sender, {})
+        key = info.get("key", None)
+        prop: Union[QMetaProperty, None] = info.get("prop", None)
+        if key is None or prop is None:
+            self.__log(
+                f"[__setData]: sender {self.__widgetInfo(sender)} has no key or prop"
+            )
+            return
+
+        # 2. 读取发送者widget的属性值
+        value = prop.read(sender)
+        if value is not None:
+            if prop.name() == "dateTime":
+                value = value.toString("yyyy-MM-dd HH:mm:ss")
+            elif prop.name() == "html" and len(sender.property("plainText")) == 0:
+                value = ""
+
+        if value is None:
+            return
+
+        # 更新关联的key
+        self.__log(
+            f"[__setData]: update key: {key}, value: {value} from widget: {self.__widgetInfo(sender)}"
+        )
+        self.setProperty(key, value)
+
+    def onPropertyChanged(self, op: int, key: str, value: Any):
+        """mapper属性值发生变化时,更新关联的widget
+
+        Args:
+            op (int): 操作类型(1=添加,0=删除)
+            key (str): 属性名字
+            value (Any): 属性值
+        """
+        if op != 1:
+            return
+
+        # 1. 获取mapper中key对应的值
+        value = self.property(key)
+        if value is None:
+            return
+
+        # 更新关联的widget
+        for widget in self._keyWidget.get(key, []):
+            self.__log(
+                f"[onPropertyChanged]: set key: {key}, value: {value} of widget: {self.__widgetInfo(widget)}"
+            )
+            try:
+                self.__setValue(widget, key, value, updated=True)
+            except Exception as e:
+                self.__log(f"[onPropertyChanged]: __setValue failed: {e}")
diff --git a/Demo/Lib/serializewidget.py b/Demo/Lib/serializewidget.py
new file mode 100644
index 00000000..10730198
--- /dev/null
+++ b/Demo/Lib/serializewidget.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'serializewidget.ui'
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_SerializeWidget(object):
+    def setupUi(self, SerializeWidget):
+        SerializeWidget.setObjectName("SerializeWidget")
+        SerializeWidget.resize(800, 600)
+        self.verticalLayout = QtWidgets.QVBoxLayout(SerializeWidget)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.splitter = QtWidgets.QSplitter(SerializeWidget)
+        self.splitter.setOrientation(QtCore.Qt.Horizontal)
+        self.splitter.setChildrenCollapsible(False)
+        self.splitter.setObjectName("splitter")
+        self.groupBox = QtWidgets.QGroupBox(self.splitter)
+        self.groupBox.setMaximumSize(QtCore.QSize(500, 16777215))
+        self.groupBox.setObjectName("groupBox")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.editJsonView = QtWidgets.QTextEdit(self.groupBox)
+        self.editJsonView.setObjectName("editJsonView")
+        self.horizontalLayout.addWidget(self.editJsonView)
+        self.groupBox_2 = QtWidgets.QGroupBox(self.splitter)
+        self.groupBox_2.setObjectName("groupBox_2")
+        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2)
+        self.verticalLayout_3.setObjectName("verticalLayout_3")
+        self.tabWidget = QtWidgets.QTabWidget(self.groupBox_2)
+        self.tabWidget.setObjectName("tabWidget")
+        self.tab = QtWidgets.QWidget()
+        self.tab.setObjectName("tab")
+        self.gridLayout = QtWidgets.QGridLayout(self.tab)
+        self.gridLayout.setObjectName("gridLayout")
+        self.doubleSpinBox = QtWidgets.QDoubleSpinBox(self.tab)
+        self.doubleSpinBox.setObjectName("doubleSpinBox")
+        self.gridLayout.addWidget(self.doubleSpinBox, 2, 1, 1, 1)
+        self.checkBox = QtWidgets.QCheckBox(self.tab)
+        self.checkBox.setObjectName("checkBox")
+        self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1)
+        self.radioButton = QtWidgets.QRadioButton(self.tab)
+        self.radioButton.setObjectName("radioButton")
+        self.gridLayout.addWidget(self.radioButton, 0, 0, 1, 1)
+        self.spinBox = QtWidgets.QSpinBox(self.tab)
+        self.spinBox.setObjectName("spinBox")
+        self.gridLayout.addWidget(self.spinBox, 2, 0, 1, 1)
+        self.lineEdit = QtWidgets.QLineEdit(self.tab)
+        self.lineEdit.setObjectName("lineEdit")
+        self.gridLayout.addWidget(self.lineEdit, 5, 0, 1, 2)
+        self.dateTimeEdit = QtWidgets.QDateTimeEdit(self.tab)
+        self.dateTimeEdit.setObjectName("dateTimeEdit")
+        self.gridLayout.addWidget(self.dateTimeEdit, 4, 0, 1, 2)
+        self.comboBox = QtWidgets.QComboBox(self.tab)
+        self.comboBox.setObjectName("comboBox")
+        self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
+        self.timeEdit = QtWidgets.QTimeEdit(self.tab)
+        self.timeEdit.setObjectName("timeEdit")
+        self.gridLayout.addWidget(self.timeEdit, 3, 0, 1, 1)
+        self.dateEdit = QtWidgets.QDateEdit(self.tab)
+        self.dateEdit.setObjectName("dateEdit")
+        self.gridLayout.addWidget(self.dateEdit, 3, 1, 1, 1)
+        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.gridLayout.addItem(spacerItem, 0, 3, 1, 1)
+        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.gridLayout.addItem(spacerItem1, 6, 0, 1, 1)
+        self.tabWidget.addTab(self.tab, "")
+        self.tab_2 = QtWidgets.QWidget()
+        self.tab_2.setObjectName("tab_2")
+        self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_2)
+        self.verticalLayout_4.setObjectName("verticalLayout_4")
+        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
+        self.plainTextEdit.setObjectName("plainTextEdit")
+        self.verticalLayout_4.addWidget(self.plainTextEdit)
+        self.textEdit = QtWidgets.QTextEdit(self.tab_2)
+        self.textEdit.setObjectName("textEdit")
+        self.verticalLayout_4.addWidget(self.textEdit)
+        self.tabWidget.addTab(self.tab_2, "")
+        self.tab_3 = QtWidgets.QWidget()
+        self.tab_3.setObjectName("tab_3")
+        self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_3)
+        self.gridLayout_2.setObjectName("gridLayout_2")
+        self.horizontalSlider = QtWidgets.QSlider(self.tab_3)
+        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
+        self.horizontalSlider.setObjectName("horizontalSlider")
+        self.gridLayout_2.addWidget(self.horizontalSlider, 1, 0, 1, 1)
+        self.spinBox_2 = QtWidgets.QSpinBox(self.tab_3)
+        self.spinBox_2.setObjectName("spinBox_2")
+        self.gridLayout_2.addWidget(self.spinBox_2, 0, 0, 1, 1)
+        spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.gridLayout_2.addItem(spacerItem2, 2, 0, 1, 1)
+        self.progressBar = QtWidgets.QProgressBar(self.tab_3)
+        self.progressBar.setProperty("value", 0)
+        self.progressBar.setOrientation(QtCore.Qt.Vertical)
+        self.progressBar.setObjectName("progressBar")
+        self.gridLayout_2.addWidget(self.progressBar, 0, 1, 3, 1)
+        self.verticalSlider = QtWidgets.QSlider(self.tab_3)
+        self.verticalSlider.setOrientation(QtCore.Qt.Vertical)
+        self.verticalSlider.setObjectName("verticalSlider")
+        self.gridLayout_2.addWidget(self.verticalSlider, 0, 2, 3, 1)
+        self.tabWidget.addTab(self.tab_3, "")
+        self.tab_4 = QtWidgets.QWidget()
+        self.tab_4.setObjectName("tab_4")
+        self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_4)
+        self.gridLayout_3.setObjectName("gridLayout_3")
+        self.groupBox_3 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_3.setObjectName("groupBox_3")
+        self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_3)
+        self.verticalLayout_6.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_6.setObjectName("verticalLayout_6")
+        self.listView = QtWidgets.QListView(self.groupBox_3)
+        self.listView.setObjectName("listView")
+        self.verticalLayout_6.addWidget(self.listView)
+        self.gridLayout_3.addWidget(self.groupBox_3, 0, 0, 1, 1)
+        self.groupBox_4 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_4.setObjectName("groupBox_4")
+        self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_4)
+        self.verticalLayout_7.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_7.setObjectName("verticalLayout_7")
+        self.treeView = QtWidgets.QTreeView(self.groupBox_4)
+        self.treeView.setObjectName("treeView")
+        self.verticalLayout_7.addWidget(self.treeView)
+        self.gridLayout_3.addWidget(self.groupBox_4, 0, 1, 1, 1)
+        self.groupBox_5 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_5.setObjectName("groupBox_5")
+        self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_5)
+        self.verticalLayout_5.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_5.setObjectName("verticalLayout_5")
+        self.tableView = QtWidgets.QTableView(self.groupBox_5)
+        self.tableView.setObjectName("tableView")
+        self.verticalLayout_5.addWidget(self.tableView)
+        self.gridLayout_3.addWidget(self.groupBox_5, 1, 0, 1, 2)
+        self.tabWidget.addTab(self.tab_4, "")
+        self.verticalLayout_3.addWidget(self.tabWidget)
+        self.verticalLayout.addWidget(self.splitter)
+
+        self.retranslateUi(SerializeWidget)
+        self.tabWidget.setCurrentIndex(0)
+        QtCore.QMetaObject.connectSlotsByName(SerializeWidget)
+
+    def retranslateUi(self, SerializeWidget):
+        _translate = QtCore.QCoreApplication.translate
+        SerializeWidget.setWindowTitle(_translate("SerializeWidget", "TestSerialize"))
+        self.groupBox.setTitle(_translate("SerializeWidget", "Json View"))
+        self.groupBox_2.setTitle(_translate("SerializeWidget", "Widget View"))
+        self.checkBox.setText(_translate("SerializeWidget", "CheckBox"))
+        self.radioButton.setText(_translate("SerializeWidget", "RadioButton"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("SerializeWidget", "Input"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("SerializeWidget", "Edit"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("SerializeWidget", "Correlation"))
+        self.groupBox_3.setTitle(_translate("SerializeWidget", "ListView"))
+        self.groupBox_4.setTitle(_translate("SerializeWidget", "TreeView"))
+        self.groupBox_5.setTitle(_translate("SerializeWidget", "TableView"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("SerializeWidget", "View"))
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    SerializeWidget = QtWidgets.QWidget()
+    ui = Ui_SerializeWidget()
+    ui.setupUi(SerializeWidget)
+    SerializeWidget.show()
+    sys.exit(app.exec_())
diff --git a/Demo/Lib/ui_frameless.py b/Demo/Lib/ui_frameless.py
new file mode 100644
index 00000000..94bc7b7a
--- /dev/null
+++ b/Demo/Lib/ui_frameless.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'frameless.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_FormFrameless(object):
+    def setupUi(self, FormFrameless):
+        FormFrameless.setObjectName("FormFrameless")
+        FormFrameless.resize(400, 300)
+        self.verticalLayout = QtWidgets.QVBoxLayout(FormFrameless)
+        self.verticalLayout.setContentsMargins(3, 3, 3, 3)
+        self.verticalLayout.setSpacing(0)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.widgetTitleBar = QtWidgets.QWidget(FormFrameless)
+        font = QtGui.QFont()
+        font.setFamily("Symbola")
+        self.widgetTitleBar.setFont(font)
+        self.widgetTitleBar.setObjectName("widgetTitleBar")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetTitleBar)
+        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+        self.horizontalLayout.setSpacing(0)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        spacerItem = QtWidgets.QSpacerItem(253, 20, QtWidgets.QSizePolicy.Expanding,
+                                           QtWidgets.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem)
+        self.buttonMinimum = QtWidgets.QPushButton(self.widgetTitleBar)
+        self.buttonMinimum.setMinimumSize(QtCore.QSize(36, 36))
+        self.buttonMinimum.setMaximumSize(QtCore.QSize(36, 36))
+        font = QtGui.QFont()
+        font.setFamily("webdings")
+        self.buttonMinimum.setFont(font)
+        self.buttonMinimum.setObjectName("buttonMinimum")
+        self.horizontalLayout.addWidget(self.buttonMinimum)
+        self.buttonMaximum = QtWidgets.QPushButton(self.widgetTitleBar)
+        self.buttonMaximum.setMinimumSize(QtCore.QSize(36, 36))
+        self.buttonMaximum.setMaximumSize(QtCore.QSize(36, 36))
+        font = QtGui.QFont()
+        font.setFamily("webdings")
+        self.buttonMaximum.setFont(font)
+        self.buttonMaximum.setObjectName("buttonMaximum")
+        self.horizontalLayout.addWidget(self.buttonMaximum)
+        self.buttonNormal = QtWidgets.QPushButton(self.widgetTitleBar)
+        self.buttonNormal.setMinimumSize(QtCore.QSize(36, 36))
+        self.buttonNormal.setMaximumSize(QtCore.QSize(36, 36))
+        font = QtGui.QFont()
+        font.setFamily("webdings")
+        self.buttonNormal.setFont(font)
+        self.buttonNormal.setObjectName("buttonNormal")
+        self.horizontalLayout.addWidget(self.buttonNormal)
+        self.buttonClose = QtWidgets.QPushButton(self.widgetTitleBar)
+        self.buttonClose.setMinimumSize(QtCore.QSize(36, 36))
+        self.buttonClose.setMaximumSize(QtCore.QSize(36, 36))
+        font = QtGui.QFont()
+        font.setFamily("webdings")
+        self.buttonClose.setFont(font)
+        self.buttonClose.setObjectName("buttonClose")
+        self.horizontalLayout.addWidget(self.buttonClose)
+        self.verticalLayout.addWidget(self.widgetTitleBar)
+        self.textEdit = QtWidgets.QTextEdit(FormFrameless)
+        self.textEdit.setFrameShape(QtWidgets.QFrame.NoFrame)
+        self.textEdit.setObjectName("textEdit")
+        self.verticalLayout.addWidget(self.textEdit)
+        self.verticalLayout.setStretch(1, 1)
+
+        self.retranslateUi(FormFrameless)
+        QtCore.QMetaObject.connectSlotsByName(FormFrameless)
+
+    def retranslateUi(self, FormFrameless):
+        _translate = QtCore.QCoreApplication.translate
+        FormFrameless.setWindowTitle(_translate("FormFrameless", "Form"))
+        self.buttonMinimum.setToolTip(_translate("FormFrameless", "Minimum"))
+        self.buttonMinimum.setText(_translate("FormFrameless", "0"))
+        self.buttonMaximum.setToolTip(_translate("FormFrameless", "Maximum"))
+        self.buttonMaximum.setText(_translate("FormFrameless", "1"))
+        self.buttonNormal.setToolTip(_translate("FormFrameless", "Normal"))
+        self.buttonNormal.setText(_translate("FormFrameless", "2"))
+        self.buttonClose.setToolTip(_translate("FormFrameless", "Close"))
+        self.buttonClose.setText(_translate("FormFrameless", "r"))
+        self.textEdit.setHtml(_translate("FormFrameless",
+                                         "\n"
+                                         "
frameless window with move and resize
"))
+
+
+if __name__ == "__main__":
+    import sys
+
+    app = QtWidgets.QApplication(sys.argv)
+    FormFrameless = QtWidgets.QWidget()
+    ui = Ui_FormFrameless()
+    ui.setupUi(FormFrameless)
+    FormFrameless.show()
+    sys.exit(app.exec_())
diff --git a/Demo/NativeEvent.py b/Demo/NativeEvent.py
index 2ccf1693..17312405 100644
--- a/Demo/NativeEvent.py
+++ b/Demo/NativeEvent.py
@@ -2,42 +2,42 @@
 # -*- coding: utf-8 -*-
 """Created on 2018年8月2日
 author: Irony
-site: https://pyqt5.com , https://github.com/892768447
+site: https://pyqt.site , https://github.com/PyQt5
 email: 892768447@qq.com
 file: win无边框调整大小
 description:
 """
 
-from ctypes.wintypes import POINT
 import ctypes.wintypes
+from ctypes.wintypes import POINT
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QWidget, QPushButton
-from PyQt5.QtWinExtras import QtWin
 import win32api
 import win32con
 import win32gui
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QCursor
+    from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
+    from PyQt5.QtWinExtras import QtWin
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QCursor
+    from PySide2.QtWidgets import QApplication, QPushButton, QWidget
+    from PySide2.QtWinExtras import QtWin
 
 
 class MINMAXINFO(ctypes.Structure):
     _fields_ = [
-        ("ptReserved",      POINT),
-        ("ptMaxSize",       POINT),
-        ("ptMaxPosition",   POINT),
-        ("ptMinTrackSize",  POINT),
-        ("ptMaxTrackSize",  POINT),
+        ("ptReserved", POINT),
+        ("ptMaxSize", POINT),
+        ("ptMaxPosition", POINT),
+        ("ptMinTrackSize", POINT),
+        ("ptMaxTrackSize", POINT),
     ]
 
 
 class Window(QWidget):
-
     BorderWidth = 5
 
     def __init__(self, *args, **kwargs):
@@ -45,16 +45,15 @@ def __init__(self, *args, **kwargs):
         # 主屏幕的可用大小(去掉任务栏)
         self._rect = QApplication.instance().desktop().availableGeometry(self)
         self.resize(800, 600)
-        self.setWindowFlags(Qt.Window
-                            | Qt.FramelessWindowHint
-                            | Qt.WindowSystemMenuHint
-                            | Qt.WindowMinimizeButtonHint
-                            | Qt.WindowMaximizeButtonHint
-                            | Qt.WindowCloseButtonHint)
+        self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint |
+                            Qt.WindowSystemMenuHint |
+                            Qt.WindowMinimizeButtonHint |
+                            Qt.WindowMaximizeButtonHint |
+                            Qt.WindowCloseButtonHint)
         # 增加薄边框
         style = win32gui.GetWindowLong(int(self.winId()), win32con.GWL_STYLE)
-        win32gui.SetWindowLong(
-            int(self.winId()), win32con.GWL_STYLE, style | win32con.WS_THICKFRAME)
+        win32gui.SetWindowLong(int(self.winId()), win32con.GWL_STYLE,
+                               style | win32con.WS_THICKFRAME)
 
         if QtWin.isCompositionEnabled():
             # 加上 Aero 边框阴影
@@ -67,8 +66,9 @@ def nativeEvent(self, eventType, message):
         if eventType == "windows_generic_MSG":
             msg = ctypes.wintypes.MSG.from_address(message.__int__())
             # 获取鼠标移动经过时的坐标
-            x = win32api.LOWORD(msg.lParam) - self.frameGeometry().x()
-            y = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()
+            pos = QCursor.pos()
+            x = pos.x() - self.frameGeometry().x()
+            y = pos.y() - self.frameGeometry().y()
             # 判断鼠标位置是否有其它控件
             if self.childAt(x, y) != None:
                 return retval, result
@@ -77,8 +77,8 @@ def nativeEvent(self, eventType, message):
                 return True, 0
             if msg.message == win32con.WM_GETMINMAXINFO:
                 # 当窗口位置改变或者大小改变时会触发该消息
-                info = ctypes.cast(
-                    msg.lParam, ctypes.POINTER(MINMAXINFO)).contents
+                info = ctypes.cast(msg.lParam,
+                                   ctypes.POINTER(MINMAXINFO)).contents
                 # 修改最大化的窗口大小为主屏幕的可用大小
                 info.ptMaxSize.x = self._rect.width()
                 info.ptMaxSize.y = self._rect.height()
@@ -121,7 +121,7 @@ def nativeEvent(self, eventType, message):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     btn = QPushButton('exit', w, clicked=app.quit)
diff --git a/Demo/NewFramelessWindow.py b/Demo/NewFramelessWindow.py
new file mode 100644
index 00000000..75dcc742
--- /dev/null
+++ b/Demo/NewFramelessWindow.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2018年4月30日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: NewFramelessWindow
+@description:
+"""
+
+import sys
+
+try:
+    from PyQt5.QtCore import QEvent, QObject, QPoint, Qt, QTimer
+    from PyQt5.QtGui import QColor, QMouseEvent, QPainter, QWindow
+    from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget
+except ImportError:
+    from PySide2.QtCore import QTimer, Qt, QEvent, QObject, QPoint
+    from PySide2.QtGui import QWindow, QPainter, QColor, QMouseEvent
+    from PySide2.QtWidgets import QApplication, QWidget, QMessageBox
+
+from Lib.ui_frameless import Ui_FormFrameless
+
+
+class FramelessObject(QObject):
+    Margins = 3  # 边缘边距
+    TitleHeight = 36  # 标题栏高度
+    Widgets = set()  # 无边框窗口集合
+
+    @classmethod
+    def set_margins(cls, margins):
+        cls.Margins = margins
+
+    @classmethod
+    def set_title_height(cls, height):
+        cls.TitleHeight = height
+
+    @classmethod
+    def add_widget(cls, widget):
+        cls.Widgets.add(widget)
+
+    @classmethod
+    def del_widget(cls, widget):
+        if widget in cls.Widgets:
+            cls.Widgets.remove(widget)
+
+    def _get_edges(self, pos, width, height):
+        """根据坐标获取方向
+        :param pos: QPoint
+        :param width: int
+        :param height: int
+        :return: Qt.Edges
+        """
+        edge = 0
+        x, y = pos.x(), pos.y()
+
+        if y <= self.Margins:
+            edge |= Qt.TopEdge
+        if x <= self.Margins:
+            edge |= Qt.LeftEdge
+        if x >= width - self.Margins:
+            edge |= Qt.RightEdge
+        if y >= height - self.Margins:
+            edge |= Qt.BottomEdge
+
+        return edge
+
+    def _get_cursor(self, edges):
+        """调整鼠标样式
+        :param edges: int or None
+        :return: Qt.CursorShape
+        """
+        if edges == Qt.LeftEdge | Qt.TopEdge or edges == Qt.RightEdge | Qt.BottomEdge:
+            return Qt.SizeFDiagCursor
+        elif edges == Qt.RightEdge | Qt.TopEdge or edges == Qt.LeftEdge | Qt.BottomEdge:
+            return Qt.SizeBDiagCursor
+        elif edges == Qt.LeftEdge or edges == Qt.RightEdge:
+            return Qt.SizeHorCursor
+        elif edges == Qt.TopEdge or edges == Qt.BottomEdge:
+            return Qt.SizeVerCursor
+
+        return Qt.ArrowCursor
+
+    def is_titlebar(self, pos):
+        """判断是否是标题栏
+        :param pos: QPoint
+        :return: bool
+        """
+        return pos.y() <= self.TitleHeight
+
+    def moveOrResize(self, window, pos, width, height):
+        edges = self._get_edges(pos, width, height)
+        if edges:
+            if window.windowState() == Qt.WindowNoState:
+                window.startSystemResize(edges)
+        else:
+            if self.is_titlebar(pos):
+                window.startSystemMove()
+                # Fixed #172 主动触发一次鼠标释放事件,否则会导致鼠标悬停出问题
+                QApplication.instance().postEvent(
+                    window,
+                    QMouseEvent(QEvent.MouseButtonRelease, QPoint(-1, -1),
+                                Qt.LeftButton, Qt.NoButton, Qt.NoModifier))
+
+    def eventFilter(self, obj, event):
+        if obj.isWindowType():
+            # top window 处理光标样式
+            if event.type() == QEvent.MouseMove and obj.windowState(
+            ) == Qt.WindowNoState:
+                obj.setCursor(
+                    self._get_cursor(
+                        self._get_edges(event.pos(), obj.width(),
+                                        obj.height())))
+            elif event.type() == QEvent.TouchUpdate:
+                self.moveOrResize(obj, event.pos(), obj.width(), obj.height())
+        elif obj in self.Widgets and isinstance(
+                event, QMouseEvent) and event.button() == Qt.LeftButton:
+            if event.type() == QEvent.MouseButtonDblClick:
+                # 双击最大化还原
+                if self.is_titlebar(event.pos()):
+                    if obj.windowState() == Qt.WindowFullScreen:
+                        pass
+                    elif obj.windowState() == Qt.WindowMaximized:
+                        obj.showNormal()
+                    else:
+                        obj.showMaximized()
+            elif event.type() == QEvent.MouseButtonPress:
+                self.moveOrResize(obj.windowHandle(), event.pos(), obj.width(),
+                                  obj.height())
+
+        return False
+
+
+class FramelessWindow(QWidget, Ui_FormFrameless):
+
+    def __init__(self, *args, **kwargs):
+        super(FramelessWindow, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+        # 无边框
+        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
+        self.setAttribute(Qt.WA_TranslucentBackground, True)
+        self.setMouseTracking(True)
+        # 隐藏还原按钮
+        self.buttonNormal.setVisible(False)
+        # 标题栏按钮信号
+        self.buttonMinimum.clicked.connect(self.showMinimized)
+        self.buttonMaximum.clicked.connect(self.showMaximized)
+        self.buttonNormal.clicked.connect(self.showNormal)
+        self.buttonClose.clicked.connect(self.close)
+        self.setStyleSheet('#widgetTitleBar{background: rgb(232, 232, 232);}')
+
+    def showMinimized(self):
+        flags = self.windowFlags()
+        if sys.platform == 'darwin':
+            # fix mac 最小化失效问题
+            self.setWindowFlags((self.windowFlags() | Qt.CustomizeWindowHint) &
+                                (~Qt.WindowTitleHint))
+        super(FramelessWindow, self).showMinimized()
+        if sys.platform == 'darwin':
+            # fix mac 最小化失效问题
+            self.setWindowFlags(flags)
+            self.show()
+
+    def changeEvent(self, event):
+        """窗口状态改变
+        :param event:
+        """
+        super(FramelessWindow, self).changeEvent(event)
+        # 窗口状态改变时修改标题栏控制按钮
+        visible = self.isMaximized()
+        self.buttonMaximum.setVisible(not visible)
+        self.buttonNormal.setVisible(visible)
+        if visible:
+            self.layout().setContentsMargins(0, 0, 0, 0)
+        else:
+            # TODO 与UI文件中的布局边距一致
+            m = FramelessObject.Margins
+            self.layout().setContentsMargins(m, m, m, m)
+
+    def paintEvent(self, event):
+        # 透明背景但是需要留下一个透明度用于鼠标捕获
+        painter = QPainter(self)
+        painter.fillRect(self.rect(), QColor(255, 255, 255, 1))
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    if not hasattr(QWindow, 'startSystemMove'):
+        QWindow.startSystemResize()
+        # 不支持
+        QMessageBox.critical(None, '错误', '当前Qt版本不支持该例子')
+        QTimer.singleShot(100, app.quit)
+    else:
+        # 安装全局事件过滤器
+        fo = FramelessObject()
+        app.installEventFilter(fo)
+
+        w1 = FramelessWindow()
+        fo.add_widget(w1)
+        w1.show()
+
+        w2 = FramelessWindow()
+        fo.add_widget(w2)
+        w2.show()
+    sys.exit(app.exec_())
diff --git a/Demo/Notification.py b/Demo/Notification.py
index f7a0ab15..889c103f 100644
--- a/Demo/Notification.py
+++ b/Demo/Notification.py
@@ -4,30 +4,30 @@
 """
 Created on 2018年9月9日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Notification
 @description: 
 """
 import base64
 
-from PyQt5.QtCore import Qt, QRectF, QSize, pyqtSignal, QTimer
-from PyQt5.QtGui import QPixmap, QImage, QPainter, QPainterPath,\
-    QColor
-from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout,\
-    QGridLayout, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect,\
-    QListWidget, QListWidgetItem
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt, QRectF, QSize, pyqtSignal, QTimer
+    from PyQt5.QtGui import QPixmap, QImage, QPainter, QPainterPath, \
+        QColor
+    from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, \
+        QGridLayout, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect, \
+        QListWidget, QListWidgetItem, QApplication, QPushButton
+except ImportError:
+    from PySide2.QtCore import Qt, QRectF, QSize, Signal as pyqtSignal, QTimer
+    from PySide2.QtGui import QPixmap, QImage, QPainter, QPainterPath, \
+        QColor
+    from PySide2.QtWidgets import QWidget, QLabel, QHBoxLayout, \
+        QGridLayout, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect, \
+        QListWidget, QListWidgetItem, QApplication, QPushButton
 
 
 class NotificationIcon:
-
     Info, Success, Warning, Error, Close = range(5)
     Types = {
         Info: None,
@@ -39,10 +39,14 @@ class NotificationIcon:
 
     @classmethod
     def init(cls):
-        cls.Types[cls.Info] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC5ElEQVRYR8VX0VHbQBB9e/bkN3QQU0FMBSEVYFcQ8xPBJLJ1FWAqOMcaxogfTAWQCiAVRKkgTgfmM4zRZu6QhGzL0p0nDPr17e7bt7tv14RX/uiV48MJgAon+8TiAMRtMFogaqUJxADPwRRzg67kl8+xbWJWANR40iPQSSFgtX/mGQkaDr56V3VAKgGos4s2JXwJoF3naMPvMS+SrpTHs032GwGkdF+DsFMVnJm/oyGGeHico0EjIjpYes+YMyVd6R/flfkpBWCCQ9zaZM2LZDfLMGXsZ5kdI/lYBmINgHHyyLd1mWdBbAFAM/GY7K2WYx1AeB4T6L1N9umbGxZ0qktATaEAdCps48D39oq/LwEw3U5CN92LfczJoewfT7MAywDCaEbAuxeLrh0zz4L+0e4aAJfGy+sP3IMxlH1vpMJoSMCJDXgWtJeJVc6ACs9HBBrYODCJAFdYvAmkPJxnNqMwYht7Bn+T/lGg3z4DGEd3RPhQ54DBvwAOVkeqagRXfTLjh+x7+8sALOtfHLuiYzWOAiLoKbD58mnIGbCmLxUepS6NQmYlUGE0JeCTTXT9JvA9E9sZgO5iIpoyc6/YzcqSwQzgGgBXB7oXpH9klpRSkxY1xW/b7Iu2zk34PILPnazCqEPAtTWA8iZ0HsOu9L0bw4DzCJeNocMGNDpQ3IKO+6NUiJ4ysZNiBv5I3zPnmJmG5oM+wbS+9+qkvGi7NAXGmeUy0ioofa+XA0jH0UaMKpdRWs/adcwMqfV/tenqpqHY/Znt+j2gJi00RUzA201dXaxh9iZdZloJS+9H1otrkbRrD5InFqpPskxEshJQ468CkSmJC+i1HigaaxCAuCljgoDhwPdOjf7rFVxxuJrMkXScjtKc1rOLNpJk6nii5XmYzbngzlZn+RIb40kPJPTBYXUt6VEDJ8Pi6bWpNFb/jFYY6YGpDeKdjBmTKdMcxDGEmP73v2a2Gr/NOycGtglQZ/MPzEqCMLGckJEAAAAASUVORK5CYII=')))
-        cls.Types[cls.Success] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACZUlEQVRYR8VXS3LTQBDtVsDbcAPMCbB3limkcAKSG4QFdnaYE2BOQLKzxSLJCeAGSUQheSnfwLmB2VJhXmpExpFHI2sk2RWv5FJPv9evP9NieuIfPzE+VSJw8qt3IMDvmahDoDYxt2UAACXMWIIowR5ffn8TJbaBWRE4CXvHAH9RgKXOgQUI48CfXZbZbiTw8Xe/w3d0zkydMkem91IZpyWOJu5sUXS+kEAqt3B+MNOLOuDqDEBLxxFHk7eza5MfIwEJDjhXTYD1s8zinYlEjsCD7FdNI9cJpEq0RFdPR47AMOzLCn69zegz6UgCP+pmfa8RSKudnPNdgCufTOLDxJtdPP7PoA1Cd8HEL5sSUCCD0B0x8bc1f8Bi6sevcgS2VXh6hMOwDz0gsUddNaxWKRjeuKfE/KlJ9Dq4UYH/o/Ns6scj+bgiMAjdayb26xLQwTfVEwg3gRcf6ARq578KuLo7VDc8psCQqwfjr4EfjYvkrAquFJ56UYpdSkAZSmNd1rrg0leOQFELgvA58OJTxVyRaAJORPOpF6UXnFUR5sDiXjs7UqsOMGMRlrWhTkJXpFL3mNrQZhA1lH3F0TiI5FurUQyMpn58VjhkSqQA4Tbw4nSVW6sBU5VXktXSeONlJH3s8jrOVr9RgVSFuNcWfzlh5n3LoKzMAPxxWuiULiQpiR2sZNnCyzIuWUr5Z1Ml0sgdHFZaShVDuR86/0huL3VXtDk/F4e11vKsTHLSCeKx7bYkW80hjLOrV1GhWH0ZrSlyh2MwdZhYfi8oZeYgLBmUiGd8sfVPM6syr2lUSYGaGBuP3QN6rVUwYV/egwAAAABJRU5ErkJggg==')))
-        cls.Types[cls.Warning] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACmElEQVRYR8VXTW7TUBD+xjYSXZFukOIsSE9AskNJJMoJmq4r7OYEwAkabhBOkB/Emt4gVIojdpgbpIumEitX6gKB7UHPkauXxLHfc4F6Z3l+vvnmm/fGhAd+6IHzQwvA9cfOITMfAdQAcx1EdVEAM/tEFADsWyaPn57MfdXClABcT1qnzHSWJiwMzrwgoF91vXGRbS6AH59ajd8hDYmoURQo67tgxoij42rv62KX/04Agu44xmciVMokT32YERgGjquvZ1+y4mQCWPUa0/sk3vQlwqssEFsAVrQbU4XKL/ai2+5PPK6waQ4AOsoDnDARh83NdmwBuJq0fQI9L6p+L7rd3+/5gbAToMPI+FbkIzRRc72mbLcGIFE7jGFRIPHddmZrvstJh1X8CHGv6sxHqe1GkPYCoGcqgcoCAPPCdr2DLQC6wqMoPEj7qdqCNKllxs30sLpjYDluDUDGG5XqhY2sal3w4PiD7c7fJnHShMtJR8zpy/8CALiwndnhBgD1/t+XAXkaZAaUVHwnHulg0W6BNEWlAQD8zna8gQB0Ne70iXCm2j55jCUAei1gxvuaO+uXAcDg7zXHSy640iKUAehOEDJFqDmGQkiPLO5Fv+KADXOqvCuIsrPGsIyQdHou22YeRMJgOdHTQTkAfGk7XrLKrWlAvOhcRgBfWiZ3RQti0zxXuUFXCXMuo0TRitfxugjbIxC5RYzI6s9kIGFh+KLOpiW22id5AUuI8IaisFG4kCQg/sFKJgtPLix3KWXGeRETRbQDuCFCV2spTYMm+2FEI1WBbYIRPTeiqFtqLZeDraaD+qrbkpgQAvfl1WsXU0p/RjIjYYhTkNFgcCVlRlRKoAAc+5aF0V//NVPoc2kTLQZKZ8lx/AMXBmMwuXUwOAAAAABJRU5ErkJggg==')))
-        cls.Types[cls.Error] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACrklEQVRYR82XW27aQBSG/4PtiNhIpStouoImKwjZAV1B07coWCpZQcgK6kh2lLeSFZSsIOwgdAdkBaUSEBQDpxpjU9vM+EJR03nDzJz/mzm3GcIrD3plfZQCeD47O1ho2jERNRmoE9AQG2BgBGBAwIiZe5Zh3JPjiG+5oxCAEF5q2iWITnMtRhOYu5XF4mr/9naYtSYXYGLbHQCXhYVTEwlom657rVqvBOB2uz71/a+ldq1SYe6ahnEhc4sSYGzbfQKOt915eh0D/ZrrnqS/SwEmrVYXRJ92Jb4OC+C65rrtuN0NgIltNwF837V4zN5Hy3V70e9NgFZrCKJ3CQDmJ9MwDsW36XzeB/AhA/CHqeuN2WxWX2paX2JraHneeynA+Pz8lCqVbxLjV5brimxAEJxqiEA8CjZVBvFy+bl2c9MV9hInoAw85qFpGEeRYQVEQjzMokcQHWxsiPne8jzh6j8AodGfyqNlHpiGcaKAkIk/gChwm2yYuv5W2FqfwLNtN5bAQ2bwySB83zENo50A8/1McaFRAU72XVek+mpk+D/JlIKI/xkee654uCbIhjVAqZIrgSgpLhiCwN4OAEj4vEB2yDybBCjsAol4ZD0nRdMQSRcUCsKUeNSw4o2mKMRGEOamoVx8FXDZKVosDYNMUHXAsBRnppo8RQcbpTgIGEkhykpFjnWxzGhPQYxt2yHgS/oIlKVYTJxImpG482nz+VG1Wh1N84pMCCGa0ULXHwmoJwCYnyzPW5fn/68dh7EgPbrMMl3gz7gro+n/7EoWD7w4a96l1NnJ1Yz5Lt6wCgFEk0r1CIkbiPnC9DxH5aHcd4FYGD5MOqVOg/muslh0/vphkm63k5eXZvA0I6qD+ZCI3jDzLxANiHn1NNvb6+30aVYgwLeeUsgFW1svsPA3Ncq4MHzVeO8AAAAASUVORK5CYII=')))
+        cls.Types[cls.Info] = QPixmap(QImage.fromData(base64.b64decode(
+            'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC5ElEQVRYR8VX0VHbQBB9e/bkN3QQU0FMBSEVYFcQ8xPBJLJ1FWAqOMcaxogfTAWQCiAVRKkgTgfmM4zRZu6QhGzL0p0nDPr17e7bt7tv14RX/uiV48MJgAon+8TiAMRtMFogaqUJxADPwRRzg67kl8+xbWJWANR40iPQSSFgtX/mGQkaDr56V3VAKgGos4s2JXwJoF3naMPvMS+SrpTHs032GwGkdF+DsFMVnJm/oyGGeHico0EjIjpYes+YMyVd6R/flfkpBWCCQ9zaZM2LZDfLMGXsZ5kdI/lYBmINgHHyyLd1mWdBbAFAM/GY7K2WYx1AeB4T6L1N9umbGxZ0qktATaEAdCps48D39oq/LwEw3U5CN92LfczJoewfT7MAywDCaEbAuxeLrh0zz4L+0e4aAJfGy+sP3IMxlH1vpMJoSMCJDXgWtJeJVc6ACs9HBBrYODCJAFdYvAmkPJxnNqMwYht7Bn+T/lGg3z4DGEd3RPhQ54DBvwAOVkeqagRXfTLjh+x7+8sALOtfHLuiYzWOAiLoKbD58mnIGbCmLxUepS6NQmYlUGE0JeCTTXT9JvA9E9sZgO5iIpoyc6/YzcqSwQzgGgBXB7oXpH9klpRSkxY1xW/b7Iu2zk34PILPnazCqEPAtTWA8iZ0HsOu9L0bw4DzCJeNocMGNDpQ3IKO+6NUiJ4ysZNiBv5I3zPnmJmG5oM+wbS+9+qkvGi7NAXGmeUy0ioofa+XA0jH0UaMKpdRWs/adcwMqfV/tenqpqHY/Znt+j2gJi00RUzA201dXaxh9iZdZloJS+9H1otrkbRrD5InFqpPskxEshJQ468CkSmJC+i1HigaaxCAuCljgoDhwPdOjf7rFVxxuJrMkXScjtKc1rOLNpJk6nii5XmYzbngzlZn+RIb40kPJPTBYXUt6VEDJ8Pi6bWpNFb/jFYY6YGpDeKdjBmTKdMcxDGEmP73v2a2Gr/NOycGtglQZ/MPzEqCMLGckJEAAAAASUVORK5CYII=')))
+        cls.Types[cls.Success] = QPixmap(QImage.fromData(base64.b64decode(
+            'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACZUlEQVRYR8VXS3LTQBDtVsDbcAPMCbB3limkcAKSG4QFdnaYE2BOQLKzxSLJCeAGSUQheSnfwLmB2VJhXmpExpFHI2sk2RWv5FJPv9evP9NieuIfPzE+VSJw8qt3IMDvmahDoDYxt2UAACXMWIIowR5ffn8TJbaBWRE4CXvHAH9RgKXOgQUI48CfXZbZbiTw8Xe/w3d0zkydMkem91IZpyWOJu5sUXS+kEAqt3B+MNOLOuDqDEBLxxFHk7eza5MfIwEJDjhXTYD1s8zinYlEjsCD7FdNI9cJpEq0RFdPR47AMOzLCn69zegz6UgCP+pmfa8RSKudnPNdgCufTOLDxJtdPP7PoA1Cd8HEL5sSUCCD0B0x8bc1f8Bi6sevcgS2VXh6hMOwDz0gsUddNaxWKRjeuKfE/KlJ9Dq4UYH/o/Ns6scj+bgiMAjdayb26xLQwTfVEwg3gRcf6ARq578KuLo7VDc8psCQqwfjr4EfjYvkrAquFJ56UYpdSkAZSmNd1rrg0leOQFELgvA58OJTxVyRaAJORPOpF6UXnFUR5sDiXjs7UqsOMGMRlrWhTkJXpFL3mNrQZhA1lH3F0TiI5FurUQyMpn58VjhkSqQA4Tbw4nSVW6sBU5VXktXSeONlJH3s8jrOVr9RgVSFuNcWfzlh5n3LoKzMAPxxWuiULiQpiR2sZNnCyzIuWUr5Z1Ml0sgdHFZaShVDuR86/0huL3VXtDk/F4e11vKsTHLSCeKx7bYkW80hjLOrV1GhWH0ZrSlyh2MwdZhYfi8oZeYgLBmUiGd8sfVPM6syr2lUSYGaGBuP3QN6rVUwYV/egwAAAABJRU5ErkJggg==')))
+        cls.Types[cls.Warning] = QPixmap(QImage.fromData(base64.b64decode(
+            'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACmElEQVRYR8VXTW7TUBD+xjYSXZFukOIsSE9AskNJJMoJmq4r7OYEwAkabhBOkB/Emt4gVIojdpgbpIumEitX6gKB7UHPkauXxLHfc4F6Z3l+vvnmm/fGhAd+6IHzQwvA9cfOITMfAdQAcx1EdVEAM/tEFADsWyaPn57MfdXClABcT1qnzHSWJiwMzrwgoF91vXGRbS6AH59ajd8hDYmoURQo67tgxoij42rv62KX/04Agu44xmciVMokT32YERgGjquvZ1+y4mQCWPUa0/sk3vQlwqssEFsAVrQbU4XKL/ai2+5PPK6waQ4AOsoDnDARh83NdmwBuJq0fQI9L6p+L7rd3+/5gbAToMPI+FbkIzRRc72mbLcGIFE7jGFRIPHddmZrvstJh1X8CHGv6sxHqe1GkPYCoGcqgcoCAPPCdr2DLQC6wqMoPEj7qdqCNKllxs30sLpjYDluDUDGG5XqhY2sal3w4PiD7c7fJnHShMtJR8zpy/8CALiwndnhBgD1/t+XAXkaZAaUVHwnHulg0W6BNEWlAQD8zna8gQB0Ne70iXCm2j55jCUAei1gxvuaO+uXAcDg7zXHSy640iKUAehOEDJFqDmGQkiPLO5Fv+KADXOqvCuIsrPGsIyQdHou22YeRMJgOdHTQTkAfGk7XrLKrWlAvOhcRgBfWiZ3RQti0zxXuUFXCXMuo0TRitfxugjbIxC5RYzI6s9kIGFh+KLOpiW22id5AUuI8IaisFG4kCQg/sFKJgtPLix3KWXGeRETRbQDuCFCV2spTYMm+2FEI1WBbYIRPTeiqFtqLZeDraaD+qrbkpgQAvfl1WsXU0p/RjIjYYhTkNFgcCVlRlRKoAAc+5aF0V//NVPoc2kTLQZKZ8lx/AMXBmMwuXUwOAAAAABJRU5ErkJggg==')))
+        cls.Types[cls.Error] = QPixmap(QImage.fromData(base64.b64decode(
+            'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACrklEQVRYR82XW27aQBSG/4PtiNhIpStouoImKwjZAV1B07coWCpZQcgK6kh2lLeSFZSsIOwgdAdkBaUSEBQDpxpjU9vM+EJR03nDzJz/mzm3GcIrD3plfZQCeD47O1ho2jERNRmoE9AQG2BgBGBAwIiZe5Zh3JPjiG+5oxCAEF5q2iWITnMtRhOYu5XF4mr/9naYtSYXYGLbHQCXhYVTEwlom657rVqvBOB2uz71/a+ldq1SYe6ahnEhc4sSYGzbfQKOt915eh0D/ZrrnqS/SwEmrVYXRJ92Jb4OC+C65rrtuN0NgIltNwF837V4zN5Hy3V70e9NgFZrCKJ3CQDmJ9MwDsW36XzeB/AhA/CHqeuN2WxWX2paX2JraHneeynA+Pz8lCqVbxLjV5brimxAEJxqiEA8CjZVBvFy+bl2c9MV9hInoAw85qFpGEeRYQVEQjzMokcQHWxsiPne8jzh6j8AodGfyqNlHpiGcaKAkIk/gChwm2yYuv5W2FqfwLNtN5bAQ2bwySB83zENo50A8/1McaFRAU72XVek+mpk+D/JlIKI/xkee654uCbIhjVAqZIrgSgpLhiCwN4OAEj4vEB2yDybBCjsAol4ZD0nRdMQSRcUCsKUeNSw4o2mKMRGEOamoVx8FXDZKVosDYNMUHXAsBRnppo8RQcbpTgIGEkhykpFjnWxzGhPQYxt2yHgS/oIlKVYTJxImpG482nz+VG1Wh1N84pMCCGa0ULXHwmoJwCYnyzPW5fn/68dh7EgPbrMMl3gz7gro+n/7EoWD7w4a96l1NnJ1Yz5Lt6wCgFEk0r1CIkbiPnC9DxH5aHcd4FYGD5MOqVOg/muslh0/vphkm63k5eXZvA0I6qD+ZCI3jDzLxANiHn1NNvb6+30aVYgwLeeUsgFW1svsPA3Ncq4MHzVeO8AAAAASUVORK5CYII=')))
         cls.Types[cls.Close] = QPixmap(QImage.fromData(base64.b64decode(
             'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAeElEQVQ4T2NkoBAwUqifgboGzJy76AIjE3NCWmL0BWwumzV/qcH/f38XpCfHGcDkUVwAUsDw9+8GBmbmAHRDcMlheAGbQnwGYw0DZA1gp+JwFUgKZyDCDQGpwuIlrGGAHHAUGUCRFygKRIqjkeKERE6+oG5eIMcFAOqSchGwiKKAAAAAAElFTkSuQmCC')))
 
@@ -52,7 +56,6 @@ def icon(cls, ntype):
 
 
 class NotificationItem(QWidget):
-
     closed = pyqtSignal(QListWidgetItem)
 
     def __init__(self, title, message, item, *args, ntype=0, callback=None, **kwargs):
@@ -152,7 +155,6 @@ def paintEvent(self, event):
 
 
 class NotificationWindow(QListWidget):
-
     _instance = None
 
     def __init__(self, *args, **kwargs):
@@ -243,15 +245,18 @@ def error(cls, title, message, callback=None):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
-    from PyQt5.QtWidgets import QApplication, QPushButton
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = QWidget()
     layout = QHBoxLayout(w)
 
+
     def callback():
         print('回调点击')
 
+
     layout.addWidget(QPushButton(
         'Info', w, clicked=lambda: NotificationWindow.info('提示', '这是一条会自动关闭的消息', callback=callback)))
     layout.addWidget(QPushButton(
@@ -268,9 +273,9 @@ def callback():
             callback=callback)))
     w.show()
 
-#     NotificationIcon.init()
-#     ww = NotificationItem('提示', '这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案 
', None,
-#                           ntype=NotificationIcon.Error)
-#     ww.bgWidget.setVisible(True)
-#     ww.show()
+    #     NotificationIcon.init()
+    #     ww = NotificationItem('提示', '这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案 
', None,
+    #                           ntype=NotificationIcon.Error)
+    #     ww.bgWidget.setVisible(True)
+    #     ww.show()
     sys.exit(app.exec_())
diff --git a/Demo/ProbeWindow.py b/Demo/ProbeWindow.py
index 7bc4b0b0..e023359c 100644
--- a/Demo/ProbeWindow.py
+++ b/Demo/ProbeWindow.py
@@ -3,23 +3,22 @@
 """
 Created on 2018年6月8日
 author: Irony
-site: https://pyqt5.com , https://github.com/892768447
+site: https://pyqt.site , https://github.com/PyQt5
 email: 892768447@qq.com
 file: ProbeWindow
 description: 简单探测窗口和放大截图
 """
 
-from PyQt5.QtCore import Qt, QRect
-from PyQt5.QtGui import QPainter, QPen, QCursor, QColor
-from PyQt5.QtWidgets import QLabel, QWidget, QApplication
 import win32gui
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt, QRect
+    from PyQt5.QtGui import QPainter, QPen, QCursor, QColor
+    from PyQt5.QtWidgets import QLabel, QWidget, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt, QRect
+    from PySide2.QtGui import QPainter, QPen, QCursor, QColor
+    from PySide2.QtWidgets import QLabel, QWidget, QApplication
 
 
 class FrameWidget(QWidget):
@@ -111,11 +110,12 @@ def paintEvent(self, event):
             painter.setPen(Qt.white)
             painter.drawText(self.rect(), Qt.AlignLeft |
                              Qt.AlignBottom, '({}, {})\nRGB: ({}, {}, {})\n{}'.format(
-                                 pos.x(), pos.y(), r, g, b, QColor(r, g, b).name()))
+                pos.x(), pos.y(), r, g, b, QColor(r, g, b).name()))
 
 
 if __name__ == '__main__':
     import sys
+
     app = QApplication(sys.argv)
     app.setQuitOnLastWindowClosed(True)
     w = Label()
diff --git a/Demo/QtThreading.py b/Demo/QtThreading.py
index 63f36970..d0533850 100644
--- a/Demo/QtThreading.py
+++ b/Demo/QtThreading.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年3月8日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Threading.QtThreading
 @description: 
@@ -12,19 +12,15 @@
 from threading import Thread
 from time import sleep
 
-from PyQt5.QtCore import QObject, pyqtSignal, QTimer, Qt
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QObject, pyqtSignal, QTimer, Qt
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QApplication
+except ImportError:
+    from PySide2.QtCore import QObject, Signal as pyqtSignal, QTimer, Qt
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QApplication
 
 
 class _Signals(QObject):
-
     updateProgress = pyqtSignal(int)
 
 
@@ -63,7 +59,7 @@ def doStart(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/README.md b/Demo/README.md
index 9fc9353e..aa260d63 100644
--- a/Demo/README.md
+++ b/Demo/README.md
@@ -23,8 +23,12 @@
   - [判断信号是否连接](#20判断信号是否连接)
   - [调用虚拟键盘](#21调用虚拟键盘)
   - [动态忙碌光标](#22动态忙碌光标)
+  - [屏幕变动监听](#23屏幕变动监听)
+  - [无边框窗口](#24无边框窗口)
+  - [属性绑定](#25属性绑定)
 
 ## 1、重启窗口Widget
+
 [运行 RestartWindow.py](RestartWindow.py)
 
 利用类变量对窗口的变量进行引用,防止被回收(导致窗口一闪而过),重启时先显示新窗口后关闭自己
@@ -32,6 +36,7 @@
 
 
 ## 2、简单的窗口贴边隐藏
+
 [运行 WeltHideWindow.py](WeltHideWindow.py)
 
 1. 大概思路
@@ -50,6 +55,7 @@
 
 
 ## 3、嵌入外部窗口
+
 [运行 EmbedWindow.py](EmbedWindow.py)
 
 1. 使用`SetParent`函数设置外部窗口的`parent`为Qt的窗口
@@ -59,8 +65,8 @@
 
 
 
-
 ## 4、简单跟随其它窗口
+
 [运行 FollowWindow.py](FollowWindow.py)
 
 1. 利用win32gui模块获取目标窗口的句柄
@@ -70,8 +76,8 @@
 
 
 
-
 ## 5、简单探测窗口和放大截图
+
 [运行 ProbeWindow.py](ProbeWindow.py)
 
 1. 利用`win32gui`模块获取鼠标所在位置的窗口大小(未去掉边框)和rgb颜色
@@ -79,8 +85,8 @@
 
 
 
-
 ## 6、无边框自定义标题栏窗口
+
 [运行 FramelessWindow.py](FramelessWindow.py) | [运行 NativeEvent.py](NativeEvent.py)
 
 1. 重写鼠标事件
@@ -98,32 +104,38 @@
 
 
 ## 7、右下角弹出框
-[运行 WindowNotify.py](WindowNotify.py)
+
+[运行 WindowNotify.py](WindowNotify.py) | [查看 notify.ui](Data/notify.ui)
 
 
 
 ## 8、程序重启
+
 [运行 AutoRestart.py](AutoRestart.py)
 
 
 
 ## 9、自定义属性
+
 [运行 CustomProperties.py](CustomProperties.py)
 
 
 
 ## 10、调用截图DLL
+
 [运行 ScreenShotDll.py](ScreenShotDll.py)
 
 
 
 ## 11、单实例应用
+
 [运行 SingleApplication.py](SingleApplication.py) | [运行 SharedMemory.py](SharedMemory.py)
 
 1. QSharedMemory
 2. QLocalSocket, QLocalServer
 
 ## 12、简单的右下角气泡提示
+
 [运行 BubbleTips.py](BubbleTips.py)
 
 1. 使用 `QWidget` 包含一个 `QLabel`, 其中 `QWidget` 通过 `paintEvent` 绘制气泡形状
@@ -133,11 +145,13 @@
 
 
 ## 13、右侧消息通知栏
+
 [运行 Notification.py](Notification.py)
 
 
 
 ## 14、验证码控件
+
 [运行 VerificationCode.py](VerificationCode.py)
 
 1. 更新为paintEvent方式,采用上下跳动
@@ -147,24 +161,26 @@
 
 
 ## 15、人脸特征点
+
 [运行 FacePoints.py](FacePoints.py)
 
 PyQt 结合 Opencv 进行人脸检测;
 由于直接在主线程中进行特征点获取,效率比较低
 
  依赖文件
- 
+
 1. [opencv](https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv)
 2. [numpy](https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy)
 3. [dlib](http://dlib.net/)
-  1. [dlib-19.4.0.win32-py2.7.exe](Data/dlib-19.4.0.win32-py2.7.exe)
-  2. [dlib-19.4.0.win32-py3.4.exe](Data/dlib-19.4.0.win32-py3.4.exe)
-  3. [dlib-19.4.0.win32-py3.5.exe](Data/dlib-19.4.0.win32-py3.5.exe)
+1. [dlib-19.4.0.win32-py2.7.exe](Data/dlib-19.4.0.win32-py2.7.exe)
+2. [dlib-19.4.0.win32-py3.4.exe](Data/dlib-19.4.0.win32-py3.4.exe)
+3. [dlib-19.4.0.win32-py3.5.exe](Data/dlib-19.4.0.win32-py3.5.exe)
 4. [shape-predictor-68-face-landmarks.dat.bz2](http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2)
 
 
 
 ## 16、使用Threading
+
 [运行  QtThreading.py](QtThreading.py)
 
 在PyQt中使用Theading线程
@@ -172,14 +188,15 @@ PyQt 结合 Opencv 进行人脸检测;
 
 
 ## 17、背景连线动画
+
 [运行 CircleLine.py](CircleLine.py)
 
 主要参考 [背景连线动画.html](Data/背景连线动画.html)
 
 
 
-
 ## 18、无边框圆角对话框
+
 [运行 FramelessDialog.py](FramelessDialog.py)
 
 1. 通过设置 `self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)` 和 `self.setAttribute(Qt.WA_TranslucentBackground, True)` 达到无边框和背景透明
@@ -190,6 +207,7 @@ PyQt 结合 Opencv 进行人脸检测;
 
 
 ## 19、调整窗口显示边框
+
 [运行 ShowFrameWhenDrag.py](ShowFrameWhenDrag.py)
 
 1. 全局设置是【】在控制面板中->调整Windows的外观和性能->去掉勾选 拖动时显示窗口内容】
@@ -202,13 +220,16 @@ PyQt 结合 Opencv 进行人脸检测;
 
 
 ## 20、判断信号是否连接
+
 [运行 IsSignalConnected.py](IsSignalConnected.py)
 
-通过 `isSignalConnected` 判断是否连接
+1. 通过 `isSignalConnected` 判断是否连接
+2. 通过对象的 `receivers` 获取连接的数量来判断
 
 
 
 ## 21、调用虚拟键盘
+
 [运行 CallVirtualKeyboard.py](CallVirtualKeyboard.py)
 
 1. Windows上调用的是`osk.exe`
@@ -218,8 +239,35 @@ PyQt 结合 Opencv 进行人脸检测;
 
 
 ## 22、动态忙碌光标
+
 [运行 GifCursor.py](GifCursor.py)
 
 通过定时器不停的修改光标图片来实现动态效果
 
-
\ No newline at end of file
+
+
+## 23、屏幕变动监听
+
+[运行 ScreenNotify.py](ScreenNotify.py)
+
+通过定时器减少不同的变化信号,尽量保证只调用一次槽函数来获取信息
+
+
+
+## 24、无边框窗口
+
+[运行 NewFramelessWindow.py](NewFramelessWindow.py)
+
+1. 该方法只针对 `Qt5.15` 以上版本有效
+2. 通过事件过滤器判断边缘设置鼠标样式
+3. 处理点击事件交通过 `QWindow.startSystemMove` 和 `QWindow.startSystemResize` 传递给系统处理
+
+
+
+## 25、属性绑定
+
+[运行 TestSerializeModel.py](TestSerializeModel.py)
+
+类似:[json数据绑定](../QTreeView#3json数据绑定)
+
+
diff --git a/Demo/RestartWindow.py b/Demo/RestartWindow.py
index 9eec2242..bd9709b3 100644
--- a/Demo/RestartWindow.py
+++ b/Demo/RestartWindow.py
@@ -1,26 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月17日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: RestartWindow
 @description: 窗口重启
-'''
-from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit,\
-    QMessageBox
+"""
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import pyqtSignal
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, \
+        QMessageBox, QApplication
+except ImportError:
+    from PySide2.QtCore import Signal as pyqtSignal
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, \
+        QMessageBox, QApplication
 
 
 class RestartWindow(QWidget):
-
     restarted = pyqtSignal(QWidget, str)
     _Self = None  # 很重要,保留窗口引用
 
@@ -55,7 +55,7 @@ def onRestart(cls, widget, path):
 
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = RestartWindow("test")
     w.show()
diff --git a/Demo/ScreenNotify.py b/Demo/ScreenNotify.py
new file mode 100644
index 00000000..f2c9f9c5
--- /dev/null
+++ b/Demo/ScreenNotify.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/4/13
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ScreenNotify
+@description: 屏幕、分辨率、DPI变化通知
+"""
+import sys
+
+try:
+    from PyQt5.QtCore import QTimer, QRect
+    from PyQt5.QtWidgets import QApplication, QPlainTextEdit
+except ImportError:
+    from PySide2.QtCore import QTimer, QRect
+    from PySide2.QtWidgets import QApplication, QPlainTextEdit
+
+
+class Window(QPlainTextEdit):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.appendPlainText('修改分辨率后查看')
+        # 记录最后一次的值(减少槽调用)
+        self.m_rect = QRect()
+        # 使用定时器来延迟触发最后一次变化
+        self.m_timer = QTimer(self, timeout=self.onSolutionChanged)
+        self.m_timer.setSingleShot(True)  # **重要** 保证多次信号尽量少的调用函数
+
+        # 主要是多屏幕->无屏幕->有屏幕
+        QApplication.instance().primaryScreenChanged.connect(lambda _: self.m_timer.start(1000))
+        # 其它信号最终基本上都会调用该信号
+        QApplication.instance().primaryScreen().virtualGeometryChanged.connect(
+            lambda _: self.m_timer.start(1000))
+        # DPI变化
+        QApplication.instance().primaryScreen().logicalDotsPerInchChanged.connect(
+            lambda _: self.m_timer.start(1000))
+
+    def onSolutionChanged(self):
+        # 获取主屏幕
+        screen = QApplication.instance().primaryScreen()
+        if self.m_rect == screen.availableVirtualGeometry():
+            return
+        self.m_rect = screen.availableVirtualGeometry()
+        # 所有屏幕可用大小
+        self.appendPlainText('\navailableVirtualGeometry: {0}'.format(str(screen.availableVirtualGeometry())))
+        # 获取所有屏幕
+        screens = QApplication.instance().screens()
+        for screen in screens:
+            self.appendPlainText(
+                'screen: {0}, geometry({1}), availableGeometry({2}), logicalDotsPerInch({3}), '
+                'physicalDotsPerInch({4}), refreshRate({5})'.format(
+                    screen.name(), screen.geometry(), screen.availableGeometry(), screen.logicalDotsPerInch(),
+                    screen.physicalDotsPerInch(), screen.refreshRate()))
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/Demo/ScreenShot/IsSignalConnected.png b/Demo/ScreenShot/IsSignalConnected.png
index e87aaeff..829976f9 100644
Binary files a/Demo/ScreenShot/IsSignalConnected.png and b/Demo/ScreenShot/IsSignalConnected.png differ
diff --git a/Demo/ScreenShot/NewFramelessWindow.gif b/Demo/ScreenShot/NewFramelessWindow.gif
new file mode 100644
index 00000000..88f4333d
Binary files /dev/null and b/Demo/ScreenShot/NewFramelessWindow.gif differ
diff --git a/Demo/ScreenShot/ScreenNotify.png b/Demo/ScreenShot/ScreenNotify.png
new file mode 100644
index 00000000..e95330cc
Binary files /dev/null and b/Demo/ScreenShot/ScreenNotify.png differ
diff --git a/Demo/ScreenShot/TestSerializeModel.gif b/Demo/ScreenShot/TestSerializeModel.gif
new file mode 100644
index 00000000..7b220a19
Binary files /dev/null and b/Demo/ScreenShot/TestSerializeModel.gif differ
diff --git a/Demo/ScreenShotDll.py b/Demo/ScreenShotDll.py
index 964df60e..0def9e7c 100644
--- a/Demo/ScreenShotDll.py
+++ b/Demo/ScreenShotDll.py
@@ -1,3 +1,4 @@
 from ctypes import CDLL
+
 dll = CDLL('Data/ScreenShot.dll')
 dll.PrScrn()
diff --git a/Demo/SharedMemory.py b/Demo/SharedMemory.py
index 0ec5919a..f654ad1e 100644
--- a/Demo/SharedMemory.py
+++ b/Demo/SharedMemory.py
@@ -1,28 +1,29 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: TestQSharedMemory
 @description: 
-'''
-from PyQt5.QtWidgets import QWidget
+"""
 
+from PyQt5.QtWidgets import QWidget
 
 from Lib.Application import SharedApplication  # @UnresolvedImport
 
-__version__ = "0.0.1"
 
 class Widget(QWidget):
-    
-    def __init__(self,*args,**kwargs):
-        super(Widget, self).__init__(*args,**kwargs)
+
+    def __init__(self, *args, **kwargs):
+        super(Widget, self).__init__(*args, **kwargs)
+
 
 if __name__ == "__main__":
-    import sys,os
+    import sys, os
+
     print(os.getpid())
     app = SharedApplication(sys.argv)
     if app.isRunning():
@@ -30,4 +31,4 @@ def __init__(self,*args,**kwargs):
         sys.exit(0)
     w = Widget()
     w.show()
-    sys.exit(app.exec_())
\ No newline at end of file
+    sys.exit(app.exec_())
diff --git a/Demo/ShowFrameWhenDrag.py b/Demo/ShowFrameWhenDrag.py
index a7c986e6..5f0993fb 100644
--- a/Demo/ShowFrameWhenDrag.py
+++ b/Demo/ShowFrameWhenDrag.py
@@ -4,20 +4,18 @@
 """
 Created on 2019年4月23日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ShowFrameWhenDrag
 @description: 调整窗口显示边框
 """
-from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\
+from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong, \
     c_ulonglong, WINFUNCTYPE, c_uint
 
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication
+except ImportError:
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication
 
 if sizeof(c_long) == sizeof(c_void_p):
     WPARAM = c_ulong
@@ -82,7 +80,7 @@ def _wndproc(self, hwnd, msg, wparam, lparam):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/Demo/SingleApplication.py b/Demo/SingleApplication.py
index d452e167..1f5f74ee 100644
--- a/Demo/SingleApplication.py
+++ b/Demo/SingleApplication.py
@@ -1,28 +1,29 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: TestQSingleApplication
 @description: 
-'''
+"""
+
 from PyQt5.QtWidgets import QTextEdit
 
 from Lib.Application import QSingleApplication  # @UnresolvedImport
 
 
-__version__ = "0.0.1"
-
 class Widget(QTextEdit):
-    
+
     def __init__(self, *args, **kwargs):
         super(Widget, self).__init__(*args, **kwargs)
 
+
 if __name__ == "__main__":
     import sys
+
     app = QSingleApplication(sys.argv)
     if app.isRunning():
         app.sendMessage("app is running")
diff --git a/Demo/TestSerializeModel.py b/Demo/TestSerializeModel.py
new file mode 100644
index 00000000..4483c4a9
--- /dev/null
+++ b/Demo/TestSerializeModel.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/08
+@file: TestSerializeModel.py
+@description:
+"""
+
+import json
+import sys
+from datetime import datetime
+
+from Lib.qpropmapper import QPropertyMapper
+from Lib.serializewidget import Ui_SerializeWidget
+
+try:
+    from PyQt5.QtCore import QRegExp, Qt
+    from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat
+    from PyQt5.QtWidgets import QApplication, QWidget
+except ImportError:
+    from PySide2.QtCore import QRegExp, Qt
+    from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat
+    from PySide2.QtWidgets import QApplication, QWidget
+
+
+class HighlightingRule:
+    def __init__(self, pattern, color):
+        self.pattern = QRegExp(pattern)
+        self.format = QTextCharFormat()
+        self.format.setForeground(color)
+
+
+class JsonHighlighter(QSyntaxHighlighter):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._rules = [
+            # numbers
+            HighlightingRule(QRegExp('([-0-9.]+)(?!([^"]*"[\\s]*\\:))'), Qt.darkRed),
+            # key
+            HighlightingRule(QRegExp('("[^"]*")\\s*\\:'), Qt.darkBlue),
+            # value
+            HighlightingRule(QRegExp(':+(?:[: []*)("[^"]*")'), Qt.darkGreen),
+        ]
+
+    def highlightBlock(self, text: str) -> None:
+        for rule in self._rules:
+            index = rule.pattern.indexIn(text)
+            while index >= 0:
+                length = rule.pattern.matchedLength()
+                self.setFormat(index, length, rule.format)
+                index = rule.pattern.indexIn(text, index + length)
+
+
+class TestWindow(QWidget, Ui_SerializeWidget):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.setupUi(self)
+        self.resize(1200, 600)
+
+        # json view
+        self.highlighter = JsonHighlighter(self.editJsonView.document())
+        self.editJsonView.textChanged.connect(self.onJsonChanged)
+
+        # serialize model
+        QPropertyMapper.Verbose = True
+        self.mapper = QPropertyMapper(self)
+        self.mapper.propertyChanged.connect(self.onPropertyChanged)
+        self.mapper.loadData(
+            {
+                "input": {
+                    "radioButton": True,
+                    "checkBox": False,
+                },
+                "name": "Irony",
+            }
+        )
+
+        # comboBox
+        self.comboBox.addItems([f"Item {i}" for i in range(10)])
+
+        self.mapper.bind(self.radioButton, "input.radioButton")
+        self.mapper.bind(self.checkBox, "input.checkBox", True)
+        self.mapper.bind(self.comboBox, "input.comboBox")
+        self.mapper.bind(self.spinBox, "age")
+        self.mapper.bind(self.doubleSpinBox, "money")
+        self.mapper.bind(self.lineEdit, "name")
+
+        # date and time
+        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.mapper.bind(self.timeEdit, "input.time", now)
+        self.mapper.bind(self.dateEdit, "input.date", now)
+        self.mapper.bind(self.dateTimeEdit, "input.dateTime", now)
+
+        # edit
+        self.mapper.bind(self.plainTextEdit, "desc.desc")
+        self.mapper.bind(self.textEdit, "desc.text")
+
+        # Correlation
+        self.mapper.bind(self.spinBox_2, "correlation.slider")
+        self.mapper.bind(self.horizontalSlider, "correlation.slider")
+        self.mapper.bind(self.progressBar, "correlation.progress")
+        self.mapper.bind(self.verticalSlider, "correlation.progress")
+
+        # get new dict
+        self.onPropertyChanged()
+
+    def onPropertyChanged(self, *args, **kwargs):
+        data = self.mapper.toJson(indent=2)
+        self.editJsonView.blockSignals(True)
+        self.editJsonView.setPlainText(data)
+        self.editJsonView.blockSignals(False)
+
+    def onJsonChanged(self):
+        text = self.editJsonView.toPlainText().strip()
+        try:
+            data = json.loads(text)
+            self.mapper.loadData(data)
+        except Exception:
+            pass
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/Demo/VerificationCode.py b/Demo/VerificationCode.py
index df25ba7f..9490d8ef 100644
--- a/Demo/VerificationCode.py
+++ b/Demo/VerificationCode.py
@@ -1,23 +1,25 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年4月5日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: widgets.WidgetCode
 @description: 
-'''
-from random import sample
+"""
 import string
+from random import sample
 
-from PyQt5.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer
-from PyQt5.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics
-from PyQt5.QtWidgets import QLabel
-
-
-__version__ = "0.0.1"
+try:
+    from PyQt5.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer
+    from PyQt5.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics, QFontDatabase
+    from PyQt5.QtWidgets import QLabel, QApplication, QWidget, QHBoxLayout, QLineEdit
+except ImportError:
+    from PySide2.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer
+    from PySide2.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics, QFontDatabase
+    from PySide2.QtWidgets import QLabel, QApplication, QWidget, QHBoxLayout, QLineEdit
 
 DEF_NOISYPOINTCOUNT = 60  # 噪点数量
 COLORLIST = ("black", "gray", "red", "green", "blue", "magenta")
@@ -28,8 +30,9 @@
 WORDS = list(string.ascii_letters + string.digits)
 SINETABLE = (0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38)
 
+
 class WidgetCode(QLabel):
-    
+
     def __init__(self, *args, **kwargs):
         super(WidgetCode, self).__init__(*args, **kwargs)
         self._sensitive = False  # 是否大小写敏感
@@ -47,33 +50,33 @@ def __init__(self, *args, **kwargs):
         self.step = 0
         self.timer = QBasicTimer()
         self.timer.start(60, self)
-        
+
     def reset(self):
         self._code = "".join(sample(WORDS, 4))  # 随机4个字符
         self.setText(self._code)
-    
+
     def check(self, code):
         return self._code == str(code) if self._sensitive else self._code.lower() == str(code).lower()
-    
+
     def setSensitive(self, sensitive):
         self._sensitive = sensitive
-    
-#     def setText(self, text):
-#         text = text if (text and len(text) == 4) else "".join(sample(WORDS, 4))  # 随机4个字符
-#         self._code = str(text)
-#         html = "".join([FONT.format(color=COLORLIST[qrand() % 6], word=t) for t in text])
-#         super(WidgetCode, self).setText(HTML.format(html=html))
-    
+
+    #     def setText(self, text):
+    #         text = text if (text and len(text) == 4) else "".join(sample(WORDS, 4))  # 随机4个字符
+    #         self._code = str(text)
+    #         html = "".join([FONT.format(color=COLORLIST[qrand() % 6], word=t) for t in text])
+    #         super(WidgetCode, self).setText(HTML.format(html=html))
+
     def mouseReleaseEvent(self, event):
         super(WidgetCode, self).mouseReleaseEvent(event)
         self.reset()
-    
+
     def timerEvent(self, event):
         if event.timerId() == self.timer.timerId():
             self.step += 1
             return self.update()
         return super(WidgetCode, self).timerEvent(event)
-    
+
     def paintEvent(self, event):
         painter = QPainter(self)
         painter.setRenderHint(QPainter.Antialiasing)
@@ -107,21 +110,20 @@ def paintEvent(self, event):
             painter.drawText(x, y - ((SINETABLE[index] * metrics.height()) / 400), ch)
             x += metrics.width(ch)
 
+
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
-    from PyQt5.QtGui import QFontDatabase
-    from PyQt5.QtWidgets import QLineEdit
+
     app = QApplication(sys.argv)
     app.setApplicationName("Validate Code")
     QFontDatabase.addApplicationFont("Data/itckrist.ttf")
     w = QWidget()
     layout = QHBoxLayout(w)
-    
+
     cwidget = WidgetCode(w, minimumHeight=35, minimumWidth=80)
     layout.addWidget(cwidget)
     lineEdit = QLineEdit(w, maxLength=4, placeholderText="请输入验证码并按回车验证",
-            returnPressed=lambda:print(cwidget.check(lineEdit.text())))
+                         returnPressed=lambda: print(cwidget.check(lineEdit.text())))
     layout.addWidget(lineEdit)
     w.show()
     sys.exit(app.exec_())
diff --git a/Demo/WeltHideWindow.py b/Demo/WeltHideWindow.py
old mode 100644
new mode 100755
index 73ba7b0d..91a49f9a
--- a/Demo/WeltHideWindow.py
+++ b/Demo/WeltHideWindow.py
@@ -4,32 +4,83 @@
 """
 Created on 2018年3月1日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: WeltHideWindow
 @description: 简单的窗口贴边隐藏
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
 
+import os
+import platform
+from subprocess import getoutput
 
-__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com'
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import (
+        QApplication,
+        QGridLayout,
+        QMessageBox,
+        QPushButton,
+        QSizePolicy,
+        QSpacerItem,
+        QWidget,
+    )
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import (
+        QApplication,
+        QGridLayout,
+        QMessageBox,
+        QPushButton,
+        QSizePolicy,
+        QSpacerItem,
+        QWidget,
+    )
 
 
-class WeltHideWindow(QWidget):
+def IsSupport():
+    """判断是否支持"""
+    if platform.system() == "Linux":
+        name = os.environ.get("XDG_SESSION_DESKTOP", "") + os.environ.get(
+            "XDG_CURRENT_DESKTOP", ""
+        )
+        if name.lower().find("gnome") != -1:
+            print("gnome desktop")
+            return False
+
+        wid = getoutput("xprop -root _NET_SUPPORTING_WM_CHECK").split(" # ")[-1]
+        print("wid:", wid)
+        if wid:
+            name = getoutput("xprop -id %s _NET_WM_NAME" % wid)
+            print("name:", name)
+            if name.lower().find("gnome") != -1:
+                print("gnome desktop")
+                return False
+
+    return True
 
+
+class WeltHideWindow(QWidget):
     def __init__(self, *args, **kwargs):
         super(WeltHideWindow, self).__init__(*args, **kwargs)
         self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
-        self.resize(800, 600)
+        self.resize(400, 300)
         self._width = QApplication.desktop().availableGeometry(self).width()
-        layout = QVBoxLayout(self)
-        layout.addWidget(QPushButton("关闭窗口", self, clicked=self.close))
+        layout = QGridLayout(self)
+        layout.addItem(
+            QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0
+        )
+        self.closeBtn = QPushButton("X", self)
+        layout.addWidget(self.closeBtn, 0, 1)
+        layout.addItem(
+            QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding), 1, 0
+        )
+        self.closeBtn.clicked.connect(self.close)
+        self.closeBtn.setMinimumSize(24, 24)
+        self.closeBtn.setMaximumSize(24, 24)
 
     def mousePressEvent(self, event):
-        '''鼠标按下事件,需要记录下坐标self._pos 和 是否可移动self._canMove'''
+        """鼠标按下事件,需要记录下坐标self._pos 和 是否可移动self._canMove"""
         super(WeltHideWindow, self).mousePressEvent(event)
         if event.button() == Qt.LeftButton:
             self._pos = event.globalPos() - self.pos()
@@ -37,13 +88,13 @@ def mousePressEvent(self, event):
             self._canMove = not self.isMaximized() or not self.isFullScreen()
 
     def mouseMoveEvent(self, event):
-        '''鼠标移动事件,动态调整窗口位置'''
+        """鼠标移动事件,动态调整窗口位置"""
         super(WeltHideWindow, self).mouseMoveEvent(event)
         if event.buttons() == Qt.LeftButton and self._canMove:
             self.move(event.globalPos() - self._pos)
 
     def mouseReleaseEvent(self, event):
-        '''鼠标弹起事件,这个时候需要判断窗口的左边是否符合贴到左边,顶部,右边一半'''
+        """鼠标弹起事件,这个时候需要判断窗口的左边是否符合贴到左边,顶部,右边一半"""
         super(WeltHideWindow, self).mouseReleaseEvent(event)
         self._canMove = False
         pos = self.pos()
@@ -60,7 +111,7 @@ def mouseReleaseEvent(self, event):
             return self.move(self._width - 1, y)
 
     def enterEvent(self, event):
-        '''鼠标进入窗口事件,用于弹出显示窗口'''
+        """鼠标进入窗口事件,用于弹出显示窗口"""
         super(WeltHideWindow, self).enterEvent(event)
         pos = self.pos()
         x = pos.x()
@@ -73,7 +124,7 @@ def enterEvent(self, event):
             return self.move(self._width - self.width(), y)
 
     def leaveEvent(self, event):
-        '''鼠标离开事件,如果原先窗口已经隐藏,并暂时显示,此时离开后需要再次隐藏'''
+        """鼠标离开事件,如果原先窗口已经隐藏,并暂时显示,此时离开后需要再次隐藏"""
         super(WeltHideWindow, self).leaveEvent(event)
         pos = self.pos()
         x = pos.x()
@@ -86,10 +137,18 @@ def leaveEvent(self, event):
             return self.move(self._width - 1, y)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
-    w = WeltHideWindow()
-    w.show()
-    sys.exit(app.exec_())
+    if not IsSupport():
+        QMessageBox.warning(
+            None,
+            "Warning",
+            "当前桌面不支持此功能",
+        )
+        app.quit()
+    else:
+        w = WeltHideWindow()
+        w.show()
+        sys.exit(app.exec_())
diff --git a/Demo/WindowNotify.py b/Demo/WindowNotify.py
index 408ff5e9..490dbd4e 100644
--- a/Demo/WindowNotify.py
+++ b/Demo/WindowNotify.py
@@ -1,27 +1,27 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: WindowNotify
 @description: 右下角弹窗
-'''
+"""
 import webbrowser
 
-from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, pyqtSignal
-from PyQt5.QtWidgets import QWidget, QPushButton
+try:
+    from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, pyqtSignal
+    from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QHBoxLayout
+except ImportError:
+    from PySide2.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, Signal as pyqtSignal
+    from PySide2.QtWidgets import QWidget, QPushButton, QApplication, QHBoxLayout
 
 from Lib.UiNotify import Ui_NotifyForm  # @UnresolvedImport
 
 
-__version__ = "0.0.1"
-
-
 class WindowNotify(QWidget, Ui_NotifyForm):
-
     SignalClosed = pyqtSignal()  # 弹窗关闭信号
 
     def __init__(self, title="", content="", timeout=5000, *args, **kwargs):
@@ -60,10 +60,10 @@ def onView(self):
         webbrowser.open_new_tab("http://alyl.vip/")
 
     def onClose(self):
-        #点击关闭按钮时
+        # 点击关闭按钮时
         print("onClose")
         self.isShow = False
-        QTimer.singleShot(100, self.closeAnimation)#启动弹回动画
+        QTimer.singleShot(100, self.closeAnimation)  # 启动弹回动画
 
     def _init(self):
         # 隐藏任务栏|去掉边框|顶层显示
@@ -112,13 +112,14 @@ def showAnimation(self):
         print("showAnimation isShow = True")
         # 显示动画
         self.isShow = True
-        self.animation.stop()#先停止之前的动画,重新开始
+        self.animation.stop()  # 先停止之前的动画,重新开始
         self.animation.setStartValue(self.pos())
         self.animation.setEndValue(self._endPos)
         self.animation.start()
         # 弹出5秒后,如果没有焦点则弹回去
         self._timer.start(self._timeout)
-#         QTimer.singleShot(self._timeout, self.closeAnimation)
+
+    #         QTimer.singleShot(self._timeout, self.closeAnimation)
 
     def closeAnimation(self):
         print("closeAnimation hasFocus", self.hasFocus())
@@ -158,9 +159,10 @@ def leaveEvent(self, event):
         if self._timeouted:
             QTimer.singleShot(1000, self.closeAnimation)
 
+
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication, QHBoxLayout
+
     app = QApplication(sys.argv)
 
     window = QWidget()
diff --git a/Demo/requirements.txt b/Demo/requirements.txt
index afed57a9..04346aa8 100644
--- a/Demo/requirements.txt
+++ b/Demo/requirements.txt
@@ -1,3 +1,4 @@
 pywin32
 numpy
 dlib
+python-box
diff --git a/Donate/PyQt_Group.png b/Donate/PyQt_Group.png
new file mode 100644
index 00000000..4be38ded
Binary files /dev/null and b/Donate/PyQt_Group.png differ
diff --git a/Donate/PyQt_Guild.png b/Donate/PyQt_Guild.png
new file mode 100644
index 00000000..68ffa8e6
Binary files /dev/null and b/Donate/PyQt_Guild.png differ
diff --git a/Donate/weixin.png b/Donate/weixin.png
index 0e7fe9d3..ac0a7284 100644
Binary files a/Donate/weixin.png and b/Donate/weixin.png differ
diff --git a/Donate/weixin2.png b/Donate/weixin2.png
new file mode 100644
index 00000000..0e7fe9d3
Binary files /dev/null and b/Donate/weixin2.png differ
diff --git a/LICENSE b/LICENSE
index 94a9ed02..8000a6fa 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,674 +1,504 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
 
- Copyright (C) 2007 Free Software Foundation, Inc. 
+    
     Copyright (C)   
 
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see   Copyright (C)   
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random
+  Hacker.
+
+  , 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/PyQtGraph/Data/__init__.py b/PyQtGraph/Data/__init__.py
index c5a3c6f8..ce54303d 100644
--- a/PyQtGraph/Data/__init__.py
+++ b/PyQtGraph/Data/__init__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 @author: wxj
 @license: (C) Hefei tongzhi electromechanical control technology co.LTD
 @contact: 
@@ -8,4 +8,4 @@
 @file: __init__.py.py
 @time: 2019/5/21 18:07
 @desc:
-'''
+"""
diff --git a/PyQtGraph/Data/graphAnalysis.py b/PyQtGraph/Data/graphAnalysis.py
index 67438538..0f30b3a6 100644
--- a/PyQtGraph/Data/graphAnalysis.py
+++ b/PyQtGraph/Data/graphAnalysis.py
@@ -8,6 +8,7 @@
 
 from PyQt5 import QtCore, QtGui, QtWidgets
 
+
 class graph_Form(object):
     def setupUi(self, Form):
         Form.setObjectName("Form")
@@ -50,4 +51,3 @@ def retranslateUi(self, Form):
         self.label.setText(_translate("Form", "图形分析"))
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "折线图"))
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Tab 2"))
-
diff --git a/PyQtGraph/Data/graphTest.py b/PyQtGraph/Data/graphTest.py
index 22cf8f7c..06ffde6d 100644
--- a/PyQtGraph/Data/graphTest.py
+++ b/PyQtGraph/Data/graphTest.py
@@ -8,6 +8,7 @@
 
 from PyQt5 import QtCore, QtGui, QtWidgets
 
+
 class graph_Form(object):
     def setupUi(self, Form):
         Form.setObjectName("Form")
@@ -51,4 +52,3 @@ def retranslateUi(self, Form):
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "折线图"))
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Tab 2"))
         self.pushButton_7.setText(_translate("Form", "分析"))
-
diff --git a/PyQtGraph/README.md b/PyQtGraph/README.md
index ef38e094..c745aaee 100644
--- a/PyQtGraph/README.md
+++ b/PyQtGraph/README.md
@@ -14,23 +14,28 @@
 6. `pg.PlotWidget()`鼠标获取X轴坐标
 
 ## 目录
+
 - [鼠标获取X轴坐标](#1鼠标获取X轴坐标)
 - [禁止右键点击功能、鼠标滚轮,添加滚动条等功能](#2禁止右键点击功能、鼠标滚轮,添加滚动条等功能)
 
 ## 1、鼠标获取X轴坐标
+
 [运行 mouseFlow.py](mouseFlow.py)
 
 
 
 ## 2、禁止右键点击功能、鼠标滚轮,添加滚动条等功能
-[运行 graph1.py](graph1.py)
+
+[运行 graph1.py](graph1.py) | [查看 graphTest.ui](Data/graphTest.ui)
 
 
 
-## 3、不用修改源码,重加载,解决右键保存图片异常;解决自定义坐标轴密集显示;禁止鼠标事件;
+## 3、不用修改源码,重加载,解决右键保存图片异常;解决自定义坐标轴密集显示;禁止鼠标事件
+
 [加载 tools.py](tools.py)
 
-## 4、QScrollArea添加和修改大小例子;
-[运行 testGraphAnalysis.py](testGraphAnalysis.py)
+## 4、QScrollArea添加和修改大小例子
+
+[运行 testGraphAnalysis.py](testGraphAnalysis.py) | [查看 graphAnalysis.ui](Data/graphAnalysis.ui)
 
 
diff --git a/PyQtGraph/graph1.py b/PyQtGraph/graph1.py
index 521ec41a..b37b8ade 100644
--- a/PyQtGraph/graph1.py
+++ b/PyQtGraph/graph1.py
@@ -1,19 +1,22 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 Created on 2019年5月21日
 @author: weike32
-@site: https://pyqt5.com ,https://github.com/weike32
+@site: https://pyqt.site ,https://github.com/weike32
 @email: 394967319@qq.com
 @file: CopyContent
 @description: 禁止右键,添加滑动窗口,点击按钮生成图片,自定义Y轴坐标,背景颜色调整
-'''
+"""
 import sys
-from PyQt5.QtWidgets import QDialog, QApplication, QWidget
-from qtpy import QtWidgets
+
 import pyqtgraph as pg
+from PyQt5.QtGui import QSpacerItem, QSizePolicy
+from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QScrollArea, QVBoxLayout
+
 from PyQtGraph.Data.graphTest import graph_Form
 
+
 class CustomViewBox(pg.ViewBox):
     def __init__(self, *args, **kwds):
         pg.ViewBox.__init__(self, *args, **kwds)
@@ -27,11 +30,12 @@ def mouseClickEvent(self, ev):
     def mouseDragEvent(self, ev):
         pg.ViewBox.mouseDragEvent(self, ev)
 
-    def wheelEvent(self,ev, axis=None):
+    def wheelEvent(self, ev, axis=None):
         # pg.ViewBox.wheelEvent(self, ev, axis)
         ev.ignore()
 
-class graphAnalysis(QDialog,graph_Form):
+
+class graphAnalysis(QDialog, graph_Form):
     def __init__(self):
         super(graphAnalysis, self).__init__()
         self.setupUi(self)
@@ -39,35 +43,37 @@ def __init__(self):
         self.tabWidget.clear()
 
     def test(self):
-        tab1 = QtWidgets.QWidget()
-        scrollArea = QtWidgets.QScrollArea(tab1)
-        scrollArea.setMinimumSize(984,550)
+        tab1 = QWidget()
+        scrollArea = QScrollArea(tab1)
+        scrollArea.setMinimumSize(984, 550)
         scrollArea.setWidgetResizable(True)
         labelsContainer = QWidget()
-        labelsContainer.setMinimumSize(0,1500)
+        labelsContainer.setMinimumSize(0, 1500)
         scrollArea.setWidget(labelsContainer)
-        layout = QtWidgets.QVBoxLayout(labelsContainer)
+        layout = QVBoxLayout(labelsContainer)
         time = ['2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00']
         value = [1.2, 2, 1, 4]
         xdict = dict(enumerate(time))
         ticks = [list(zip(range(4), tuple(time)))]
         vb = CustomViewBox()
-        plt = pg.PlotWidget(title="标题这里填写",viewBox=vb)
+        plt = pg.PlotWidget(title="标题这里填写", viewBox=vb)
         plt.setBackground(background=None)
         plt.plot(list(xdict.keys()), value)
         plt.getPlotItem().getAxis("bottom").setTicks(ticks)
-        temp = QtWidgets.QWidget()
-        temp.setMinimumSize(900,300)
-        temp.setMaximumSize(900,300)
-        layout1 = QtWidgets.QVBoxLayout(temp)
+        temp = QWidget()
+        temp.setMinimumSize(900, 300)
+        temp.setMaximumSize(900, 300)
+        layout1 = QVBoxLayout(temp)
         layout1.addWidget(plt)
         layout.addWidget(temp)
-        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum,
+                                 QSizePolicy.Expanding)
         layout.addItem(spacerItem)
         self.tabWidget.addTab(tab1, '这里tabWidget修改标签')
 
-if __name__ =="__main__":
+
+if __name__ == "__main__":
     app = QApplication(sys.argv)
     w = graphAnalysis()
     w.show()
-    sys.exit(app.exec_())
\ No newline at end of file
+    sys.exit(app.exec_())
diff --git a/PyQtGraph/mouseFlow.py b/PyQtGraph/mouseFlow.py
index 9d9f9fb4..1d3503b7 100644
--- a/PyQtGraph/mouseFlow.py
+++ b/PyQtGraph/mouseFlow.py
@@ -1,18 +1,20 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 Created on 2019年5月2日
 @author: weike32
-@site: https://pyqt5.com ,https://github.com/weike32
+@site: https://pyqt.site ,https://github.com/weike32
 @email: 394967319@qq.com
 @file: CopyContent
 @description: 查阅了很多博客,如果有异,可以联系作者邮箱。本Demo仅作学习参考用,保有后续相关权益。
-'''
+"""
 import sys
-from PyQt5.QtWidgets import QApplication, QMainWindow
-from PyQt5 import QtCore
+
 import numpy as np
 import pyqtgraph as pg
+from PyQt5 import QtCore
+from PyQt5.QtWidgets import QApplication, QMainWindow
+
 
 class Ui_Form(object):
     def setupUi(self, Form):
@@ -22,6 +24,7 @@ def setupUi(self, Form):
         self.graphicsView.setGeometry(QtCore.QRect(75, 131, 621, 441))
         self.graphicsView.setObjectName("graphicsView")
 
+
 class MyWindow(QMainWindow, Ui_Form):
     def __init__(self, parent=None):
         super(MyWindow, self).__init__(parent)
@@ -33,12 +36,15 @@ def __init__(self, parent=None):
         self.graphicsView.addItem(self.label)
         self.setMouseTracking(True)
         self.graphicsView.scene().sigMouseMoved.connect(self.onMouseMoved)
+
     def onMouseMoved(self, evt):
         if self.graphicsView.plotItem.vb.mapSceneToView(evt):
-            point =self.graphicsView.plotItem.vb.mapSceneToView(evt)
+            point = self.graphicsView.plotItem.vb.mapSceneToView(evt)
             self.label.setHtml("横坐标:{0}
".format(point.x()))
+
+
 if __name__ == '__main__':
     app = QApplication(sys.argv)
     myWin = MyWindow()
     myWin.show()
-    sys.exit(app.exec_())
\ No newline at end of file
+    sys.exit(app.exec_())
diff --git a/PyQtGraph/testGraphAnalysis.py b/PyQtGraph/testGraphAnalysis.py
index 7cdbb0bc..23f23a4c 100644
--- a/PyQtGraph/testGraphAnalysis.py
+++ b/PyQtGraph/testGraphAnalysis.py
@@ -1,18 +1,19 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 Created on 2019年8月17日
 @author: weike32
-@site: https://pyqt5.com ,https://github.com/weike32
+@site: https://pyqt.site ,https://github.com/weike32
 @email: 394967319@qq.com
 @file: CopyContent
 @description:
-'''
+"""
 import sys
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QDialog, QApplication, QWidget
-from qtpy import QtWidgets
+
 import pyqtgraph as pg
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QSpacerItem, QSizePolicy
+from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QScrollArea, QVBoxLayout
 
 from PyQtGraph.Data.graphAnalysis import graph_Form
 
@@ -26,38 +27,41 @@ def __init__(self, *args, **kwds):
     def mouseClickEvent(self, ev):
         if ev.button() == pg.QtCore.Qt.RightButton:
             self.autoRange()
+
     def mouseDragEvent(self, ev):
         pg.ViewBox.mouseDragEvent(self, ev)
-    def wheelEvent(self,ev,axis=None):
+
+    def wheelEvent(self, ev, axis=None):
         ev.ignore()
 
-class graphAnalysis(QDialog,graph_Form):
+
+class graphAnalysis(QDialog, graph_Form):
     def __init__(self):
         super(graphAnalysis, self).__init__()
         self.setupUi(self)
         self.pushButton_7.clicked.connect(self.test)
         self.tabWidget.clear()
 
-    def handleChanged(self,item,column):
+    def handleChanged(self, item, column):
         count = item.childCount()
         if item.checkState(column) == Qt.Checked:
             for index in range(count):
-                item.child(index).setCheckState(0,Qt.Checked)
+                item.child(index).setCheckState(0, Qt.Checked)
         if item.checkState(column) == Qt.Unchecked:
             for index in range(count):
-                item.child(index).setCheckState(0,Qt.Unchecked)
+                item.child(index).setCheckState(0, Qt.Unchecked)
 
     def test(self):
 
-        tab1 = QtWidgets.QWidget()
-        scrollArea = QtWidgets.QScrollArea(tab1)
-        scrollArea.setMinimumSize(650,550)
+        tab1 = QWidget()
+        scrollArea = QScrollArea(tab1)
+        scrollArea.setMinimumSize(650, 550)
         scrollArea.setWidgetResizable(True)
 
         labelsContainer = QWidget()
-        labelsContainer.setMinimumSize(0,3000+200)
+        labelsContainer.setMinimumSize(0, 3000 + 200)
         scrollArea.setWidget(labelsContainer)
-        layout = QtWidgets.QVBoxLayout(labelsContainer)
+        layout = QVBoxLayout(labelsContainer)
 
         time = ['2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00']
         value = [1.2, 2, 1, 4]
@@ -65,26 +69,28 @@ def test(self):
         ticks = [list(zip(range(4), tuple(time)))]
         for i in range(11):
             vb1 = CustomViewBox()
-            plt1 = pg.PlotWidget(title="Basic array plotting%s"%i, viewBox=vb1)
+            plt1 = pg.PlotWidget(title="Basic array plotting%s" % i, viewBox=vb1)
             plt1.resize(500, 500)
             plt1.setBackground(background=None)
             plt1.plot(list(xdict.keys()), value)
             plt1.getPlotItem().getAxis("bottom").setTicks(ticks)
-            temp1 = QtWidgets.QWidget()
+            temp1 = QWidget()
             temp1.setMinimumSize(600, 300)
             temp1.setMaximumSize(600, 300)
-            layout2 = QtWidgets.QVBoxLayout(temp1)
+            layout2 = QVBoxLayout(temp1)
             layout2.addWidget(plt1)
             layout.addWidget(temp1)
-        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum,
+                                 QSizePolicy.Expanding)
         layout.addItem(spacerItem)
         # print(layout.count())
         self.tabWidget.addTab(tab1, '12')
         for i in range(self.tabWidget.count()):
             self.tabWidget.widget(i)
 
-if __name__ =="__main__":
+
+if __name__ == "__main__":
     app = QApplication(sys.argv)
     w = graphAnalysis()
     w.show()
-    sys.exit(app.exec_())
\ No newline at end of file
+    sys.exit(app.exec_())
diff --git a/PyQtGraph/tools.py b/PyQtGraph/tools.py
index edfb931b..5a867de3 100644
--- a/PyQtGraph/tools.py
+++ b/PyQtGraph/tools.py
@@ -1,27 +1,31 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 Created on 2019年5月21日
 @author: weike32
-@site: https://pyqt5.com ,https://github.com/weike32
+@site: https://pyqt.site ,https://github.com/weike32
 @email: 394967319@qq.com
 @file: CopyContent
 @description: 工具类
-'''
+"""
+import pyqtgraph as pg
 from pyqtgraph.exporters.ImageExporter import ImageExporter, Exporter
 from pyqtgraph.parametertree import Parameter
-import pyqtgraph as pg
-#不用修改源码,重加载,解决右键保存图片异常
+
+
+# 不用修改源码,重加载,解决右键保存图片异常
 def widthChanged(self):
     sr = self.getSourceRect()
     ar = float(sr.height()) / sr.width()
     self.params.param('height').setValue(int(self.params['width'] * ar), blockSignal=self.heightChanged)
 
+
 def heightChanged(self):
     sr = self.getSourceRect()
     ar = float(sr.width()) / sr.height()
     self.params.param('width').setValue(int(self.params['height'] * ar), blockSignal=self.widthChanged)
 
+
 def New__init__(self, item):
     Exporter.__init__(self, item)
     tr = self.getTargetRect()
@@ -42,11 +46,14 @@ def New__init__(self, item):
     ])
     self.params.param('width').sigValueChanged.connect(self.widthChanged)
     self.params.param('height').sigValueChanged.connect(self.heightChanged)
+
+
 ImageExporter.heightChanged = heightChanged
 ImageExporter.widthChanged = widthChanged
 ImageExporter.__init__ = New__init__
 
-#解决自定义坐标轴密集显示
+
+# 解决自定义坐标轴密集显示
 class MyStringAxis(pg.AxisItem):
     def __init__(self, xdict, *args, **kwargs):
         pg.AxisItem.__init__(self, *args, **kwargs)
@@ -62,6 +69,8 @@ def tickStrings(self, values, scale, spacing):
                 vstr = ""
             strings.append(vstr)
         return strings
+
+
 # 禁止鼠标事件
 class CustomViewBox(pg.ViewBox):
     def __init__(self, *args, **kwds):
@@ -76,5 +85,5 @@ def mouseClickEvent(self, ev):
     def mouseDragEvent(self, ev):
         pg.ViewBox.mouseDragEvent(self, ev)
 
-    def wheelEvent(self,ev, axis=None):
-        pg.ViewBox.wheelEvent(self, ev, axis)
\ No newline at end of file
+    def wheelEvent(self, ev, axis=None):
+        pg.ViewBox.wheelEvent(self, ev, axis)
diff --git a/QAxWidget/README.md b/QAxWidget/README.md
index 8811751d..2c8d8cdd 100644
--- a/QAxWidget/README.md
+++ b/QAxWidget/README.md
@@ -4,10 +4,11 @@
   - [显示Word、Excel、PDF文件](#1显示WordExcelPDF文件)
 
 ## 1、显示Word、Excel、PDF文件
+
 [运行 ViewOffice.py](ViewOffice.py)
 
 1. 利用 `Word.Application` 打开Word文件
 1. 利用 `Excel.Application` 打开Excel文件
 1. 利用 `Adobe PDF Reader` 打开PDF文件(前提先装PDF软件)
 
-
\ No newline at end of file
+
diff --git a/QAxWidget/ViewOffice.py b/QAxWidget/ViewOffice.py
index f30d753f..3c331809 100644
--- a/QAxWidget/ViewOffice.py
+++ b/QAxWidget/ViewOffice.py
@@ -1,24 +1,20 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年4月6日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ViewOffice
 @description: 
-'''
+"""
+
 from PyQt5.QAxContainer import QAxWidget
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFileDialog,\
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFileDialog, \
     QMessageBox
 
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-
 class AxWidget(QWidget):
 
     def __init__(self, *args, **kwargs):
@@ -68,6 +64,7 @@ def closeEvent(self, event):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = AxWidget()
     w.show()
diff --git a/QCalendarWidget/CalendarQssStyle.py b/QCalendarWidget/CalendarQssStyle.py
index b385325e..9d2e0924 100644
--- a/QCalendarWidget/CalendarQssStyle.py
+++ b/QCalendarWidget/CalendarQssStyle.py
@@ -1,19 +1,23 @@
 """
 Created on 2018年1月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CalendarQssStyle
 @description: 日历美化样式
 """
 import sys
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QTextCharFormat, QBrush, QColor
-from PyQt5.QtWidgets import QApplication, QCalendarWidget
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QTextCharFormat, QBrush, QColor
+    from PyQt5.QtWidgets import QApplication, QCalendarWidget
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QTextCharFormat, QBrush, QColor
+    from PySide2.QtWidgets import QApplication, QCalendarWidget
 
-
-StyleSheet = '''
+StyleSheet = """
 /*顶部导航区域*/
 #qt_calendar_navigationbar {
     background-color: rgb(0, 188, 212);
@@ -101,7 +105,7 @@
     outline: 0px;/*去掉选中后的虚线框*/
     selection-background-color: rgb(0, 188, 212); /*选中背景颜色*/
 }
-'''
+"""
 
 
 class CalendarWidget(QCalendarWidget):
diff --git a/QCalendarWidget/README.md b/QCalendarWidget/README.md
index 62bd8815..0a6e8adb 100644
--- a/QCalendarWidget/README.md
+++ b/QCalendarWidget/README.md
@@ -4,8 +4,9 @@
   - [QSS美化日历样式](#1QSS美化日历样式)
 
 ## 1、QSS美化日历样式
+
 [运行 CalendarQssStyle.py](CalendarQssStyle.py)
 
 对日历控件的部分控件进行QSS美化,顶部背景颜色和高度,上下月按钮、月份选择、年选择、菜单
 
-
\ No newline at end of file
+
diff --git a/QColumnView/FileManager.py b/QColumnView/FileManager.py
new file mode 100644
index 00000000..810e489a
--- /dev/null
+++ b/QColumnView/FileManager.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/04/08
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: FileManager.py
+@description:
+"""
+
+try:
+    from PyQt5.QtWidgets import (
+        QApplication,
+        QColumnView,
+        QFileSystemModel,
+        QGridLayout,
+        QMessageBox,
+        QPushButton,
+        QSizePolicy,
+        QSpacerItem,
+        QWidget,
+    )
+except Exception:
+    from PySide2.QtWidgets import (
+        QApplication,
+        QColumnView,
+        QFileSystemModel,
+        QGridLayout,
+        QMessageBox,
+        QPushButton,
+        QSizePolicy,
+        QSpacerItem,
+        QWidget,
+    )
+
+
+class FileManager(QWidget):  # type: ignore
+    def __init__(self, *args, **kwargs):
+        super(FileManager, self).__init__(*args, **kwargs)
+        self.resize(800, 600)
+        self._view = QColumnView(self)
+        self._btn = QPushButton("确定", self)
+        layout = QGridLayout(self)
+        layout.addWidget(self._view, 0, 0, 1, 2)
+        layout.addItem(
+            QSpacerItem(
+                40,
+                20,
+                QSizePolicy.Expanding,
+                QSizePolicy.Minimum,
+            ),
+            1,
+            0,
+        )
+        layout.addWidget(self._btn, 1, 1, 1, 1)
+        layout.setRowStretch(1, 0)
+        self._model = QFileSystemModel(self)
+        self._model.setRootPath("")  # 设置根路径
+        self._view.setModel(self._model)  # type: ignore
+        self._btn.clicked.connect(self.onAccept)  # type: ignore
+
+    def onAccept(self):
+        path = self._model.filePath(self._view.currentIndex())
+        print("path", path)
+        if path:
+            QMessageBox.information(self, "提示", "路径:%s" % path)
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = FileManager()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QColumnView/README.md b/QColumnView/README.md
index e69de29b..bea28a78 100644
--- a/QColumnView/README.md
+++ b/QColumnView/README.md
@@ -0,0 +1,16 @@
+# QColumnView
+
+- 目录
+  - [文件系统浏览器](#1文件系统浏览器)
+
+## 1、文件系统浏览器
+
+[运行 FileManager.py](FileManager.py)
+
+一个省市区关联的三级联动,数据源在data.json中
+
+1. 通过`QFileSystemModel`模型来显示文件系统
+2. 结合`QColumnView`的`setMode`函数显示文件系统模型
+3. 通过`QFileSystemModel.filePath(QColumnView.currentIndex())`获取当前选中的路径
+
+
diff --git a/QColumnView/ScreenShot/FileManager.png b/QColumnView/ScreenShot/FileManager.png
new file mode 100644
index 00000000..c69afdce
Binary files /dev/null and b/QColumnView/ScreenShot/FileManager.png differ
diff --git a/QComboBox/CenterText.py b/QComboBox/CenterText.py
new file mode 100644
index 00000000..30eef32a
--- /dev/null
+++ b/QComboBox/CenterText.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2022/09/03
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CenterText.py
+@description: 文字居中对齐
+"""
+
+import sys
+
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QApplication, QStyle, QVBoxLayout, QWidget
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QApplication, QStyle, QVBoxLayout, QWidget
+
+from Lib.CtComboBox import CtComboBox
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+
+        c1 = CtComboBox(self)
+        c1.addItems(['item-%s' % i for i in range(10)])
+        layout.addWidget(c1)
+
+        # 可编辑
+        c2 = CtComboBox(self)
+        c2.setEditable(True)
+        c2.lineEdit().setAlignment(Qt.AlignCenter)
+        c2.addItems(['item-%s' % i for i in range(10)])
+        layout.addWidget(c2)
+
+        # 带图标
+        c3 = CtComboBox(self)
+        for i in range(10):
+            c3.addItem(c3.style().standardIcon(QStyle.SP_ComputerIcon),
+                       'item-%s' % i)
+        layout.addWidget(c3)
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QComboBox/CityLinkage.py b/QComboBox/CityLinkage.py
index 603f342d..203888f4 100644
--- a/QComboBox/CityLinkage.py
+++ b/QComboBox/CityLinkage.py
@@ -3,8 +3,8 @@
 
 """
 Created on 2018年1月27日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CityLinkage
 @description: 下拉联动
@@ -12,16 +12,18 @@
 import json
 import sys
 
-from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
-from PyQt5.QtGui import QStandardItemModel, QStandardItem
-from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox,\
-    QLabel, QSpacerItem, QSizePolicy
 import chardet
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
+    from PyQt5.QtGui import QStandardItemModel, QStandardItem
+    from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox, \
+        QLabel, QSpacerItem, QSizePolicy
+except ImportError:
+    from PySide2.QtCore import Qt, QSortFilterProxyModel, QRegExp
+    from PySide2.QtGui import QStandardItemModel, QStandardItem
+    from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox, \
+        QLabel, QSpacerItem, QSizePolicy
 
 
 class SortFilterProxyModel(QSortFilterProxyModel):
diff --git a/QComboBox/Lib/CtComboBox.py b/QComboBox/Lib/CtComboBox.py
new file mode 100644
index 00000000..3539ff11
--- /dev/null
+++ b/QComboBox/Lib/CtComboBox.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2022/09/04
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CtComboBox.py
+@description: 文字居中对齐
+"""
+
+try:
+    from PyQt5.QtCore import QRect, Qt
+    from PyQt5.QtGui import QIcon, QPalette
+    from PyQt5.QtWidgets import QComboBox, QProxyStyle
+except ImportError:
+    from PySide2.QtCore import QRect, Qt
+    from PySide2.QtGui import QIcon, QPalette
+    from PySide2.QtWidgets import QComboBox, QProxyStyle
+
+
+class ComboBoxStyle(QProxyStyle):
+
+    def drawControl(self, element, option, painter, widget=None):
+        if element == QProxyStyle.CE_ComboBoxLabel:
+            # https://github.com/qt/qtbase/blob/5.15.2/src/widgets/styles/qcommonstyle.cpp#L2200
+            editRect = self.subControlRect(QProxyStyle.CC_ComboBox, option,
+                                           QProxyStyle.SC_ComboBoxEditField,
+                                           widget)
+            painter.save()
+            painter.setClipRect(editRect)
+            if not option.currentIcon.isNull():
+                # 绘制图标
+                mode = QIcon.Normal if (
+                    option.state &
+                    QProxyStyle.State_Enabled) else QIcon.Disabled
+                pixmap = option.currentIcon.pixmap(
+                    widget.window().windowHandle() if widget else None,
+                    option.iconSize, mode)
+                iconRect = QRect(editRect)
+                iconRect.setWidth(option.iconSize.width() + 4)
+                iconRect = self.alignedRect(option.direction,
+                                            Qt.AlignLeft | Qt.AlignVCenter,
+                                            iconRect.size(), editRect)
+                if option.editable:
+                    painter.fillRect(iconRect,
+                                     option.palette.brush(QPalette.Base))
+                self.drawItemPixmap(painter, iconRect, Qt.AlignCenter, pixmap)
+
+                if option.direction == Qt.RightToLeft:
+                    editRect.translate(-4 - option.iconSize.width(), 0)
+                else:
+                    editRect.translate(option.iconSize.width() + 4, 0)
+            if option.currentText and not option.editable:
+                # 考虑右边箭头位置
+                arrowRect = self.subControlRect(QProxyStyle.CC_ComboBox, option,
+                                                QProxyStyle.SC_ComboBoxArrow,
+                                                widget)
+                editRect.setWidth(editRect.width() + arrowRect.width())
+                # 绘制居中文字
+                self.drawItemText(
+                    painter, editRect.adjusted(1, 0, -1, 0),
+                    self.visualAlignment(option.direction, Qt.AlignCenter),
+                    option.palette, option.state & QProxyStyle.State_Enabled,
+                    option.currentText)
+            painter.restore()
+            return
+        super(ComboBoxStyle, self).drawControl(element, option, painter, widget)
+
+
+class CtComboBox(QComboBox):
+
+    def __init__(self, *args, **kwargs):
+        super(CtComboBox, self).__init__(*args, **kwargs)
+        # 绑定每个元素添加信号,用于设置文本居中
+        self.model().rowsInserted.connect(self._onRowsInserted)
+        self.setStyle(ComboBoxStyle())
+
+    def _onRowsInserted(self, index, first, last):
+        if first < 0:
+            return
+        for i in range(first, last + 1):
+            self.view().model().item(i).setTextAlignment(Qt.AlignCenter)
diff --git a/QComboBox/Lib/__init__.py b/QComboBox/Lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/QComboBox/README.md b/QComboBox/README.md
index 80a20776..aa75ef6c 100644
--- a/QComboBox/README.md
+++ b/QComboBox/README.md
@@ -2,8 +2,10 @@
 
 - 目录
   - [下拉数据关联](#1下拉数据关联)
+  - [文本居中显示](#2文本居中显示)
 
 ## 1、下拉数据关联
+
 [运行 CityLinkage.py](CityLinkage.py)
 
 一个省市区关联的三级联动,数据源在data.json中
@@ -12,4 +14,13 @@
 2. 并根据唯一编码过滤,为了不影响内容显示,唯一编码的角色为`ToolTipRole`
 3. 用`QColumnView`可以实现类似效果
 
-
\ No newline at end of file
+
+
+## 2、文本居中显示
+
+[运行 CenterText.py](CenterText.py)
+
+1. 使用`QProxyStyle`对文件居中显示
+2. 新增得item数据使用`setTextAlignment`对齐
+
+
diff --git a/QComboBox/ScreenShot/CenterText.png b/QComboBox/ScreenShot/CenterText.png
new file mode 100644
index 00000000..a99d8fad
Binary files /dev/null and b/QComboBox/ScreenShot/CenterText.png differ
diff --git a/QFileSystemModel/CustomIcon.py b/QFileSystemModel/CustomIcon.py
index 9eb15b80..9b322288 100644
--- a/QFileSystemModel/CustomIcon.py
+++ b/QFileSystemModel/CustomIcon.py
@@ -3,28 +3,29 @@
 
 """
 Created on 2018年1月26日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: FileSystemModel
+@file: CustomIcon
 @description: 
 """
-import sys
-
-from PyQt5.QtCore import QFileInfo
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication,\
-    QTreeView
 
+import sys
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-# 图标提供类
+try:
+    from PyQt5.QtCore import QFileInfo
+    from PyQt5.QtGui import QIcon
+    from PyQt5.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication, \
+        QTreeView
+except ImportError:
+    from PySide2.QtCore import QFileInfo
+    from PySide2.QtGui import QIcon
+    from PySide2.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication, \
+        QTreeView
 
 
 class FileIconProvider(QFileIconProvider):
+    """图标提供类"""
 
     def __init__(self, *args, **kwargs):
         super(FileIconProvider, self).__init__(*args, **kwargs)
diff --git a/QFileSystemModel/README.md b/QFileSystemModel/README.md
index fc76971f..cd0d7e9e 100644
--- a/QFileSystemModel/README.md
+++ b/QFileSystemModel/README.md
@@ -4,9 +4,10 @@
   - [自定义图标](#1自定义图标)
 
 ## 1、自定义图标
+
 [运行 CustomIcon.py](CustomIcon.py)
 
 1. 继承 `QFileIconProvider` 类实现自己的图标提供器
 2. 重写 `def icon(self, type_info)` 方法根据文件类型返回对应的图标
 
-
\ No newline at end of file
+
diff --git a/QFlowLayout/Data/CoverItemWidget.ui b/QFlowLayout/Data/CoverItemWidget.ui
new file mode 100644
index 00000000..2f245e13
--- /dev/null
+++ b/QFlowLayout/Data/CoverItemWidget.ui
@@ -0,0 +1,85 @@
+
+
+ CoverItemWidget 
+ 
+  
+   
+    200 
+    256 
+    
+   
+  
+   
+    200 
+    256 
+    
+   
+  
+    
+  
+   
+    12 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   - 
+    
+     
+      
+       180 +180 +
 +
 +
+       +
+      true +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      
+       10 +
 +
 +
+       +
 +
+- 
+    
+     
+       +
 +
+ 
+  
+ 
+  
+   CoverLabel 
+   QLabel 
+   
+   
+  
+  
diff --git a/QFlowLayout/Data/CoverLabel.ui b/QFlowLayout/Data/CoverLabel.ui
new file mode 100644
index 00000000..2e7e0378
--- /dev/null
+++ b/QFlowLayout/Data/CoverLabel.ui
@@ -0,0 +1,109 @@
+
+
+ CoverLabel 
+ 
+  
+   
+    0 
+    0 
+    180 
+    180 
+    
+   
+  
+   PointingHandCursor 
+   
+  
+    
+  
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   - 
+    
+     
+      Qt::Vertical +
 +
+      
+       20 +135 +
 +
 +
 +
+- 
+    
+     
+      #widgetBottom {
+    background-color: rgba(0, 0, 0, 150);
+} +
 +
+      - 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+- 
+       
+        
+          +
 +
+- 
+       
+        
+         Qt::Horizontal +
 +
+         
+          40 +20 +
 +
 +
 +
+- 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+ +
 +
+ 
+  
+  
diff --git a/QFlowLayout/Data/Svg_icon_headset_sm.svg b/QFlowLayout/Data/Svg_icon_headset_sm.svg
new file mode 100644
index 00000000..98a1a359
--- /dev/null
+++ b/QFlowLayout/Data/Svg_icon_headset_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QFlowLayout/Data/Svg_icon_loading.svg b/QFlowLayout/Data/Svg_icon_loading.svg
new file mode 100644
index 00000000..0b00b799
--- /dev/null
+++ b/QFlowLayout/Data/Svg_icon_loading.svg
@@ -0,0 +1,31 @@
+
+    
+        
+             
+     
+    
+        
+            
+                 
+            
+                 
+         
+     
+ 
\ No newline at end of file
diff --git a/QFlowLayout/Data/Svg_icon_play_sm.svg b/QFlowLayout/Data/Svg_icon_play_sm.svg
new file mode 100644
index 00000000..08a47f20
--- /dev/null
+++ b/QFlowLayout/Data/Svg_icon_play_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QFlowLayout/HotPlaylist.py b/QFlowLayout/HotPlaylist.py
index 2fef1672..6b276c84 100644
--- a/QFlowLayout/HotPlaylist.py
+++ b/QFlowLayout/HotPlaylist.py
@@ -1,194 +1,47 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
-Created on 2018年2月4日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: TencentMovieHotPlay_Flow
-@description: 
-'''
+@file: HotPlaylist.py
+@description:
+"""
+
 import os
 import sys
-import webbrowser
-
-from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal
-from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
-    QBrush, QPaintEvent, QPixmap
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-from PyQt5.QtSvg import QSvgWidget
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
-    QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider
 
+from Lib.CoverItemWidget import CoverItemWidget
 from Lib.flowlayout import FlowLayout  # @UnresolvedImport
 from lxml.etree import HTML  # @UnresolvedImport
 
+try:
+    from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal
+    from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PyQt5.QtSvg import QSvgWidget
+    from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QScrollArea,
+                                 QWidget)
+except ImportError:
+    from PySide2.QtCore import Qt, QTimer, QUrl, pyqtSignal
+    from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PySide2.QtSvg import QSvgWidget
+    from PySide2.QtWidgets import (QAbstractSlider, QApplication, QScrollArea,
+                                   QWidget)
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-# offset=0,30,60,90
-Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
+# offset=0,35,70,105
+Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}"
 
-# 播放量图标
-Svg_icon_play_sm = '''
-     
-'''.encode()
+Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50"
 
-Svg_icon_loading = '''
-    
-        
-             
-     
-    
-        
-            
-                 
-            
-                 
-         
-     
- '''.encode()
+Referer = b"https://music.163.com/"
 
-# 主演
+# 作者
 Actor = '''{title}  '''
 
 
-class CoverLabel(QLabel):
-
-    def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
-        super(CoverLabel, self).__init__(*args, **kwargs)
-#         super(CoverLabel, self).__init__(
-#             ' 主演: " + \
-                "".join([Actor.format(**dict(fd.items()))
-                         for fd in li.xpath(".//div[@class='figure_desc']/a")])
+            a = li.find('.//div/a')
+            play_url = "https://music.163.com/" + a.get("href")  # 歌单播放地址
+            img = li.find(".//div/img")
+            cover_url = img.get("src")  # 封面图片
+            playlist_title = a.get("title")  # 歌单名
+            # 歌手
+            author_info = li.xpath(".//p[2]/a")[0]
+            playlist_author = "".format(
+                Actor.format(href="https://music.163.com/" +
+                             author_info.get("href"),
+                             title=author_info.get("title")))
             # 播放数
-            figure_count = (
-                li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
+            play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0]
             path = "cache/{0}.jpg".format(
-                os.path.splitext(os.path.basename(video_url))[0])
+                os.path.splitext(os.path.basename(cover_url).split('?')[0])[0])
             cover_path = "Data/pic_v.png"
             if os.path.isfile(path):
                 cover_path = path
-            iwidget = ItemWidget(cover_path, figure_info, figure_title,
-                                 figure_score, figure_desc, figure_count, video_url, cover_url, path, self)
+
+            # print(cover_path, playlist_title,
+            #       playlist_author, play_count, play_url, cover_url, path)
+            iwidget = CoverItemWidget(self, manager=self._manager)
+            iwidget.init(cover_path, playlist_title, playlist_author,
+                         play_count, play_url, cover_url, path)
             self._layout.addWidget(iwidget)
 
 
@@ -283,9 +142,11 @@ def __init__(self, *args, **kwargs):
         # 连接竖着的滚动条滚动事件
         self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered)
         # 进度条
-        self.loadWidget = QSvgWidget(
-            self, minimumHeight=120, minimumWidth=120, visible=False)
-        self.loadWidget.load(Svg_icon_loading)
+        self.loadWidget = QSvgWidget(self,
+                                     minimumHeight=120,
+                                     minimumWidth=120,
+                                     visible=False)
+        self.loadWidget.load('Data/Svg_icon_loading.svg')
 
     def setLoadStarted(self, started):
         self._loadStart = started
@@ -297,7 +158,8 @@ def onActionTriggered(self, action):
         if action != QAbstractSlider.SliderMove or self._loadStart:
             return
         # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
-        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
+        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar(
+        ).maximum():
             # 可以下一页了
             self._widget.load()
 
@@ -306,9 +168,7 @@ def resizeEvent(self, event):
         self.loadWidget.setGeometry(
             int((self.width() - self.loadWidget.minimumWidth()) / 2),
             int((self.height() - self.loadWidget.minimumHeight()) / 2),
-            self.loadWidget.minimumWidth(),
-            self.loadWidget.minimumHeight()
-        )
+            self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight())
 
 
 if __name__ == "__main__":
diff --git a/QFlowLayout/Lib/CoverItemWidget.py b/QFlowLayout/Lib/CoverItemWidget.py
new file mode 100644
index 00000000..5ef3ff00
--- /dev/null
+++ b/QFlowLayout/Lib/CoverItemWidget.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverItemWidget.py
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QSize, QUrl
+    from PyQt5.QtGui import QPaintEvent, QPixmap
+    from PyQt5.QtNetwork import QNetworkRequest
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import QSize, QUrl
+    from PySide2.QtGui import QPaintEvent, QPixmap
+    from PySide2.QtNetwork import QNetworkRequest
+    from PySide2.QtWidgets import QWidget
+
+from .Ui_CoverItemWidget import Ui_CoverItemWidget  # @UnresolvedImport
+
+
+class CoverItemWidget(QWidget, Ui_CoverItemWidget):
+
+    def __init__(self, *args, **kwargs):
+        self._manager = kwargs.pop('manager', None)
+        super(CoverItemWidget, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, playlist_title, playlist_author, play_count,
+             play_url, cover_url, img_path):
+        self.img_path = img_path
+        self.cover_url = cover_url
+        # 图片label
+        self.labelCover.init(cover_path, play_url, play_count)
+
+        # 歌单
+        self.labelTitle.setText(playlist_title)
+
+        # 作者
+        self.labelAuthor.setText(playlist_author)
+
+    def setCover(self, path):
+        self.labelCover.setCoverPath(path)
+        self.labelCover.setPixmap(QPixmap(path))
+
+    def sizeHint(self):
+        # 每个item控件的大小
+        return QSize(200, 256)
+
+    def event(self, event):
+        if isinstance(event, QPaintEvent):
+            if event.rect().height() > 20 and hasattr(self, "labelCover"):
+                if self.labelCover.cover_path.find("pic_v.png") > -1:  # 封面未加载
+                    # print("start download img:", self.cover_url)
+                    req = QNetworkRequest(QUrl(self.cover_url))
+                    # 设置两个自定义属性方便后期reply中处理
+                    req.setAttribute(QNetworkRequest.User + 1, self)
+                    req.setAttribute(QNetworkRequest.User + 2, self.img_path)
+                    if self._manager:
+                        self._manager.get(req)  # 调用父窗口中的下载器下载
+        return super(CoverItemWidget, self).event(event)
diff --git a/QFlowLayout/Lib/CoverLabel.py b/QFlowLayout/Lib/CoverLabel.py
new file mode 100644
index 00000000..dbee15af
--- /dev/null
+++ b/QFlowLayout/Lib/CoverLabel.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverLabel.py
+@description:
+"""
+import webbrowser
+
+try:
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QLabel
+except ImportError:
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QLabel
+
+from .Ui_CoverLabel import Ui_CoverLabel  # @UnresolvedImport
+
+
+class CoverLabel(QLabel, Ui_CoverLabel):
+
+    def __init__(self, *args, **kwargs):
+        super(CoverLabel, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, play_url, play_count):
+        self.cover_path = cover_path
+        self.play_url = play_url
+        self.setPixmap(QPixmap(cover_path))
+        self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg'))
+        self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg'))
+        self.labelCount.setStyleSheet('color: #999999;')
+        self.labelCount.setText(play_count)
+
+    def setCoverPath(self, path):
+        self.cover_path = path
+
+    def mouseReleaseEvent(self, event):
+        super(CoverLabel, self).mouseReleaseEvent(event)
+        webbrowser.open_new_tab(self.play_url)
diff --git a/QFlowLayout/Lib/Ui_CoverItemWidget.py b/QFlowLayout/Lib/Ui_CoverItemWidget.py
new file mode 100644
index 00000000..c048f686
--- /dev/null
+++ b/QFlowLayout/Lib/Ui_CoverItemWidget.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverItemWidget.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverItemWidget(object):
+    def setupUi(self, CoverItemWidget):
+        CoverItemWidget.setObjectName("CoverItemWidget")
+        CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget)
+        self.verticalLayout.setContentsMargins(10, 10, 10, 10)
+        self.verticalLayout.setSpacing(12)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.labelCover = CoverLabel(CoverItemWidget)
+        self.labelCover.setMinimumSize(QtCore.QSize(180, 180))
+        self.labelCover.setText("")
+        self.labelCover.setScaledContents(True)
+        self.labelCover.setAlignment(QtCore.Qt.AlignCenter)
+        self.labelCover.setObjectName("labelCover")
+        self.verticalLayout.addWidget(self.labelCover)
+        self.labelTitle = QtWidgets.QLabel(CoverItemWidget)
+        font = QtGui.QFont()
+        font.setPointSize(10)
+        self.labelTitle.setFont(font)
+        self.labelTitle.setText("")
+        self.labelTitle.setObjectName("labelTitle")
+        self.verticalLayout.addWidget(self.labelTitle)
+        self.labelAuthor = QtWidgets.QLabel(CoverItemWidget)
+        self.labelAuthor.setText("")
+        self.labelAuthor.setObjectName("labelAuthor")
+        self.verticalLayout.addWidget(self.labelAuthor)
+
+        self.retranslateUi(CoverItemWidget)
+        QtCore.QMetaObject.connectSlotsByName(CoverItemWidget)
+
+    def retranslateUi(self, CoverItemWidget):
+        pass
+from .CoverLabel import CoverLabel
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverItemWidget = QtWidgets.QWidget()
+    ui = Ui_CoverItemWidget()
+    ui.setupUi(CoverItemWidget)
+    CoverItemWidget.show()
+    sys.exit(app.exec_())
diff --git a/QFlowLayout/Lib/Ui_CoverLabel.py b/QFlowLayout/Lib/Ui_CoverLabel.py
new file mode 100644
index 00000000..86e1526a
--- /dev/null
+++ b/QFlowLayout/Lib/Ui_CoverLabel.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverLabel.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverLabel(object):
+    def setupUi(self, CoverLabel):
+        CoverLabel.setObjectName("CoverLabel")
+        CoverLabel.resize(180, 180)
+        CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
+        CoverLabel.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel)
+        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.verticalLayout.setSpacing(0)
+        self.verticalLayout.setObjectName("verticalLayout")
+        spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.verticalLayout.addItem(spacerItem)
+        self.widgetBottom = QtWidgets.QWidget(CoverLabel)
+        self.widgetBottom.setStyleSheet("#widgetBottom {\n"
+"    background-color: rgba(0, 0, 0, 150);\n"
+"}")
+        self.widgetBottom.setObjectName("widgetBottom")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.labelHeadset = QtWidgets.QLabel(self.widgetBottom)
+        self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelHeadset.setText("")
+        self.labelHeadset.setObjectName("labelHeadset")
+        self.horizontalLayout.addWidget(self.labelHeadset)
+        self.labelCount = QtWidgets.QLabel(self.widgetBottom)
+        self.labelCount.setText("")
+        self.labelCount.setObjectName("labelCount")
+        self.horizontalLayout.addWidget(self.labelCount)
+        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem1)
+        self.labelPlay = QtWidgets.QLabel(self.widgetBottom)
+        self.labelPlay.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelPlay.setText("")
+        self.labelPlay.setObjectName("labelPlay")
+        self.horizontalLayout.addWidget(self.labelPlay)
+        self.verticalLayout.addWidget(self.widgetBottom)
+
+        self.retranslateUi(CoverLabel)
+        QtCore.QMetaObject.connectSlotsByName(CoverLabel)
+
+    def retranslateUi(self, CoverLabel):
+        pass
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverLabel = QtWidgets.QWidget()
+    ui = Ui_CoverLabel()
+    ui.setupUi(CoverLabel)
+    CoverLabel.show()
+    sys.exit(app.exec_())
diff --git a/QFlowLayout/Lib/flowlayout.py b/QFlowLayout/Lib/flowlayout.py
index ef1d54e3..3cc9a9a3 100644
--- a/QFlowLayout/Lib/flowlayout.py
+++ b/QFlowLayout/Lib/flowlayout.py
@@ -41,10 +41,14 @@
 ##
 #############################################################################
 
-
-from PyQt5.QtCore import QPoint, QRect, QSize, Qt
-from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, QSizePolicy,
-                             QWidget)
+try:
+    from PyQt5.QtCore import QPoint, QRect, QSize, Qt
+    from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton,
+                                 QSizePolicy, QWidget)
+except ImportError:
+    from PySide2.QtCore import QPoint, QRect, QSize, Qt
+    from PySide2.QtWidgets import (QApplication, QLayout, QPushButton,
+                                   QSizePolicy, QWidget)
 
 
 class Window(QWidget):
@@ -152,7 +156,6 @@ def doLayout(self, rect, testOnly):
 
 
 if __name__ == '__main__':
-
     import sys
 
     app = QApplication(sys.argv)
diff --git a/QFlowLayout/README.md b/QFlowLayout/README.md
index dfd85619..21868ec8 100644
--- a/QFlowLayout/README.md
+++ b/QFlowLayout/README.md
@@ -1,21 +1,22 @@
 # QListView
 
 - 目录
-  - [腾讯视频热播列表](#1腾讯视频热播列表)
+  - [音乐热歌列表](#1音乐热歌列表)
+
+## 1、音乐热歌列表
 
-## 1、腾讯视频热播列表
 [运行 HotPlaylist.py](HotPlaylist.py)
 
 简单思路说明:
 
- - 利用`QScrollArea`滚动显示,自定义的`QFlowLayout`做布局来放置自定义的Widget
- - `QNetworkAccessManager`异步下载网页和图片
- - `QScrollArea`滚动到底部触发下一页加载
+- 利用`QScrollArea`滚动显示,自定义的`QFlowLayout`做布局来放置自定义的Widget
+- `QNetworkAccessManager`异步下载网页和图片
+- `QScrollArea`滚动到底部触发下一页加载
 
 自定义控件说明:
 
- - 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
- - 字体颜色用qss设置
- - 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
+- 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
+- 字体颜色用qss设置
+- 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
 
-
\ No newline at end of file
+
diff --git a/QFlowLayout/testxpath.py b/QFlowLayout/testxpath.py
new file mode 100644
index 00000000..e167f231
--- /dev/null
+++ b/QFlowLayout/testxpath.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/21
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: testxpath.py
+@description:
+"""
+
+import os
+
+from lxml.etree import HTML  # @UnresolvedImport
+
+# 作者
+Actor = '''{title}  '''
+
+
+def _makeItem(lis):
+    for li in lis:
+        a = li.find('.//div/a')
+        play_url = "https://music.163.com/" + a.get("href")  # 视频播放地址
+        img = li.find(".//div/img")
+        cover_url = img.get("src")  # 封面图片
+        playlist_title = a.get("title")  # 歌单名
+        # figure_info = a.find("div/span")
+        figure_info = "aaa"  #if figure_info is None else figure_info.text  # 影片信息
+        figure_score = ""  # 评分
+        # 歌手
+        figure = li.xpath(".//p[2]/a")[0]
+        playlist_author = "".format(
+            Actor.format(href="https://music.163.com/" +figure.get("href"),title=figure.get("title")))
+        # 播放数
+        play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0]
+        path = "cache/{0}.jpg".format(
+            os.path.splitext(os.path.basename(cover_url).split('?')[0])[0])
+        cover_path = "Data/pic_v.png"
+        if os.path.isfile(path):
+            cover_path = path
+
+        print(cover_path, playlist_title,  playlist_author,
+              play_count, play_url, cover_url, path)
+        # iwidget = ItemWidget(cover_path, playlist_title,
+        #                       playlist_author, play_count, play_url,
+        #                      cover_url, path, self)
+        # self._layout.addWidget(iwidget)
+
+
+def _parseHtml(html):
+    html = HTML(html)
+    # 查找所有的li
+    lis = html.xpath("//ul[@id='m-pl-container']/li")
+    # if not lis:
+    #     self.Page = -1  # 后面没有页面了
+    #     return
+    # self.Page += 1
+    _makeItem(lis)
+
+
+if __name__ == '__main__':
+    data = open(r'D:\Computer\Desktop\163.html', 'rb').read()
+    _parseHtml(data)
\ No newline at end of file
diff --git a/QFont/AwesomeFont.py b/QFont/AwesomeFont.py
index 7d8098de..58b1baf2 100644
--- a/QFont/AwesomeFont.py
+++ b/QFont/AwesomeFont.py
@@ -1,24 +1,25 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: AwesomeFont
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 import sys
 
-from PyQt5.QtGui import QFontDatabase, QFont
-from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout,\
-    QScrollArea, QPushButton
+try:
+    from PyQt5.QtGui import QFontDatabase, QFont
+    from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, \
+        QScrollArea, QPushButton
+except ImportError:
+    from PySide2.QtGui import QFontDatabase, QFont
+    from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, \
+        QScrollArea, QPushButton
 
 from Lib.FontAwesome import FontAwesomes  # @UnresolvedImport
 
@@ -44,7 +45,7 @@ def __init__(self):
                     minimumHeight=33,
                     font=QFont("FontAwesome", 14)),
                     row, col, 1, 1)
-        
+
         self.showMaximized()
 
     def resizeEvent(self, event):
diff --git "a/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py" "b/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py"
index 624999c6..216d2e35 100644
--- "a/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py"
+++ "b/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py"
@@ -1,5 +1,6 @@
 # from bs4 import BeautifulSoup
 import re
+
 cheatsheet = open("cheatsheet.txt", "rb").read().decode()
 
 re_fa = re.compile("      fa(.*)")
diff --git a/QFont/Lib/FontAwesome.py b/QFont/Lib/FontAwesome.py
index 60ca9cae..3527c2ea 100644
--- a/QFont/Lib/FontAwesome.py
+++ b/QFont/Lib/FontAwesome.py
@@ -1,21 +1,22 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年3月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FontAwesome
 @description: 
-'''
+"""
+
+
 # FontAwesome 版本: 4.7.0
 # 字体图标地址: http://fontawesome.io/icons/
 # 字体字符地址: http://fontawesome.io/cheatsheet/
 
 
 class FontAwesomes:
-
     FA = None
 
     @classmethod
diff --git a/QFont/README.md b/QFont/README.md
index 21a12969..28d0cd86 100644
--- a/QFont/README.md
+++ b/QFont/README.md
@@ -4,8 +4,9 @@
   - [加载自定义字体](#1加载自定义字体)
 
 ## 1、加载自定义字体
+
 [运行 AwesomeFont.py](AwesomeFont.py)
 
 通过`QFontDatabase.addApplicationFont`加载字体文件
 
-
\ No newline at end of file
+
diff --git a/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py b/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py
index 9cbbd759..4551bd4f 100644
--- a/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py
+++ b/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py
@@ -4,20 +4,18 @@
 """
 Created on 2018年9月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: AnimationShadowEffect
 @description: 边框动画阴影动画
 """
-from PyQt5.QtCore import QPropertyAnimation, pyqtProperty
-from PyQt5.QtWidgets import QGraphicsDropShadowEffect
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QPropertyAnimation, pyqtProperty
+    from PyQt5.QtWidgets import QGraphicsDropShadowEffect
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation, Property as pyqtProperty
+    from PySide2.QtWidgets import QGraphicsDropShadowEffect
 
 
 class AnimationShadowEffect(QGraphicsDropShadowEffect):
diff --git a/QGraphicsDropShadowEffect/README.md b/QGraphicsDropShadowEffect/README.md
index 6e5be6fb..20d5d9d3 100644
--- a/QGraphicsDropShadowEffect/README.md
+++ b/QGraphicsDropShadowEffect/README.md
@@ -4,6 +4,7 @@
   - [边框阴影动画](#1边框阴影动画)
 
 ## 1、边框阴影动画
+
 [运行 ShadowEffect.py](ShadowEffect.py)
 
 1. 通过`setGraphicsEffect`设置控件的边框阴影
@@ -11,4 +12,4 @@
 3. 通过`QPropertyAnimation`属性动画不断改变`radius`的值并调用`setBlurRadius`更新半径值
 4. 不能对父控件使用
 
-
\ No newline at end of file
+
diff --git a/QGraphicsDropShadowEffect/ShadowEffect.py b/QGraphicsDropShadowEffect/ShadowEffect.py
index 9c03cc6e..b121e852 100644
--- a/QGraphicsDropShadowEffect/ShadowEffect.py
+++ b/QGraphicsDropShadowEffect/ShadowEffect.py
@@ -4,23 +4,22 @@
 """
 Created on 2018年9月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ShadowEffect
 @description: 
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPixmap
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit
-
-from Lib.AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport
 
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit, QApplication
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+from Lib.AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport
 
 
 class Window(QWidget):
@@ -64,7 +63,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QGraphicsView/AddQWidget.py b/QGraphicsView/AddQWidget.py
index 1689972c..ff8026c9 100644
--- a/QGraphicsView/AddQWidget.py
+++ b/QGraphicsView/AddQWidget.py
@@ -3,22 +3,22 @@
 
 """
 Created on 2017年12月23日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: AddQWidget
 @description: 
 """
 import sys
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout,\
-    QApplication, QGraphicsView, QGraphicsScene
-
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout, \
+        QApplication, QGraphicsView, QGraphicsScene
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout, \
+        QApplication, QGraphicsView, QGraphicsScene
 
 
 class ToolTipItem(QWidget):
diff --git a/QGraphicsView/Data/bg.jpg b/QGraphicsView/Data/bg.jpg
new file mode 100644
index 00000000..db164572
Binary files /dev/null and b/QGraphicsView/Data/bg.jpg differ
diff --git a/QGraphicsView/Data/icons/basic.png b/QGraphicsView/Data/icons/basic.png
new file mode 100644
index 00000000..7627b244
Binary files /dev/null and b/QGraphicsView/Data/icons/basic.png differ
diff --git a/QGraphicsView/Data/icons/business.png b/QGraphicsView/Data/icons/business.png
new file mode 100644
index 00000000..479d2e0d
Binary files /dev/null and b/QGraphicsView/Data/icons/business.png differ
diff --git a/QGraphicsView/Data/icons/cloudService.png b/QGraphicsView/Data/icons/cloudService.png
new file mode 100644
index 00000000..3c3579f1
Binary files /dev/null and b/QGraphicsView/Data/icons/cloudService.png differ
diff --git a/QGraphicsView/Data/icons/dataBase.png b/QGraphicsView/Data/icons/dataBase.png
new file mode 100644
index 00000000..716e0cff
Binary files /dev/null and b/QGraphicsView/Data/icons/dataBase.png differ
diff --git a/QGraphicsView/Data/icons/engineering.png b/QGraphicsView/Data/icons/engineering.png
new file mode 100644
index 00000000..4c68bd45
Binary files /dev/null and b/QGraphicsView/Data/icons/engineering.png differ
diff --git a/QGraphicsView/Data/icons/network.png b/QGraphicsView/Data/icons/network.png
new file mode 100644
index 00000000..dde507a2
Binary files /dev/null and b/QGraphicsView/Data/icons/network.png differ
diff --git a/QGraphicsView/Data/icons/science.png b/QGraphicsView/Data/icons/science.png
new file mode 100644
index 00000000..01c37711
Binary files /dev/null and b/QGraphicsView/Data/icons/science.png differ
diff --git a/QGraphicsView/Data/icons/softwareEngineering.png b/QGraphicsView/Data/icons/softwareEngineering.png
new file mode 100644
index 00000000..51be3221
Binary files /dev/null and b/QGraphicsView/Data/icons/softwareEngineering.png differ
diff --git a/QGraphicsView/Data/icons/wireframe.png b/QGraphicsView/Data/icons/wireframe.png
new file mode 100644
index 00000000..040e0f42
Binary files /dev/null and b/QGraphicsView/Data/icons/wireframe.png differ
diff --git a/QGraphicsView/DragGraphics.py b/QGraphicsView/DragGraphics.py
new file mode 100644
index 00000000..2ba56060
--- /dev/null
+++ b/QGraphicsView/DragGraphics.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/09
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: DragGraphics.py
+@description:
+"""
+
+import json
+import os
+
+try:
+    from PyQt5.QtCore import QMimeData, Qt
+    from PyQt5.QtGui import QDrag, QIcon, QPixmap
+    from PyQt5.QtWidgets import (QApplication, QGraphicsPixmapItem,
+                                 QGraphicsScene, QGraphicsView, QHBoxLayout,
+                                 QListWidget, QListWidgetItem, QTreeWidget,
+                                 QTreeWidgetItem, QWidget)
+except ImportError:
+    from PySide2.QtCore import QMimeData, Qt
+    from PySide2.QtGui import QDrag, QIcon, QPixmap
+    from PySide2.QtWidgets import (QApplication, QGraphicsPixmapItem,
+                                   QGraphicsScene, QGraphicsView, QHBoxLayout,
+                                   QListWidget, QListWidgetItem, QTreeWidget,
+                                   QTreeWidgetItem, QWidget)
+
+
+class ListWidget(QListWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(ListWidget, self).__init__(*args, **kwargs)
+        self.setDragEnabled(True)
+        self.setDragDropMode(QListWidget.DragOnly)
+        self.setDefaultDropAction(Qt.IgnoreAction)
+        self.setEditTriggers(QListWidget.NoEditTriggers)
+        self.setResizeMode(QListWidget.Adjust)
+        self.setViewMode(QListWidget.IconMode)
+
+    def startDrag(self, supportedActions):
+        items = self.selectedItems()
+        if not items:
+            return
+        # 这里就简单的根据名字提示来传递数据了,实际上可以传递任意数据
+        data = QMimeData()
+        data.setData('application/node-items',
+                     json.dumps([item.toolTip() for item in items]).encode())
+        # 这里简单显示第一个缩略图
+        pixmap = items[0].icon().pixmap(36, 36)
+        drag = QDrag(self)
+        drag.setMimeData(data)
+        drag.setPixmap(pixmap)
+        drag.setHotSpot(pixmap.rect().center())
+        drag.exec_(supportedActions)
+
+
+class GraphicsView(QGraphicsView):
+
+    def __init__(self, *args, **kwargs):
+        super(GraphicsView, self).__init__(*args, **kwargs)
+        self.setAcceptDrops(True)
+        self._scene = QGraphicsScene(self)  # 场景
+        self.setScene(self._scene)
+
+    def dragEnterEvent(self, event):
+        """判断拖入的数据是否支持"""
+        mimeData = event.mimeData()
+        if not mimeData.hasFormat('application/node-items'):
+            event.ignore()
+            return
+
+        event.acceptProposedAction()
+
+    dragMoveEvent = dragEnterEvent
+
+    def dropEvent(self, event):
+        """获取拖拽的数据并绘制对于的图形"""
+        datas = event.mimeData().data('application/node-items')
+        datas = json.loads(datas.data().decode())
+        print('datas:', datas)
+
+        path = os.path.join(os.path.dirname(__file__), 'Data/icons')
+        for name in datas:
+            item = QGraphicsPixmapItem(QPixmap(os.path.join(path, name)))
+            item.setFlags(QGraphicsPixmapItem.ItemIsFocusable |
+                          QGraphicsPixmapItem.ItemIsMovable)
+            self._scene.addItem(item)
+            pos = self.mapToScene(event.pos())
+            item.moveBy(pos.x(), pos.y())
+
+
+class DragGraphics(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(DragGraphics, self).__init__(*args, **kwargs)
+        self.resize(800, 600)
+        layout = QHBoxLayout(self)
+
+        # 左侧树形控制
+        self.treeWidget = QTreeWidget(self)
+        self.treeWidget.header().setVisible(False)
+        self.treeWidget.setMaximumWidth(300)
+        layout.addWidget(self.treeWidget)
+
+        # 右侧图形显示
+        self.graphicsView = GraphicsView(self)
+        layout.addWidget(self.graphicsView)
+
+        self._init_trees()
+
+    def _init_trees(self):
+        """初始化树形控件中的图形节点列表"""
+        # 1. 获取所有图标
+        path = os.path.join(os.path.dirname(__file__), 'Data/icons')
+        icons = [os.path.join(path, name) for name in os.listdir(path)]
+
+        # 2. 添加根节点
+        for i in range(2):
+            item = QTreeWidgetItem(self.treeWidget)
+            item.setText(0, 'View %d' % i)
+
+            # 3. 添加子节点作为容器用于存放图标
+            itemc = QTreeWidgetItem(item)
+            child = ListWidget(self.treeWidget)
+            self.treeWidget.setItemWidget(itemc, 0, child)
+
+            # 4. 添加图标
+            for icon in icons:
+                item = QListWidgetItem(child)
+                item.setIcon(QIcon(icon))
+                item.setToolTip(os.path.basename(icon))
+
+        self.treeWidget.expandAll()
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = DragGraphics()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QGraphicsView/ImageView.py b/QGraphicsView/ImageView.py
new file mode 100644
index 00000000..f6789c54
--- /dev/null
+++ b/QGraphicsView/ImageView.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/11/12
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ImageView
+@description: 图片查看控件,支持移动、放大、缩小
+"""
+
+import os
+
+try:
+    from PyQt5.QtCore import QPointF, Qt, QRectF, QSizeF
+    from PyQt5.QtGui import QPainter, QColor, QImage, QPixmap
+    from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene
+except ImportError:
+    from PySide2.QtCore import QPointF, Qt, QRectF, QSizeF
+    from PySide2.QtGui import QPainter, QColor, QImage, QPixmap
+    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene
+
+
+class ImageView(QGraphicsView):
+    """图片查看控件"""
+
+    def __init__(self, *args, **kwargs):
+        image = kwargs.pop('image', None)
+        background = kwargs.pop('background', None)
+        super(ImageView, self).__init__(*args, **kwargs)
+        self.setCursor(Qt.OpenHandCursor)
+        self.setBackground(background)
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
+                            QPainter.SmoothPixmapTransform)
+        self.setCacheMode(self.CacheBackground)
+        self.setViewportUpdateMode(self.SmartViewportUpdate)
+        self._item = QGraphicsPixmapItem()  # 放置图像
+        self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable |
+                            QGraphicsPixmapItem.ItemIsMovable)
+        self._scene = QGraphicsScene(self)  # 场景
+        self.setScene(self._scene)
+        self._scene.addItem(self._item)
+        rect = QApplication.instance().desktop().availableGeometry(self)
+        self.resize(int(rect.width() * 2 / 3), int(rect.height() * 2 / 3))
+
+        self.pixmap = None
+        self._delta = 0.1  # 缩放
+        self.setPixmap(image)
+
+    def setBackground(self, color):
+        """设置背景颜色
+        :param color: 背景颜色
+        :type color: QColor or str or GlobalColor
+        """
+        if isinstance(color, QColor):
+            self.setBackgroundBrush(color)
+        elif isinstance(color, (str, Qt.GlobalColor)):
+            color = QColor(color)
+            if color.isValid():
+                self.setBackgroundBrush(color)
+
+    def setPixmap(self, pixmap, fitIn=True):
+        """加载图片
+        :param pixmap: 图片或者图片路径
+        :param fitIn: 是否适应
+        :type pixmap: QPixmap or QImage or str
+        :type fitIn: bool
+        """
+        if isinstance(pixmap, QPixmap):
+            self.pixmap = pixmap
+        elif isinstance(pixmap, QImage):
+            self.pixmap = QPixmap.fromImage(pixmap)
+        elif isinstance(pixmap, str) and os.path.isfile(pixmap):
+            self.pixmap = QPixmap(pixmap)
+        else:
+            return
+        self._item.setPixmap(self.pixmap)
+        self._item.update()
+        self.setSceneDims()
+        if fitIn:
+            self.fitInView(QRectF(self._item.pos(), QSizeF(
+                self.pixmap.size())), Qt.KeepAspectRatio)
+        self.update()
+
+    def setSceneDims(self):
+        if not self.pixmap:
+            return
+        self.setSceneRect(QRectF(QPointF(0, 0), QPointF(self.pixmap.width(), self.pixmap.height())))
+
+    def fitInView(self, rect, flags=Qt.IgnoreAspectRatio):
+        """剧中适应
+        :param rect: 矩形范围
+        :param flags:
+        :return:
+        """
+        if not self.scene() or rect.isNull():
+            return
+        unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
+        self.scale(1 / unity.width(), 1 / unity.height())
+        viewRect = self.viewport().rect()
+        sceneRect = self.transform().mapRect(rect)
+        x_ratio = viewRect.width() / sceneRect.width()
+        y_ratio = viewRect.height() / sceneRect.height()
+        if flags == Qt.KeepAspectRatio:
+            x_ratio = y_ratio = min(x_ratio, y_ratio)
+        elif flags == Qt.KeepAspectRatioByExpanding:
+            x_ratio = y_ratio = max(x_ratio, y_ratio)
+        self.scale(x_ratio, y_ratio)
+        self.centerOn(rect.center())
+
+    def wheelEvent(self, event):
+        if event.angleDelta().y() > 0:
+            self.zoomIn()
+        else:
+            self.zoomOut()
+
+    def zoomIn(self):
+        """放大"""
+        self.zoom(1 + self._delta)
+
+    def zoomOut(self):
+        """缩小"""
+        self.zoom(1 - self._delta)
+
+    def zoom(self, factor):
+        """缩放
+        :param factor: 缩放的比例因子
+        """
+        _factor = self.transform().scale(
+            factor, factor).mapRect(QRectF(0, 0, 1, 1)).width()
+        if _factor < 0.07 or _factor > 100:
+            # 防止过大过小
+            return
+        self.scale(factor, factor)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    w = ImageView(image='Data/bg.jpg', background=Qt.black)
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QGraphicsView/README.md b/QGraphicsView/README.md
index 733d782b..5db21ca5 100644
--- a/QGraphicsView/README.md
+++ b/QGraphicsView/README.md
@@ -3,8 +3,11 @@
 - 目录
   - [绘制世界地图](#1绘制世界地图)
   - [添加QWidget](#2添加QWidget)
+  - [图片查看器](#3图片查看器)
+  - [图标拖拽](#4图标拖拽)
 
 ## 1、绘制世界地图
+
 [运行 WorldMap.py](WorldMap.py)
 
 1. 解析json数据生成 `QPolygonF`
@@ -13,8 +16,28 @@
 
 
 ## 2、添加QWidget
+
 [运行 AddQWidget.py](AddQWidget.py)
 
 通过 `QGraphicsScene.addWidget` 添加自定义QWidget
 
-
\ No newline at end of file
+
+
+## 3、图片查看器
+
+[运行 ImageView.py](ImageView.py)
+
+支持放大缩小和移动
+
+
+
+## 3、图标拖拽
+
+[运行 DragGraphics.py](DragGraphics.py)
+
+该示例主要是包含左侧树状图标列表和右侧视图显示,从左侧拖拽到右侧
+
+1. 重写`QListWidget`的`startDrag`函数用来封装拖拽数据
+2. 重写`QGraphicsView`的`dragEnterEvent`、`dragMoveEvent`、`dropEvent`函数用来处理拖拽事件
+
+
diff --git a/QGraphicsView/ScreenShot/DragGraphics.gif b/QGraphicsView/ScreenShot/DragGraphics.gif
new file mode 100644
index 00000000..65f0c4ba
Binary files /dev/null and b/QGraphicsView/ScreenShot/DragGraphics.gif differ
diff --git a/QGraphicsView/ScreenShot/ImageView.gif b/QGraphicsView/ScreenShot/ImageView.gif
new file mode 100644
index 00000000..150634c2
Binary files /dev/null and b/QGraphicsView/ScreenShot/ImageView.gif differ
diff --git a/QGraphicsView/WorldMap.py b/QGraphicsView/WorldMap.py
index 3efc7b17..32428dfb 100644
--- a/QGraphicsView/WorldMap.py
+++ b/QGraphicsView/WorldMap.py
@@ -3,8 +3,8 @@
 
 """
 Created on 2017年12月17日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: WorldMap
 @description: 
@@ -12,19 +12,19 @@
 import json
 import math
 
-from PyQt5.QtCore import Qt, QPointF, QRectF
-from PyQt5.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush
-from PyQt5.QtOpenGL import QGLFormat
-from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPolygonItem
-
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt, QPointF, QRectF
+    from PyQt5.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush
+    from PyQt5.QtOpenGL import QGLFormat
+    from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem
+except ImportError:
+    from PySide2.QtCore import Qt, QPointF, QRectF
+    from PySide2.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush
+    from PySide2.QtOpenGL import QGLFormat
+    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem
 
 
 class GraphicsView(QGraphicsView):
-
     # 背景区域颜色
     backgroundColor = QColor(31, 31, 47)
     # 边框颜色
@@ -94,8 +94,8 @@ def __init__(self, *args, **kwargs):
         AnchorUnderMouse             鼠标当前位置被用作锚点
         '''
         self.setTransformationAnchor(self.AnchorUnderMouse)
-#         if QGLFormat.hasOpenGL():  # 如果开启了OpenGL则使用OpenGL Widget
-#             self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers)))
+        #         if QGLFormat.hasOpenGL():  # 如果开启了OpenGL则使用OpenGL Widget
+        #             self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers)))
         '''
         #参考 http://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum
         FullViewportUpdate           当场景的任何可见部分改变或重新显示时,QGraphicsView将更新整个视口。 当QGraphicsView花费更多的时间来计算绘制的内容(比如重复更新很多小项目)时,这种方法是最快的。 这是不支持部分更新(如QGLWidget)的视口以及需要禁用滚动优化的视口的首选更新模式。
@@ -157,7 +157,7 @@ def initMap(self):
 
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     print("OpenGL Status:", QGLFormat.hasOpenGL())
     view = GraphicsView()
diff --git a/QGridLayout/Data/CoverItemWidget.ui b/QGridLayout/Data/CoverItemWidget.ui
new file mode 100644
index 00000000..2f245e13
--- /dev/null
+++ b/QGridLayout/Data/CoverItemWidget.ui
@@ -0,0 +1,85 @@
+
+
+ CoverItemWidget 
+ 
+  
+   
+    200 
+    256 
+    
+   
+  
+   
+    200 
+    256 
+    
+   
+  
+    
+  
+   
+    12 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   - 
+    
+     
+      
+       180 +180 +
 +
 +
+       +
+      true +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      
+       10 +
 +
 +
+       +
 +
+- 
+    
+     
+       +
 +
+ 
+  
+ 
+  
+   CoverLabel 
+   QLabel 
+   
+   
+  
+  
diff --git a/QGridLayout/Data/CoverLabel.ui b/QGridLayout/Data/CoverLabel.ui
new file mode 100644
index 00000000..2e7e0378
--- /dev/null
+++ b/QGridLayout/Data/CoverLabel.ui
@@ -0,0 +1,109 @@
+
+
+ CoverLabel 
+ 
+  
+   
+    0 
+    0 
+    180 
+    180 
+    
+   
+  
+   PointingHandCursor 
+   
+  
+    
+  
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   - 
+    
+     
+      Qt::Vertical +
 +
+      
+       20 +135 +
 +
 +
 +
+- 
+    
+     
+      #widgetBottom {
+    background-color: rgba(0, 0, 0, 150);
+} +
 +
+      - 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+- 
+       
+        
+          +
 +
+- 
+       
+        
+         Qt::Horizontal +
 +
+         
+          40 +20 +
 +
 +
 +
+- 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+ +
 +
+ 
+  
+  
diff --git a/QGridLayout/Data/Svg_icon_headset_sm.svg b/QGridLayout/Data/Svg_icon_headset_sm.svg
new file mode 100644
index 00000000..98a1a359
--- /dev/null
+++ b/QGridLayout/Data/Svg_icon_headset_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QGridLayout/Data/Svg_icon_loading.svg b/QGridLayout/Data/Svg_icon_loading.svg
new file mode 100644
index 00000000..0b00b799
--- /dev/null
+++ b/QGridLayout/Data/Svg_icon_loading.svg
@@ -0,0 +1,31 @@
+
+    
+        
+             
+     
+    
+        
+            
+                 
+            
+                 
+         
+     
+ 
\ No newline at end of file
diff --git a/QGridLayout/Data/Svg_icon_play_sm.svg b/QGridLayout/Data/Svg_icon_play_sm.svg
new file mode 100644
index 00000000..08a47f20
--- /dev/null
+++ b/QGridLayout/Data/Svg_icon_play_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QGridLayout/HotPlaylist.py b/QGridLayout/HotPlaylist.py
index a2022c2c..ff5500ad 100644
--- a/QGridLayout/HotPlaylist.py
+++ b/QGridLayout/HotPlaylist.py
@@ -1,192 +1,45 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
-'''
-Created on 2018年2月4日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: TencentMovieHotPlay
-@description: 
-'''
+@file: HotPlaylist.py
+@description:
+"""
+
 import os
 import sys
-import webbrowser
-
-from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal
-from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
-    QBrush, QPaintEvent, QPixmap
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-from PyQt5.QtSvg import QSvgWidget
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
-    QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\
-    QAbstractSlider
 
+from Lib.CoverItemWidget import CoverItemWidget
 from lxml.etree import HTML  # @UnresolvedImport
 
+try:
+    from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal
+    from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PyQt5.QtSvg import QSvgWidget
+    from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QGridLayout,
+                                 QScrollArea, QWidget)
+except ImportError:
+    from PySide2.QtCore import Qt, QTimer, QUrl, pyqtSignal
+    from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PySide2.QtSvg import QSvgWidget
+    from PySide2.QtWidgets import (QAbstractSlider, QApplication, QGridLayout,
+                                   QScrollArea, QWidget)
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+# offset=0,35,70,105
+Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}"
 
-# offset=0,30,60,90
-Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
+Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50"
 
-# 播放量图标
-Svg_icon_play_sm = '''
-     
-'''.encode()
+Referer = b"https://music.163.com/"
 
-Svg_icon_loading = '''
-    
-        
-             
-     
-    
-        
-            
-                 
-            
-                 
-         
-     
- '''.encode()
-
-# 主演
+# 作者
 Actor = '''{title}  '''
 
 
-class CoverLabel(QLabel):
-
-    def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
-        super(CoverLabel, self).__init__(*args, **kwargs)
-        self.setCursor(Qt.PointingHandCursor)
-        self.setScaledContents(True)
-        self.setMinimumSize(220, 308)
-        self.setMaximumSize(220, 308)
-        self.cover_path = cover_path
-        self.cover_title = cover_title
-        self.video_url = video_url
-        self.setPixmap(QPixmap(cover_path))
-
-    def setCoverPath(self, path):
-        self.cover_path = path
-
-    def mouseReleaseEvent(self, event):
-        super(CoverLabel, self).mouseReleaseEvent(event)
-        webbrowser.open_new_tab(self.video_url)
-
-    def paintEvent(self, event):
-        super(CoverLabel, self).paintEvent(event)
-        if hasattr(self, "cover_title") and self.cover_title != "":
-            # 底部绘制文字
-            painter = QPainter(self)
-            rect = self.rect()
-            # 粗略字体高度
-            painter.save()
-            fheight = self.fontMetrics().height()
-            # 底部矩形框背景渐变颜色
-            bottomRectColor = QLinearGradient(
-                rect.width() / 2, rect.height() - 24 - fheight,
-                rect.width() / 2, rect.height())
-            bottomRectColor.setSpread(QGradient.PadSpread)
-            bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70))
-            bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50))
-            # 画半透明渐变矩形框
-            painter.setPen(Qt.NoPen)
-            painter.setBrush(QBrush(bottomRectColor))
-            painter.drawRect(rect.x(), rect.height() - 24 -
-                             fheight, rect.width(), 24 + fheight)
-            painter.restore()
-            # 距离底部一定高度画文字
-            font = self.font() or QFont()
-            font.setPointSize(8)
-            painter.setFont(font)
-            painter.setPen(Qt.white)
-            rect.setHeight(rect.height() - 12)  # 底部减去一定高度
-            painter.drawText(rect, Qt.AlignHCenter |
-                             Qt.AlignBottom, self.cover_title)
-
-
-class ItemWidget(QWidget):
-
-    def __init__(self, cover_path, figure_info, figure_title,
-                 figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs):
-        super(ItemWidget, self).__init__(*args, **kwargs)
-        self.setMaximumSize(220, 380)
-        self.setMaximumSize(220, 380)
-        self.img_path = img_path
-        self.cover_url = cover_url
-        layout = QVBoxLayout(self)
-        layout.setContentsMargins(0, 0, 0, 0)
-        # 图片label
-        self.clabel = CoverLabel(cover_path, figure_info, video_url, self)
-        layout.addWidget(self.clabel)
-
-        # 片名和分数
-        flayout = QHBoxLayout()
-        flayout.addWidget(QLabel(figure_title, self))
-        flayout.addItem(QSpacerItem(
-            20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
-        flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;"))
-        layout.addLayout(flayout)
-
-        # 主演
-        layout.addWidget(
-            QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True))
-
-        # 播放量
-        blayout = QHBoxLayout()
-        count_icon = QSvgWidget(self)
-        count_icon.setMaximumSize(16, 16)
-        count_icon.load(Svg_icon_play_sm)
-        blayout.addWidget(count_icon)
-        blayout.addWidget(
-            QLabel(figure_count, self, styleSheet="color: #999999;"))
-        layout.addLayout(blayout)
-
-    def setCover(self, path):
-        self.clabel.setCoverPath(path)
-        self.clabel.setPixmap(QPixmap(path))
-#         self.clabel.setText('主演: " + \
-                    "".join([Actor.format(**dict(fd.items()))
-                             for fd in li.xpath(".//div[@class='figure_desc']/a")])
+                a = li.find('.//div/a')
+                play_url = "https://music.163.com/" + a.get("href")  # 歌单播放地址
+                img = li.find(".//div/img")
+                cover_url = img.get("src")  # 封面图片
+                playlist_title = a.get("title")  # 歌单名
+                # 歌手
+                author_info = li.xpath(".//p[2]/a")[0]
+                playlist_author = "".format(
+                    Actor.format(href="https://music.163.com/" +
+                                 author_info.get("href"),
+                                 title=author_info.get("title")))
                 # 播放数
-                figure_count = (
-                    li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
+                play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0]
                 path = "cache/{0}.jpg".format(
-                    os.path.splitext(os.path.basename(video_url))[0])
+                    os.path.splitext(
+                        os.path.basename(cover_url).split('?')[0])[0])
                 cover_path = "Data/pic_v.png"
                 if os.path.isfile(path):
                     cover_path = path
-                iwidget = ItemWidget(cover_path, figure_info, figure_title,
-                                     figure_score, figure_desc, figure_count, video_url, cover_url, path, self)
+
+                # print(cover_path, playlist_title,
+                #       playlist_author, play_count, play_url, cover_url, path)
+                iwidget = CoverItemWidget(self, manager=self._manager)
+                iwidget.init(cover_path, playlist_title, playlist_author,
+                             play_count, play_url, cover_url, path)
                 self._layout.addWidget(iwidget, row_count + row, col)
 
 
@@ -299,9 +159,11 @@ def __init__(self, *args, **kwargs):
         # 连接竖着的滚动条滚动事件
         self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered)
         # 进度条
-        self.loadWidget = QSvgWidget(
-            self, minimumHeight=120, minimumWidth=120, visible=False)
-        self.loadWidget.load(Svg_icon_loading)
+        self.loadWidget = QSvgWidget(self,
+                                     minimumHeight=120,
+                                     minimumWidth=120,
+                                     visible=False)
+        self.loadWidget.load('Data/Svg_icon_loading.svg')
 
     def setLoadStarted(self, started):
         self._loadStart = started
@@ -313,7 +175,8 @@ def onActionTriggered(self, action):
         if action != QAbstractSlider.SliderMove or self._loadStart:
             return
         # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
-        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
+        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar(
+        ).maximum():
             # 可以下一页了
             self._widget.load()
 
@@ -322,9 +185,7 @@ def resizeEvent(self, event):
         self.loadWidget.setGeometry(
             int((self.width() - self.loadWidget.minimumWidth()) / 2),
             int((self.height() - self.loadWidget.minimumHeight()) / 2),
-            self.loadWidget.minimumWidth(),
-            self.loadWidget.minimumHeight()
-        )
+            self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight())
 
 
 if __name__ == "__main__":
diff --git a/QGridLayout/Lib/CoverItemWidget.py b/QGridLayout/Lib/CoverItemWidget.py
new file mode 100644
index 00000000..5ef3ff00
--- /dev/null
+++ b/QGridLayout/Lib/CoverItemWidget.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverItemWidget.py
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QSize, QUrl
+    from PyQt5.QtGui import QPaintEvent, QPixmap
+    from PyQt5.QtNetwork import QNetworkRequest
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import QSize, QUrl
+    from PySide2.QtGui import QPaintEvent, QPixmap
+    from PySide2.QtNetwork import QNetworkRequest
+    from PySide2.QtWidgets import QWidget
+
+from .Ui_CoverItemWidget import Ui_CoverItemWidget  # @UnresolvedImport
+
+
+class CoverItemWidget(QWidget, Ui_CoverItemWidget):
+
+    def __init__(self, *args, **kwargs):
+        self._manager = kwargs.pop('manager', None)
+        super(CoverItemWidget, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, playlist_title, playlist_author, play_count,
+             play_url, cover_url, img_path):
+        self.img_path = img_path
+        self.cover_url = cover_url
+        # 图片label
+        self.labelCover.init(cover_path, play_url, play_count)
+
+        # 歌单
+        self.labelTitle.setText(playlist_title)
+
+        # 作者
+        self.labelAuthor.setText(playlist_author)
+
+    def setCover(self, path):
+        self.labelCover.setCoverPath(path)
+        self.labelCover.setPixmap(QPixmap(path))
+
+    def sizeHint(self):
+        # 每个item控件的大小
+        return QSize(200, 256)
+
+    def event(self, event):
+        if isinstance(event, QPaintEvent):
+            if event.rect().height() > 20 and hasattr(self, "labelCover"):
+                if self.labelCover.cover_path.find("pic_v.png") > -1:  # 封面未加载
+                    # print("start download img:", self.cover_url)
+                    req = QNetworkRequest(QUrl(self.cover_url))
+                    # 设置两个自定义属性方便后期reply中处理
+                    req.setAttribute(QNetworkRequest.User + 1, self)
+                    req.setAttribute(QNetworkRequest.User + 2, self.img_path)
+                    if self._manager:
+                        self._manager.get(req)  # 调用父窗口中的下载器下载
+        return super(CoverItemWidget, self).event(event)
diff --git a/QGridLayout/Lib/CoverLabel.py b/QGridLayout/Lib/CoverLabel.py
new file mode 100644
index 00000000..dbee15af
--- /dev/null
+++ b/QGridLayout/Lib/CoverLabel.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverLabel.py
+@description:
+"""
+import webbrowser
+
+try:
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QLabel
+except ImportError:
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QLabel
+
+from .Ui_CoverLabel import Ui_CoverLabel  # @UnresolvedImport
+
+
+class CoverLabel(QLabel, Ui_CoverLabel):
+
+    def __init__(self, *args, **kwargs):
+        super(CoverLabel, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, play_url, play_count):
+        self.cover_path = cover_path
+        self.play_url = play_url
+        self.setPixmap(QPixmap(cover_path))
+        self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg'))
+        self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg'))
+        self.labelCount.setStyleSheet('color: #999999;')
+        self.labelCount.setText(play_count)
+
+    def setCoverPath(self, path):
+        self.cover_path = path
+
+    def mouseReleaseEvent(self, event):
+        super(CoverLabel, self).mouseReleaseEvent(event)
+        webbrowser.open_new_tab(self.play_url)
diff --git a/QGridLayout/Lib/Ui_CoverItemWidget.py b/QGridLayout/Lib/Ui_CoverItemWidget.py
new file mode 100644
index 00000000..c048f686
--- /dev/null
+++ b/QGridLayout/Lib/Ui_CoverItemWidget.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverItemWidget.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverItemWidget(object):
+    def setupUi(self, CoverItemWidget):
+        CoverItemWidget.setObjectName("CoverItemWidget")
+        CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget)
+        self.verticalLayout.setContentsMargins(10, 10, 10, 10)
+        self.verticalLayout.setSpacing(12)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.labelCover = CoverLabel(CoverItemWidget)
+        self.labelCover.setMinimumSize(QtCore.QSize(180, 180))
+        self.labelCover.setText("")
+        self.labelCover.setScaledContents(True)
+        self.labelCover.setAlignment(QtCore.Qt.AlignCenter)
+        self.labelCover.setObjectName("labelCover")
+        self.verticalLayout.addWidget(self.labelCover)
+        self.labelTitle = QtWidgets.QLabel(CoverItemWidget)
+        font = QtGui.QFont()
+        font.setPointSize(10)
+        self.labelTitle.setFont(font)
+        self.labelTitle.setText("")
+        self.labelTitle.setObjectName("labelTitle")
+        self.verticalLayout.addWidget(self.labelTitle)
+        self.labelAuthor = QtWidgets.QLabel(CoverItemWidget)
+        self.labelAuthor.setText("")
+        self.labelAuthor.setObjectName("labelAuthor")
+        self.verticalLayout.addWidget(self.labelAuthor)
+
+        self.retranslateUi(CoverItemWidget)
+        QtCore.QMetaObject.connectSlotsByName(CoverItemWidget)
+
+    def retranslateUi(self, CoverItemWidget):
+        pass
+from .CoverLabel import CoverLabel
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverItemWidget = QtWidgets.QWidget()
+    ui = Ui_CoverItemWidget()
+    ui.setupUi(CoverItemWidget)
+    CoverItemWidget.show()
+    sys.exit(app.exec_())
diff --git a/QGridLayout/Lib/Ui_CoverLabel.py b/QGridLayout/Lib/Ui_CoverLabel.py
new file mode 100644
index 00000000..86e1526a
--- /dev/null
+++ b/QGridLayout/Lib/Ui_CoverLabel.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverLabel.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverLabel(object):
+    def setupUi(self, CoverLabel):
+        CoverLabel.setObjectName("CoverLabel")
+        CoverLabel.resize(180, 180)
+        CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
+        CoverLabel.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel)
+        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.verticalLayout.setSpacing(0)
+        self.verticalLayout.setObjectName("verticalLayout")
+        spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.verticalLayout.addItem(spacerItem)
+        self.widgetBottom = QtWidgets.QWidget(CoverLabel)
+        self.widgetBottom.setStyleSheet("#widgetBottom {\n"
+"    background-color: rgba(0, 0, 0, 150);\n"
+"}")
+        self.widgetBottom.setObjectName("widgetBottom")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.labelHeadset = QtWidgets.QLabel(self.widgetBottom)
+        self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelHeadset.setText("")
+        self.labelHeadset.setObjectName("labelHeadset")
+        self.horizontalLayout.addWidget(self.labelHeadset)
+        self.labelCount = QtWidgets.QLabel(self.widgetBottom)
+        self.labelCount.setText("")
+        self.labelCount.setObjectName("labelCount")
+        self.horizontalLayout.addWidget(self.labelCount)
+        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem1)
+        self.labelPlay = QtWidgets.QLabel(self.widgetBottom)
+        self.labelPlay.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelPlay.setText("")
+        self.labelPlay.setObjectName("labelPlay")
+        self.horizontalLayout.addWidget(self.labelPlay)
+        self.verticalLayout.addWidget(self.widgetBottom)
+
+        self.retranslateUi(CoverLabel)
+        QtCore.QMetaObject.connectSlotsByName(CoverLabel)
+
+    def retranslateUi(self, CoverLabel):
+        pass
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverLabel = QtWidgets.QWidget()
+    ui = Ui_CoverLabel()
+    ui.setupUi(CoverLabel)
+    CoverLabel.show()
+    sys.exit(app.exec_())
diff --git a/QGridLayout/Lib/__init__.py b/QGridLayout/Lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/QGridLayout/README.md b/QGridLayout/README.md
index 10495c6a..106220a6 100644
--- a/QGridLayout/README.md
+++ b/QGridLayout/README.md
@@ -1,21 +1,22 @@
 # QListView
 
 - 目录
-  - [腾讯视频热播列表](#1腾讯视频热播列表)
+  - [音乐热歌列表](#1音乐热歌列表)
+
+## 1、音乐热歌列表
 
-## 1、腾讯视频热播列表
 [运行 HotPlaylist.py](HotPlaylist.py)
 
 简单思路说明:
 
- - 利用`QScrollArea`滚动显示,`QGridLayout`做布局来放置自定义的Widget
- - `QNetworkAccessManager`异步下载网页和图片
- - `QScrollArea`滚动到底部触发下一页加载
+- 利用`QScrollArea`滚动显示,`QGridLayout`做布局来放置自定义的Widget
+- `QNetworkAccessManager`异步下载网页和图片
+- `QScrollArea`滚动到底部触发下一页加载
 
 自定义控件说明:
 
- - 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
- - 字体颜色用qss设置
- - 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
+- 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
+- 字体颜色用qss设置
+- 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
 
-
\ No newline at end of file
+
diff --git a/QHBoxLayout/Data/BaseHorizontalLayout.ui b/QHBoxLayout/Data/BaseHorizontalLayout.ui
new file mode 100644
index 00000000..0d0410ec
--- /dev/null
+++ b/QHBoxLayout/Data/BaseHorizontalLayout.ui
@@ -0,0 +1,38 @@
+
+
+ BaseHorizontalLayout 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   - 
+    
+     
+      通过 右键 -> 布局 -> 水平布局 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      右侧按钮 +
 +
 +
+ 
+  
+  
diff --git a/QHBoxLayout/Data/HorizontalLayoutMargin.ui b/QHBoxLayout/Data/HorizontalLayoutMargin.ui
new file mode 100644
index 00000000..89673b34
--- /dev/null
+++ b/QHBoxLayout/Data/HorizontalLayoutMargin.ui
@@ -0,0 +1,60 @@
+
+
+ HorizontalLayoutMargin 
+ 
+  
+   
+    0 
+    0 
+    457 
+    216 
+    
+   
+  
+   Form 
+   
+  
+   #VerticalLayoutMargin {
+    background: #5aaadb;
+}
+#label {
+    background: #85c440;
+} 
+   
+  
+   
+    20 
+    
+   
+    20 
+    
+   - 
+    
+     
+      通过设置Margin和Spacing设置边距
+以及两个控件之间的间隔距离 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      Spcasing 为 20 +
 +
 +
+- 
+    
+     
+      Spcasing 为 20 +
 +
 +
+ 
+  
+  
diff --git a/QHBoxLayout/Data/HorizontalLayoutStretch.ui b/QHBoxLayout/Data/HorizontalLayoutStretch.ui
new file mode 100644
index 00000000..a45f50cb
--- /dev/null
+++ b/QHBoxLayout/Data/HorizontalLayoutStretch.ui
@@ -0,0 +1,62 @@
+
+
+ HorizontalLayoutStretch 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   #label {
+    background: #5aaadb;
+}
+#label_2 {
+    background: #85c440;
+}
+#label_3 {
+    background: #f2b63c;
+} 
+   
+  
+   - 
+    
+     
+      1/6 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      2/6 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      3/6 +
 +
+      Qt::AlignCenter +
 +
 +
+ 
+  
+  
diff --git a/QHBoxLayout/README.md b/QHBoxLayout/README.md
index e69de29b..fa074093 100644
--- a/QHBoxLayout/README.md
+++ b/QHBoxLayout/README.md
@@ -0,0 +1,35 @@
+# QHBoxLayout
+
+- 目录
+  - [水平布局](#1水平布局)
+  - [边距和间隔](#2边距和间隔)
+  - [比例分配](#3比例分配)
+
+## 1、水平布局
+
+[查看 BaseHorizontalLayout.ui](Data/BaseHorizontalLayout.ui)
+
+
+
+## 2、边距和间隔
+
+[查看 HorizontalLayoutMargin.ui](Data/HorizontalLayoutMargin.ui)
+
+1. 通过`setContentsMargins(-1, -1, 20, -1)`设置左上右下的边距,-1表示默认值
+2. 通过`setSpacing`设置控件之间的间隔
+
+
+
+## 3、比例分配
+
+[查看 HorizontalLayoutStretch.ui](Data/HorizontalLayoutStretch.ui)
+
+通过`setStretch`设置各个部分的占比 分别为:1/6 2/6 3/6
+
+```python
+self.horizontalLayout.setStretch(0, 1)
+self.horizontalLayout.setStretch(1, 2)
+self.horizontalLayout.setStretch(2, 3)
+```
+
+
diff --git a/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png b/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png
new file mode 100644
index 00000000..37f0c3f8
Binary files /dev/null and b/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png differ
diff --git a/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png b/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png
new file mode 100644
index 00000000..32dc9ec4
Binary files /dev/null and b/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png differ
diff --git a/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png b/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png
new file mode 100644
index 00000000..3c568f9a
Binary files /dev/null and b/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png differ
diff --git a/QLabel/CircleImage.py b/QLabel/CircleImage.py
index 0c805cb0..7b59420e 100644
--- a/QLabel/CircleImage.py
+++ b/QLabel/CircleImage.py
@@ -3,19 +3,21 @@
 
 """
 Created on 2018年1月20日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CircleImage
 @description: 圆形图片
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPixmap, QPainter, QPainterPath
-from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPixmap, QPainter, QPainterPath
+    from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPixmap, QPainter, QPainterPath
+    from PySide2.QtWidgets import QLabel, QWidget, QHBoxLayout, QApplication
 
 
 class Label(QLabel):
@@ -67,7 +69,6 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == "__main__":
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QLabel/ImageRotate.py b/QLabel/ImageRotate.py
index f0dd48d6..142299b7 100644
--- a/QLabel/ImageRotate.py
+++ b/QLabel/ImageRotate.py
@@ -4,22 +4,22 @@
 """
 Created on 2018年11月19日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 
 @description: 
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPixmap, QPainter, QImage
-from PyQt5.QtWidgets import QWidget, QLabel, QPushButton,\
-    QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPixmap, QPainter, QImage
+    from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, \
+        QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPixmap, QPainter, QImage
+    from PySide2.QtWidgets import QWidget, QLabel, QPushButton, \
+        QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QApplication
 
 
 class Window(QWidget):
@@ -73,12 +73,12 @@ def doClockwise(self):
         self.srcImage = image  # 替换
         self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage))
 
-#         # 下面这个旋转方法针对90度的倍数,否则图片会变大
-#         trans = QTransform()
-#         trans.rotate(90)
-#         self.srcImage = self.srcImage.transformed(
-#             trans, Qt.SmoothTransformation)
-#         self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage))
+    #         # 下面这个旋转方法针对90度的倍数,否则图片会变大
+    #         trans = QTransform()
+    #         trans.rotate(90)
+    #         self.srcImage = self.srcImage.transformed(
+    #             trans, Qt.SmoothTransformation)
+    #         self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage))
 
     def doAnticlockwise(self):
         # 逆时针45度
@@ -96,6 +96,7 @@ def doAnticlockwise(self):
         self.srcImage = image  # 替换
         self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage))
 
+
 #         # 下面这个旋转方法针对90度的倍数,否则图片会变大
 #         trans = QTransform()
 #         trans.rotate(90)
@@ -106,7 +107,7 @@ def doAnticlockwise(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QLabel/ImageSlipped.py b/QLabel/ImageSlipped.py
index dfcac07d..1ea7818b 100644
--- a/QLabel/ImageSlipped.py
+++ b/QLabel/ImageSlipped.py
@@ -4,20 +4,18 @@
 """
 Created on 2018年10月18日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ImageSlipped
 @description: 
 """
-from PyQt5.QtGui import QPixmap, QPainter
-from PyQt5.QtWidgets import QWidget
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtGui import QPixmap, QPainter
+    from PyQt5.QtWidgets import QWidget, QApplication
+except ImportError:
+    from PySide2.QtGui import QPixmap, QPainter
+    from PySide2.QtWidgets import QWidget, QApplication
 
 
 class SlippedImgWidget(QWidget):
@@ -73,7 +71,7 @@ def paintEvent(self, event):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = SlippedImgWidget('Data/bg1.jpg', 'Data/fg1.png')
     w.show()
diff --git a/QLabel/Lib/NinePatch.py b/QLabel/Lib/NinePatch.py
index 966903ae..e157f1bf 100644
--- a/QLabel/Lib/NinePatch.py
+++ b/QLabel/Lib/NinePatch.py
@@ -4,22 +4,19 @@
 """
 Created on 2018年10月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: NinePatch
 @description: 
 """
 from math import fabs
 
-from PyQt5.QtCore import QRect
-from PyQt5.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QRect
+    from PyQt5.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha
+except ImportError:
+    from PySide2.QtCore import QRect
+    from PySide2.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha
 
 
 class _Exception(Exception):
@@ -44,7 +41,8 @@ def __str__(self):
 class ExceptionIncorrectWidthAndHeight(_Exception):
 
     def __str__(self):
-        return "Input incorrect width width and height. Minimum width = :{imgW} . Minimum height = :{imgH}".format(imgW=self.imgW, imgH=self.imgH)
+        return "Input incorrect width width and height. Minimum width = :{imgW} . Minimum height = :{imgH}".format(
+            imgW=self.imgW, imgH=self.imgH)
 
 
 class ExceptionIncorrectHeight(_Exception):
@@ -62,11 +60,11 @@ def __str__(self):
 class NinePatch:
 
     def __init__(self, fileName):
-        self.CachedImage = None    # 缓存图片
+        self.CachedImage = None  # 缓存图片
         self.OldWidth = -1
         self.OldHeight = -1
         self.ResizeDistancesX = []
-        self.ResizeDistancesY = []    # [(int,int)]数组
+        self.ResizeDistancesY = []  # [(int,int)]数组
         self.setImage(fileName)
 
     def width(self):
@@ -103,7 +101,8 @@ def SetImageSize(self, width, height):
         for i in range(len(self.ResizeDistancesY)):
             resizeHeight += self.ResizeDistancesY[i][1]
 
-        if (width < (self.Image.width() - 2 - resizeWidth) and height < (self.Image.height() - 2 - resizeHeight)):
+        if (width < (self.Image.width() - 2 - resizeWidth) and height < (
+                self.Image.height() - 2 - resizeHeight)):
             raise ExceptionIncorrectWidthAndHeight(
                 self.Image.width() - 2, self.Image.height() - 2)
 
@@ -123,7 +122,8 @@ def SetImageSize(self, width, height):
     @classmethod
     def GetContentAreaRect(self, width, height):
         # print("GetContentAreaRect :  width:%d height:%d" % (width, height))
-        return (QRect(self.ContentArea.x(), self.ContentArea.y(), (width - (self.Image.width() - 2 - self.ContentArea.width())),
+        return (QRect(self.ContentArea.x(), self.ContentArea.y(),
+                      (width - (self.Image.width() - 2 - self.ContentArea.width())),
                       (height - (self.Image.height() - 2 - self.ContentArea.height()))))
 
     def DrawScaledPart(self, oldRect, newRect, painter):
@@ -189,7 +189,8 @@ def GetResizeArea(self):
         for i in range(self.Image.width()):
             if (self.IsColorBlack(self.Image.pixel(i, j)) and left == 0):
                 left = i
-            if (left and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(self.Image.pixel(i + 1, j))):
+            if (left and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(
+                    self.Image.pixel(i + 1, j))):
                 right = i
                 left -= 1
                 # print("ResizeDistancesX.append ", left, " ", right - left)
@@ -204,7 +205,8 @@ def GetResizeArea(self):
             if (self.IsColorBlack(self.Image.pixel(i, j)) and top == 0):
                 top = j
 
-            if (top and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(self.Image.pixel(i, j + 1))):
+            if (top and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(
+                    self.Image.pixel(i, j + 1))):
                 bot = j
                 top -= 1
                 # print("ResizeDistancesY.append ", top, " ", bot - top)
@@ -241,10 +243,10 @@ def UpdateCachedImage(self, width, height):
         # print("after GetFactor: ", width, height, factorX, factorY)
         lostX = 0.0
         lostY = 0.0
-        x1 = 0    # for image parts X
-        y1 = 0    # for image parts Y
-#         widthResize    # width for image parts
-#         heightResize    # height for image parts
+        x1 = 0  # for image parts X
+        y1 = 0  # for image parts Y
+        #         widthResize    # width for image parts
+        #         heightResize    # height for image parts
         resizeX = 0
         resizeY = 0
         offsetX = 0
@@ -310,7 +312,8 @@ def UpdateCachedImage(self, width, height):
         offsetY = 0
         for i in range(len(self.ResizeDistancesY)):
             self.DrawConstPart(QRect(x1 + 1, y1 + 1, widthResize, self.ResizeDistancesY[i][0] - y1),
-                               QRect(x1 + offsetX, y1 + offsetY, widthResize, self.ResizeDistancesY[i][0] - y1), painter)
+                               QRect(x1 + offsetX, y1 + offsetY, widthResize,
+                                     self.ResizeDistancesY[i][0] - y1), painter)
             y1 = self.ResizeDistancesY[i][0]
             resizeY = round(float(self.ResizeDistancesY[i][1]) * factorY)
             lostY += resizeY - (float(self.ResizeDistancesY[i][1]) * factorY)
@@ -334,7 +337,8 @@ def UpdateCachedImage(self, width, height):
         offsetX = 0
         for i in range(len(self.ResizeDistancesX)):
             self.DrawConstPart(QRect(x1 + 1, y1 + 1, self.ResizeDistancesX[i][0] - x1, heightResize),
-                               QRect(x1 + offsetX, y1 + offsetY, self.ResizeDistancesX[i][0] - x1, heightResize), painter)
+                               QRect(x1 + offsetX, y1 + offsetY, self.ResizeDistancesX[i][0] - x1,
+                                     heightResize), painter)
             x1 = self.ResizeDistancesX[i][0]
             resizeX = round(float(self.ResizeDistancesX[i][1]) * factorX)
             lostX += resizeX - (float(self.ResizeDistancesX[i][1]) * factorX)
diff --git a/QLabel/Lib/QtNinePatch/sip/configure.py b/QLabel/Lib/QtNinePatch/sip/configure.py
index eae22a59..a3cc255c 100644
--- a/QLabel/Lib/QtNinePatch/sip/configure.py
+++ b/QLabel/Lib/QtNinePatch/sip/configure.py
@@ -5,9 +5,8 @@
 import shutil
 
 import PyQt5
-from PyQt5.QtCore import PYQT_CONFIGURATION
 import sipconfig
-
+from PyQt5.QtCore import PYQT_CONFIGURATION
 
 # 模块名
 moduleName = 'QtNinePatch'
@@ -35,14 +34,13 @@
     '-b', "build/" + build_file,
     '-I', config.default_sip_dir + '/PyQt5',
     PYQT_CONFIGURATION.get('sip_flags', ''),
-    '%s.sip' % moduleName,
+          '%s.sip' % moduleName,
 ])
 
 os.makedirs('build', exist_ok=True)
 print(sip_cmd)
 os.system(sip_cmd)
 
-
 # Create the Makefile.
 makefile = sipconfig.SIPModuleMakefile(
     config, build_file, dir='build'
diff --git a/QLabel/Lib/QtNinePatch2.py b/QLabel/Lib/QtNinePatch2.py
index b0a84354..7697cc69 100644
--- a/QLabel/Lib/QtNinePatch2.py
+++ b/QLabel/Lib/QtNinePatch2.py
@@ -4,22 +4,19 @@
 """
 Created on 2018年10月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtNinePatch
 @description: 
 """
 from math import floor
 
-from PyQt5.QtCore import Qt, QRect
-from PyQt5.QtGui import qAlpha, QPixmap, QPainter
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt, QRect
+    from PyQt5.QtGui import qAlpha, QPixmap, QPainter
+except ImportError:
+    from PySide2.QtCore import Qt, QRect
+    from PySide2.QtGui import qAlpha, QPixmap, QPainter
 
 
 class Part:
diff --git a/QLabel/Lib/res_rc.py b/QLabel/Lib/res_rc.py
index baab0b94..0bebdc4c 100644
--- a/QLabel/Lib/res_rc.py
+++ b/QLabel/Lib/res_rc.py
@@ -6,7 +6,10 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore
+try:
+    from PyQt5 import QtCore
+except ImportError:
+    from PySide2 import QtCore
 
 qt_resource_data = b"\
 \x00\x00\x19\xf0\
@@ -462,10 +465,13 @@
     rcc_version = 2
     qt_resource_struct = qt_resource_struct_v2
 
+
 def qInitResources():
     QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
 
+
 def qCleanupResources():
     QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
 
+
 qInitResources()
diff --git a/QLabel/Lib/xpmres.py b/QLabel/Lib/xpmres.py
index fea8f797..b589c8d1 100644
--- a/QLabel/Lib/xpmres.py
+++ b/QLabel/Lib/xpmres.py
@@ -1,18 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月23日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: xpmres
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 # 这里把转换的xpm数组直接放到py文件中当做一个变量
 
diff --git a/QLabel/NinePatch.py b/QLabel/NinePatch.py
index 936faf7e..f072dc10 100644
--- a/QLabel/NinePatch.py
+++ b/QLabel/NinePatch.py
@@ -4,23 +4,20 @@
 """
 Created on 2018年10月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: NinePatch
 @description: 
 """
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
 import sys
 
-from PyQt5.QtGui import QImage, QPainter
-from PyQt5.QtWidgets import QApplication, QLabel, QWidget
+try:
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication, QWidget
+except ImportError:
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication, QWidget
 
 from Lib.NinePatch import NinePatch
 
@@ -29,7 +26,7 @@ class Label(QWidget):
 
     def __init__(self, *args, **kwargs):
         super(Label, self).__init__(*args, **kwargs)
-        #.9 格式的图片
+        # .9 格式的图片
         self.image = NinePatch('Data/skin_aio_friend_bubble_pressed.9.png')
 
     def paintEvent(self, event):
diff --git a/QLabel/QtNinePatch.py b/QLabel/QtNinePatch.py
index 8fbdbc9c..497b7bfa 100644
--- a/QLabel/QtNinePatch.py
+++ b/QLabel/QtNinePatch.py
@@ -4,22 +4,15 @@
 """
 Created on 2018年10月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: testQtNinePatch
 @description: 
 """
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
 import sys
-
 from ctypes import CDLL
+
 from PyQt5.QtGui import QImage
 from PyQt5.QtWidgets import QApplication, QLabel
 
@@ -32,7 +25,7 @@ class Label(QLabel):
 
     def __init__(self, *args, **kwargs):
         super(Label, self).__init__(*args, **kwargs)
-        #.9 格式的图片
+        # .9 格式的图片
         self.image = QImage('Data/skin_aio_friend_bubble_pressed.9.png')
 
     def showEvent(self, event):
diff --git a/QLabel/QtNinePatch2.py b/QLabel/QtNinePatch2.py
index 187b15ce..f8614612 100644
--- a/QLabel/QtNinePatch2.py
+++ b/QLabel/QtNinePatch2.py
@@ -4,23 +4,20 @@
 """
 Created on 2018年10月25日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtNinePatch2
 @description: 
 """
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
 import sys
 
-from PyQt5.QtGui import QImage
-from PyQt5.QtWidgets import QApplication, QLabel
+try:
+    from PyQt5.QtGui import QImage
+    from PyQt5.QtWidgets import QApplication, QLabel
+except ImportError:
+    from PySide2.QtGui import QImage
+    from PySide2.QtWidgets import QApplication, QLabel
 
 from Lib import QtNinePatch2
 
@@ -29,7 +26,7 @@ class Label(QLabel):
 
     def __init__(self, *args, **kwargs):
         super(Label, self).__init__(*args, **kwargs)
-        #.9 格式的图片
+        # .9 格式的图片
         self.image = QImage('Data/skin_aio_friend_bubble_pressed.9.png')
 
     def showEvent(self, event):
diff --git a/QLabel/README.md b/QLabel/README.md
index acfc3d53..b14fca5a 100644
--- a/QLabel/README.md
+++ b/QLabel/README.md
@@ -8,6 +8,7 @@
   - [圆形图片](#5圆形图片)
 
 ## 1、图片加载显示
+
 [运行 ShowImage.py](ShowImage.py)
 
 通过3种方式加载图片文件和显示gif图片
@@ -32,6 +33,7 @@
 
 
 ## 2、图片旋转
+
 [运行 ImageRotate.py](ImageRotate.py)
 
 1. 水平翻转 `QImage.mirrored(True, False)`
@@ -42,6 +44,7 @@
 
 
 ## 3、仿网页图片错位显示
+
 [运行 ImageSlipped.py](ImageSlipped.py)
 
 1. 设置`setMouseTracking(True)`开启鼠标跟踪
@@ -51,6 +54,7 @@
 
 
 ## 4、显示.9格式图片(气泡)
+
 [运行 NinePatch.py](NinePatch.py) | [运行 QtNinePatch.py](QtNinePatch.py) | [运行 QtNinePatch2.py](QtNinePatch2.py)
 
 什么叫.9.PNG呢,这是安卓开发里面的一种特殊的图片
@@ -64,16 +68,18 @@
 
 在Github开源库中搜索到两个C++版本的
 
-1.一个是NinePatchQt https://github.com/Roninsc2/NinePatchQt
+1.一个是NinePatchQt 
 
-2.一个是QtNinePatch https://github.com/soramimi/QtNinePatch
+2.一个是QtNinePatch 
 
 ### For PyQt
+
 1、目前针对第一个库在2年前用Python参考源码重新写一个见 `Lib/NinePatch.py`
 
 2、这次针对第二个库用Python编写的见`Lib/QtNinePatch2.py`。用C++编写的pyd版本见`Lib/QtNinePatch`目录
 
 ### 说明
+
 1、建议优先使用pyd版本的(后续提供Python3.4 3.5 3.6 3.7 编译好的32为库文件),也可以自行编译,编译步骤见下文。
 
 2、其次可以使用Python写的第二个版本`Lib/QtNinePatch2.py`(个人觉得方便调用)
@@ -103,8 +109,9 @@ qt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
 
 
 ### 5、圆形图片
+
 [运行 CircleImage.py](CircleImage.py)
 
 使用`QPainter`的`setClipPath`方法结合`QPainterPath`对图片进行裁剪从而实现圆形图片。
 
-
\ No newline at end of file
+
diff --git a/QLabel/ShowImage.py b/QLabel/ShowImage.py
index ac3fb9f6..41ca08ea 100644
--- a/QLabel/ShowImage.py
+++ b/QLabel/ShowImage.py
@@ -1,29 +1,28 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月23日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ShowImage
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtCore import QResource
-from PyQt5.QtGui import QPixmap, QMovie
-from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel
+try:
+    from PyQt5.QtCore import QResource
+    from PyQt5.QtGui import QPixmap, QMovie
+    from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel
+except ImportError:
+    from PySide2.QtCore import QResource
+    from PySide2.QtGui import QPixmap, QMovie
+    from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel
 
-from Lib import res_rc  # @UnresolvedImport @UnusedImport
 from Lib.xpmres import image_head  # @UnresolvedImport
 
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-
 class ImageView(QWidget):
 
     def __init__(self, *args, **kwargs):
diff --git a/QListView/CustomWidgetItem.py b/QListView/CustomWidgetItem.py
index 6a8a1be8..3bffcac1 100644
--- a/QListView/CustomWidgetItem.py
+++ b/QListView/CustomWidgetItem.py
@@ -1,22 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from PyQt5.QtCore import QSize
-from PyQt5.QtGui import QStandardItemModel, QStandardItem
-from PyQt5.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit,\
-    QPushButton
 
 
-# Created on 2018年8月4日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: QListView.显示自定义Widget
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+"""
+Created on 2018年8月4日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QListView.显示自定义Widget
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QSize
+    from PyQt5.QtGui import QStandardItemModel, QStandardItem
+    from PyQt5.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit, \
+        QPushButton, QApplication
+except ImportError:
+    from PySide2.QtCore import QSize
+    from PySide2.QtGui import QStandardItemModel, QStandardItem
+    from PySide2.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit, \
+        QPushButton, QApplication
 
 
 class CustomWidget(QWidget):
@@ -56,7 +60,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = ListView()
     w.show()
diff --git a/QListView/CustomWidgetSortItem.py b/QListView/CustomWidgetSortItem.py
index 09fe9036..3a68ef20 100644
--- a/QListView/CustomWidgetSortItem.py
+++ b/QListView/CustomWidgetSortItem.py
@@ -1,26 +1,29 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from random import choice, randint
-import string
-from time import time
 
-from PyQt5.QtCore import QSortFilterProxyModel, Qt, QSize
-from PyQt5.QtGui import QStandardItem, QStandardItemModel
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView,\
-    QHBoxLayout, QLineEdit
 
+"""
+Created on 2018年8月4日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QListView.显示自定义Widget并排序
+@description:
+"""
+import string
+from random import choice, randint
+from time import time
 
-# Created on 2018年8月4日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: QListView.显示自定义Widget并排序
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QSortFilterProxyModel, Qt, QSize
+    from PyQt5.QtGui import QStandardItem, QStandardItemModel
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView, \
+        QHBoxLayout, QLineEdit, QApplication
+except ImportError:
+    from PySide2.QtCore import QSortFilterProxyModel, Qt, QSize
+    from PySide2.QtGui import QStandardItem, QStandardItemModel
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView, \
+        QHBoxLayout, QLineEdit, QApplication
 
 
 def randomChar(y):
@@ -55,11 +58,11 @@ def lessThan(self, source_left, source_right):
             leftData = leftData.split('-')[-1]
             rightData = rightData.split('-')[-1]
             return leftData < rightData
-#         elif self.sortOrder() == Qt.AscendingOrder:
-#             #按照名字升序排序
-#             leftData = leftData.split('-')[0]
-#             rightData = rightData.split('-')[0]
-#             return leftData < rightData
+        #         elif self.sortOrder() == Qt.AscendingOrder:
+        #             #按照名字升序排序
+        #             leftData = leftData.split('-')[0]
+        #             rightData = rightData.split('-')[0]
+        #             return leftData < rightData
         return super(SortFilterProxyModel, self).lessThan(source_left, source_right)
 
 
@@ -89,7 +92,7 @@ def __init__(self, *args, **kwargs):
             times = time() + randint(0, 30)  # 当前时间随机+
             value = '{}-{}'.format(name, times)  # 内容用-分开
             item = QStandardItem(value)
-#             item.setData(value, Qt.UserRole + 2)
+            #             item.setData(value, Qt.UserRole + 2)
             self.dmodel.appendRow(item)
             # 索引
             index = self.fmodel.mapFromSource(item.index())
@@ -109,7 +112,7 @@ def sortByName(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QListView/ImageView.py b/QListView/ImageView.py
new file mode 100644
index 00000000..b030806c
--- /dev/null
+++ b/QListView/ImageView.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/4/15
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ImageView
+@description: 
+"""
+import os
+
+try:
+    from PyQt5.QtCore import QPointF, Qt, QRectF, QSizeF
+    from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPainter, QColor, QImage, QPixmap
+    from PyQt5.QtWidgets import QApplication, QListView, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene
+except ImportError:
+    from PySide2.QtCore import QPointF, Qt, QRectF, QSizeF
+    from PySide2.QtGui import QStandardItem, QStandardItemModel, QPainter, QColor, QImage, QPixmap
+    from PySide2.QtWidgets import QApplication, QListView, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene
+
+ScrollPixel = 40
+
+
+class BigImageView(QGraphicsView):
+    """图片查看控件"""
+
+    def __init__(self, *args, **kwargs):
+        image = kwargs.pop('image', None)
+        background = kwargs.pop('background', None)
+        super(BigImageView, self).__init__(*args, **kwargs)
+        self.setCursor(Qt.OpenHandCursor)
+        self.setBackground(background)
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
+                            QPainter.SmoothPixmapTransform)
+        self.setCacheMode(self.CacheBackground)
+        self.setViewportUpdateMode(self.SmartViewportUpdate)
+        self._item = QGraphicsPixmapItem()  # 放置图像
+        self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable |
+                            QGraphicsPixmapItem.ItemIsMovable)
+        self._scene = QGraphicsScene(self)  # 场景
+        self.setScene(self._scene)
+        self._scene.addItem(self._item)
+        rect = QApplication.instance().desktop().availableGeometry()
+        self.resize(int(rect.width() * 2 / 3), int(rect.height() * 2 / 3))
+
+        self.pixmap = None
+        self._delta = 0.1  # 缩放
+        self.setPixmap(image)
+
+    def setBackground(self, color):
+        """设置背景颜色
+        :param color: 背景颜色
+        :type color: QColor or str or GlobalColor
+        """
+        if isinstance(color, QColor):
+            self.setBackgroundBrush(color)
+        elif isinstance(color, (str, Qt.GlobalColor)):
+            color = QColor(color)
+            if color.isValid():
+                self.setBackgroundBrush(color)
+
+    def setPixmap(self, pixmap, fitIn=True):
+        """加载图片
+        :param pixmap: 图片或者图片路径
+        :param fitIn: 是否适应
+        :type pixmap: QPixmap or QImage or str
+        :type fitIn: bool
+        """
+        if isinstance(pixmap, QPixmap):
+            self.pixmap = pixmap
+        elif isinstance(pixmap, QImage):
+            self.pixmap = QPixmap.fromImage(pixmap)
+        elif isinstance(pixmap, str) and os.path.isfile(pixmap):
+            self.pixmap = QPixmap(pixmap)
+        else:
+            return
+        self._item.setPixmap(self.pixmap)
+        self._item.update()
+        self.setSceneDims()
+        if fitIn:
+            self.fitInView(QRectF(self._item.pos(), QSizeF(
+                self.pixmap.size())), Qt.KeepAspectRatio)
+        self.update()
+
+    def setSceneDims(self):
+        if not self.pixmap:
+            return
+        self.setSceneRect(QRectF(QPointF(0, 0), QPointF(self.pixmap.width(), self.pixmap.height())))
+
+    def fitInView(self, rect, flags=Qt.IgnoreAspectRatio):
+        """剧中适应
+        :param rect: 矩形范围
+        :param flags:
+        :return:
+        """
+        if not self.scene() or rect.isNull():
+            return
+        unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
+        self.scale(1 / unity.width(), 1 / unity.height())
+        viewRect = self.viewport().rect()
+        sceneRect = self.transform().mapRect(rect)
+        x_ratio = viewRect.width() / sceneRect.width()
+        y_ratio = viewRect.height() / sceneRect.height()
+        if flags == Qt.KeepAspectRatio:
+            x_ratio = y_ratio = min(x_ratio, y_ratio)
+        elif flags == Qt.KeepAspectRatioByExpanding:
+            x_ratio = y_ratio = max(x_ratio, y_ratio)
+        self.scale(x_ratio, y_ratio)
+        self.centerOn(rect.center())
+
+    def wheelEvent(self, event):
+        if event.angleDelta().y() > 0:
+            self.zoomIn()
+        else:
+            self.zoomOut()
+
+    def zoomIn(self):
+        """放大"""
+        self.zoom(1 + self._delta)
+
+    def zoomOut(self):
+        """缩小"""
+        self.zoom(1 - self._delta)
+
+    def zoom(self, factor):
+        """缩放
+        :param factor: 缩放的比例因子
+        """
+        _factor = self.transform().scale(
+            factor, factor).mapRect(QRectF(0, 0, 1, 1)).width()
+        if _factor < 0.07 or _factor > 100:
+            # 防止过大过小
+            return
+        self.scale(factor, factor)
+
+
+class ImageView(QListView):
+
+    def __init__(self, *args, **kwargs):
+        super(ImageView, self).__init__(*args, **kwargs)
+        self.setFrameShape(self.NoFrame)
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setEditTriggers(self.NoEditTriggers)
+        self.setDropIndicatorShown(True)
+        self.setDragDropMode(self.DragDrop)
+        self.setDefaultDropAction(Qt.IgnoreAction)
+        self.setSelectionMode(self.ExtendedSelection)
+        self.setVerticalScrollMode(self.ScrollPerPixel)
+        self.setHorizontalScrollMode(self.ScrollPerPixel)
+        self.setFlow(self.LeftToRight)
+        self.setWrapping(True)
+        self.setResizeMode(self.Adjust)
+        self.setSpacing(6)
+        self.setViewMode(self.IconMode)
+        self.setWordWrap(True)
+        self.setSelectionRectVisible(True)
+        self.setContextMenuPolicy(Qt.CustomContextMenu)
+        # 解决拖动到顶部或者底部自动滚动
+        self.setAutoScrollMargin(150)
+        self.verticalScrollBar().setSingleStep(ScrollPixel)
+        # 设置model
+        self.dmodel = QStandardItemModel(self)
+        self.setModel(self.dmodel)
+
+        # 大图控件
+        self.bigView = BigImageView(background='#323232')
+
+    def addItem(self, image):
+        if isinstance(image, str):
+            image = QPixmap(image)
+        # 添加一个item
+        item = QStandardItem()
+        # 记录原始图片
+        item.setData(image, Qt.UserRole + 1)  # 用于双击的时候取出来
+        # 缩放成小图并显示
+        item.setData(image.scaled(60, 60, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), Qt.DecorationRole)
+        # 添加item到界面中
+        self.dmodel.appendRow(item)
+
+    def count(self):
+        return self.dmodel.rowCount()
+
+    def setCurrentRow(self, row):
+        self.setCurrentIndex(self.dmodel.index(row, 0))
+
+    def currentRow(self):
+        return self.currentIndex().row()
+
+    def updateGeometries(self):
+        # 一次滑动20px
+        super(ImageView, self).updateGeometries()
+        self.verticalScrollBar().setSingleStep(ScrollPixel)
+
+    def closeEvent(self, event):
+        # 关闭预览窗口
+        self.bigView.close()
+        super(ImageView, self).closeEvent(event)
+
+    def wheelEvent(self, event):
+        # 修复滑动bug
+        if self.flow() == QListView.LeftToRight:
+            bar = self.horizontalScrollBar()
+            value = ScrollPixel if event.angleDelta().y() < 0 else (0 - ScrollPixel)
+            bar.setSliderPosition(bar.value() + value)
+        else:
+            super(ImageView, self).wheelEvent(event)
+
+    def mouseDoubleClickEvent(self, event):
+        # 列表双击,如果有item则进入item处理流程,否则调用打开图片功能
+        index = self.indexAt(event.pos())
+        if index and index.isValid():
+            item = self.dmodel.itemFromIndex(index)
+            if item:
+                # 取出原图用来新窗口显示
+                image = item.data(Qt.UserRole + 1)
+                self.bigView.setPixmap(image)
+                self.bigView.show()
+            return
+        super(ImageView, self).mouseDoubleClickEvent(event)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    w = ImageView()
+    w.show()
+
+    # 添加模拟图片
+    for i in range(3):
+        for name in os.listdir('ScreenShot'):
+            w.addItem(os.path.join('ScreenShot', name))
+    sys.exit(app.exec_())
diff --git a/QListView/README.md b/QListView/README.md
index 95a16ef9..c70c393d 100644
--- a/QListView/README.md
+++ b/QListView/README.md
@@ -6,6 +6,7 @@
   - [自定义角色排序](#3自定义角色排序)
 
 ## 1、显示自定义Widget
+
 [运行 CustomWidgetItem.py](CustomWidgetItem.py)
 
 通过设置 `setIndexWidget(QModelIndex, QWidget)` 可以设置自定义 `QWidget`
@@ -13,6 +14,7 @@
 
 
 ## 2、显示自定义Widget并排序
+
 [运行 CustomWidgetSortItem.py](CustomWidgetSortItem.py)
 
 1. 对QListView设置代理 `QSortFilterProxyModel`
@@ -21,9 +23,11 @@
 
 
 ## 3、自定义角色排序
+
 [运行 SortItemByRole.py](SortItemByRole.py)
 
 需求:
+
 1. 5种分类(唐、宋、元、明、清) 和 未分类
 2. 选中唐则按照 唐、宋、元、明、清、未分类排序
 3. 选中宋则按照 宋、唐、元、明、清、未分类排序
@@ -31,9 +35,11 @@
 5. 取消排序则恢复到加载时候顺序,如:未分类、唐、唐、明、清、未分类、宋、元、未分类
 
 思路:
+
 1. 定义`IdRole = Qt.UserRole + 1`            用于恢复默认排序
 2. 定义`ClassifyRole = Qt.UserRole + 2`      用于按照分类序号排序
 3. 定义5种分类的id
+
     ```python
     NameDict = {
         '唐': ['Tang', 0],
@@ -50,14 +56,18 @@
         4: '清',
     }
     ```
+
 4. item设置 `setData(id, IdRole)` 用于恢复默认排序
 5. item设置 `setData(cid, ClassifyRole)` 用于标识该item的分类
 6. 继承 `QSortFilterProxyModel` 增加 `setSortIndex(self, index)` 方法, 目的在于记录要置顶(不参与排序)的分类ID
+
     ```python
     def setSortIndex(self, index):
         self._topIndex = index
     ```
+
 7. 继承 `QSortFilterProxyModel` 重写 `lessThan` 方法, 判断分类ID是否等于要置顶的ID, 如果是则修改为-1, 这样就永远在最前面
+
     ```python
     if self.sortRole() == ClassifyRole and \
             source_left.column() == self.sortColumn() and \
@@ -76,12 +86,16 @@
     
             return leftIndex < rightIndex
     ```
+
 8. 恢复默认排序
+
     ```python
     self.fmodel.setSortRole(IdRole)     # 必须设置排序角色为ID
     self.fmodel.sort(0)                 # 排序第一列按照ID升序
     ```
+
 9. 根据分类排序, 这里要注意要先通过 `setSortRole` 设置其它角色再设置目标角色
+
     ```python
     self.fmodel.setSortIndex(1)
     self.fmodel.setSortRole(IdRole)
@@ -89,4 +103,4 @@
     self.fmodel.sort(0)
     ```
 
-
\ No newline at end of file
+
diff --git a/QListView/SortItemByRole.py b/QListView/SortItemByRole.py
index 269bd6f8..d4ccd876 100644
--- a/QListView/SortItemByRole.py
+++ b/QListView/SortItemByRole.py
@@ -4,23 +4,21 @@
 """
 Created on 2018年12月27日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QListView.SortItemByRole
 @description: 
 """
 from random import choice
 
-from PyQt5.QtCore import QSortFilterProxyModel, Qt
-from PyQt5.QtGui import QStandardItem, QStandardItemModel
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QListView, QPushButton
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QSortFilterProxyModel, Qt
+    from PyQt5.QtGui import QStandardItem, QStandardItemModel
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView, QPushButton
+except ImportError:
+    from PySide2.QtCore import QSortFilterProxyModel, Qt
+    from PySide2.QtGui import QStandardItem, QStandardItemModel
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView, QPushButton
 
 
 class SortFilterProxyModel(QSortFilterProxyModel):
@@ -72,8 +70,8 @@ def lessThan(self, source_left, source_right):
     4: '清',
 }
 
-IdRole = Qt.UserRole + 1            # 用于恢复排序
-ClassifyRole = Qt.UserRole + 2      # 用于按照分类序号排序
+IdRole = Qt.UserRole + 1  # 用于恢复排序
+ClassifyRole = Qt.UserRole + 2  # 用于按照分类序号排序
 
 
 class Window(QWidget):
@@ -96,8 +94,8 @@ def __init__(self, *args, **kwargs):
 
     def restoreSort(self):
         # 恢复默认排序
-        self.fmodel.setSortRole(IdRole)     # 必须设置排序角色为ID
-        self.fmodel.sort(0)                 # 排序第一列按照ID升序
+        self.fmodel.setSortRole(IdRole)  # 必须设置排序角色为ID
+        self.fmodel.sort(0)  # 排序第一列按照ID升序
 
     def sortByClassify(self):
         self.fmodel.setSortIndex(NameDict.get(
@@ -141,8 +139,9 @@ def _initItems(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QListWidget/Data/CoverItemWidget.ui b/QListWidget/Data/CoverItemWidget.ui
new file mode 100644
index 00000000..2f245e13
--- /dev/null
+++ b/QListWidget/Data/CoverItemWidget.ui
@@ -0,0 +1,85 @@
+
+
+ CoverItemWidget 
+ 
+  
+   
+    200 
+    256 
+    
+   
+  
+   
+    200 
+    256 
+    
+   
+  
+    
+  
+   
+    12 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   
+    10 
+    
+   - 
+    
+     
+      
+       180 +180 +
 +
 +
+       +
+      true +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      
+       10 +
 +
 +
+       +
 +
+- 
+    
+     
+       +
 +
+ 
+  
+ 
+  
+   CoverLabel 
+   QLabel 
+   
+   
+  
+  
diff --git a/QListWidget/Data/CoverLabel.ui b/QListWidget/Data/CoverLabel.ui
new file mode 100644
index 00000000..2e7e0378
--- /dev/null
+++ b/QListWidget/Data/CoverLabel.ui
@@ -0,0 +1,109 @@
+
+
+ CoverLabel 
+ 
+  
+   
+    0 
+    0 
+    180 
+    180 
+    
+   
+  
+   PointingHandCursor 
+   
+  
+    
+  
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   
+    0 
+    
+   - 
+    
+     
+      Qt::Vertical +
 +
+      
+       20 +135 +
 +
 +
 +
+- 
+    
+     
+      #widgetBottom {
+    background-color: rgba(0, 0, 0, 150);
+} +
 +
+      - 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+- 
+       
+        
+          +
 +
+- 
+       
+        
+         Qt::Horizontal +
 +
+         
+          40 +20 +
 +
 +
 +
+- 
+       
+        
+         
+          16 +16 +
 +
 +
+          +
 +
+ +
 +
+ 
+  
+  
diff --git a/QListWidget/Data/Svg_icon_headset_sm.svg b/QListWidget/Data/Svg_icon_headset_sm.svg
new file mode 100644
index 00000000..98a1a359
--- /dev/null
+++ b/QListWidget/Data/Svg_icon_headset_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QListWidget/Data/Svg_icon_loading.svg b/QListWidget/Data/Svg_icon_loading.svg
new file mode 100644
index 00000000..0b00b799
--- /dev/null
+++ b/QListWidget/Data/Svg_icon_loading.svg
@@ -0,0 +1,31 @@
+
+    
+        
+             
+     
+    
+        
+            
+                 
+            
+                 
+         
+     
+ 
\ No newline at end of file
diff --git a/QListWidget/Data/Svg_icon_play_sm.svg b/QListWidget/Data/Svg_icon_play_sm.svg
new file mode 100644
index 00000000..08a47f20
--- /dev/null
+++ b/QListWidget/Data/Svg_icon_play_sm.svg
@@ -0,0 +1,3 @@
+
+     
\ No newline at end of file
diff --git a/QListWidget/DeleteCustomItem.py b/QListWidget/DeleteCustomItem.py
index a2d49330..ab8516e0 100644
--- a/QListWidget/DeleteCustomItem.py
+++ b/QListWidget/DeleteCustomItem.py
@@ -4,25 +4,23 @@
 """
 Created on 2018年11月4日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 删除Item
 @description: 
 """
-from PyQt5.QtCore import QSize, pyqtSignal
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton,\
-    QListWidgetItem, QVBoxLayout, QListWidget
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QSize, pyqtSignal
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton, \
+        QListWidgetItem, QVBoxLayout, QListWidget, QApplication
+except ImportError:
+    from PySide2.QtCore import QSize, Signal as pyqtSignal
+    from PySide2.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton, \
+        QListWidgetItem, QVBoxLayout, QListWidget, QApplication
 
 
 class ItemWidget(QWidget):
-
     itemDeleted = pyqtSignal(QListWidgetItem)
 
     def __init__(self, text, item, *args, **kwargs):
@@ -91,8 +89,9 @@ def testData(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, 'text')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QListWidget/DragDrop.py b/QListWidget/DragDrop.py
index dcc8818c..0df4479e 100644
--- a/QListWidget/DragDrop.py
+++ b/QListWidget/DragDrop.py
@@ -4,21 +4,20 @@
 """
 Created on 2018年9月14日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: DragListWidget
 @description: 
 """
-from PyQt5.QtCore import Qt, QSize, QRect, QPoint
-from PyQt5.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor
-from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt, QSize, QRect, QPoint
+    from PyQt5.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor
+    from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt, QSize, QRect, QPoint
+    from PySide2.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor
+    from PySide2.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand, QApplication
 
 
 class DropListWidget(QListWidget):
@@ -162,7 +161,7 @@ def initItems(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyleSheet("""QListWidget {
         outline: 0px;
diff --git a/QListWidget/FoldWidget.py b/QListWidget/FoldWidget.py
index d522c95b..7f75a8d8 100644
--- a/QListWidget/FoldWidget.py
+++ b/QListWidget/FoldWidget.py
@@ -4,22 +4,17 @@
 """
 Created on 2019年5月27日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FoldWidget
 @description: 自定义item折叠控件仿QTreeWidget
 """
 
 from PyQt5.QtCore import QSize
-from PyQt5.QtWidgets import QWidget, QPushButton, QFormLayout,\
+from PyQt5.QtWidgets import QWidget, QPushButton, QFormLayout, \
     QLineEdit, QListWidget, QListWidgetItem, QCheckBox
 
 
-__Author__ = "Irony"
-__Copyright__ = "Copyright (c) 2019"
-__Version__ = "Version 1.0"
-
-
 class CustomWidget(QWidget):
 
     def __init__(self, item, *args, **kwargs):
@@ -79,8 +74,10 @@ def __init__(self, *args, **kwargs):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+
+    cgitb.enable(format='text')
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     # 通过qss改变按钮的高度
     app.setStyleSheet('#testBtn{min-height:40px;}')
diff --git a/QListWidget/HotPlaylist.py b/QListWidget/HotPlaylist.py
index bcba0d65..e457b5cb 100644
--- a/QListWidget/HotPlaylist.py
+++ b/QListWidget/HotPlaylist.py
@@ -1,195 +1,45 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
-'''
-Created on 2018年2月4日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: TencentMovieHotPlay_ListWidget
-@description: 
-'''
+@file: HotPlaylist.py
+@description:
+"""
+
 import os
 import sys
-import webbrowser
-
-from PyQt5.QtCore import QSize, Qt, QUrl, QTimer
-from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
-    QBrush, QPaintEvent, QPixmap
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-from PyQt5.QtSvg import QSvgWidget
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
-    QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\
-    QListWidget, QListWidgetItem
 
+from Lib.CoverItemWidget import CoverItemWidget
 from lxml.etree import HTML  # @UnresolvedImport
 
+try:
+    from PyQt5.QtCore import QTimer, QUrl
+    from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PyQt5.QtSvg import QSvgWidget
+    from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QListWidget,
+                                 QListWidgetItem)
+except ImportError:
+    from PySide2.QtCore import QTimer, QUrl
+    from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PySide2.QtSvg import QSvgWidget
+    from PySide2.QtWidgets import (QAbstractSlider, QApplication, QListWidget,
+                                   QListWidgetItem)
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+# offset=0,35,70,105
+Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}"
 
-# offset=0,30,60,90
-Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
+Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50"
 
-# 播放量图标
-Svg_icon_play_sm = '''
-     
-'''.encode()
+Referer = b"https://music.163.com/"
 
-Svg_icon_loading = '''
-    
-        
-             
-     
-    
-        
-            
-                 
-            
-                 
-         
-     
- '''.encode()
-
-# 主演
+# 作者
 Actor = '''{title}  '''
 
 
-class CoverLabel(QLabel):
-
-    def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
-        super(CoverLabel, self).__init__(*args, **kwargs)
-#         super(CoverLabel, self).__init__(
-#             ' 主演: " + \
-                "".join([Actor.format(**dict(fd.items()))
-                         for fd in li.xpath(".//div[@class='figure_desc']/a")])
+            a = li.find('.//div/a')
+            play_url = "https://music.163.com/" + a.get("href")  # 歌单播放地址
+            img = li.find(".//div/img")
+            cover_url = img.get("src")  # 封面图片
+            playlist_title = a.get("title")  # 歌单名
+            # 歌手
+            author_info = li.xpath(".//p[2]/a")[0]
+            playlist_author = "".format(
+                Actor.format(href="https://music.163.com/" +
+                             author_info.get("href"),
+                             title=author_info.get("title")))
             # 播放数
-            figure_count = (
-                li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
+            play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0]
             path = "cache/{0}.jpg".format(
-                os.path.splitext(os.path.basename(video_url))[0])
+                os.path.splitext(os.path.basename(cover_url).split('?')[0])[0])
             cover_path = "Data/pic_v.png"
             if os.path.isfile(path):
                 cover_path = path
-            iwidget = ItemWidget(cover_path, figure_info, figure_title,
-                                 figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self)
+
+            # print(cover_path, playlist_title,
+            #       playlist_author, play_count, play_url, cover_url, path)
+            iwidget = CoverItemWidget(self, manager=self._manager)
+            iwidget.init(cover_path, playlist_title, playlist_author,
+                         play_count, play_url, cover_url, path)
             item = QListWidgetItem(self)
             item.setSizeHint(iwidget.sizeHint())
             self.setItemWidget(item, iwidget)
@@ -289,7 +147,8 @@ def onActionTriggered(self, action):
         if action != QAbstractSlider.SliderMove or self._loadStart:
             return
         # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
-        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
+        if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar(
+        ).maximum():
             # 可以下一页了
             self.load()
 
@@ -298,9 +157,7 @@ def resizeEvent(self, event):
         self.loadWidget.setGeometry(
             int((self.width() - self.loadWidget.minimumWidth()) / 2),
             int((self.height() - self.loadWidget.minimumHeight()) / 2),
-            self.loadWidget.minimumWidth(),
-            self.loadWidget.minimumHeight()
-        )
+            self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight())
 
 
 if __name__ == "__main__":
diff --git a/QListWidget/Lib/CoverItemWidget.py b/QListWidget/Lib/CoverItemWidget.py
new file mode 100644
index 00000000..5ef3ff00
--- /dev/null
+++ b/QListWidget/Lib/CoverItemWidget.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverItemWidget.py
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QSize, QUrl
+    from PyQt5.QtGui import QPaintEvent, QPixmap
+    from PyQt5.QtNetwork import QNetworkRequest
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import QSize, QUrl
+    from PySide2.QtGui import QPaintEvent, QPixmap
+    from PySide2.QtNetwork import QNetworkRequest
+    from PySide2.QtWidgets import QWidget
+
+from .Ui_CoverItemWidget import Ui_CoverItemWidget  # @UnresolvedImport
+
+
+class CoverItemWidget(QWidget, Ui_CoverItemWidget):
+
+    def __init__(self, *args, **kwargs):
+        self._manager = kwargs.pop('manager', None)
+        super(CoverItemWidget, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, playlist_title, playlist_author, play_count,
+             play_url, cover_url, img_path):
+        self.img_path = img_path
+        self.cover_url = cover_url
+        # 图片label
+        self.labelCover.init(cover_path, play_url, play_count)
+
+        # 歌单
+        self.labelTitle.setText(playlist_title)
+
+        # 作者
+        self.labelAuthor.setText(playlist_author)
+
+    def setCover(self, path):
+        self.labelCover.setCoverPath(path)
+        self.labelCover.setPixmap(QPixmap(path))
+
+    def sizeHint(self):
+        # 每个item控件的大小
+        return QSize(200, 256)
+
+    def event(self, event):
+        if isinstance(event, QPaintEvent):
+            if event.rect().height() > 20 and hasattr(self, "labelCover"):
+                if self.labelCover.cover_path.find("pic_v.png") > -1:  # 封面未加载
+                    # print("start download img:", self.cover_url)
+                    req = QNetworkRequest(QUrl(self.cover_url))
+                    # 设置两个自定义属性方便后期reply中处理
+                    req.setAttribute(QNetworkRequest.User + 1, self)
+                    req.setAttribute(QNetworkRequest.User + 2, self.img_path)
+                    if self._manager:
+                        self._manager.get(req)  # 调用父窗口中的下载器下载
+        return super(CoverItemWidget, self).event(event)
diff --git a/QListWidget/Lib/CoverLabel.py b/QListWidget/Lib/CoverLabel.py
new file mode 100644
index 00000000..dbee15af
--- /dev/null
+++ b/QListWidget/Lib/CoverLabel.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/22
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CoverLabel.py
+@description:
+"""
+import webbrowser
+
+try:
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QLabel
+except ImportError:
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QLabel
+
+from .Ui_CoverLabel import Ui_CoverLabel  # @UnresolvedImport
+
+
+class CoverLabel(QLabel, Ui_CoverLabel):
+
+    def __init__(self, *args, **kwargs):
+        super(CoverLabel, self).__init__(*args, **kwargs)
+        self.setupUi(self)
+
+    def init(self, cover_path, play_url, play_count):
+        self.cover_path = cover_path
+        self.play_url = play_url
+        self.setPixmap(QPixmap(cover_path))
+        self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg'))
+        self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg'))
+        self.labelCount.setStyleSheet('color: #999999;')
+        self.labelCount.setText(play_count)
+
+    def setCoverPath(self, path):
+        self.cover_path = path
+
+    def mouseReleaseEvent(self, event):
+        super(CoverLabel, self).mouseReleaseEvent(event)
+        webbrowser.open_new_tab(self.play_url)
diff --git a/QListWidget/Lib/Ui_CoverItemWidget.py b/QListWidget/Lib/Ui_CoverItemWidget.py
new file mode 100644
index 00000000..c048f686
--- /dev/null
+++ b/QListWidget/Lib/Ui_CoverItemWidget.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverItemWidget.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverItemWidget(object):
+    def setupUi(self, CoverItemWidget):
+        CoverItemWidget.setObjectName("CoverItemWidget")
+        CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256))
+        CoverItemWidget.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget)
+        self.verticalLayout.setContentsMargins(10, 10, 10, 10)
+        self.verticalLayout.setSpacing(12)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.labelCover = CoverLabel(CoverItemWidget)
+        self.labelCover.setMinimumSize(QtCore.QSize(180, 180))
+        self.labelCover.setText("")
+        self.labelCover.setScaledContents(True)
+        self.labelCover.setAlignment(QtCore.Qt.AlignCenter)
+        self.labelCover.setObjectName("labelCover")
+        self.verticalLayout.addWidget(self.labelCover)
+        self.labelTitle = QtWidgets.QLabel(CoverItemWidget)
+        font = QtGui.QFont()
+        font.setPointSize(10)
+        self.labelTitle.setFont(font)
+        self.labelTitle.setText("")
+        self.labelTitle.setObjectName("labelTitle")
+        self.verticalLayout.addWidget(self.labelTitle)
+        self.labelAuthor = QtWidgets.QLabel(CoverItemWidget)
+        self.labelAuthor.setText("")
+        self.labelAuthor.setObjectName("labelAuthor")
+        self.verticalLayout.addWidget(self.labelAuthor)
+
+        self.retranslateUi(CoverItemWidget)
+        QtCore.QMetaObject.connectSlotsByName(CoverItemWidget)
+
+    def retranslateUi(self, CoverItemWidget):
+        pass
+from .CoverLabel import CoverLabel
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverItemWidget = QtWidgets.QWidget()
+    ui = Ui_CoverItemWidget()
+    ui.setupUi(CoverItemWidget)
+    CoverItemWidget.show()
+    sys.exit(app.exec_())
diff --git a/QListWidget/Lib/Ui_CoverLabel.py b/QListWidget/Lib/Ui_CoverLabel.py
new file mode 100644
index 00000000..86e1526a
--- /dev/null
+++ b/QListWidget/Lib/Ui_CoverLabel.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'CoverLabel.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.2
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_CoverLabel(object):
+    def setupUi(self, CoverLabel):
+        CoverLabel.setObjectName("CoverLabel")
+        CoverLabel.resize(180, 180)
+        CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
+        CoverLabel.setWindowTitle("")
+        self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel)
+        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.verticalLayout.setSpacing(0)
+        self.verticalLayout.setObjectName("verticalLayout")
+        spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.verticalLayout.addItem(spacerItem)
+        self.widgetBottom = QtWidgets.QWidget(CoverLabel)
+        self.widgetBottom.setStyleSheet("#widgetBottom {\n"
+"    background-color: rgba(0, 0, 0, 150);\n"
+"}")
+        self.widgetBottom.setObjectName("widgetBottom")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.labelHeadset = QtWidgets.QLabel(self.widgetBottom)
+        self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelHeadset.setText("")
+        self.labelHeadset.setObjectName("labelHeadset")
+        self.horizontalLayout.addWidget(self.labelHeadset)
+        self.labelCount = QtWidgets.QLabel(self.widgetBottom)
+        self.labelCount.setText("")
+        self.labelCount.setObjectName("labelCount")
+        self.horizontalLayout.addWidget(self.labelCount)
+        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem1)
+        self.labelPlay = QtWidgets.QLabel(self.widgetBottom)
+        self.labelPlay.setMinimumSize(QtCore.QSize(16, 16))
+        self.labelPlay.setText("")
+        self.labelPlay.setObjectName("labelPlay")
+        self.horizontalLayout.addWidget(self.labelPlay)
+        self.verticalLayout.addWidget(self.widgetBottom)
+
+        self.retranslateUi(CoverLabel)
+        QtCore.QMetaObject.connectSlotsByName(CoverLabel)
+
+    def retranslateUi(self, CoverLabel):
+        pass
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    CoverLabel = QtWidgets.QWidget()
+    ui = Ui_CoverLabel()
+    ui.setupUi(CoverLabel)
+    CoverLabel.show()
+    sys.exit(app.exec_())
diff --git a/QListWidget/Lib/__init__.py b/QListWidget/Lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/QListWidget/README.md b/QListWidget/README.md
index a1292098..faaa1ab7 100644
--- a/QListWidget/README.md
+++ b/QListWidget/README.md
@@ -3,11 +3,12 @@
 - 目录
   - [删除自定义Item](#1删除自定义Item)
   - [自定义可拖拽Item](#2自定义可拖拽Item)
-  - [腾讯视频热播列表](#3腾讯视频热播列表)
+  - [音乐热歌列表](#3音乐热歌列表)
   - [仿折叠控件效果](#4仿折叠控件效果)
   - [列表常用信号](#5列表常用信号)
 
 ## 1、删除自定义Item
+
 [运行 DeleteCustomItem.py](DeleteCustomItem.py)
 
 1. 删除item时先要通过`QListWidget.indexFromItem(item).row()`得到它的行数
@@ -18,24 +19,26 @@
 
 
 ## 2、自定义可拖拽Item
+
 [运行 DragDrop.py](DragDrop.py)
 
 
 
-## 3、腾讯视频热播列表
+## 3、音乐热歌列表
+
 [运行 HotPlaylist.py](HotPlaylist.py)
 
 简单思路说明:
 
- - 利用`QListWidget`设置一些特殊的参数达到可以横向自动显示
- - `QNetworkAccessManager`异步下载网页和图片
- - 滚动到底部触发下一页加载
+- 利用`QListWidget`设置一些特殊的参数达到可以横向自动显示
+- `QNetworkAccessManager`异步下载网页和图片
+- 滚动到底部触发下一页加载
 
 自定义控件说明:
 
- - 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
- - 字体颜色用qss设置
- - 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
+- 主要是多个layout和控件的结合,其中图片`QLabel`为自定义,通过`setPixmap`设置图片,重写`paintEvent`绘制底部渐变矩形框和白色文字
+- 字体颜色用qss设置
+- 图标利用了`QSvgWidget`显示,可以是svg 动画(如圆形加载图)
 
 `QListWidget`的参数设置
 
@@ -46,6 +49,7 @@
 
 
 ## 4、仿折叠控件效果
+
 [运行 FoldWidget.py](FoldWidget.py)
 
 1. 利用`QListWidget`设置Item的自定义控件
@@ -56,8 +60,9 @@
 
 
 ## 5、列表常用信号
+
 [运行 SignalsExample.py](SignalsExample.py)
 
-根据官网文档 https://doc.qt.io/qt-5/qlistwidget.html#signals 中的信号介绍编写
+根据官网文档  中的信号介绍编写
 
-
\ No newline at end of file
+
diff --git a/QListWidget/SignalsExample.py b/QListWidget/SignalsExample.py
index bb17e421..38f20c2b 100644
--- a/QListWidget/SignalsExample.py
+++ b/QListWidget/SignalsExample.py
@@ -4,20 +4,22 @@
 """
 Created on 2019年7月3日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QListWidget.SignalsExample
 @description: 
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QColor
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QListWidget, QPlainTextEdit,\
-    QListWidgetItem, QAbstractItemView, QListView
 
-
-__Author__ = "Irony"
-__Copyright__ = "Copyright (c) 2019"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QColor
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QListWidget, QPlainTextEdit, \
+        QListWidgetItem, QAbstractItemView, QListView, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QColor
+    from PySide2.QtWidgets import QWidget, QHBoxLayout, QListWidget, QPlainTextEdit, \
+        QListWidgetItem, QAbstractItemView, QListView, QApplication
 
 
 def formatColor(text, color):
@@ -118,7 +120,7 @@ def onItemSelectionChanged(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QMenu/MultiSelect.py b/QMenu/MultiSelect.py
index 17bf9bd5..31a78c8a 100644
--- a/QMenu/MultiSelect.py
+++ b/QMenu/MultiSelect.py
@@ -4,20 +4,16 @@
 """
 Created on 2018年10月24日
 @author: Irony
-@site: https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: MultiSelect
 @description: 
 """
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\
-    QAction
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMenu, QAction
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMenu, QAction
 
 
 class Window(QWidget):
@@ -70,8 +66,9 @@ def _checkAction(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, 'text')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.resize(400, 400)
diff --git a/QMenu/QQMenu.py b/QMenu/QQMenu.py
new file mode 100644
index 00000000..a561eb47
--- /dev/null
+++ b/QMenu/QQMenu.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/4/7
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QQMenu
+@description: 
+"""
+import string
+from random import choice, randint
+
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPixmap, QPainter, QFont, QIcon
+    from PyQt5.QtWidgets import QLabel, QMenu, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPixmap, QPainter, QFont, QIcon
+    from PySide2.QtWidgets import QLabel, QMenu, QApplication
+
+Style = """
+QMenu {
+    /* 半透明效果 */
+    background-color: rgba(255, 255, 255, 230);
+    border: none;
+    border-radius: 4px;
+}
+
+QMenu::item {
+    border-radius: 4px;
+    /* 这个距离很麻烦需要根据菜单的长度和图标等因素微调 */
+    padding: 8px 48px 8px 36px; /* 36px是文字距离左侧距离*/
+    background-color: transparent;
+}
+
+/* 鼠标悬停和按下效果 */
+QMenu::item:selected {
+    border-radius: 0px;
+    /* 半透明效果 */
+    background-color: rgba(232, 232, 232, 232);
+}
+
+/* 禁用效果 */
+QMenu::item:disabled {
+    background-color: transparent;
+}
+
+/* 图标距离左侧距离 */
+QMenu::icon {
+    left: 15px;
+}
+
+/* 分割线效果 */
+QMenu::separator {
+    height: 1px;
+    background-color: rgb(232, 236, 243);
+}
+"""
+
+
+def get_icon():
+    # 测试模拟图标
+    pixmap = QPixmap(16, 16)
+    pixmap.fill(Qt.transparent)
+    painter = QPainter()
+    painter.begin(pixmap)
+    painter.setFont(QFont('Webdings', 11))
+    painter.setPen(Qt.GlobalColor(randint(4, 18)))
+    painter.drawText(0, 0, 16, 16, Qt.AlignCenter,
+                     choice(string.ascii_letters))
+    painter.end()
+    return QIcon(pixmap)
+
+
+def about_qt():
+    # 关于Qt
+    QApplication.instance().aboutQt()
+
+
+class Window(QLabel):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.resize(400, 400)
+        self.setAlignment(Qt.AlignCenter)
+        self.setText('右键弹出菜单')
+        self.context_menu = QMenu(self)
+        self.init_menu()
+
+    def contextMenuEvent(self, event):
+        self.context_menu.exec_(event.globalPos())
+
+    def init_menu(self):
+        # 背景透明
+        self.context_menu.setAttribute(Qt.WA_TranslucentBackground)
+        # 无边框、去掉自带阴影
+        self.context_menu.setWindowFlags(
+            self.context_menu.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
+
+        # 模拟菜单项
+        for i in range(10):
+            if i % 2 == 0:
+                action = self.context_menu.addAction('菜单 %d' % i, about_qt)
+                action.setEnabled(i % 4)
+            elif i % 3 == 0:
+                self.context_menu.addAction(get_icon(), '菜单 %d' % i, about_qt)
+            if i % 4 == 0:
+                self.context_menu.addSeparator()
+            if i % 5 == 0:
+                # 二级菜单
+                # 二级菜单
+                menu = QMenu('二级菜单 %d' % i, self.context_menu)
+                # 背景透明
+                menu.setAttribute(Qt.WA_TranslucentBackground)
+                # 无边框、去掉自带阴影
+                menu.setWindowFlags(menu.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
+                for j in range(3):
+                    menu.addAction(get_icon(), '子菜单 %d' % j)
+                self.context_menu.addMenu(menu)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    app.setStyleSheet(Style)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QMenu/README.md b/QMenu/README.md
index 1c00fea6..f3657b91 100644
--- a/QMenu/README.md
+++ b/QMenu/README.md
@@ -2,8 +2,10 @@
 
 - 目录
   - [菜单设置多选并且不关闭](#1菜单设置多选并且不关闭)
+  - [仿QQ右键菜单](#2仿QQ右键菜单)
 
 ## 1、菜单设置多选并且不关闭
+
 [运行 MultiSelect.py](MultiSelect.py)
 
 有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),
@@ -35,4 +37,10 @@ def _menu_mouseReleaseEvent(self, event):
     action.activate(action.Trigger)
 ```
 
-
\ No newline at end of file
+
+
+## 2、仿QQ右键菜单
+
+[运行 QQMenu.py](QQMenu.py)
+
+
diff --git a/QMenu/ScreenShot/QQMenu.gif b/QMenu/ScreenShot/QQMenu.gif
new file mode 100644
index 00000000..e989d9dd
Binary files /dev/null and b/QMenu/ScreenShot/QQMenu.gif differ
diff --git a/QMessageBox/ChineseText.py b/QMessageBox/ChineseText.py
index e1e766df..8ba72778 100644
--- a/QMessageBox/ChineseText.py
+++ b/QMessageBox/ChineseText.py
@@ -4,19 +4,17 @@
 """
 Created on 2019年7月10日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ChineseText
 @description: 修改消息对话框文字汉化
 """
 import sys
 
-from PyQt5.QtWidgets import QApplication, QMessageBox
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtWidgets import QApplication, QMessageBox
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QMessageBox
 
 TextStyle = """
 QMessageBox QPushButton[text="OK"] {
diff --git a/QMessageBox/CountDownClose.py b/QMessageBox/CountDownClose.py
index 2252ce00..e582bb58 100644
--- a/QMessageBox/CountDownClose.py
+++ b/QMessageBox/CountDownClose.py
@@ -4,22 +4,19 @@
 """
 Created on 2018年6月22日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: MessageBox
 @description: 
 """
 from random import randrange
 
-from PyQt5.QtCore import QTimer
-from PyQt5.QtWidgets import QMessageBox
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtWidgets import QApplication, QMessageBox, QPushButton
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtWidgets import QApplication, QMessageBox, QPushButton
 
 
 class MessageBox(QMessageBox):
@@ -53,7 +50,7 @@ def doCountDown(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication, QPushButton
+
     app = QApplication(sys.argv)
     w = QPushButton('点击弹出对话框')
     w.resize(200, 200)
diff --git a/QMessageBox/CustomColorIcon.py b/QMessageBox/CustomColorIcon.py
index 53d95c13..e41748c3 100644
--- a/QMessageBox/CustomColorIcon.py
+++ b/QMessageBox/CustomColorIcon.py
@@ -1,23 +1,21 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月17日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CustomBtnIcon
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 import sys
 
-from PyQt5.QtWidgets import QApplication, QMessageBox
-
+try:
+    from PyQt5.QtWidgets import QApplication, QMessageBox
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QMessageBox
 
 app = QApplication(sys.argv)
 app.setStyleSheet('''QDialogButtonBox {
@@ -62,15 +60,15 @@
 }
 ''')
 QMessageBox.information(None, "information", "消息",
-                     QMessageBox.Apply |
-                     QMessageBox.Cancel |
-                     QMessageBox.Close |
-                     QMessageBox.Discard |
-                     QMessageBox.Help |
-                     QMessageBox.No |
-                     QMessageBox.Ok |
-                     QMessageBox.Open |
-                     QMessageBox.Reset |
-                     QMessageBox.Save |
-                     QMessageBox.Yes)
+                        QMessageBox.Apply |
+                        QMessageBox.Cancel |
+                        QMessageBox.Close |
+                        QMessageBox.Discard |
+                        QMessageBox.Help |
+                        QMessageBox.No |
+                        QMessageBox.Ok |
+                        QMessageBox.Open |
+                        QMessageBox.Reset |
+                        QMessageBox.Save |
+                        QMessageBox.Yes)
 sys.exit()
diff --git a/QMessageBox/README.md b/QMessageBox/README.md
index 84a1e914..9342245c 100644
--- a/QMessageBox/README.md
+++ b/QMessageBox/README.md
@@ -6,6 +6,7 @@
   - [消息框按钮文字汉化](#3消息框按钮文字汉化)
 
 ## 1、消息对话框倒计时关闭
+
 [运行 CountDownClose.py](CountDownClose.py)
 
 1. 通过继承`QMessageBox`实现倒计时关闭的对话框
@@ -14,15 +15,17 @@
 
 
 ## 2、自定义图标等
+
 [运行 CustomColorIcon.py](CustomColorIcon.py)
 
 
 
 ## 3、消息框按钮文字汉化
+
 [运行 ChineseText.py](ChineseText.py)
 
 1. 因为Qt5的翻译文件还是沿用旧的Qt4的结构导致部分地方无法翻译
 2. 可以通过手动重新编译翻译文件解决问题
 3. 这里可以通过QSS特性修改按钮文字,详细见代码
 
-
\ No newline at end of file
+
diff --git a/QMetaObject/CallInThread.py b/QMetaObject/CallInThread.py
new file mode 100644
index 00000000..f5fdf824
--- /dev/null
+++ b/QMetaObject/CallInThread.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/23
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CallInThread.py
+@description:
+"""
+import time
+from datetime import datetime
+from threading import Thread
+
+from PyQt5.QtCore import (Q_ARG, Q_RETURN_ARG, QMetaObject, Qt, QThread,
+                          pyqtSignal, pyqtSlot)
+from PyQt5.QtWidgets import QApplication, QHBoxLayout, QTextBrowser, QWidget
+
+
+class ThreadQt(QThread):
+
+    def __init__(self, textBrowser, *args, **kwargs):
+        super(ThreadQt, self).__init__(*args, **kwargs)
+        self._textBrowser = textBrowser
+
+    def stop(self):
+        self.requestInterruption()
+
+    def run(self):
+        while not self.isInterruptionRequested():
+            text = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+            # 通过`invokeMethod`直接调用对应的槽函数
+            # 1. 获取函数`isReadOnly1`返回值, self.parent() 是Window窗口对象
+            # NOTE:注意这里获取返回值要用 `Qt.DirectConnection` 方式
+            retValue = QMetaObject.invokeMethod(self.parent(), 'isReadOnly1',
+                                                Qt.DirectConnection,
+                                                Q_RETURN_ARG(bool))
+            # 2. 通过`invokeMethod`队列调用对应控件槽函数`append`
+            argValue = Q_ARG(str, text + ' readOnly: ' + str(retValue))
+            QMetaObject.invokeMethod(self._textBrowser, 'append',
+                                     Qt.QueuedConnection, argValue)
+            self.sleep(1)
+
+
+class ThreadPy(Thread):
+
+    def __init__(self, textBrowser, parent, *args, **kwargs):
+        super(ThreadPy, self).__init__(*args, **kwargs)
+        self._running = True
+        self._textBrowser = textBrowser
+        self._parent = parent
+
+    def stop(self):
+        self._running = False
+
+    def run(self):
+        while self._running:
+            text = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+            # 通过`invokeMethod`队列调用对应控件信号,`self._parent`是Window窗口对象
+            QMetaObject.invokeMethod(self._parent, 'appendText',
+                                     Qt.QueuedConnection,
+                                     Q_ARG(str, text + ' from Signal'))
+            # 通过`invokeMethod`队列调用对应控件槽函数`append`
+            QMetaObject.invokeMethod(self._textBrowser, 'append',
+                                     Qt.QueuedConnection,
+                                     Q_ARG(str, text + ' to Slot'))
+            time.sleep(1)
+
+
+class Window(QWidget):
+
+    # 更新信号
+    appendText = pyqtSignal(str)
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        self.textBrowser1 = QTextBrowser(self)
+        self.textBrowser2 = QTextBrowser(self)
+        layout.addWidget(self.textBrowser1)
+        layout.addWidget(self.textBrowser2)
+
+        self.appendText.connect(self.textBrowser2.append)
+
+        # Qt线程
+        self.thread1 = ThreadQt(self.textBrowser1, self)
+        self.thread1.start()
+
+        # PY线程
+        self.thread2 = ThreadPy(self.textBrowser2, self)
+        self.thread2.start()
+
+    @pyqtSlot(result=bool)
+    def isReadOnly1(self):
+        # 线程中直接调用该槽函数获取UI中的内容
+        return self.textBrowser1.isReadOnly()
+
+    def closeEvent(self, event):
+        self.thread1.stop()
+        self.thread2.stop()
+        self.thread1.wait()
+        self.thread2.join()
+        super(Window, self).closeEvent(event)
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QMetaObject/README.en.md b/QMetaObject/README.en.md
new file mode 100644
index 00000000..e69de29b
diff --git a/QMetaObject/README.md b/QMetaObject/README.md
new file mode 100644
index 00000000..46cf1ed0
--- /dev/null
+++ b/QMetaObject/README.md
@@ -0,0 +1,25 @@
+# QMetaObject
+
+- 目录
+  - [在线程中操作UI](#1在线程中操作UI)
+
+## 1、在线程中操作UI
+
+[运行 CallInThread.py](CallInThread.py)
+
+如果想在`QThread`或者`threading.Thread`中不通过信号直接操作UI,则可以使用`QMetaObject.invokeMethod`调用。
+
+该函数一般有常用的几种调用方法:
+
+1. 直接调用槽函数:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.QueuedConnection)`
+2. 直接调用信号:`QMetaObject.invokeMethod(uiobj, 'signal_method', Qt.QueuedConnection)`
+3. 调用信号或槽函数并传递参数:`QMetaObject.invokeMethod(uiobj, 'method', Qt.QueuedConnection, Q_ARG(str, 'text'))`
+4. 调用槽函数得到返回值:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.DirectConnection, Q_RETURN_ARG(str))`
+5. 调用带参数的槽函数得到返回值:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.DirectConnection, Q_RETURN_ARG(int), Q_ARG(bool, False))`, 传入bool类型的参数,获取int类型返回值
+
+这里需要注意:
+
+1. 调用函数都是异步队列方式,需要使用`Qt.QueuedConnection`
+2. 而要得到返回值则必须使用同步方式, 即`Qt.DirectConnection`
+
+
diff --git a/QMetaObject/ScreenShot/CallInThread.png b/QMetaObject/ScreenShot/CallInThread.png
new file mode 100644
index 00000000..62ed7f62
Binary files /dev/null and b/QMetaObject/ScreenShot/CallInThread.png differ
diff --git a/QPainter/Data/qt-logo.png b/QPainter/Data/qt-logo.png
new file mode 100644
index 00000000..90e6f905
Binary files /dev/null and b/QPainter/Data/qt-logo.png differ
diff --git a/QPainter/Draw.py b/QPainter/Draw.py
new file mode 100644
index 00000000..c1e8f808
--- /dev/null
+++ b/QPainter/Draw.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2022年12月12日
+@site: https://pyqt.site , https://github.com/PyQt5
+@description: QPainter画图
+"""
+import sys
+try:
+    from PyQt5.QtWidgets import QApplication, QWidget, qApp
+    from PyQt5.QtGui import QPainter, QFont, QColor, QPixmap
+    from PyQt5.QtCore import Qt, pyqtSignal
+    from PyQt5.Qt import QPoint, QPolygon
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QWidget, qApp
+    from PySide2.QtGui import QPainter, QFont, QColor, QPixmap
+    from PySide2.QtCore import Qt, pyqtSignal
+    from PySide2.Qt import QPoint, QPolygon
+
+
+
+class draw(QWidget):
+
+
+    drawsig = pyqtSignal(bool)
+
+    def __init__(self):
+        super(draw, self).__init__()
+        self.setWindowTitle("QPainter画图")
+        self._painter = QPainter()
+        self.scale = 1.0
+        self.pixmap = QPixmap()
+        self.setMouseTracking(True)
+        self.setFocusPolicy(Qt.WheelFocus)
+        self.drawEnable = False
+        self.points = []
+        self.current_points = []
+        self.drawsig.connect(self.setDrawEnable)
+
+
+    def setDrawEnable(self, enable=False):
+        self.drawEnable = enable
+        self.update()
+
+    def mouseMoveEvent(self, ev):
+        if self.drawEnable:
+            def in_end_range(curr, first):
+                return first.x() - 5 <= curr.x() <= first.x() + 5 and first.y() - 5 <= curr.y() <= first.y() + 5
+
+            if len(self.current_points) > 0 and in_end_range(ev.pos(), self.current_points[0]):
+                self.current_points.append(self.current_points[0])
+                self.points.append(self.current_points)
+                self.current_points = []
+            else:
+                self.current_points.append(ev.pos())
+        elif len(self.current_points) > 0:
+            self.current_points.append(ev.pos())
+            self.points.append(self.current_points)
+            self.current_points = []
+
+        self.update()
+
+    def mousePressEvent(self, ev):
+        if Qt.LeftButton & ev.button():
+            self.drawsig.emit(True)
+    def mouseReleaseEvent(self, ev):
+        if Qt.LeftButton & ev.button():
+            self.drawsig.emit(False)
+
+    def paintEvent(self, ev):
+        if len(self.points) <= 0 and len(self.current_points) <= 0 :  return
+        p = self._painter
+        p.begin(self)
+        p.setRenderHint(QPainter.Antialiasing)
+        p.setRenderHint(QPainter.HighQualityAntialiasing)
+        p.setRenderHint(QPainter.SmoothPixmapTransform)
+        p.scale(self.scale, self.scale)
+        p.setPen(QColor(0, 0, 0))
+        for pts in self.points:
+            p.drawPolyline(QPolygon(pts))
+        if len(self.current_points) > 0:
+            p.drawPolyline(QPolygon(self.current_points))
+        p.end()
+
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    mainWin = draw()
+    mainWin.show()
+    sys.exit(app.exec_())
+
diff --git a/QPainter/README.md b/QPainter/README.md
index 42512411..29a0050d 100644
--- a/QPainter/README.md
+++ b/QPainter/README.md
@@ -1 +1,17 @@
-# QPainter
\ No newline at end of file
+# QPainter
+
+- 目录
+  - [利用QPainter绘制各种图形](#QPainter绘制各种图形)
+  - [简易画板](#简易画板)
+  
+## 1、QPainter绘制各种图形
+
+[运行 StockDialog.py](StockDialog.py)
+
+
+
+## 2、简易画板
+
+[运行 Draw.py](Draw.py)
+
+
diff --git a/QPainter/ScreenShot/Draw.gif b/QPainter/ScreenShot/Draw.gif
new file mode 100644
index 00000000..65f85619
Binary files /dev/null and b/QPainter/ScreenShot/Draw.gif differ
diff --git a/QPainter/ScreenShot/StockDialog.gif b/QPainter/ScreenShot/StockDialog.gif
new file mode 100644
index 00000000..97d964e7
Binary files /dev/null and b/QPainter/ScreenShot/StockDialog.gif differ
diff --git a/QPainter/StockDialog.py b/QPainter/StockDialog.py
new file mode 100644
index 00000000..996dbe1f
--- /dev/null
+++ b/QPainter/StockDialog.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2022年12月12日
+@site: https://pyqt.site , https://github.com/PyQt5
+@description: QPainter绘制各种图形
+"""
+import sys
+
+try:
+    from PyQt5.QtWidgets import QApplication, QWidget, qApp
+    from PyQt5.QtGui import QPainter, QFont, QColor, QPixmap
+    from PyQt5.QtCore import Qt, pyqtSignal, QPointF
+    from PyQt5.Qt import QPoint, QPolygon, QSplitter, QFrame, QGridLayout, QLabel,\
+        QComboBox, QSpinBox, QPalette, QStackedWidget, QVBoxLayout,\
+        QPushButton, QColorDialog, QPen,QLinearGradient, QConicalGradient,\
+        QRadialGradient, QBrush, QRect, QPainterPath, QFileDialog
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QWidget, qApp
+    from PySide2.QtGui import QPainter, QFont, QColor, QPixmap
+    from PySide2.QtCore import Qt, pyqtSignal, QPointF
+    from PySide2.Qt import QPoint, QPolygon, QSplitter, QFrame, QGridLayout, QLabel,\
+        QComboBox, QSpinBox, QPalette, QStackedWidget, QVBoxLayout,\
+        QPushButton, QColorDialog, QPen, QLinearGradient, QConicalGradient,\
+        QRadialGradient, QBrush, QRect , QPainterPath, QFileDialog
+
+
+class StockDialog(QWidget):
+    def __init__(self, parent=None):
+        super(StockDialog, self).__init__(parent)
+        self.setWindowTitle("QPainter绘制各种图形")
+
+        mainSplitter = QSplitter(Qt.Horizontal)
+        mainSplitter.setOpaqueResize(True)
+        frame = QFrame(mainSplitter)
+        mainLayout = QGridLayout(frame)
+        mainLayout.setSpacing(6)
+
+        label1 = QLabel("形状:")
+        label2 = QLabel("画笔线宽:")
+        label3 = QLabel("画笔颜色:")
+        label4 = QLabel("画笔风格:")
+        label5 = QLabel("画笔顶端:")
+        label6 = QLabel("画笔连接点:")
+        label7 = QLabel("画刷风格:")
+        label8 = QLabel("画刷颜色:")
+
+        self.shapeComboBox = QComboBox()
+        self.shapeComboBox.addItem("Line", "Line")
+        self.shapeComboBox.addItem("Rectangle", "Rectangle")
+        self.shapeComboBox.addItem('Rounded Rectangle', 'Rounded Rectangle')
+        self.shapeComboBox.addItem('Ellipse', 'Ellipse')
+        self.shapeComboBox.addItem('Pie', 'Pie')
+        self.shapeComboBox.addItem('Chord', 'Chord')
+        self.shapeComboBox.addItem('Path', 'Path')
+        self.shapeComboBox.addItem('Polygon', 'Polygon')
+        self.shapeComboBox.addItem('Polyline', 'Polyline')
+        self.shapeComboBox.addItem('Arc', 'Arc')
+        self.shapeComboBox.addItem('Points', 'Points')
+        self.shapeComboBox.addItem('Text', 'Text')
+        self.shapeComboBox.addItem('Pixmap', 'Pixmap')
+
+        self.widthSpinBox = QSpinBox()
+        self.widthSpinBox.setRange(0, 20)
+
+        self.penColorFrame = QFrame()
+        self.penColorFrame.setAutoFillBackground(True)
+        self.penColorFrame.setPalette(QPalette(Qt.blue))
+        self.penColorPushButton = QPushButton("更改")
+
+        self.penStyleComboBox = QComboBox()
+        self.penStyleComboBox.addItem("Solid", Qt.SolidLine)
+        self.penStyleComboBox.addItem('Dash', Qt.DashLine)
+        self.penStyleComboBox.addItem('Dot', Qt.DotLine)
+        self.penStyleComboBox.addItem('Dash Dot', Qt.DashDotLine)
+        self.penStyleComboBox.addItem('Dash Dot Dot', Qt.DashDotDotLine)
+        self.penStyleComboBox.addItem('None', Qt.NoPen)
+
+        self.penCapComboBox = QComboBox()
+        self.penCapComboBox.addItem("Flat", Qt.FlatCap)
+        self.penCapComboBox.addItem('Square', Qt.SquareCap)
+        self.penCapComboBox.addItem('Round', Qt.RoundCap)
+
+        self.penJoinComboBox = QComboBox()
+        self.penJoinComboBox.addItem("Miter", Qt.MiterJoin)
+        self.penJoinComboBox.addItem('Bebel', Qt.BevelJoin)
+        self.penJoinComboBox.addItem('Round', Qt.RoundJoin)
+
+        self.brushStyleComboBox = QComboBox()
+        self.brushStyleComboBox.addItem("Linear Gradient", Qt.LinearGradientPattern)
+        self.brushStyleComboBox.addItem('Radial Gradient', Qt.RadialGradientPattern)
+        self.brushStyleComboBox.addItem('Conical Gradient', Qt.ConicalGradientPattern)
+        self.brushStyleComboBox.addItem('Texture', Qt.TexturePattern)
+        self.brushStyleComboBox.addItem('Solid', Qt.SolidPattern)
+        self.brushStyleComboBox.addItem('Horizontal', Qt.HorPattern)
+        self.brushStyleComboBox.addItem('Vertical', Qt.VerPattern)
+        self.brushStyleComboBox.addItem('Cross', Qt.CrossPattern)
+        self.brushStyleComboBox.addItem('Backward Diagonal', Qt.BDiagPattern)
+        self.brushStyleComboBox.addItem('Forward Diagonal', Qt.FDiagPattern)
+        self.brushStyleComboBox.addItem('Diagonal Cross', Qt.DiagCrossPattern)
+        self.brushStyleComboBox.addItem('Dense 1', Qt.Dense1Pattern)
+        self.brushStyleComboBox.addItem('Dense 2', Qt.Dense2Pattern)
+        self.brushStyleComboBox.addItem('Dense 3', Qt.Dense3Pattern)
+        self.brushStyleComboBox.addItem('Dense 4', Qt.Dense4Pattern)
+        self.brushStyleComboBox.addItem('Dense 5', Qt.Dense5Pattern)
+        self.brushStyleComboBox.addItem('Dense 6', Qt.Dense6Pattern)
+        self.brushStyleComboBox.addItem('Dense 7', Qt.Dense7Pattern)
+        self.brushStyleComboBox.addItem('None', Qt.NoBrush)
+
+        self.brushColorFrame = QFrame()
+        self.brushColorFrame.setAutoFillBackground(True)
+        self.brushColorFrame.setPalette(QPalette(Qt.green))
+        self.brushColorPushButton = QPushButton("更改")
+
+        labelCol = 0
+        contentCol = 1
+
+        # 建立布局
+        mainLayout.addWidget(label1, 1, labelCol)
+        mainLayout.addWidget(self.shapeComboBox, 1, contentCol)
+        mainLayout.addWidget(label2, 2, labelCol)
+        mainLayout.addWidget(self.widthSpinBox, 2, contentCol)
+        mainLayout.addWidget(label3, 4, labelCol)
+        mainLayout.addWidget(self.penColorFrame, 4, contentCol)
+        mainLayout.addWidget(self.penColorPushButton, 4, 3)
+        mainLayout.addWidget(label4, 6, labelCol)
+        mainLayout.addWidget(self.penStyleComboBox, 6, contentCol)
+        mainLayout.addWidget(label5, 8, labelCol)
+        mainLayout.addWidget(self.penCapComboBox, 8, contentCol)
+        mainLayout.addWidget(label6, 10, labelCol)
+        mainLayout.addWidget(self.penJoinComboBox, 10, contentCol)
+        mainLayout.addWidget(label7, 12, labelCol)
+        mainLayout.addWidget(self.brushStyleComboBox, 12, contentCol)
+        mainLayout.addWidget(label8, 14, labelCol)
+        mainLayout.addWidget(self.brushColorFrame, 14, contentCol)
+        mainLayout.addWidget(self.brushColorPushButton, 14, 3)
+        mainSplitter1 = QSplitter(Qt.Horizontal)
+        mainSplitter1.setOpaqueResize(True)
+
+        stack1 = QStackedWidget()
+        stack1.setFrameStyle(QFrame.Panel | QFrame.Raised)
+        self.area = PaintArea()
+        stack1.addWidget(self.area)
+        frame1 = QFrame(mainSplitter1)
+        mainLayout1 = QVBoxLayout(frame1)
+        mainLayout1.setSpacing(6)
+        mainLayout1.addWidget(stack1)
+
+        layout = QGridLayout(self)
+        layout.addWidget(mainSplitter1, 0, 0)
+        layout.addWidget(mainSplitter, 0, 1)
+        self.setLayout(layout)
+
+        # 信号和槽函数
+        self.shapeComboBox.activated.connect(self.slotShape)
+        self.widthSpinBox.valueChanged.connect(self.slotPenWidth)
+        self.penColorPushButton.clicked.connect(self.slotPenColor)
+        self.penStyleComboBox.activated.connect(self.slotPenStyle)
+        self.penCapComboBox.activated.connect(self.slotPenCap)
+        self.penJoinComboBox.activated.connect(self.slotPenJoin)
+        self.brushStyleComboBox.activated.connect(self.slotBrush)
+        self.brushColorPushButton.clicked.connect(self.slotBrushColor)
+
+        self.slotShape(self.shapeComboBox.currentIndex())
+        self.slotPenWidth(self.widthSpinBox.value())
+        self.slotBrush(self.brushStyleComboBox.currentIndex())
+
+    def slotShape(self, value):
+        shape = self.area.Shape[value]
+        self.area.setShape(shape)
+
+    def slotPenWidth(self, value):
+        color = self.penColorFrame.palette().color(QPalette.Window)
+        style = Qt.PenStyle(self.penStyleComboBox.itemData(self.penStyleComboBox.currentIndex(), Qt.UserRole))
+        cap = Qt.PenCapStyle(self.penCapComboBox.itemData(self.penCapComboBox.currentIndex(), Qt.UserRole))
+        join = Qt.PenJoinStyle(self.penJoinComboBox.itemData(self.penJoinComboBox.currentIndex(), Qt.UserRole))
+        self.area.setPen(QPen(color, value, style, cap, join))
+
+    def slotPenStyle(self, value):
+        self.slotPenWidth(value)
+
+    def slotPenCap(self, value):
+        self.slotPenWidth(value)
+
+    def slotPenJoin(self, value):
+        self.slotPenWidth(value)
+
+    def slotPenColor(self):
+        color = QColorDialog.getColor(Qt.blue)
+        self.penColorFrame.setPalette(QPalette(color))
+        self.area.setPen(QPen(color))
+
+    def slotBrushColor(self):
+        color = QColorDialog.getColor(Qt.blue)
+        self.brushColorFrame.setPalette(QPalette(color))
+        self.slotBrush(self.brushStyleComboBox.currentIndex())
+
+    def slotBrush(self, value):
+        color = self.brushColorFrame.palette().color(QPalette.Window)
+        style = Qt.BrushStyle(self.brushStyleComboBox.itemData(value, Qt.UserRole))
+
+        if (style == Qt.LinearGradientPattern):
+            linearGradient = QLinearGradient(0, 0, 400, 400)
+            linearGradient.setColorAt(0.0, Qt.white)
+            linearGradient.setColorAt(0.2, color)
+            linearGradient.setColorAt(1.0, Qt.black)
+            self.area.setBrush(linearGradient)
+        elif style == Qt.RadialGradientPattern:
+            radialGradient = QRadialGradient(200, 200, 80, 70, 70);
+            radialGradient.setColorAt(0.0, Qt.white)
+            radialGradient.setColorAt(0.2, Qt.green)
+            radialGradient.setColorAt(1.0, Qt.black)
+            self.area.setBrush(radialGradient)
+        elif (style == Qt.ConicalGradientPattern):
+            conicalGradient = QConicalGradient(200, 200, 30)
+            conicalGradient.setColorAt(0.0, Qt.white)
+            conicalGradient.setColorAt(0.2, color)
+            conicalGradient.setColorAt(1.0, Qt.black)
+            self.area.setBrush(conicalGradient)
+        elif (style == Qt.TexturePattern):
+            self.area.setBrush(QBrush(QPixmap("./Data/qt-logo.png")))
+        else:
+            self.area.setBrush(QBrush(color, style))
+
+
+class PaintArea(QWidget):
+    def __init__(self):
+        super(PaintArea, self).__init__()
+        self.Shape = ["Line", "Rectangle", 'Rounded Rectangle', "Ellipse", "Pie", 'Chord',
+                      "Path", "Polygon", "Polyline", "Arc", "Points", "Text", "Pixmap"]
+        self.setPalette(QPalette(Qt.white))
+        self.setAutoFillBackground(True)
+        self.setMinimumSize(500, 500)
+        self.pen = QPen()
+        self.brush = QBrush()
+
+    def setShape(self, s):
+        self.shape = s
+        self.update()
+
+    def setPen(self, p):
+        self.pen = p
+        self.update()
+
+    def setBrush(self, b):
+        self.brush = b
+        self.update()
+
+    def paintEvent(self, QPaintEvent):
+        p = QPainter(self)
+        p.setPen(self.pen)
+        p.setBrush(self.brush)
+
+        rect = QRect(50, 100, 300, 200)
+        points = [QPoint(150, 100), QPoint(300, 150), QPoint(350, 250), QPoint(100, 300)]
+        startAngle = 30 * 16
+        spanAngle = 120 * 16
+
+        if self.shape == "Line":
+            p.drawLine(rect.topLeft(), rect.bottomRight())
+        elif self.shape == "Rectangle":
+            p.drawRect(rect)
+        elif self.shape == 'Rounded Rectangle':
+            p.drawRoundedRect(rect, 25, 25, Qt.RelativeSize)
+        elif self.shape == "Ellipse":
+            p.drawEllipse(rect)
+        elif self.shape == "Polygon":
+            p.drawPolygon(QPolygon(points), Qt.WindingFill)
+        elif self.shape == "Polyline":
+            p.drawPolyline(QPolygon(points))
+        elif self.shape == "Points":
+            p.drawPoints(QPolygon(points))
+        elif self.shape == "Pie":
+            p.drawPie(rect, startAngle, spanAngle)
+        elif self.shape == "Arc":
+            p.drawArc(rect, startAngle, spanAngle)
+        elif self.shape == "Chord":
+            p.drawChord(rect, startAngle, spanAngle)
+        elif self.shape == "Path":
+            path = QPainterPath()
+            path.addRect(150, 150, 100, 100)
+            path.moveTo(100, 100)
+            path.cubicTo(300, 100, 200, 200, 300, 300)
+            path.cubicTo(100, 300, 200, 200, 100, 100)
+            p.drawPath(path)
+        elif self.shape == "Text":
+            p.drawText(rect, Qt.AlignCenter, "Hello Qt!")
+        elif self.shape == "Pixmap":
+            p.drawPixmap(150, 150, QPixmap("./Data/qt-logo.png"))
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    form = StockDialog()
+    form.show()
+    app.exec_()
+
diff --git a/QProcess/GetCmdResult.py b/QProcess/GetCmdResult.py
new file mode 100644
index 00000000..f7ea5469
--- /dev/null
+++ b/QProcess/GetCmdResult.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/01
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: GetCmdResult.py
+@description:
+"""
+
+import sys
+
+try:
+    from PyQt5.QtCore import QProcess
+    from PyQt5.QtWidgets import (QApplication, QLabel, QPushButton,
+                                 QTextBrowser, QVBoxLayout, QWidget)
+except ImportError:
+    from PySide2.QtCore import QProcess
+    from PySide2.QtWidgets import (QApplication, QLabel, QPushButton,
+                                   QTextBrowser, QVBoxLayout, QWidget)
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.setWindowTitle('执行命令得到结果')
+        layout = QVBoxLayout(self)
+        layout.addWidget(QLabel('点击执行 ping www.baidu.com', self))
+
+        self.buttonRunSync = QPushButton('同步执行', self)
+        layout.addWidget(self.buttonRunSync)
+        self.buttonRunSync.clicked.connect(self.run_ping)
+
+        self.buttonRunASync = QPushButton('异步执行', self)
+        layout.addWidget(self.buttonRunASync)
+        self.buttonRunASync.clicked.connect(self.run_ping)
+
+        self.resultView = QTextBrowser(self)
+        layout.addWidget(self.resultView)
+
+        self._pingProcess = None
+
+    def run_ping(self):
+        sender = self.sender()  # 同步或者异步按钮
+        self.buttonRunSync.setEnabled(False)
+        self.buttonRunASync.setEnabled(False)
+
+        if self._pingProcess:
+            self._pingProcess.terminate()
+
+        self._pingProcess = QProcess(self)
+        self._pingProcess.setProgram('ping')
+        if sys.platform.startswith('win'):
+            self._pingProcess.setArguments(['-n', '5', 'www.baidu.com'])
+            self._pingProcess.setArguments(['-n', '5', 'www.baidu.com'])
+        elif sys.platform.startswith('darwin') or sys.platform.startswith(
+                'linux'):
+            self._pingProcess.setArguments(['-c', '5', 'www.baidu.com'])
+        # 合并输出流和错误流,执行完毕后通过readAll可以一次性读取所有结果
+        self._pingProcess.setProcessChannelMode(QProcess.MergedChannels)
+        self._pingProcess.started.connect(self.on_started)
+
+        if sender == self.buttonRunASync:
+            # 异步执行
+            self._pingProcess.finished.connect(self.on_finished)
+            self._pingProcess.errorOccurred.connect(self.on_error)
+            self._pingProcess.start()
+        elif sender == self.buttonRunSync:
+            # 同步执行
+            self._pingProcess.start()
+            if self._pingProcess.waitForFinished():
+                self.on_finished(self._pingProcess.exitCode(),
+                                 self._pingProcess.exitStatus())
+            else:
+                self.resultView.append('ping process read timeout')
+                self.on_error(self._pingProcess.error())
+
+    def on_started(self):
+        self.resultView.append('ping process started')
+
+    def on_finished(self, exitCode, exitStatus):
+        self.resultView.append(
+            'ping process finished, exitCode: %s, exitStatus: %s' %
+            (exitCode, exitStatus))
+        # 读取所有结果
+        result = self._pingProcess.readAll().data()
+        try:
+            import chardet
+            encoding = chardet.detect(result)
+            self.resultView.append(result.decode(encoding['encoding']))
+        except Exception:
+            self.resultView.append(result.decode('utf-8', errors='ignore'))
+        self._pingProcess.kill()
+        self._pingProcess = None
+        self.buttonRunSync.setEnabled(True)
+        self.buttonRunASync.setEnabled(True)
+
+    def on_error(self, error):
+        self.resultView.append('ping process error: %s, message: %s' %
+                               (error, self._pingProcess.errorString()))
+        self._pingProcess.kill()
+        self._pingProcess = None
+        self.buttonRunSync.setEnabled(True)
+        self.buttonRunASync.setEnabled(True)
+
+
+if __name__ == '__main__':
+    import cgitb
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QProcess/InteractiveRun.py b/QProcess/InteractiveRun.py
new file mode 100644
index 00000000..2c538c1c
--- /dev/null
+++ b/QProcess/InteractiveRun.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2023/02/01
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: InteractiveRun.py
+@description:
+"""
+
+import os
+import sys
+
+try:
+    import chardet
+except ImportError:
+    print('chardet not found')
+
+try:
+    from PyQt5.QtCore import QProcess
+    from PyQt5.QtWidgets import (QApplication, QLineEdit, QPushButton,
+                                 QTextBrowser, QVBoxLayout, QWidget)
+except ImportError:
+    from PySide2.QtCore import QProcess
+    from PySide2.QtWidgets import (QApplication, QLineEdit, QPushButton,
+                                   QTextBrowser, QVBoxLayout, QWidget)
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+
+        command = 'ping www.baidu.com'
+        if sys.platform.startswith('win'):
+            command = 'ping -n 5 www.baidu.com'
+        elif sys.platform.startswith('darwin') or sys.platform.startswith(
+                'linux'):
+            command = 'ping -c 5 www.baidu.com'
+        else:
+            raise RuntimeError('Unsupported platform: %s' % sys.platform)
+
+        self.cmdEdit = QLineEdit(command, self)
+        layout.addWidget(self.cmdEdit)
+
+        self.buttonRun = QPushButton('执行命令', self)
+        layout.addWidget(self.buttonRun)
+        self.buttonRun.clicked.connect(self.run_command)
+
+        self.resultView = QTextBrowser(self)
+        layout.addWidget(self.resultView)
+
+        self._cmdProcess = None
+        self._init()
+
+    def closeEvent(self, event):
+        if self._cmdProcess:
+            self._cmdProcess.writeData('exit'.encode() + os.linesep.encode())
+            self._cmdProcess.waitForFinished()
+            if self._cmdProcess:
+                self._cmdProcess.terminate()
+        super(Window, self).closeEvent(event)
+
+    def _init(self):
+        if self._cmdProcess:
+            return
+        # 打开终端shell
+        self._cmdProcess = QProcess(self)
+        self._cmdProcess.setProgram(
+            'cmd' if sys.platform.startswith('win') else 'bash')
+        # 合并输出流和错误流,只从标准输出流读取数据
+        self._cmdProcess.setProcessChannelMode(QProcess.MergedChannels)
+        self._cmdProcess.started.connect(self.on_started)
+        self._cmdProcess.finished.connect(self.on_finished)
+        self._cmdProcess.errorOccurred.connect(self.on_error)
+        self._cmdProcess.readyReadStandardOutput.connect(
+            self.on_readyReadStandardOutput)
+        self._cmdProcess.start()
+
+    def run_command(self):
+        self._init()
+        command = self.cmdEdit.text().strip()
+        if not command:
+            return
+        command = command.encode(sys.getdefaultencoding()) + os.linesep.encode(
+            sys.getdefaultencoding())
+        self._cmdProcess.writeData(command)
+
+    def on_started(self):
+        self.resultView.append('ping process started, pid: %s' %
+                               self._cmdProcess.processId())
+
+    def on_finished(self, exitCode, exitStatus):
+        print('ping process finished, exitCode: %s, exitStatus: %s' %
+              (exitCode, exitStatus))
+        self._cmdProcess.kill()
+        self._cmdProcess = None
+
+    def on_error(self, error):
+        self.resultView.append('ping process error: %s, message: %s' %
+                               (error, self._cmdProcess.errorString()))
+        self._cmdProcess.kill()
+        self._cmdProcess = None
+
+    def on_readyReadStandardOutput(self):
+        # 读取已有结果
+        result = self._cmdProcess.readAllStandardOutput().data()
+        try:
+            encoding = chardet.detect(result)
+            self.resultView.append(result.decode(encoding['encoding']))
+        except Exception:
+            self.resultView.append(result.decode('utf-8', errors='ignore'))
+
+
+if __name__ == '__main__':
+    import cgitb
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QProcess/README.en.md b/QProcess/README.en.md
new file mode 100644
index 00000000..e69de29b
diff --git a/QProcess/README.md b/QProcess/README.md
new file mode 100644
index 00000000..74bee0b9
--- /dev/null
+++ b/QProcess/README.md
@@ -0,0 +1,36 @@
+# QProcess
+
+- 目录
+  - [执行命令得到结果](#1执行命令得到结果)
+  - [交互执行命令](#2交互执行命令)
+
+## 1、执行命令得到结果
+
+[运行 GetCmdResult.py](GetCmdResult.py)
+
+`QProcess` 常用执行命令方式有以下几种:
+
+1. `QProcess.execute('ping', ['www.baidu.com'])`:同步执行,返回值为进程退出码
+2. `QProcess.startDetached('ping', ['www.baidu.com'], '工作路径')`:返回值为是否启动成功,该命令一般用于启动某个程序后就不管了
+3. 通过构造`QProcess`对象,然后通过`QProcess.start()`启动进程,并分为同步和异步两种方式获取输出
+
+示例代码为第3种方式:
+
+1. 通过`setProcessChannelMode(QProcess.MergedChannels)`合并标准输出和错误输出
+2. `waitForFinished`为同步方式,然后调用`readAll`读取所有输出
+3. 也可以绑定`finished`信号,然后通过`readAll`读取所有输出
+
+
+
+## 2、交互执行命令
+
+[运行 InteractiveRun.py](InteractiveRun.py)
+
+`QProcess` 也可以用于交互式执行命令,具体需要如下几步:
+
+1. 通过`setProcessChannelMode(QProcess.MergedChannels)`合并标准输出和错误输出
+2. 通过`start`启动进程
+3. 通过`readyReadStandardOutput`信号读取进程输出
+4. 通过`writeData`向进程写入数据
+
+
diff --git a/QProcess/ScreenShot/GetCmdResult.gif b/QProcess/ScreenShot/GetCmdResult.gif
new file mode 100644
index 00000000..bdf05960
Binary files /dev/null and b/QProcess/ScreenShot/GetCmdResult.gif differ
diff --git a/QProcess/ScreenShot/InteractiveRun.gif b/QProcess/ScreenShot/InteractiveRun.gif
new file mode 100644
index 00000000..225144da
Binary files /dev/null and b/QProcess/ScreenShot/InteractiveRun.gif differ
diff --git a/QProgressBar/ColourfulProgress.py b/QProgressBar/ColourfulProgress.py
new file mode 100644
index 00000000..2c629d0c
--- /dev/null
+++ b/QProgressBar/ColourfulProgress.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2022/02/25
+@author: Irony
+@site: https://pyqt.site, https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ColourfulProgress.py
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QLineF, QRect, QRectF, Qt
+    from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen, QTransform
+    from PyQt5.QtWidgets import (QApplication, QGridLayout, QProgressBar,
+                                 QSlider, QStyleOptionProgressBar, QWidget)
+except ImportError:
+    from PySide2.QtCore import QRect, Qt, QRectF, QLineF
+    from PySide2.QtGui import QColor, QPainter, QPen, QTransform, QPainterPath
+    from PySide2.QtWidgets import (QApplication, QProgressBar,
+                                   QStyleOptionProgressBar, QWidget, QSlider,
+                                   QGridLayout)
+
+from Lib.QStyleAnimation import QProgressStyleAnimation
+
+
+class ColourfulProgress(QProgressBar):
+
+    def __init__(self, *args, **kwargs):
+        self._color = kwargs.pop('color', QColor(43, 194, 83))
+        self._fps = kwargs.pop('fps', 60)
+        self._lineWidth = kwargs.pop('lineWidth', 50)  # 线条宽度
+        self._radius = kwargs.pop('radius', None)  # None为自动计算圆角
+        self._animation = None
+        super(ColourfulProgress, self).__init__(*args, **kwargs)
+        self.setColor(self._color)
+        self.setFps(self._fps)
+        self.setLineWidth(self._lineWidth)
+        self.setRadius(self._radius)
+
+    def setColor(self, color):
+        """
+        :type color: QColor
+        :param color: 颜色
+        """
+        self._color = QColor(color) if isinstance(
+            color, (QColor, Qt.GlobalColor)) else QColor(43, 194, 83)
+
+    def setFps(self, fps):
+        """
+        :type fps: int
+        :param fps: 帧率
+        """
+        self._fps = max(int(fps), 1) if isinstance(fps, (int, float)) else 60
+
+    def setLineWidth(self, width):
+        """
+        :type width: int
+        :param width: 线条宽度
+        """
+        self._lineWidth = max(int(width), 0) if isinstance(width,
+                                                           (int, float)) else 50
+
+    def setRadius(self, radius):
+        """
+        :type radius: int
+        :param radius: 半径
+        """
+        self._radius = max(int(radius), 1) if isinstance(radius,
+                                                         (int, float)) else None
+
+    def paintEvent(self, _):
+        """
+        重写绘制事件,参考 qfusionstyle.cpp 中的 CE_ProgressBarContents 绘制方法
+        """
+        option = QStyleOptionProgressBar()
+        self.initStyleOption(option)
+
+        painter = QPainter(self)
+        painter.setRenderHint(QPainter.Antialiasing)
+        painter.translate(0.5, 0.5)
+
+        vertical = option.orientation == Qt.Vertical  # 是否垂直
+        inverted = option.invertedAppearance  # 是否反转
+        # 是否显示动画
+        indeterminate = (option.minimum == option.maximum) or (
+            option.minimum < option.progress < option.maximum)
+        rect = option.rect
+
+        if vertical:
+            rect = QRect(rect.left(), rect.top(), rect.height(),
+                         rect.width())  # 翻转宽度和高度
+            m = QTransform.fromTranslate(rect.height(), 0)
+            m.rotate(90.0)
+            painter.setTransform(m, True)
+
+        maxWidth = rect.width()
+        progress = max(option.progress, option.minimum)
+        totalSteps = max(1, option.maximum - option.minimum)
+        progressSteps = progress - option.minimum
+        progressBarWidth = int(progressSteps * maxWidth / totalSteps)
+        width = progressBarWidth  # 已进行的进度宽度
+        radius = max(1, (min(width,
+                             self.width() if vertical else self.height()) //
+                         4) if self._radius is None else self._radius)
+
+        reverse = (not vertical and
+                   option.direction == Qt.RightToLeft) or vertical
+        if inverted:
+            reverse = not reverse
+
+        # 绘制范围
+        path = QPainterPath()
+        if not reverse:
+            progressBar = QRectF(rect.left(), rect.top(), width, rect.height())
+        else:
+            progressBar = QRectF(rect.right() - width, rect.top(), width,
+                                 rect.height())
+
+        # 切割范围
+        path.addRoundedRect(progressBar, radius, radius)
+        painter.setClipPath(path)
+
+        # 绘制背景颜色
+        painter.setPen(Qt.NoPen)
+        painter.setBrush(self._color)
+        painter.drawRoundedRect(progressBar, radius, radius)
+
+        if not indeterminate:
+            if self._animation:
+                self._animation.stop()
+                self._animation = None
+        else:
+            # 叠加颜色覆盖后出现类似线条间隔的效果
+            color = self._color.lighter(320)
+            color.setAlpha(80)
+            painter.setPen(QPen(color, self._lineWidth))
+
+            if self._animation:
+                if self._animation.state() == QProgressStyleAnimation.Stopped:
+                    # FIXME: 最小化后动画会停止
+                    self._animation.start()
+                step = int(self._animation.animationStep() % self._lineWidth)
+            else:
+                step = 0
+                self._animation = QProgressStyleAnimation(self._fps, self)
+                self._animation.start()
+
+            # 动画斜线绘制
+            startX = int(progressBar.left() - rect.height() - self._lineWidth)
+            endX = int(rect.right() + self._lineWidth)
+
+            if (not inverted and not vertical) or (inverted and vertical):
+                lines = [
+                    QLineF(x + step, progressBar.bottom(),
+                           x + rect.height() + step, progressBar.top())
+                    for x in range(startX, endX, self._lineWidth)
+                ]
+            else:
+                lines = [
+                    QLineF(x - step, progressBar.bottom(),
+                           x + rect.height() - step, progressBar.top())
+                    for x in range(startX, endX, self._lineWidth)
+                ]
+            painter.drawLines(lines)
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+
+    w = QWidget()
+    layout = QGridLayout(w)
+
+    w1 = ColourfulProgress(color=QColor('#85c440'))
+    w1.setMinimumWidth(300)
+    w1.setMaximumWidth(300)
+    w1.setRange(0, 100)
+    layout.addWidget(w1, 0, 0, 1, 1)
+
+    w2 = ColourfulProgress(color=QColor('#f2b63c'))
+    w2.setMinimumWidth(300)
+    w2.setMaximumWidth(300)
+    w2.setInvertedAppearance(True)
+    w2.setRange(0, 100)
+    layout.addWidget(w2, 1, 0, 1, 1)
+
+    w3 = ColourfulProgress(color=QColor('#db3a27'))
+    w3.setMinimumHeight(300)
+    w3.setMaximumHeight(300)
+    w3.setOrientation(Qt.Vertical)
+    w3.setRange(0, 100)
+    layout.addWidget(w3, 0, 1, 2, 1)
+
+    w4 = ColourfulProgress(color=QColor('#5aaadb'))
+    w4.setMinimumHeight(300)
+    w4.setMaximumHeight(300)
+    w4.setInvertedAppearance(True)
+    w4.setOrientation(Qt.Vertical)
+    w4.setRange(0, 100)
+    layout.addWidget(w4, 0, 2, 2, 1)
+
+    slider = QSlider(Qt.Horizontal)
+    slider.setRange(0, 100)
+    slider.valueChanged.connect(w1.setValue)
+    slider.valueChanged.connect(w2.setValue)
+    slider.valueChanged.connect(w3.setValue)
+    slider.valueChanged.connect(w4.setValue)
+    slider.setValue(50)
+    layout.addWidget(slider, 2, 0, 1, 3)
+
+    w.show()
+
+    sys.exit(app.exec_())
diff --git a/QProgressBar/Lib/DWaterProgress.py b/QProgressBar/Lib/DWaterProgress.py
new file mode 100644
index 00000000..9242ada2
--- /dev/null
+++ b/QProgressBar/Lib/DWaterProgress.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/1/1
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: DWaterProgress
+@see https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp
+@description:
+"""
+import math
+
+try:
+    from PyQt5.QtCore import pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize
+    from PyQt5.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, \
+        QBrush, QPen
+    from PyQt5.QtSvg import QSvgRenderer
+    from PyQt5.QtWidgets import QProgressBar, QGraphicsDropShadowEffect
+except ImportError:
+    from PySide2.QtCore import Slot as pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize
+    from PySide2.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, \
+        QBrush, QPen
+    from PySide2.QtSvg import QSvgRenderer
+    from PySide2.QtWidgets import QProgressBar, QGraphicsDropShadowEffect
+
+WATER_FRONT = """
+   
+"""
+WATER_BACK = """
+   
+"""
+
+
+class Pop:
+    # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L36
+
+    def __init__(self, size, xs, ys, xo=0, yo=0):
+        self.size = size
+        self.xSpeed = xs
+        self.ySpeed = ys
+        self.xOffset = xo
+        self.yOffset = yo
+
+
+class DWaterProgress(QProgressBar):
+
+    def __init__(self, *args, **kwargs):
+        super(DWaterProgress, self).__init__(*args, **kwargs)
+        self.waterFrontImage = QImage()
+        self.waterBackImage = QImage()
+        self.waterFrontSvg = QSvgRenderer(WATER_FRONT.encode())
+        self.waterBackSvg = QSvgRenderer(WATER_BACK.encode())
+        self.pops = []
+        self.initPops()
+        self.setTextVisible(True)
+        self.interval = 33
+        self.timer = QTimer(self)
+        self.timer.setInterval(self.interval)
+        self.timer.timeout.connect(self.onTimerOut)
+        self.resizePixmap(self.size())
+        self.frontXOffset = self.width()
+        self.backXOffset = 0
+        effect = QGraphicsDropShadowEffect(self)
+        effect.setOffset(0, 6)
+        effect.setColor(QColor(1, 153, 248, 255 * 5 / 20))
+        effect.setBlurRadius(12)
+        self.setGraphicsEffect(effect)
+
+    def initPops(self):
+        self.pops = [Pop(7, -1.8, 0.6), Pop(8, 1.2, 1.0), Pop(11, 0.8, 1.6)]
+
+    @pyqtSlot()
+    def start(self):
+        self.timer.start()
+
+    @pyqtSlot()
+    def stop(self):
+        self.timer.stop()
+
+    def resizePixmap(self, sz):
+        # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L192
+        # resize water
+        waterWidth = 500 * sz.width() / 100
+        waterHeight = 110 * sz.height() / 100
+        waterSize = QSizeF(waterWidth, waterHeight).toSize()
+
+        if self.waterFrontImage.size() != waterSize:
+            image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32)
+            image.fill(Qt.transparent)
+            waterPainter = QPainter(image)
+            self.waterFrontSvg.render(waterPainter)
+            self.waterFrontImage = image
+
+        if self.waterBackImage.size() != waterSize:
+            image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32)
+            image.fill(Qt.transparent)  # partly transparent red-ish background
+            waterPainter = QPainter(image)
+            self.waterBackSvg.render(waterPainter)
+            self.waterBackImage = image
+
+    def onTimerOut(self):
+        # interval can not be zero, and limit to 1
+        self.interval = max(1, self.interval)
+        # move 60% per second
+        frontXDeta = 40.0 / (1000.0 / self.interval)
+        # move 90% per second
+        backXDeta = 60.0 / (1000.0 / self.interval)
+
+        canvasWidth = int(self.width() * self.devicePixelRatioF())
+        self.frontXOffset -= frontXDeta * canvasWidth / 100
+        self.backXOffset += backXDeta * canvasWidth / 100
+
+        if self.frontXOffset > canvasWidth:
+            self.frontXOffset = canvasWidth
+
+        if self.frontXOffset < - (self.waterFrontImage.width() - canvasWidth):
+            self.frontXOffset = canvasWidth
+
+        if self.backXOffset > self.waterBackImage.width():
+            self.backXOffset = 0
+
+        # update pop
+        # move 25% per second default
+        speed = 25 / (1000.0 / self.interval)  # 100 / self.height()
+        for pop in self.pops:
+            # yOffset 0 ~ 100
+            pop.yOffset += speed * pop.ySpeed
+            if pop.yOffset < 0:
+                pass
+            if pop.yOffset > self.value():
+                pop.yOffset = 0
+            pop.xOffset = math.sin((pop.yOffset / 100) * 2 * 3.14) * 18 * pop.xSpeed + 50
+        self.update()
+
+    def paint(self, painter):
+        painter.setRenderHint(QPainter.Antialiasing)
+
+        pixelRatio = self.devicePixelRatioF()
+        rect = QRectF(0, 0, self.width() * pixelRatio, self.height() * pixelRatio)
+        sz = QSizeF(self.width() * pixelRatio, self.height() * pixelRatio).toSize()
+
+        self.resizePixmap(sz)
+
+        yOffset = rect.toRect().topLeft().y() + (100 - self.value() - 10) * sz.height() / 100
+
+        # draw water
+        waterImage = QImage(sz, QImage.Format_ARGB32_Premultiplied)
+        waterPainter = QPainter()
+        waterPainter.begin(waterImage)
+        waterPainter.setRenderHint(QPainter.Antialiasing)
+        waterPainter.setCompositionMode(QPainter.CompositionMode_Source)
+
+        pointStart = QPointF(sz.width() / 2, 0)
+        pointEnd = QPointF(sz.width() / 2, sz.height())
+        linear = QLinearGradient(pointStart, pointEnd)
+        startColor = QColor('#1F08FF')
+        startColor.setAlphaF(1)
+        endColor = QColor('#50FFF7')
+        endColor.setAlphaF(0.28)
+        linear.setColorAt(0, startColor)
+        linear.setColorAt(1, endColor)
+        linear.setSpread(QGradient.PadSpread)
+        waterPainter.setPen(Qt.NoPen)
+        waterPainter.setBrush(linear)
+        waterPainter.drawEllipse(waterImage.rect().center(), sz.width() / 2 + 1, sz.height() / 2 + 1)
+
+        waterPainter.setCompositionMode(QPainter.CompositionMode_SourceOver)
+        waterPainter.drawImage(int(self.backXOffset), yOffset, self.waterBackImage)
+        waterPainter.drawImage(int(self.backXOffset) - self.waterBackImage.width(), yOffset,
+                               self.waterBackImage)
+        waterPainter.drawImage(int(self.frontXOffset), yOffset, self.waterFrontImage)
+        waterPainter.drawImage(int(self.frontXOffset) - self.waterFrontImage.width(), yOffset,
+                               self.waterFrontImage)
+
+        # draw pop
+        if self.value() > 30:
+            for pop in self.pops:
+                popPath = QPainterPath()
+                popPath.addEllipse(pop.xOffset * sz.width() / 100, (100 - pop.yOffset) * sz.height() / 100,
+                                   pop.size * sz.width() / 100, pop.size * sz.height() / 100)
+                waterPainter.fillPath(popPath, QColor(255, 255, 255, 255 * 0.3))
+
+        if self.isTextVisible():
+            font = waterPainter.font()
+            rectValue = QRect()
+            progressText = self.text().strip('%')
+
+            if progressText == '100':
+                font.setPixelSize(sz.height() * 35 / 100)
+                waterPainter.setFont(font)
+
+                rectValue.setWidth(sz.width() * 60 / 100)
+                rectValue.setHeight(sz.height() * 35 / 100)
+                rectValue.moveCenter(rect.center().toPoint())
+                waterPainter.setPen(Qt.white)
+                waterPainter.drawText(rectValue, Qt.AlignCenter, progressText)
+            else:
+                font.setPixelSize(sz.height() * 40 / 100)
+                waterPainter.setFont(font)
+
+                rectValue.setWidth(sz.width() * 45 / 100)
+                rectValue.setHeight(sz.height() * 40 / 100)
+                rectValue.moveCenter(rect.center().toPoint())
+                rectValue.moveLeft(rect.left() + rect.width() * 0.45 * 0.5)
+
+                waterPainter.setPen(Qt.white)
+                waterPainter.drawText(rectValue, Qt.AlignCenter, progressText)
+                font.setPixelSize(font.pixelSize() / 2)
+                waterPainter.setFont(font)
+                rectPerent = QRect(QPoint(rectValue.right(), rectValue.bottom() - rect.height() * 20 / 100),
+                                   QPoint(rectValue.right() + rect.width() * 20 / 100, rectValue.bottom()))
+
+                waterPainter.drawText(rectPerent, Qt.AlignCenter, '%')
+
+        waterPainter.end()
+
+        maskPixmap = QPixmap(sz)
+        maskPixmap.fill(Qt.transparent)
+        path = QPainterPath()
+        path.addEllipse(QRectF(0, 0, sz.width(), sz.height()))
+        maskPainter = QPainter()
+        maskPainter.begin(maskPixmap)
+        maskPainter.setRenderHint(QPainter.Antialiasing)
+        maskPainter.setPen(QPen(Qt.white, 1))
+        maskPainter.fillPath(path, QBrush(Qt.white))
+        maskPainter.end()
+
+        mode = QPainter.CompositionMode_SourceIn
+        contentImage = QImage(sz, QImage.Format_ARGB32_Premultiplied)
+        contentPainter = QPainter()
+        contentPainter.begin(contentImage)
+        contentPainter.setCompositionMode(QPainter.CompositionMode_Source)
+        contentPainter.fillRect(contentImage.rect(), Qt.transparent)
+        contentPainter.setCompositionMode(QPainter.CompositionMode_SourceOver)
+        contentPainter.drawImage(0, 0, maskPixmap.toImage())
+        contentPainter.setCompositionMode(mode)
+        contentPainter.drawImage(0, 0, waterImage)
+        contentPainter.setCompositionMode(QPainter.CompositionMode_DestinationOver)
+        contentPainter.end()
+
+        contentImage.setDevicePixelRatio(pixelRatio)
+        painter.drawImage(self.rect(), contentImage)
+
+    def paintEvent(self, event):
+        painter = QPainter(self)
+        self.paint(painter)
+
+    def sizeHint(self):
+        return QSize(100, 100)
diff --git a/QProgressBar/Lib/QStyleAnimation.py b/QProgressBar/Lib/QStyleAnimation.py
new file mode 100644
index 00000000..c2c91657
--- /dev/null
+++ b/QProgressBar/Lib/QStyleAnimation.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2022/02/26
+@author: Irony
+@site: https://pyqt.site, https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QStyleAnimation.py
+@description:
+"""
+from enum import IntEnum
+
+try:
+    from PyQt5.QtCore import (QAbstractAnimation, QCoreApplication, QEvent,
+                              QTime)
+except ImportError:
+    from PySide2.QtCore import (QAbstractAnimation, QCoreApplication, QEvent,
+                                QTime)
+
+ScrollBarFadeOutDuration = 200.0
+ScrollBarFadeOutDelay = 450.0
+
+StyleAnimationUpdate = 213
+
+
+class QStyleAnimation(QAbstractAnimation):
+    FrameRate = IntEnum(
+        'FrameRate',
+        ['DefaultFps', 'SixtyFps', 'ThirtyFps', 'TwentyFps', 'FifteenFps'])
+
+    def __init__(self, *args, **kwargs):
+        super(QStyleAnimation, self).__init__(*args, **kwargs)
+        self._delay = 0
+        self._duration = -1
+        self._startTime = QTime.currentTime()
+        self._fps = self.FrameRate.ThirtyFps
+        self._skip = 0
+
+    def target(self):
+        return self.parent()
+
+    def duration(self):
+        return self._duration
+
+    def setDuration(self, duration):
+        self._duration = duration
+
+    def delay(self):
+        return self._delay
+
+    def setDelay(self, delay):
+        self._delay = delay
+
+    def startTime(self):
+        return self._startTime
+
+    def setStartTime(self, time):
+        self._startTime = time
+
+    def frameRate(self):
+        return self._fps
+
+    def setFrameRate(self, fps):
+        self._fps = fps
+
+    def updateTarget(self):
+        event = QEvent(QEvent.Type(StyleAnimationUpdate))
+        event.setAccepted(False)
+        QCoreApplication.sendEvent(self.target(), event)
+        if not event.isAccepted():
+            self.stop()
+
+    def start(self):
+        self._skip = 0
+        super(QStyleAnimation, self).start(QAbstractAnimation.KeepWhenStopped)
+
+    def isUpdateNeeded(self):
+        return self.currentTime() > self._delay
+
+    def updateCurrentTime(self, _):
+        self._skip += 1
+        if self._skip >= self._fps:
+            self._skip = 0
+            if self.parent() and self.isUpdateNeeded():
+                self.updateTarget()
+
+
+class QProgressStyleAnimation(QStyleAnimation):
+
+    def __init__(self, speed, *args, **kwargs):
+        super(QProgressStyleAnimation, self).__init__(*args, **kwargs)
+        self._speed = speed
+        self._step = -1
+
+    def animationStep(self):
+        return self.currentTime() / (1000.0 / self._speed)
+
+    def progressStep(self, width):
+        step = self.animationStep()
+        progress = (step * width / self._speed) % width
+        if ((step * width / self._speed) % (2 * width)) >= width:
+            progress = width - progress
+        return progress
+
+    def speed(self):
+        return self._speed
+
+    def setSpeed(self, speed):
+        self._speed = speed
+
+    def isUpdateNeeded(self):
+        if super(QProgressStyleAnimation, self).isUpdateNeeded():
+            current = self.animationStep()
+            if self._step == -1 or self._step != current:
+                self._step = current
+                return True
+        return False
diff --git a/QProgressBar/Lib/WaterRippleProgressBar.py b/QProgressBar/Lib/WaterRippleProgressBar.py
index 3673a712..605e56dd 100644
--- a/QProgressBar/Lib/WaterRippleProgressBar.py
+++ b/QProgressBar/Lib/WaterRippleProgressBar.py
@@ -1,28 +1,28 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Created on 2018年4月1日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: WaterRippleProgressBar
-# description:
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+"""
+Created on 2018年4月1日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: WaterRippleProgressBar
+@description:
+"""
 
 import math
 
-from PyQt5.QtCore import QTimer, Qt, QRectF, QSize
-from PyQt5.QtGui import QPainter, QPainterPath, QColor, QFont
-from PyQt5.QtWidgets import QProgressBar
+try:
+    from PyQt5.QtCore import QTimer, Qt, QRectF, QSize
+    from PyQt5.QtGui import QPainter, QPainterPath, QColor, QFont
+    from PyQt5.QtWidgets import QProgressBar
+except ImportError:
+    from PySide2.QtCore import QTimer, Qt, QRectF, QSize
+    from PySide2.QtGui import QPainter, QPainterPath, QColor, QFont
+    from PySide2.QtWidgets import QProgressBar
 
 
 class WaterRippleProgressBar(QProgressBar):
-
     # 浪高百分比
     waterHeight = 1
     # 密度
@@ -84,7 +84,7 @@ def paintEvent(self, event):
         # 正弦曲线公式 y = A * sin(ωx + φ) + k
         # 当前值所占百分比
         percent = 1 - (self.value() - self.minimum()) / \
-            (self.maximum() - self.minimum())
+                  (self.maximum() - self.minimum())
         # w表示周期,6为人为定义
         w = 6 * self.waterDensity * math.pi / self.width()
         # A振幅 高度百分比,1/26为人为定义
diff --git a/QProgressBar/MetroCircleProgress.py b/QProgressBar/MetroCircleProgress.py
index 3b1fe1f6..58f4f464 100644
--- a/QProgressBar/MetroCircleProgress.py
+++ b/QProgressBar/MetroCircleProgress.py
@@ -4,27 +4,25 @@
 """
 Created on 2018年9月日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: MetroCircleProgress
 @description: 
 """
-from PyQt5.QtCore import QSequentialAnimationGroup, pyqtProperty,\
-    QPauseAnimation, QPropertyAnimation, QParallelAnimationGroup,\
-    QObject, QSize, Qt, pyqtSignal, QRectF
-from PyQt5.QtGui import QPainter, QColor
-from PyQt5.QtWidgets import QWidget, QVBoxLayout
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \
+        QParallelAnimationGroup, QObject, QSize, Qt, QRectF, pyqtSignal, pyqtProperty
+    from PyQt5.QtGui import QPainter, QColor
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
+except ImportError:
+    from PySide2.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \
+        QParallelAnimationGroup, QObject, QSize, Qt, QRectF, Signal as pyqtSignal, Property as pyqtProperty
+    from PySide2.QtGui import QPainter, QColor
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout
 
 
 class CircleItem(QObject):
-
     X = 0  # x坐标
     Opacity = 1  # 透明度0~1
     valueChanged = pyqtSignal()
@@ -52,7 +50,6 @@ def qBound(miv, cv, mxv):
 
 
 class MetroCircleProgress(QWidget):
-
     Radius = 5  # 半径
     Color = QColor(24, 189, 155)  # 圆圈颜色
     BackgroundColor = QColor(Qt.transparent)  # 背景颜色
@@ -189,7 +186,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QProgressBar/PercentProgressBar.py b/QProgressBar/PercentProgressBar.py
index 80f5d6c7..8e469103 100644
--- a/QProgressBar/PercentProgressBar.py
+++ b/QProgressBar/PercentProgressBar.py
@@ -4,25 +4,23 @@
 """
 Created on 2018年9月4日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PercentProgressBar
 @description: 
 """
-from PyQt5.QtCore import pyqtProperty, QSize, Qt, QRectF, QTimer
-from PyQt5.QtGui import QColor, QPainter, QFont
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QSlider
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import pyqtProperty, QSize, Qt, QRectF, QTimer
+    from PyQt5.QtGui import QColor, QPainter, QFont
+    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QSlider
+except ImportError:
+    from PySide2.QtCore import Property as pyqtProperty, QSize, Qt, QRectF, QTimer
+    from PySide2.QtGui import QColor, QPainter, QFont
+    from PySide2.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QSlider
 
 
 class PercentProgressBar(QWidget):
-
     MinValue = 0
     MaxValue = 100
     Value = 0
@@ -300,8 +298,10 @@ def __init__(self, *args, **kwargs):
         self.staticPercentProgressBar.showFreeArea = True
         self.staticPercentProgressBar.ShowSmallCircle = True
         vlayout.addWidget(self.staticPercentProgressBar)
-        vlayout.addWidget(QSlider(self, minimum=0, maximum=100, orientation=Qt.Horizontal,
-                                  valueChanged=self.staticPercentProgressBar.setValue))
+
+        self.slider = QSlider(self, minimum=0, maximum=100, orientation=Qt.Horizontal)
+        self.slider.valueChanged.connect(self.staticPercentProgressBar.setValue)
+        vlayout.addWidget(self.slider)
 
         self._timer.start(100)
 
@@ -316,8 +316,9 @@ def updateValue(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QProgressBar/README.md b/QProgressBar/README.md
index eba728f3..0db222fe 100644
--- a/QProgressBar/README.md
+++ b/QProgressBar/README.md
@@ -6,8 +6,11 @@
   - [百分比进度条](#3百分比进度条)
   - [Metro进度条](#4Metro进度条)
   - [水波纹进度条](#5水波纹进度条)
+  - [圆形水位进度条](#6圆形水位进度条)
+  - [多彩动画进度条](#7多彩动画进度条)
 
 ## 1、常规样式美化
+
 [运行 SimpleStyle.py](SimpleStyle.py)
 
 主要改变背景颜色、高度、边框、块颜色、边框、圆角
@@ -15,25 +18,45 @@
 
 
 ## 2、圆圈进度条
+
 [运行 RoundProgressBar.py](RoundProgressBar.py)
 
 
 
 ## 3、百分比进度条
+
 [运行 PercentProgressBar.py](PercentProgressBar.py)
 
 
 
 ## 4、Metro进度条
+
 [运行 MetroCircleProgress.py](MetroCircleProgress.py)
 
 
 
 ## 5、水波纹进度条
+
 [运行 WaterProgressBar.py](WaterProgressBar.py)
 
 1. 利用正弦函数根据0-width的范围计算y坐标
 2. 利用 `QPainterPath` 矩形或者圆形作为背景
 3. 用 `QPainterPath` 把y坐标用 `lineTo` 连接起来形成一个U字形+上方波浪的闭合区间
 
-
\ No newline at end of file
+
+
+## 6、圆形水位进度条
+
+[运行 WaterProgress.py](WaterProgress.py)
+
+参考 
+
+
+
+## 7、多彩动画进度条
+
+[运行 ColourfulProgress.py](ColourfulProgress.py)
+
+动画实现参考 qfusionstyle.cpp 中的 CE_ProgressBarContents 绘制方法
+
+
diff --git a/QProgressBar/RoundProgressBar.py b/QProgressBar/RoundProgressBar.py
index d406a667..a52cef19 100644
--- a/QProgressBar/RoundProgressBar.py
+++ b/QProgressBar/RoundProgressBar.py
@@ -4,25 +4,23 @@
 """
 Created on 2018年9月4日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 界面美化.圆形进度条.CircleProgressBar
 @description: 
 """
-from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt
-from PyQt5.QtGui import QColor, QPainter
-from PyQt5.QtWidgets import QWidget, QHBoxLayout
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt
+    from PyQt5.QtGui import QColor, QPainter
+    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
+except ImportError:
+    from PySide2.QtCore import QSize, Property as pyqtProperty, QTimer, Qt
+    from PySide2.QtGui import QColor, QPainter
+    from PySide2.QtWidgets import QApplication, QWidget, QHBoxLayout
 
 
 class CircleProgressBar(QWidget):
-
     Color = QColor(24, 189, 155)  # 圆圈颜色
     Clockwise = True  # 顺时针还是逆时针
     Delta = 36
@@ -105,7 +103,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QProgressBar/ScreenShot/ColourfulProgress.gif b/QProgressBar/ScreenShot/ColourfulProgress.gif
new file mode 100644
index 00000000..3489cd96
Binary files /dev/null and b/QProgressBar/ScreenShot/ColourfulProgress.gif differ
diff --git a/QProgressBar/ScreenShot/WaterProgress.gif b/QProgressBar/ScreenShot/WaterProgress.gif
new file mode 100644
index 00000000..f434c172
Binary files /dev/null and b/QProgressBar/ScreenShot/WaterProgress.gif differ
diff --git a/QProgressBar/SimpleStyle.py b/QProgressBar/SimpleStyle.py
index 2ad17c8e..b8aa5b73 100644
--- a/QProgressBar/SimpleStyle.py
+++ b/QProgressBar/SimpleStyle.py
@@ -1,26 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月30日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: SimpleStyle
 @description: 
-'''
-from random import randint
-import sys
-
-from PyQt5.QtCore import QTimer
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar
+"""
 
+import sys
+from random import randint
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar
 
-StyleSheet = '''
+StyleSheet = """
 /*设置红色进度条*/
 #RedProgressBar {
     text-align: center; /*进度值居中*/
@@ -50,7 +50,7 @@
     width: 10px; /*区块宽度*/
     margin: 0.5px;
 }
-'''
+"""
 
 
 class ProgressBar(QProgressBar):
diff --git a/QProgressBar/WaterProgress.py b/QProgressBar/WaterProgress.py
new file mode 100644
index 00000000..97bbc201
--- /dev/null
+++ b/QProgressBar/WaterProgress.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/1/1
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: WaterProgress
+@description: 
+"""
+import sys
+
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout
+
+from Lib.DWaterProgress import DWaterProgress
+
+
+class WaterProgressWindow(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(WaterProgressWindow, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+
+        self.progress = DWaterProgress(self)
+        self.progress.setFixedSize(100, 100)
+        self.progress.setValue(0)
+        self.progress.start()
+
+        layout.addWidget(self.progress)
+
+        self.timer = QTimer(self, timeout=self.updateProgress)
+        self.timer.start(50)
+
+    def updateProgress(self):
+        value = self.progress.value()
+        if value == 100:
+            self.progress.setValue(0)
+        else:
+            self.progress.setValue(value + 1)
+
+
+if __name__ == '__main__':
+    import cgitb
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = WaterProgressWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QProgressBar/WaterProgressBar.py b/QProgressBar/WaterProgressBar.py
index 5704fe06..30aa6d43 100644
--- a/QProgressBar/WaterProgressBar.py
+++ b/QProgressBar/WaterProgressBar.py
@@ -1,27 +1,27 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Created on 2018年4月1日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: WaterProgressBar
-# description:
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
+"""
+Created on 2018年4月1日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: WaterProgressBar
+@description:
+"""
 
 from random import randint
 
-from PyQt5.Qt import QSpinBox
-from PyQt5.QtCore import QTimer
-from PyQt5.QtGui import QPixmap, QIcon
-from PyQt5.QtWidgets import QWidget, QFormLayout, QRadioButton, QPushButton,\
-    QColorDialog
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtGui import QPixmap, QIcon
+    from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QRadioButton, QPushButton, QColorDialog, \
+        QSpinBox
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtGui import QPixmap, QIcon
+    from PySide2.QtWidgets import QApplication, QWidget, QFormLayout, QRadioButton, QPushButton, QColorDialog, \
+        QSpinBox
 
 from Lib.WaterRippleProgressBar import WaterRippleProgressBar  # @UnresolvedImport
 
@@ -53,11 +53,13 @@ def __init__(self, *args, **kwargs):
         layout.addWidget(
             QPushButton('设置随机0-100固定值', self, clicked=self.setRandomValue))
 
-        layout.addRow('振幅(浪高)',
-                      QSpinBox(self, value=1, valueChanged=self.bar.setWaterHeight))
+        spb1 = QSpinBox(self, value=1)
+        spb1.valueChanged.connect(self.bar.setWaterHeight)
+        layout.addRow('振幅(浪高)', spb1)
 
-        layout.addRow('周期(密度)',
-                      QSpinBox(self, value=1, valueChanged=self.bar.setWaterDensity))
+        spb2 = QSpinBox(self, value=1)
+        spb2.valueChanged.connect(self.bar.setWaterDensity)
+        layout.addRow('周期(密度)', spb2)
 
         layout.addWidget(self.bar)
 
@@ -116,9 +118,10 @@ def updateValue(self):
             value = 0
         self.bar.setValue(value)
 
+
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QPropertyAnimation/Data/pointtool.pyx b/QPropertyAnimation/Data/pointtool.pyx
index bd47b667..3300cc66 100644
--- a/QPropertyAnimation/Data/pointtool.pyx
+++ b/QPropertyAnimation/Data/pointtool.pyx
@@ -1,10 +1,8 @@
 from libc.math cimport pow
 
-
 def getDistance(p1, p2):
     return pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)
 
-
 def findClose(points):
     cdef int plen = len(points)
     cdef int i = 0
diff --git a/QPropertyAnimation/Data/setup.py b/QPropertyAnimation/Data/setup.py
index 09839f6e..3112b44c 100644
--- a/QPropertyAnimation/Data/setup.py
+++ b/QPropertyAnimation/Data/setup.py
@@ -10,8 +10,9 @@
 # )
 
 from distutils.core import setup
+
 from Cython.Build import cythonize
 
 setup(
     ext_modules=cythonize("pointtool.pyx"),
-)
\ No newline at end of file
+)
diff --git a/QPropertyAnimation/FadeInOut.py b/QPropertyAnimation/FadeInOut.py
index edf30eae..024c65c2 100644
--- a/QPropertyAnimation/FadeInOut.py
+++ b/QPropertyAnimation/FadeInOut.py
@@ -1,20 +1,22 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from PyQt5.QtCore import QPropertyAnimation
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
 
 
-# Created on 2018年6月14日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: FadeInOut
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+"""
+Created on 2018年6月14日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: FadeInOut
+@description:
+"""
+
+try:
+    from PyQt5.QtCore import QPropertyAnimation
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
 
 
 class Window(QWidget):
@@ -55,7 +57,7 @@ def doClose(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QPropertyAnimation/FlipWidgetAnimation.py b/QPropertyAnimation/FlipWidgetAnimation.py
index c41947b9..1adc2648 100644
--- a/QPropertyAnimation/FlipWidgetAnimation.py
+++ b/QPropertyAnimation/FlipWidgetAnimation.py
@@ -4,21 +4,22 @@
 """
 Created on 2019年5月15日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 翻转动画
 @description: 
 """
-from PyQt5.QtCore import Qt, pyqtSignal, QTimer
-from PyQt5.QtGui import QPixmap
-from PyQt5.QtWidgets import QStackedWidget, QLabel
-
-from Lib.FlipWidget import FlipWidget
 
+try:
+    from PyQt5.QtCore import Qt, pyqtSignal, QTimer
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QApplication, QStackedWidget, QLabel
+except ImportError:
+    from PySide2.QtCore import Qt, Signal as pyqtSignal, QTimer
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QApplication, QStackedWidget, QLabel
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+from Lib.FlipWidget import FlipWidget
 
 
 class LoginWidget(QLabel):
@@ -97,22 +98,22 @@ def showWidget(self):
 
     def jumpLoginWidget(self):
         # 翻转到登录界面
-        self.setWindowOpacity(0)                 # 类似隐藏,但是保留了任务栏
+        self.setWindowOpacity(0)  # 类似隐藏,但是保留了任务栏
         self.setCurrentWidget(self.loginWidget)  # 很重要,一定要先切换过去,不然会导致第一次截图有误
-        image1 = self.loginWidget.grab()       # 截图1
-        image2 = self.settingWidget.grab()     # 截图2
-        padding = 100                          # 扩大边距 @UnusedVariable
+        image1 = self.loginWidget.grab()  # 截图1
+        image2 = self.settingWidget.grab()  # 截图2
+        padding = 100  # 扩大边距 @UnusedVariable
         self.flipWidget.setGeometry(self.geometry())
         # .adjusted(-padding, -padding, padding, padding))
         self.flipWidget.updateImages(FlipWidget.Right, image2, image1)
 
     def jumpSettingWidget(self):
         # 翻转到设置界面
-        self.setWindowOpacity(0)                  # 类似隐藏,但是保留了任务栏
+        self.setWindowOpacity(0)  # 类似隐藏,但是保留了任务栏
         self.setCurrentWidget(self.settingWidget)  # 很重要,一定要先切换过去,不然会导致第一次截图有误
-        image1 = self.loginWidget.grab()       # 截图1
-        image2 = self.settingWidget.grab()     # 截图2
-        padding = 100                          # 扩大边距 @UnusedVariable
+        image1 = self.loginWidget.grab()  # 截图1
+        image2 = self.settingWidget.grab()  # 截图2
+        padding = 100  # 扩大边距 @UnusedVariable
         self.flipWidget.setGeometry(self.geometry())
         # .adjusted(-padding, -padding, padding, padding))
         self.flipWidget.updateImages(FlipWidget.Left, image1, image2)
@@ -120,7 +121,7 @@ def jumpSettingWidget(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QPropertyAnimation/Lib/FlipWidget.py b/QPropertyAnimation/Lib/FlipWidget.py
index 25ed75c4..6878e632 100644
--- a/QPropertyAnimation/Lib/FlipWidget.py
+++ b/QPropertyAnimation/Lib/FlipWidget.py
@@ -4,26 +4,27 @@
 """
 Created on 2019年5月15日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: FlipWidget
 @description: 动画翻转窗口
 """
-from PyQt5.QtCore import pyqtSignal, Qt, QPropertyAnimation, QEasingCurve,\
-    pyqtProperty, QPointF
-from PyQt5.QtGui import QPainter, QTransform
-from PyQt5.QtWidgets import QWidget
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
+try:
+    from PyQt5.QtCore import pyqtSignal, pyqtProperty, Qt, QPropertyAnimation, QEasingCurve, QPointF
+    from PyQt5.QtGui import QPainter, QTransform
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import Signal as pyqtSignal, Property as pyqtProperty, Qt, QPropertyAnimation, \
+        QEasingCurve, QPointF
+    from PySide2.QtGui import QPainter, QTransform
+    from PySide2.QtWidgets import QWidget
 
 
 class FlipWidget(QWidget):
-
-    Left = 0                        # 从右往左
-    Right = 1                       # 从左往右
-    Scale = 3                       # 图片缩放比例
+    Left = 0  # 从右往左
+    Right = 1  # 从左往右
+    Scale = 3  # 图片缩放比例
     finished = pyqtSignal()
 
     def __init__(self, *args, **kwargs):
diff --git a/QPropertyAnimation/Lib/SlidingStackedWidget.py b/QPropertyAnimation/Lib/SlidingStackedWidget.py
index 0d35b792..b10bb341 100644
--- a/QPropertyAnimation/Lib/SlidingStackedWidget.py
+++ b/QPropertyAnimation/Lib/SlidingStackedWidget.py
@@ -4,25 +4,23 @@
 """
 Created on 2018年11月24日
 author: Irony
-site: https://pyqt5.com , https://github.com/892768447
+site: https://pyqt.site , https://github.com/PyQt5
 email: 892768447@qq.com
 file:
 description: 参考 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt
 """
-from PyQt5.QtCore import Qt, pyqtProperty, QEasingCurve, QPoint, \
-    QPropertyAnimation, QParallelAnimationGroup, QTimer
-from PyQt5.QtWidgets import QStackedWidget
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt, pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \
+        QParallelAnimationGroup, QTimer
+    from PyQt5.QtWidgets import QStackedWidget
+except ImportError:
+    from PySide2.QtCore import Qt, Property as pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \
+        QParallelAnimationGroup, QTimer
+    from PySide2.QtWidgets import QStackedWidget
 
 
 class SlidingStackedWidget(QStackedWidget):
-
     LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC = range(5)
 
     def __init__(self, *args, **kwargs):
@@ -210,7 +208,7 @@ def setCurrentWidget(self, widget):
     def animationDoneSlot(self):
         """动画结束处理函数"""
         # 由于重写了setCurrentIndex方法所以这里要用父类本身的方法
-#         self.setCurrentIndex(self._next)
+        #         self.setCurrentIndex(self._next)
         QStackedWidget.setCurrentIndex(self, self._next)
         w = self.widget(self._now)
         w.hide()
diff --git a/QPropertyAnimation/Lib/UiImageSlider.py b/QPropertyAnimation/Lib/UiImageSlider.py
index a47fb819..78be1306 100644
--- a/QPropertyAnimation/Lib/UiImageSlider.py
+++ b/QPropertyAnimation/Lib/UiImageSlider.py
@@ -6,7 +6,11 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore, QtGui, QtWidgets
+try:
+    from PyQt5 import QtCore, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtWidgets
+
 
 class Ui_Form(object):
     def setupUi(self, Form):
@@ -89,14 +93,15 @@ def retranslateUi(self, Form):
         self.pushButtonStart.setText(_translate("Form", "轮播开始"))
         self.pushButtonStop.setText(_translate("Form", "轮播停止"))
 
+
 from Lib.SlidingStackedWidget import SlidingStackedWidget
 
 if __name__ == "__main__":
     import sys
+
     app = QtWidgets.QApplication(sys.argv)
     Form = QtWidgets.QWidget()
     ui = Ui_Form()
     ui.setupUi(Form)
     Form.show()
     sys.exit(app.exec_())
-
diff --git a/QPropertyAnimation/MenuAnimation.py b/QPropertyAnimation/MenuAnimation.py
index e8a84c56..4e8c7cdc 100644
--- a/QPropertyAnimation/MenuAnimation.py
+++ b/QPropertyAnimation/MenuAnimation.py
@@ -4,20 +4,18 @@
 """
 Created on 2018年8月22日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: MenuAnimation
 @description: 
 """
-from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QRect
-from PyQt5.QtWidgets import QWidget, QMenu, QApplication
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QRect
+    from PyQt5.QtWidgets import QWidget, QMenu, QApplication
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation, QEasingCurve, QRect
+    from PySide2.QtWidgets import QWidget, QMenu, QApplication
 
 
 class Window(QWidget):
@@ -61,7 +59,8 @@ def initMenu(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
+
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QPropertyAnimation/PageSwitching.py b/QPropertyAnimation/PageSwitching.py
index 739f4040..9477eb4e 100644
--- a/QPropertyAnimation/PageSwitching.py
+++ b/QPropertyAnimation/PageSwitching.py
@@ -4,27 +4,25 @@
 """
 Created on 2018年11月24日
 author: Irony
-site: https://pyqt5.com , https://github.com/892768447
+site: https://pyqt.site , https://github.com/PyQt5
 email: 892768447@qq.com
 file: PageSwitching
 description:
 """
 import os
 
-from PyQt5.QtCore import QEasingCurve, Qt
-from PyQt5.QtGui import QPixmap
-from PyQt5.QtWidgets import QWidget, QLabel
+try:
+    from PyQt5.QtCore import QEasingCurve, Qt
+    from PyQt5.QtGui import QPixmap
+    from PyQt5.QtWidgets import QWidget, QLabel, QApplication
+except ImportError:
+    from PySide2.QtCore import QEasingCurve, Qt
+    from PySide2.QtGui import QPixmap
+    from PySide2.QtWidgets import QWidget, QLabel, QApplication
 
 from Lib.UiImageSlider import Ui_Form  # @UnresolvedImport
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
-
 class ImageSliderWidget(QWidget, Ui_Form):
 
     def __init__(self, *args, **kwargs):
@@ -76,7 +74,7 @@ def setOrientation(self, checked):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = ImageSliderWidget()
     w.show()
diff --git a/QPropertyAnimation/README.md b/QPropertyAnimation/README.md
index 8a1e0ec5..3041e069 100644
--- a/QPropertyAnimation/README.md
+++ b/QPropertyAnimation/README.md
@@ -9,6 +9,7 @@
   - [窗口翻转动画(仿QQ)](#6窗口翻转动画仿QQ)
 
 ## 1、窗口淡入淡出
+
 [运行 FadeInOut.py](FadeInOut.py)
 
 1. 使用`QPropertyAnimation`对窗口的`windowOpacity`透明度属性进行修改
@@ -20,8 +21,9 @@
     1. 绑定动画完成后`finished`信号连接到`close`关闭窗口函数
 
 
-   
+
 ## 2、右键菜单动画
+
 [运行 MenuAnimation.py](MenuAnimation.py)
 
 1. 使用`QPropertyAnimation`对菜单控件的`geometry`属性进行修改
@@ -30,6 +32,7 @@
 
 
 ## 3、点阵特效
+
 [运行 RlatticeEffect.py](RlatticeEffect.py)
 
 1. emmm,我也不知道这个动画叫啥名字,反正就是仿照网页做的
@@ -45,6 +48,7 @@
 1. PS: pyd是python3.4生成的,删掉pyd也能运行
 
 这部分是js的核心
+
 ```js
 // for each point find the 5 closest points
 for(var i = 0; i < points.length; i++) {
@@ -78,6 +82,7 @@ for(var i = 0; i < points.length; i++) {
 ```
 
 这部分是py的核心
+
 ```python
 def findClose(points):
     plen = len(points)
@@ -105,10 +110,11 @@ def findClose(points):
 
 
 ## 4、页面切换/图片轮播动画
-[运行 PageSwitching.py](PageSwitching.py)
+
+[运行 PageSwitching.py](PageSwitching.py) | [查看 UiImageSlider.ui](Data/UiImageSlider.ui)
 
 1. 使用`QPropertyAnimation`对`QStackedWidget`中的子控件进行pos位移操作实现动画切换特效
-1. 主要代码参考http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt
+1. 主要代码参考
 1. 增加了自动切换函数
 
 函数调用:
@@ -121,6 +127,7 @@ def findClose(points):
 
 
 ## 5、窗口抖动
+
 [运行 ShakeWindow.py](ShakeWindow.py)
 
 通过`QPropertyAnimation`对控件的pos属性进行死去活来的修改
@@ -128,6 +135,7 @@ def findClose(points):
 
 
 ## 6、窗口翻转动画(仿QQ)
+
 [运行 FlipWidgetAnimation.py](FlipWidgetAnimation.py)
 
 1. 用了两个`QLabel`来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
@@ -136,4 +144,4 @@ def findClose(points):
 4. 通过`setWindowOpacity`控制主窗口的显示隐藏(保留任务栏),当然也可以用`hide`
 5. 动画窗口`FlipWidget.py`主要实现两张图片的翻转显示,考虑到0-90和90-180之前的情况,以及图片的缩放动画
 
-
\ No newline at end of file
+
diff --git a/QPropertyAnimation/RlatticeEffect.py b/QPropertyAnimation/RlatticeEffect.py
index 7c443374..a57c37b1 100644
--- a/QPropertyAnimation/RlatticeEffect.py
+++ b/QPropertyAnimation/RlatticeEffect.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年11月22日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: RlatticeEffect
 @description: 
@@ -12,29 +12,29 @@
 from random import random
 from time import time
 
-from PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\
-    Qt, QRectF, pyqtSignal
-from PyQt5.QtGui import QColor, QPainterPath, QPainter
-from PyQt5.QtWidgets import QWidget
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
+try:
+    from PyQt5.QtCore import QPropertyAnimation, QObject, QEasingCurve, Qt, QRectF, pyqtSignal, pyqtProperty
+    from PyQt5.QtGui import QColor, QPainterPath, QPainter
+    from PyQt5.QtWidgets import QApplication, QWidget
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation, QObject, QEasingCurve, Qt, QRectF, Signal as pyqtSignal, \
+        Property as pyqtProperty
+    from PySide2.QtGui import QColor, QPainterPath, QPainter
+    from PySide2.QtWidgets import QApplication, QWidget
 
 try:
     from Lib import pointtool  # @UnusedImport @UnresolvedImport
+
     getDistance = pointtool.getDistance
     findClose = pointtool.findClose
 except:
     import math
 
+
     def getDistance(p1, p2):
         return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)
 
+
     def findClose(points):
         plen = len(points)
         for i in range(plen):
@@ -66,8 +66,7 @@ def __init__(self, x, y):
 
 
 class Point(QObject):
-
-    valueChanged = pyqtSignal()
+    valueChanged = pyqtSignal(int)
 
     def __init__(self, x, ox, y, oy, *args, **kwargs):
         super(Point, self).__init__(*args, **kwargs)
@@ -90,12 +89,12 @@ def initAnimation(self):
         # 属性动画
         if not hasattr(self, 'xanimation'):
             self.xanimation = QPropertyAnimation(
-                self, b'x', self, valueChanged=self.valueChanged.emit,
-                easingCurve=QEasingCurve.InOutSine)
+                self, b'x', self, easingCurve=QEasingCurve.InOutSine)
+            self.xanimation.valueChanged.connect(self.valueChanged.emit)
             self.yanimation = QPropertyAnimation(
-                self, b'y', self, valueChanged=self.valueChanged.emit,
-                easingCurve=QEasingCurve.InOutSine,
-                finished=self.updateAnimation)
+                self, b'y', self, easingCurve=QEasingCurve.InOutSine)
+            self.yanimation.valueChanged.connect(self.valueChanged.emit)
+            self.yanimation.finished.connect(self.updateAnimation)
             self.updateAnimation()
 
     def updateAnimation(self):
@@ -138,6 +137,9 @@ def __init__(self, *args, **kwargs):
         self.target = Target(self.width() / 2, self.height() / 2)
         self.initPoints()
 
+    def update(self, *args):
+        super(Window, self).update()
+
     def paintEvent(self, event):
         super(Window, self).paintEvent(event)
         painter = QPainter()
@@ -220,8 +222,9 @@ def animate(self, painter):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QPropertyAnimation/ShakeWindow.py b/QPropertyAnimation/ShakeWindow.py
index b161ce3b..6c7d9c95 100644
--- a/QPropertyAnimation/ShakeWindow.py
+++ b/QPropertyAnimation/ShakeWindow.py
@@ -4,18 +4,18 @@
 """
 Created on 2019年5月8日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ShakeWindow
 @description: 抖动动画
 """
-from PyQt5.QtCore import QPropertyAnimation, QPoint
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QPropertyAnimation, QPoint
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation, QPoint
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
 
 
 class Window(QWidget):
@@ -66,7 +66,7 @@ def doShakeWindow(self, target):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QProxyStyle/Lib/TabBarStyle.py b/QProxyStyle/Lib/TabBarStyle.py
index 12d7c8ab..59406ccf 100644
--- a/QProxyStyle/Lib/TabBarStyle.py
+++ b/QProxyStyle/Lib/TabBarStyle.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年12月27日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: TabBarStyle
 @description: 
@@ -13,13 +13,6 @@
 from PyQt5.QtWidgets import QProxyStyle
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
-
 class TabBarStyle(QProxyStyle):
 
     def sizeFromContents(self, types, option, size, widget):
diff --git a/QProxyStyle/Lib/TabCornerStyle.py b/QProxyStyle/Lib/TabCornerStyle.py
new file mode 100644
index 00000000..fe793540
--- /dev/null
+++ b/QProxyStyle/Lib/TabCornerStyle.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021年06月23日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TabCornerStyle
+@description: 
+"""
+
+try:
+    from PyQt5.QtCore import QRect
+    from PyQt5.QtWidgets import QProxyStyle, QStyle
+except ImportError:
+    from PySide2.QtCore import QRect
+    from PySide2.QtWidgets import QProxyStyle, QStyle
+
+
+class TabCornerStyle(QProxyStyle):
+
+    def subElementRect(self, element, option, widget):
+        try:
+            rect = super(TabCornerStyle, self).subElementRect(element, option, widget)
+            if element == QStyle.SE_TabWidgetRightCorner and rect.isValid():
+                # 标签tab的矩形范围
+                tab_rect = self.subElementRect(QStyle.SE_TabWidgetTabBar, option, widget)
+                # 内容面板的矩形范围
+                panel_rect = self.subElementRect(QStyle.SE_TabWidgetTabPane, option, widget)
+                ext_height = 2 * self.pixelMetric(QStyle.PM_TabBarBaseHeight, option, widget)
+                # 修正过后填充的矩形范围
+                cor_rect = QRect(tab_rect.x() + tab_rect.width() + ext_height, tab_rect.y() + ext_height,
+                                 panel_rect.width() - tab_rect.width() - 2 * ext_height,
+                                 tab_rect.height() - 2 * ext_height)
+                return cor_rect
+            return rect
+        except Exception as e:
+            print(e)
+            return QRect()
diff --git a/QProxyStyle/README.md b/QProxyStyle/README.md
index 7d9fe674..2ec8a71c 100644
--- a/QProxyStyle/README.md
+++ b/QProxyStyle/README.md
@@ -2,12 +2,26 @@
 
 - 目录
   - [QTabWidget Tab文字方向](#1qtabwidget-tab文字方向)
+  - [QTabWidget 角落控件位置](#2qtabwidget-角落控件位置)
 
 ## 1、QTabWidget Tab文字方向
+
 [运行 TabTextDirection.py](TabTextDirection.py)
 
 1. 通过 `app.setStyle(TabBarStyle())` 设置代理样式
 2. `sizeFromContents` 转置size
 3. `drawControl` 绘制文字
 
-
\ No newline at end of file
+
+
+## 2、QTabWidget 角落控件位置
+
+[运行 TabCornerWidget.py](TabCornerWidget.py)
+
+1. 通过 `app.setStyle(TabCornerStyle())` 设置代理样式
+2. `setCornerWidget` 设置自定义角落控件
+
+原理是通过代理样式中对 `SE_TabWidgetRightCorner` 计算的结果进行校正,使得角落控件占满右边空白位置,
+然后再配合自定义控件中使用 `QSpacerItem` 占据右边位置使得 + 号按钮居左,表现效果为 + 号按钮跟随标签的增加和减少
+
+
diff --git a/QProxyStyle/ScreenShot/TabCornerStyle.png b/QProxyStyle/ScreenShot/TabCornerStyle.png
new file mode 100644
index 00000000..4133f625
Binary files /dev/null and b/QProxyStyle/ScreenShot/TabCornerStyle.png differ
diff --git a/QProxyStyle/TabCornerWidget.py b/QProxyStyle/TabCornerWidget.py
new file mode 100644
index 00000000..39d71462
--- /dev/null
+++ b/QProxyStyle/TabCornerWidget.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021年06月23日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TabCornerWidget
+@description: 
+"""
+
+try:
+    from PyQt5.QtCore import pyqtSignal, Qt
+    from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QSpacerItem, QSizePolicy, \
+        QTabWidget
+except ImportError:
+    from PySide2.QtCore import Signal as pyqtSignal, Qt
+    from PySide2.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QSpacerItem, QSizePolicy, \
+        QTabWidget
+
+from QProxyStyle.Lib.TabCornerStyle import TabCornerStyle
+
+
+class TabCornerWidget(QWidget):
+    signalTabAdd = pyqtSignal()
+
+    def __init__(self, *args, **kwargs):
+        super(TabCornerWidget, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        layout.setContentsMargins(0, 0, 0, 0)
+        self.buttonAdd = QPushButton('+', self, toolTip='添加新标签页', clicked=self.signalTabAdd.emit)
+        layout.addWidget(self.buttonAdd)
+        layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
+
+    def resizeEvent(self, event):
+        super(TabCornerWidget, self).resizeEvent(event)
+        # 更新按钮高度
+        if hasattr(self, 'buttonAdd'):
+            self.buttonAdd.setFixedSize(self.height(), self.height())
+
+
+if __name__ == '__main__':
+    import sys
+
+    app = QApplication(sys.argv)
+    app.setStyle(TabCornerStyle())
+
+    tab1 = QTabWidget()
+    cor1 = TabCornerWidget(tab1)
+    cor1.signalTabAdd.connect(lambda: tab1.addTab(QWidget(tab1), 'tab' + str(tab1.count() + 1)))
+    tab1.setCornerWidget(cor1, Qt.TopRightCorner)
+    tab1.show()
+
+    tab2 = QTabWidget()
+    tab2.setTabPosition(QTabWidget.South)  # tab 标签方向
+    cor2 = TabCornerWidget(tab2)
+    cor2.signalTabAdd.connect(lambda: tab2.addTab(QWidget(tab2), 'tab' + str(tab2.count() + 1)))
+    tab2.setCornerWidget(cor2, Qt.BottomRightCorner)
+    tab2.show()
+
+    for i in range(10):
+        tab1.addTab(QWidget(tab1), 'tab' + str(i + 1))
+        tab2.addTab(QWidget(tab1), 'tab' + str(i + 1))
+
+    sys.exit(app.exec_())
diff --git a/QProxyStyle/TabTextDirection.py b/QProxyStyle/TabTextDirection.py
index 22dc4d86..52ba5687 100644
--- a/QProxyStyle/TabTextDirection.py
+++ b/QProxyStyle/TabTextDirection.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年12月27日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: TestTabWidget
 @description: 
@@ -14,13 +14,6 @@
 from Lib.TabBarStyle import TabBarStyle
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
-
 class TabWidget(QTabWidget):
 
     def __init__(self, *args, **kwargs):
@@ -44,6 +37,7 @@ def __init__(self, *args, **kwargs):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyle(TabBarStyle())
     w = Window()
diff --git a/QPushButton/BottomLineProgress.py b/QPushButton/BottomLineProgress.py
index 7a19e59e..00352f54 100644
--- a/QPushButton/BottomLineProgress.py
+++ b/QPushButton/BottomLineProgress.py
@@ -1,25 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年2月1日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PushButtonLine
 @description: 
-'''
-from random import randint
-import sys
-
-from PyQt5.QtCore import QTimer, QThread, pyqtSignal
-from PyQt5.QtGui import QPainter, QColor, QPen
-from PyQt5.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout
+"""
 
+import sys
+from random import randint
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QTimer, QThread, pyqtSignal
+    from PyQt5.QtGui import QPainter, QColor, QPen
+    from PyQt5.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout
+except ImportError:
+    from PySide2.QtCore import QTimer, QThread, Signal as pyqtSignal
+    from PySide2.QtGui import QPainter, QColor, QPen
+    from PySide2.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout
 
 StyleSheet = '''
 PushButtonLine {
@@ -32,7 +33,6 @@
 
 
 class LoadingThread(QThread):
-
     valueChanged = pyqtSignal(float)  # 当前值/最大值
 
     def __init__(self, *args, **kwargs):
@@ -41,12 +41,13 @@ def __init__(self, *args, **kwargs):
 
     def run(self):
         for i in range(self.totalValue + 1):
+            if self.isInterruptionRequested():
+                break
             self.valueChanged.emit(i / self.totalValue)
             QThread.msleep(randint(50, 100))
 
 
 class PushButtonLine(QPushButton):
-
     lineColor = QColor(0, 150, 136)
 
     def __init__(self, *args, **kwargs):
@@ -57,6 +58,9 @@ def __init__(self, *args, **kwargs):
         self._timer = QTimer(self, timeout=self.update)
         self.clicked.connect(self.start)
 
+    def __del__(self):
+        self.stop()
+
     def paintEvent(self, event):
         super(PushButtonLine, self).paintEvent(event)
         if not self._timer.isActive():
@@ -79,14 +83,21 @@ def start(self):
         self.setText(self._waitText)
 
     def stop(self):
-        self.loadingThread.valueChanged.disconnect(self.setPercent)
-        self.loadingThread.terminate()
-        self.loadingThread.deleteLater()
-        QThread.msleep(100)  # 延迟等待deleteLater执行完毕
-        del self.loadingThread
-        self._percent = 0
-        self._timer.stop()
-        self.setText(self._text)
+        try:
+            if hasattr(self, "loadingThread"):
+                if self.loadingThread.isRunning():
+                    self.loadingThread.requestInterruption()
+                    self.loadingThread.quit()
+                    self.loadingThread.wait(2000)
+                del self.loadingThread
+        except RuntimeError:
+            pass
+        try:
+            self._percent = 0
+            self._timer.stop()
+            self.setText(self._text)
+        except RuntimeError:
+            pass
 
     def setPercent(self, v):
         self._percent = v
diff --git a/QPushButton/Data/Images/avatar.jpg b/QPushButton/Data/Images/avatar.jpg
new file mode 100644
index 00000000..32856da1
Binary files /dev/null and b/QPushButton/Data/Images/avatar.jpg differ
diff --git a/QPushButton/FontRotate.py b/QPushButton/FontRotate.py
index c9cae277..e8a728a2 100644
--- a/QPushButton/FontRotate.py
+++ b/QPushButton/FontRotate.py
@@ -1,31 +1,30 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年2月1日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PushButtonFont
 @description: 
-'''
-
+"""
 
 import sys
 
-from PyQt5.QtCore import QPropertyAnimation, Qt, QRectF
-from PyQt5.QtGui import QFontDatabase
-from PyQt5.QtWidgets import QPushButton, QApplication, QStyleOptionButton,\
-    QStylePainter, QStyle
-
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QPropertyAnimation, Qt, QRectF
+    from PyQt5.QtGui import QFontDatabase
+    from PyQt5.QtWidgets import QPushButton, QApplication, QStyleOptionButton, \
+        QStylePainter, QStyle
+except ImportError:
+    from PySide2.QtCore import QPropertyAnimation, Qt, QRectF
+    from PySide2.QtGui import QFontDatabase
+    from PySide2.QtWidgets import QPushButton, QApplication, QStyleOptionButton, \
+        QStylePainter, QStyle
 
 
 class PushButtonFont(QPushButton):
-
     LoadingText = "\uf110"
 
     def __init__(self, *args, **kwargs):
diff --git a/QPushButton/NormalStyle.py b/QPushButton/NormalStyle.py
index 831064b4..ec36a89f 100644
--- a/QPushButton/NormalStyle.py
+++ b/QPushButton/NormalStyle.py
@@ -1,23 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月29日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: NormalStyle
 @description: 
-'''
-import sys
-from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication
+"""
 
+import sys
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication
+except ImportError:
+    from PySide2.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication
 
-StyleSheet = '''
+StyleSheet = """
 /*这里是通用设置,所有按钮都有效,不过后面的可以覆盖这个*/
 QPushButton {
     border: none; /*去掉边框*/
@@ -85,7 +85,7 @@
     color: white; /*文字颜色*/
     background-color: #9c27b0;
 }
-'''
+"""
 
 
 class Window(QWidget):
diff --git a/QPushButton/README.md b/QPushButton/README.md
index 2328a51e..7467438d 100644
--- a/QPushButton/README.md
+++ b/QPushButton/README.md
@@ -5,8 +5,11 @@
   - [按钮底部线条进度](#2按钮底部线条进度)
   - [按钮文字旋转进度](#3按钮文字旋转进度)
   - [按钮常用信号](#4按钮常用信号)
+  - [旋转动画按钮](#5旋转动画按钮)
+  - [弹性动画按钮](#6弹性动画按钮)
 
 ## 1、普通样式
+
 [运行 NormalStyle.py](NormalStyle.py)
 
 主要改变背景颜色、鼠标按下颜色、鼠标悬停颜色、圆角、圆形、文字颜色
@@ -14,6 +17,7 @@
 
 
 ## 2、按钮底部线条进度
+
 [运行 BottomLineProgress.py](BottomLineProgress.py)
 
 在按钮下方画一条线,根据百分值绘制
@@ -21,6 +25,7 @@
 
 
 ## 3、按钮文字旋转进度
+
 [运行 FontRotate.py](FontRotate.py)
 
 利用字体,使用FontAwesome字体来显示一个圆形进度条,然后利用旋转动画
@@ -28,9 +33,22 @@
 
 
 ## 4、按钮常用信号
+
 [运行 SignalsExample.py](SignalsExample.py)
 
-根据官网文档 https://doc.qt.io/qt-5/qabstractbutton.html#signals 中的信号介绍编写
+根据官网文档  中的信号介绍编写
 按钮的点击、按下、释放、选中信号演示
 
-
\ No newline at end of file
+
+
+## 5、旋转动画按钮
+
+[运行 RotateButton.py](RotateButton.py)
+
+
+
+## 6、弹性动画按钮
+
+[运行 RubberBandButton.py](RubberBandButton.py)
+
+
diff --git a/QPushButton/RotateButton.py b/QPushButton/RotateButton.py
new file mode 100644
index 00000000..e9b9f084
--- /dev/null
+++ b/QPushButton/RotateButton.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2019年1月2日
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: Widgets.RotateButton
+@description:
+"""
+
+import os
+import sys
+
+try:
+    from PyQt5.QtCore import QPointF, QPropertyAnimation, QRectF, Qt, pyqtProperty
+    from PyQt5.QtGui import QColor, QImage, QPainter, QPainterPath, QPixmap
+    from PyQt5.QtWidgets import (
+        QGraphicsDropShadowEffect,
+        QPushButton,
+        QStyle,
+        QStyleOptionButton,
+        QStylePainter,
+        QApplication,
+        QWidget,
+        QHBoxLayout,
+    )
+except ImportError:
+    from PySide2.QtCore import QPointF, QPropertyAnimation, QRectF, Qt, pyqtProperty
+    from PySide2.QtGui import QColor, QImage, QPainter, QPainterPath, QPixmap
+    from PySide2.QtWidgets import (
+        QGraphicsDropShadowEffect,
+        QPushButton,
+        QStyle,
+        QStyleOptionButton,
+        QStylePainter,
+        QApplication,
+        QWidget,
+        QHBoxLayout,
+    )
+
+
+class RotateButton(QPushButton):
+
+    STARTVALUE = 0  # 起始旋转角度
+    ENDVALUE = 360  # 结束旋转角度
+    DURATION = 540  # 动画完成总时间
+
+    def __init__(self, *args, **kwargs):
+        super(RotateButton, self).__init__(*args, **kwargs)
+        self.setCursor(Qt.PointingHandCursor)
+        self._angle = 0  # 角度
+        self._padding = 10  # 阴影边距
+        self._image = ""  # 图片路径
+        self._shadowColor = QColor(33, 33, 33)  # 阴影颜色
+        self._pixmap = None  # 图片对象
+        # 属性动画
+        self._animation = QPropertyAnimation(self, b"angle", self)
+        self._animation.setLoopCount(1)  # 只循环一次
+
+    def paintEvent(self, event):
+        """绘制事件"""
+        text = self.text()
+        option = QStyleOptionButton()
+        self.initStyleOption(option)
+        option.text = ""  # 不绘制文字
+        painter = QStylePainter(self)
+        painter.setRenderHint(QStylePainter.Antialiasing)
+        painter.setRenderHint(QStylePainter.HighQualityAntialiasing)
+        painter.setRenderHint(QStylePainter.SmoothPixmapTransform)
+        painter.drawControl(QStyle.CE_PushButton, option)
+        # 变换坐标为正中间
+        painter.translate(self.rect().center())
+        painter.rotate(self._angle)  # 旋转
+
+        # 绘制图片
+        if self._pixmap and not self._pixmap.isNull():
+            w = self.width()
+            h = self.height()
+            pos = QPointF(-self._pixmap.width() / 2, -self._pixmap.height() / 2)
+            painter.drawPixmap(pos, self._pixmap)
+        elif text:
+            # 在变换坐标后的正中间画文字
+            fm = self.fontMetrics()
+            w = fm.width(text)
+            h = fm.height()
+            rect = QRectF(0 - w * 2, 0 - h, w * 2 * 2, h * 2)
+            painter.drawText(rect, Qt.AlignCenter, text)
+        else:
+            super(RotateButton, self).paintEvent(event)
+
+    def enterEvent(self, _):
+        """鼠标进入事件"""
+        # 设置阴影
+        # 边框阴影效果
+        effect = QGraphicsDropShadowEffect(self)
+        effect.setBlurRadius(self._padding * 2)
+        effect.setOffset(0, 0)
+        effect.setColor(self._shadowColor)
+        self.setGraphicsEffect(effect)
+
+        # 开启旋转动画
+        self._animation.stop()
+        cv = self._animation.currentValue() or self.STARTVALUE
+        self._animation.setDuration(
+            self.DURATION if cv == 0 else int(cv / self.ENDVALUE * self.DURATION)
+        )
+        self._animation.setStartValue(cv)
+        self._animation.setEndValue(self.ENDVALUE)
+        self._animation.start()
+
+    def leaveEvent(self, _):
+        """鼠标离开事件"""
+        # 取消阴影
+        self.setGraphicsEffect(None)
+
+        # 旋转动画
+        self._animation.stop()
+        cv = self._animation.currentValue() or self.ENDVALUE
+        self._animation.setDuration(int(cv / self.ENDVALUE * self.DURATION))
+        self._animation.setStartValue(cv)
+        self._animation.setEndValue(self.STARTVALUE)
+        self._animation.start()
+
+    def setPixmap(self, path):
+        if not os.path.exists(path):
+            self._image = ""
+            self._pixmap = None
+            return
+        self._image = path
+        size = (
+            max(
+                min(self.width(), self.height()),
+                min(self.minimumWidth(), self.minimumHeight()),
+            )
+            - self._padding
+        )  # 需要边距的边框
+        radius = int(size / 2)
+        image = QImage(size, size, QImage.Format_ARGB32_Premultiplied)
+        image.fill(Qt.transparent)  # 填充背景为透明
+        pixmap = QPixmap(path).scaled(
+            size, size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation
+        )
+        # QPainter
+        painter = QPainter()
+        painter.begin(image)
+        painter.setRenderHint(QPainter.Antialiasing, True)
+        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
+        # QPainterPath
+        path = QPainterPath()
+        path.addRoundedRect(0, 0, size, size, radius, radius)
+        # 切割圆
+        painter.setClipPath(path)
+        painter.drawPixmap(0, 0, pixmap)
+        painter.end()
+        self._pixmap = QPixmap.fromImage(image)
+        self.update()
+
+    def pixmap(self):
+        return self._pixmap
+
+    @pyqtProperty(str)
+    def image(self):
+        return self._image
+
+    @image.setter
+    def image(self, path):
+        self.setPixmap(path)
+
+    @pyqtProperty(int)
+    def angle(self):
+        return self._angle
+
+    @angle.setter
+    def angle(self, value):
+        self._angle = value
+        self.update()
+
+    @pyqtProperty(int)
+    def padding(self):
+        return self._padding
+
+    @padding.setter
+    def padding(self, value):
+        self._padding = value
+
+    @pyqtProperty(QColor)
+    def shadowColor(self):
+        return self._shadowColor
+
+    @shadowColor.setter
+    def shadowColor(self, color):
+        self._shadowColor = QColor(color)
+
+
+class TestWindow(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+
+        btn = RotateButton("pyqt.site", self)
+        btn.setMinimumHeight(96)
+        btn.setToolTip("旋转按钮")
+        layout.addWidget(btn)
+
+        btn = RotateButton("", self)
+        btn.setMinimumHeight(96)
+        btn.setObjectName("imageLabel1")
+        btn.setPixmap("./Data/Images/avatar.jpg")
+        layout.addWidget(btn)
+
+        btn = RotateButton("", self)
+        btn.setMinimumHeight(96)
+        btn.setObjectName("imageLabel2")
+        layout.addWidget(btn)
+
+
+if __name__ == "__main__":
+    import cgitb
+
+    cgitb.enable(1, None, 5, "text")
+
+    # cd to current dir
+    os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
+
+    app = QApplication(sys.argv)
+    app.setStyleSheet(
+        """
+    RotateButton {
+        font-size: 48px;
+    }
+    #imageLabel1, #imageLabel2 {
+        background: transparent;
+    }
+    #imageLabel2 {
+        qproperty-image: "./Data/Images/avatar.jpg";
+    }
+    """
+    )
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QPushButton/RubberBandButton.py b/QPushButton/RubberBandButton.py
new file mode 100644
index 00000000..75af92eb
--- /dev/null
+++ b/QPushButton/RubberBandButton.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2019年1月3日
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: Widgets.RubberBandButton
+@description: 
+"""
+
+try:
+    from PyQt5.QtCore import (
+        QEasingCurve,
+        QParallelAnimationGroup,
+        QPropertyAnimation,
+        QRectF,
+        Qt,
+        pyqtProperty,
+    )
+    from PyQt5.QtGui import QColor, QPainter
+    from PyQt5.QtWidgets import (
+        QPushButton,
+        QStyle,
+        QStyleOptionButton,
+        QStylePainter,
+        QWidget,
+        QApplication,
+        QHBoxLayout,
+    )
+except ImportError:
+    from PySide2.QtCore import (
+        QEasingCurve,
+        QParallelAnimationGroup,
+        QPropertyAnimation,
+        QRectF,
+        Qt,
+        Property as pyqtProperty,
+    )
+    from PySide2.QtGui import QColor, QPainter
+    from PySide2.QtWidgets import (
+        QPushButton,
+        QStyle,
+        QStyleOptionButton,
+        QApplication,
+        QStylePainter,
+        QWidget,
+        QHBoxLayout,
+    )
+
+
+class RubberBandButton(QPushButton):
+
+    def __init__(self, *args, **kwargs):
+        self._bgcolor = QColor(kwargs.pop("bgcolor", Qt.green))
+        super(RubberBandButton, self).__init__(*args, **kwargs)
+        self.setFlat(True)
+        self.setCursor(Qt.PointingHandCursor)
+        self._width = 0
+        self._height = 0
+
+    def paintEvent(self, event):
+        self._initAnimate()
+        painter = QStylePainter(self)
+        painter.setRenderHint(QPainter.Antialiasing, True)
+        painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
+        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
+        painter.setBrush(QColor(self._bgcolor))
+        painter.setPen(QColor(self._bgcolor))
+        painter.drawEllipse(
+            QRectF(
+                (self.minimumWidth() - self._width) / 2,
+                (self.minimumHeight() - self._height) / 2,
+                self._width,
+                self._height,
+            )
+        )
+        # 绘制本身的文字和图标
+        options = QStyleOptionButton()
+        options.initFrom(self)
+        size = options.rect.size()
+        size.transpose()
+        options.rect.setSize(size)
+        options.features = QStyleOptionButton.Flat
+        options.text = self.text()
+        options.icon = self.icon()
+        options.iconSize = self.iconSize()
+        painter.drawControl(QStyle.CE_PushButton, options)
+        event.accept()
+
+    def _initAnimate(self):
+        if hasattr(self, "_animate"):
+            return
+        self._width = self.minimumWidth() * 7 / 8
+        self._height = self.minimumHeight() * 7 / 8
+        #         self._width=175
+        #         self._height=175
+
+        # 宽度动画
+        wanimate = QPropertyAnimation(self, b"rWidth")
+        wanimate.setEasingCurve(QEasingCurve.OutElastic)
+        wanimate.setDuration(700)
+        wanimate.valueChanged.connect(self.update)
+
+        # 插入宽度线性值
+        wanimate.setKeyValueAt(0, self._width)
+        #         wanimate.setKeyValueAt(0.1, 180)
+        #         wanimate.setKeyValueAt(0.2, 185)
+        #         wanimate.setKeyValueAt(0.3, 190)
+        #         wanimate.setKeyValueAt(0.4, 195)
+        wanimate.setKeyValueAt(0.5, self._width + 6)
+        #         wanimate.setKeyValueAt(0.6, 195)
+        #         wanimate.setKeyValueAt(0.7, 190)
+        #         wanimate.setKeyValueAt(0.8, 185)
+        #         wanimate.setKeyValueAt(0.9, 180)
+        wanimate.setKeyValueAt(1, self._width)
+
+        # 高度动画
+        hanimate = QPropertyAnimation(self, b"rHeight")
+        hanimate.setEasingCurve(QEasingCurve.OutElastic)
+        hanimate.setDuration(700)
+
+        # 插入高度线性值
+        hanimate.setKeyValueAt(0, self._height)
+        #         hanimate.setKeyValueAt(0.1, 170)
+        #         hanimate.setKeyValueAt(0.3, 165)
+        hanimate.setKeyValueAt(0.5, self._height - 6)
+        #         hanimate.setKeyValueAt(0.7, 165)
+        #         hanimate.setKeyValueAt(0.9, 170)
+        hanimate.setKeyValueAt(1, self._height)
+
+        # 设置动画组
+        self._animate = QParallelAnimationGroup(self)
+        self._animate.addAnimation(wanimate)
+        self._animate.addAnimation(hanimate)
+
+    def enterEvent(self, event):
+        super(RubberBandButton, self).enterEvent(event)
+        self._animate.stop()
+        self._animate.start()
+
+    @pyqtProperty(int)
+    def rWidth(self):
+        return self._width
+
+    @rWidth.setter
+    def rWidth(self, value):
+        self._width = value
+
+    @pyqtProperty(int)
+    def rHeight(self):
+        return self._height
+
+    @rHeight.setter
+    def rHeight(self, value):
+        self._height = value
+
+    @pyqtProperty(QColor)
+    def bgColor(self):
+        return self._bgcolor
+
+    @bgColor.setter
+    def bgColor(self, color):
+        self._bgcolor = QColor(color)
+
+
+class TestWindow(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        layout.addWidget(RubberBandButton("d", self, bgcolor="#01847E"))
+        layout.addWidget(RubberBandButton("v", self, bgcolor="#7FA91F"))
+        layout.addWidget(RubberBandButton("N", self, bgcolor="#FC8416"))
+        layout.addWidget(RubberBandButton("U", self, bgcolor="#66D3c0"))
+        layout.addWidget(RubberBandButton("a", self, bgcolor="#F28195"))
+
+
+if __name__ == "__main__":
+    import cgitb, sys
+
+    cgitb.enable(1, None, 5, "text")
+
+    app = QApplication(sys.argv)
+    app.setStyleSheet(
+        """
+    RubberBandButton {
+        min-width: 100px;
+        max-width: 100px;
+        min-height: 100px;
+        max-height: 100px;
+        border: none;
+        color: white;
+        outline: none;
+        margin: 4px;
+        font-family: webdings;
+        font-size: 60px;
+    }
+    """
+    )
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QPushButton/ScreenShot/RotateButton.gif b/QPushButton/ScreenShot/RotateButton.gif
new file mode 100644
index 00000000..6b1c3aa4
Binary files /dev/null and b/QPushButton/ScreenShot/RotateButton.gif differ
diff --git a/QPushButton/ScreenShot/RubberBandButton.gif b/QPushButton/ScreenShot/RubberBandButton.gif
new file mode 100644
index 00000000..dcf1e81a
Binary files /dev/null and b/QPushButton/ScreenShot/RubberBandButton.gif differ
diff --git a/QPushButton/SignalsExample.py b/QPushButton/SignalsExample.py
index 0b3cf664..35163e81 100644
--- a/QPushButton/SignalsExample.py
+++ b/QPushButton/SignalsExample.py
@@ -4,17 +4,16 @@
 """
 Created on 2019年7月2日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QPushButton.SignalsExample
 @description: 按钮信号例子
 """
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QPlainTextEdit
 
-
-__Author__ = "Irony"
-__Copyright__ = "Copyright (c) 2019"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit
 
 
 class Window(QWidget):
@@ -26,14 +25,23 @@ def __init__(self, *args, **kwargs):
         btn1 = QPushButton('按钮点击信号', self)
         btn1.setObjectName('ClickBtn')
         btn1.clicked.connect(self.onClicked)
-
         layout.addWidget(btn1)
-        layout.addWidget(QPushButton(
-            '按钮按下信号', self, objectName='PressBtn', pressed=self.onPressed))
-        layout.addWidget(QPushButton(
-            '按钮释放信号', self, objectName='ReleaseBtn', released=self.onReleased))
-        layout.addWidget(QPushButton(
-            '按钮选中信号', self, checkable=True, objectName='ToggleBtn', toggled=self.onToggled))
+
+        btn2 = QPushButton('按钮按下信号', self)
+        btn2.setObjectName('PressBtn')
+        btn2.pressed.connect(self.onPressed)
+        layout.addWidget(btn2)
+
+        btn3 = QPushButton('按钮释放信号', self)
+        btn3.setObjectName('ReleaseBtn')
+        btn3.released.connect(self.onReleased)
+        layout.addWidget(btn3)
+
+        btn4 = QPushButton('按钮释放信号', self)
+        btn4.setObjectName('ToggleBtn')
+        btn4.setCheckable(True)
+        btn4.toggled.connect(self.onToggled)
+        layout.addWidget(btn4)
 
         self.resultView = QPlainTextEdit(self)
         self.resultView.setReadOnly(True)
@@ -58,7 +66,7 @@ def onToggled(self, checked):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QScrollArea/Lib/SettingUi.py b/QScrollArea/Lib/SettingUi.py
index a9bfdffc..d25f3ef2 100644
--- a/QScrollArea/Lib/SettingUi.py
+++ b/QScrollArea/Lib/SettingUi.py
@@ -6,7 +6,11 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore, QtGui, QtWidgets
+try:
+    from PyQt5 import QtCore, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtWidgets
+
 
 class Ui_Setting(object):
     def setupUi(self, Setting):
@@ -151,7 +155,8 @@ def setupUi(self, Setting):
         self.comboBox.addItem("")
         self.comboBox.addItem("")
         self.horizontalLayout_2.addWidget(self.comboBox)
-        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding,
+                                           QtWidgets.QSizePolicy.Minimum)
         self.horizontalLayout_2.addItem(spacerItem)
         self.formLayout_9.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
         self.checkBox_30 = QtWidgets.QCheckBox(self.widget_2)
@@ -171,7 +176,8 @@ def setupUi(self, Setting):
         self.pushButton_4 = QtWidgets.QPushButton(self.widget_2)
         self.pushButton_4.setObjectName("pushButton_4")
         self.horizontalLayout_3.addWidget(self.pushButton_4)
-        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding,
+                                            QtWidgets.QSizePolicy.Minimum)
         self.horizontalLayout_3.addItem(spacerItem1)
         self.formLayout_9.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
         self.verticalLayout.addWidget(self.widget_2)
@@ -469,7 +475,8 @@ def retranslateUi(self, Setting):
         self.checkBox_23.setText(_translate("Setting", "启用一声问候消息"))
         self.checkBox_24.setText(_translate("Setting", "启用设备连接提醒"))
         self.right5.setText(_translate("Setting", "当插入安卓设备时,提示安装或者更新QQ手机版"))
-        self.label_3.setText(_translate("Setting", " 您可以设置是否在屏幕右下角收到来自QQ空间的通知,进入设置 
"))
+        self.label_3.setText(_translate("Setting",
+                                        " 您可以设置是否在屏幕右下角收到来自QQ空间的通知,进入设置 
"))
         self.label_4.setText(_translate("Setting", "好友上线提醒"))
         self.radioButton.setText(_translate("Setting", "关闭好友上线提醒"))
         self.radioButton_2.setText(_translate("Setting", "全部好友上线提醒"))
@@ -485,10 +492,10 @@ def retranslateUi(self, Setting):
 
 if __name__ == "__main__":
     import sys
+
     app = QtWidgets.QApplication(sys.argv)
     Setting = QtWidgets.QWidget()
     ui = Ui_Setting()
     ui.setupUi(Setting)
     Setting.show()
     sys.exit(app.exec_())
-
diff --git a/QScrollArea/QQSettingPanel.py b/QScrollArea/QQSettingPanel.py
index 6dccd7c0..2a5236aa 100644
--- a/QScrollArea/QQSettingPanel.py
+++ b/QScrollArea/QQSettingPanel.py
@@ -1,22 +1,21 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from PyQt5.QtWidgets import QWidget
-
-from Lib.SettingUi import Ui_Setting  # @UnresolvedImport
 
+"""
+Created on 2018年3月28日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QQSettingPanel
+@description:
+"""
 
-# Created on 2018年3月28日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: QQSettingPanel
-# description:
+try:
+    from PyQt5.QtWidgets import QApplication, QWidget
+except ImportError:
+    from PySide2.QtWidgets import QApplication, QWidget
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+from Lib.SettingUi import Ui_Setting  # @UnresolvedImport
 
 
 class Window(QWidget, Ui_Setting):
@@ -59,9 +58,9 @@ def onItemClicked(self, item):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
-    app.setStyleSheet(open("Data/style.qss", "rb").read().decode("utf-8"))
+    app.setStyleSheet(open('Data/style.qss', 'rb').read().decode('utf-8'))
     w = Window()
     w.show()
     sys.exit(app.exec_())
diff --git a/QScrollArea/README.md b/QScrollArea/README.md
index 65ff31b1..52294bd5 100644
--- a/QScrollArea/README.md
+++ b/QScrollArea/README.md
@@ -4,7 +4,8 @@
   - [仿QQ设置面板](#1仿QQ设置面板)
 
 ## 1、仿QQ设置面板
-[运行 QQSettingPanel.py](QQSettingPanel.py)
+
+[运行 QQSettingPanel.py](QQSettingPanel.py) | [查看 setting.ui](Data/setting.ui)
 
 1. 左侧为`QListWidget`,右侧使用`QScrollArea`设置`QVBoxLayout`,然后依次往里面添加QWidget
 2. 右侧添加`QWidget`的时候有两种方案
@@ -12,9 +13,10 @@
     2. 左侧list添加item时给定右侧对应的widget变量值
 
 相关事件:
+
 1. 绑定左侧`QListWidget`的`itemClicked`的到该item的索引
 2. 绑定右侧滚动条的`valueChanged`事件得到pos
 
 注意:当`itemClicked`时定位滚动条的值时,需要设置一个标志位用来避免`valueChanged`重复调用item的定位
 
-
\ No newline at end of file
+
diff --git a/QScrollBar/README.md b/QScrollBar/README.md
index 1de1b8b0..6445e59a 100644
--- a/QScrollBar/README.md
+++ b/QScrollBar/README.md
@@ -4,6 +4,7 @@
   - [滚动条样式美化](#1滚动条样式美化)
 
 ## 1、滚动条样式美化
+
 [运行 StyleScrollBar.py](StyleScrollBar.py)
 
 使用QSS和图片对滚动条进行美化(horizontal 横向、vertical 纵向)
diff --git a/QScrollBar/StyleScrollBar.py b/QScrollBar/StyleScrollBar.py
index 96c591d2..f5d4d347 100644
--- a/QScrollBar/StyleScrollBar.py
+++ b/QScrollBar/StyleScrollBar.py
@@ -1,22 +1,22 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月20日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ScrollBar
 @description: 
-'''
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QTextEdit, QApplication
+"""
 import chardet
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QTextEdit, QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QTextEdit, QApplication
 
 
 class Window(QTextEdit):
@@ -38,6 +38,7 @@ def __init__(self, parent=None):
 
 if __name__ == "__main__":
     import sys
+
     app = QApplication(sys.argv)
     app.setApplicationName("滚动条样式")
     app.setApplicationDisplayName("滚动条样式")
diff --git a/QSerialPort/Lib/UiSerialPort.py b/QSerialPort/Lib/UiSerialPort.py
index 0da8c351..934e2156 100644
--- a/QSerialPort/Lib/UiSerialPort.py
+++ b/QSerialPort/Lib/UiSerialPort.py
@@ -6,19 +6,20 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt5 import QtCore, QtWidgets
+
 
 class Ui_FormSerialPort(object):
     def setupUi(self, FormSerialPort):
         FormSerialPort.setObjectName("FormSerialPort")
         FormSerialPort.resize(721, 597)
         FormSerialPort.setStyleSheet("#labelStatus {\n"
-"    border-radius: 13px;\n"
-"    background-color: gray;\n"
-"}\n"
-"#labelStatus[isOn=\"true\"] {\n"
-"    background-color: green;\n"
-"}")
+                                     "    border-radius: 13px;\n"
+                                     "    background-color: gray;\n"
+                                     "}\n"
+                                     "#labelStatus[isOn=\"true\"] {\n"
+                                     "    background-color: green;\n"
+                                     "}")
         self.gridLayout = QtWidgets.QGridLayout(FormSerialPort)
         self.gridLayout.setObjectName("gridLayout")
         self.groupBox = QtWidgets.QGroupBox(FormSerialPort)
@@ -86,7 +87,8 @@ def setupUi(self, FormSerialPort):
         self.labelStatus.setProperty("isOn", False)
         self.labelStatus.setObjectName("labelStatus")
         self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.labelStatus)
-        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum,
+                                           QtWidgets.QSizePolicy.Expanding)
         self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem)
         self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
         self.textBrowser = QtWidgets.QTextBrowser(FormSerialPort)
@@ -166,10 +168,10 @@ def retranslateUi(self, FormSerialPort):
 
 if __name__ == "__main__":
     import sys
+
     app = QtWidgets.QApplication(sys.argv)
     FormSerialPort = QtWidgets.QWidget()
     ui = Ui_FormSerialPort()
     ui.setupUi(FormSerialPort)
     FormSerialPort.show()
     sys.exit(app.exec_())
-
diff --git a/QSerialPort/README.md b/QSerialPort/README.md
index ab9916bb..00c3de0b 100644
--- a/QSerialPort/README.md
+++ b/QSerialPort/README.md
@@ -4,9 +4,10 @@
   - [串口调试小助手](#1串口调试小助手)
 
 ## 1、串口调试小助手
-[运行 SerialDebugAssistant.py](SerialDebugAssistant.py)
 
-用`QSerialPort`写了个类似串口调试小助手的工具, 这个类的官方资料: http://doc.qt.io/qt-5/qserialport.html
+[运行 SerialDebugAssistant.py](SerialDebugAssistant.py) | [查看 UiSerialPort.ui](Data/UiSerialPort.ui)
+
+用`QSerialPort`写了个类似串口调试小助手的工具, 这个类的官方资料: 
 
 1. 通过`QSerialPortInfo.availablePorts()` 获取所有可用的串口
 1. `QSerialPort.setPortName` 设置串口名
@@ -16,5 +17,4 @@
 1. `QSerialPort.setStopBits` 设置停止位
 1. `QSerialPort.setFlowControl` 设置流控制
 
-
-
\ No newline at end of file
+
diff --git a/QSerialPort/SerialDebugAssistant.py b/QSerialPort/SerialDebugAssistant.py
index 0f87638f..5f5c8e41 100644
--- a/QSerialPort/SerialDebugAssistant.py
+++ b/QSerialPort/SerialDebugAssistant.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年11月6日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: SerialDebugAssistant
 @description: 串口调试小助手
@@ -16,13 +16,6 @@
 from Lib.UiSerialPort import Ui_FormSerialPort  # @UnresolvedImport
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
-
 class Window(QWidget, Ui_FormSerialPort):
 
     def __init__(self, *args, **kwargs):
@@ -51,7 +44,7 @@ def on_buttonConnect_clicked(self):
             QMessageBox.critical(self, '错误', '没有选择串口')
             return
         port = self._ports[name]
-#         self._serial.setPort(port)
+        #         self._serial.setPort(port)
         # 根据名字设置串口(也可以用上面的函数)
         self._serial.setPortName(port.systemLocation())
         # 设置波特率
@@ -139,8 +132,10 @@ def closeEvent(self, event):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+
+    cgitb.enable(format='text')
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QSlider/ClickJumpSlider.py b/QSlider/ClickJumpSlider.py
index 3e7f00a3..d12f97a5 100644
--- a/QSlider/ClickJumpSlider.py
+++ b/QSlider/ClickJumpSlider.py
@@ -4,21 +4,20 @@
 """
 Created on 2018年11月5日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ClickJumpSlider
 @description: 
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\
-    QFormLayout, QLabel
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QApplication, QSlider, QStyleOptionSlider, QStyle, QWidget, QFormLayout, \
+        QLabel
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QApplication, QSlider, QStyleOptionSlider, QStyle, QWidget, QFormLayout, \
+        QLabel
 
 
 class ClickJumpSlider(QSlider):
@@ -55,31 +54,34 @@ def __init__(self, *args, **kwargs):
         layout = QFormLayout(self)
 
         self.label1 = QLabel('0', self)
-        layout.addRow(self.label1, ClickJumpSlider(
-            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))
+        self.slider1 = ClickJumpSlider(Qt.Horizontal)
+        self.slider1.valueChanged.connect(lambda v: self.label1.setText(str(v)))
+        layout.addRow(self.label1, self.slider1)
 
         # 横向-反向显示
         self.label2 = QLabel('0', self)
-        layout.addRow(self.label2, ClickJumpSlider(
-            Qt.Horizontal, invertedAppearance=True,
-            valueChanged=lambda v: self.label2.setText(str(v))))
+        self.slider2 = ClickJumpSlider(Qt.Horizontal, invertedAppearance=True)
+        self.slider2.valueChanged.connect(lambda v: self.label2.setText(str(v)))
+        layout.addRow(self.label2, self.slider2)
 
         self.label3 = QLabel('0', self)
-        layout.addRow(self.label3, ClickJumpSlider(
-            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))
+        self.slider3 = ClickJumpSlider(Qt.Vertical, minimumHeight=200)
+        self.slider3.valueChanged.connect(lambda v: self.label3.setText(str(v)))
+        layout.addRow(self.label3, self.slider3)
 
         # 纵向反向显示
         self.label4 = QLabel('0', self)
-        layout.addRow(self.label4, ClickJumpSlider(
-            Qt.Vertical, invertedAppearance=True,
-            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))
+        self.slider4 = ClickJumpSlider(Qt.Vertical, invertedAppearance=True, minimumHeight=200)
+        self.slider4.valueChanged.connect(lambda v: self.label4.setText(str(v)))
+        layout.addRow(self.label4, self.slider4)
 
 
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = DemoWindow()
     w.show()
diff --git a/QSlider/LfSlider.py b/QSlider/LfSlider.py
new file mode 100644
index 00000000..2c900cfa
--- /dev/null
+++ b/QSlider/LfSlider.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/4/9
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: LfSlider
+@description: 降低值变化频率
+"""
+from datetime import datetime
+
+try:
+    from PyQt5.QtCore import pyqtSignal, QTimer, Qt
+    from PyQt5.QtWidgets import QApplication, QSlider, QWidget, QVBoxLayout, QPlainTextEdit, QHBoxLayout, \
+        QGroupBox
+except ImportError:
+    from PySide2.QtCore import Signal as pyqtSignal, QTimer, Qt
+    from PySide2.QtWidgets import QApplication, QSlider, QWidget, QVBoxLayout, QPlainTextEdit, QHBoxLayout, \
+        QGroupBox
+
+
+class LfSlider(QSlider):
+    valueChanged = pyqtSignal(int)
+
+    def __init__(self, *args, **kwargs):
+        delay = kwargs.pop('delay', 500)
+        super(LfSlider, self).__init__(*args, **kwargs)
+        self.lastValue = self.value()
+        self.uTimer = QTimer(self)
+        self.uTimer.timeout.connect(self.onValueChanged)
+        self.uTimer.start(delay)
+
+    def onValueChanged(self):
+        if self.lastValue != self.value():
+            self.lastValue = self.value()
+            self.valueChanged.emit(self.lastValue)
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+
+        # 左侧原始
+        left_group = QGroupBox('原始QSlider', self)
+        left_layout = QVBoxLayout(left_group)
+        self.leftLabel = QPlainTextEdit(self)
+        left_layout.addWidget(self.leftLabel)
+
+        self.leftSlider = QSlider(Qt.Horizontal, self)
+        self.leftSlider.valueChanged.connect(self.onLeftChanged)
+        left_layout.addWidget(self.leftSlider)
+
+        layout.addWidget(left_group)
+
+        # 右侧低频率变化
+        right_group = QGroupBox('LfSlider', self)
+        right_layout = QVBoxLayout(right_group)
+        self.rightLabel = QPlainTextEdit(self)
+        right_layout.addWidget(self.rightLabel)
+
+        self.rightSlider = LfSlider(Qt.Horizontal, self)
+        self.rightSlider.valueChanged.connect(self.onRightChanged)
+        right_layout.addWidget(self.rightSlider)
+
+        layout.addWidget(right_group)
+
+    def onLeftChanged(self, value):
+        self.leftLabel.appendPlainText(datetime.now().strftime("[%H:%M:%S.%f]   ") + str(value))
+
+    def onRightChanged(self, value):
+        self.rightLabel.appendPlainText(datetime.now().strftime("[%H:%M:%S.%f]   ") + str(value))
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QSlider/PaintQSlider.py b/QSlider/PaintQSlider.py
index 0d154e8b..d72a7574 100644
--- a/QSlider/PaintQSlider.py
+++ b/QSlider/PaintQSlider.py
@@ -4,24 +4,18 @@
 """
 Created on 2018年5月15日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PaintQSlider
 @description: 
 """
+
 from PyQt5.QtCore import Qt, QRect, QPointF
 from PyQt5.QtGui import QPainter, QColor
-from PyQt5.QtWidgets import QSlider, QWidget, QVBoxLayout, QProxyStyle, QStyle,\
+from PyQt5.QtWidgets import QSlider, QWidget, QVBoxLayout, QProxyStyle, QStyle, \
     QStyleOptionSlider
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
-
-
 class SliderStyle(QProxyStyle):
 
     def subControlRect(self, control, option, subControl, widget=None):
@@ -124,6 +118,7 @@ def __init__(self, *args, **kwargs):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.setStyleSheet('QWidget {background: gray;}')
diff --git a/QSlider/QssQSlider.py b/QSlider/QssQSlider.py
index af65e491..7b3a0f33 100644
--- a/QSlider/QssQSlider.py
+++ b/QSlider/QssQSlider.py
@@ -4,20 +4,18 @@
 """
 Created on 2018年5月15日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QssQSlider
 @description: 通过QSS美化QSlider
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSlider
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QSlider
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QSlider
 
 StyleSheet = """
 QWidget {
@@ -76,7 +74,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyleSheet(StyleSheet)
     w = Window()
diff --git a/QSlider/README.md b/QSlider/README.md
index 488c2930..ee33d3be 100644
--- a/QSlider/README.md
+++ b/QSlider/README.md
@@ -3,8 +3,10 @@
 - 目录
   - [滑动条点击定位](#1滑动条点击定位)
   - [双层圆环样式](#2双层圆环样式)
+  - [低频率值变化](#3低频率值变化)
 
 ## 1、滑动条点击定位
+
 [运行 ClickJumpSlider.py](ClickJumpSlider.py)
 
 1. `QSlider`对鼠标点击然后跳转到该位置的支持不是很好,通过重写鼠标点击事件`mousePressEvent`来达到效果
@@ -40,7 +42,16 @@ def mousePressEvent(self, event):
 
 
 ## 2、双层圆环样式
+
 [运行 QssQSlider.py](QssQSlider.py) | [运行 PaintQSlider.py](PaintQSlider.py)
 
 
-
\ No newline at end of file
+
+
+## 3、低频率值变化
+
+[运行 LfSlider.py](LfSlider.py)
+
+覆盖了`valueChanged`信号,通过使用定时器来延迟发送值变化,如果无法覆盖信号则可以自定义一个新的信号
+
+
diff --git a/QSlider/ScreenShot/LfSlider.gif b/QSlider/ScreenShot/LfSlider.gif
new file mode 100644
index 00000000..035f3118
Binary files /dev/null and b/QSlider/ScreenShot/LfSlider.gif differ
diff --git a/QSplashScreen/Data/splash.gif b/QSplashScreen/Data/splash.gif
new file mode 100644
index 00000000..8553388e
Binary files /dev/null and b/QSplashScreen/Data/splash.gif differ
diff --git a/QSplashScreen/GifSplashScreen.py b/QSplashScreen/GifSplashScreen.py
new file mode 100644
index 00000000..d409dd04
--- /dev/null
+++ b/QSplashScreen/GifSplashScreen.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/6/11
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file:
+@description: 
+"""
+
+from time import sleep
+
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QMovie
+    from PyQt5.QtWidgets import QApplication, QSplashScreen, QWidget
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QMovie
+    from PySide2.QtWidgets import QApplication, QSplashScreen, QWidget
+
+
+class GifSplashScreen(QSplashScreen):
+
+    def __init__(self, *args, **kwargs):
+        super(GifSplashScreen, self).__init__(*args, **kwargs)
+        self.movie = QMovie('Data/splash.gif')
+        self.movie.frameChanged.connect(self.onFrameChanged)
+        self.movie.start()
+
+    def onFrameChanged(self, _):
+        self.setPixmap(self.movie.currentPixmap())
+
+    def finish(self, widget):
+        self.movie.stop()
+        super(GifSplashScreen, self).finish(widget)
+
+
+class BusyWindow(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(BusyWindow, self).__init__(*args, **kwargs)
+        # 模拟耗时操作,一般来说耗时的加载数据应该放到线程
+        for i in range(5):
+            sleep(1)
+            splash.showMessage('加载进度: %d' % i, Qt.AlignHCenter | Qt.AlignBottom, Qt.white)
+            QApplication.instance().processEvents()
+
+        splash.showMessage('初始化完成', Qt.AlignHCenter | Qt.AlignBottom, Qt.white)
+        splash.finish(self)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+
+    global splash
+    splash = GifSplashScreen()
+    splash.show()
+
+    w = BusyWindow()
+    w.show()
+
+    # 测试二
+    # def createWindow():
+    #     app.w = QWidget()
+    #     # 模拟初始5秒后再显示
+    #     splash.showMessage('等待界面显示', Qt.AlignHCenter | Qt.AlignBottom, Qt.white)
+    #     QTimer.singleShot(3000, lambda: (
+    #         splash.showMessage('初始化完成', Qt.AlignHCenter | Qt.AlignBottom, Qt.white), app.w.show(),
+    #         splash.finish(app.w)))
+
+    # 模拟耗时5秒。但是不能用sleep
+    # 可以使用子线程加载耗时的数据
+    # 主线程中循环设置UI可以配合QApplication.instance().processEvents()
+    # QTimer.singleShot(3000, createWindow)
+
+    splash.showMessage('等待创建界面', Qt.AlignHCenter | Qt.AlignBottom, Qt.white)
+
+    sys.exit(app.exec_())
diff --git a/QSplashScreen/README.en.md b/QSplashScreen/README.en.md
new file mode 100644
index 00000000..e69de29b
diff --git a/QSplashScreen/README.md b/QSplashScreen/README.md
new file mode 100644
index 00000000..6c16f8bb
--- /dev/null
+++ b/QSplashScreen/README.md
@@ -0,0 +1,12 @@
+# QSplashScreen
+
+- 目录
+  - [启动画面动画](#1启动画面动画)
+
+## 1、启动画面动画
+
+[运行 GifSplashScreen.py](GifSplashScreen.py)
+
+结合 `QMovie` 的 `frameChanged` 信号 不停地设置新的pixmap图片
+
+
diff --git a/QSplashScreen/ScreenShot/GifSplashScreen.gif b/QSplashScreen/ScreenShot/GifSplashScreen.gif
new file mode 100644
index 00000000..0c226350
Binary files /dev/null and b/QSplashScreen/ScreenShot/GifSplashScreen.gif differ
diff --git a/QSplitter/README.md b/QSplitter/README.md
index f52a7ca3..e7844300 100644
--- a/QSplitter/README.md
+++ b/QSplitter/README.md
@@ -4,6 +4,7 @@
   - [分割窗口的分割条重绘](#1分割窗口的分割条重绘)
 
 ## 1、分割窗口的分割条重绘
+
 [运行 RewriteHandle.py](RewriteHandle.py)
 
 1. 原理在于`QSplitter`在创建分割条的时候会调用`createHandle`函数
@@ -11,4 +12,4 @@
 1. 通过`QSplitterHandle`的`paintEvent`实现绘制其它形状,
 1. 重写`mousePressEvent`和`mouseMoveEvent`来实现鼠标的其它事件
 
-
\ No newline at end of file
+
diff --git a/QSplitter/RewriteHandle.py b/QSplitter/RewriteHandle.py
index 241f6c28..d69ab630 100644
--- a/QSplitter/RewriteHandle.py
+++ b/QSplitter/RewriteHandle.py
@@ -4,25 +4,23 @@
 """
 Created on 2018年3月21日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: Splitter
 @description: 
 """
 import sys
 
-from PyQt5.QtCore import Qt, QPointF, pyqtSignal
-from PyQt5.QtGui import QPainter, QPolygonF
-from PyQt5.QtWidgets import QTextEdit, QListWidget,\
-    QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle
-
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"\
-
+try:
+    from PyQt5.QtCore import Qt, QPointF, pyqtSignal
+    from PyQt5.QtGui import QPainter, QPolygonF
+    from PyQt5.QtWidgets import QTextEdit, QListWidget, \
+        QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle
+except ImportError:
+    from PySide2.QtCore import Qt, QPointF, Signal as pyqtSignal
+    from PySide2.QtGui import QPainter, QPolygonF
+    from PySide2.QtWidgets import QTextEdit, QListWidget, \
+        QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle
 
 
 class SplitterHandle(QSplitterHandle):
@@ -49,7 +47,7 @@ def mouseMoveEvent(self, event):
         else:
             # 设置默认的鼠标样式并可以移动
             self.setCursor(Qt.SplitHCursor if self.orientation()
-                           == Qt.Horizontal else Qt.SplitVCursor)
+                                              == Qt.Horizontal else Qt.SplitVCursor)
             super(SplitterHandle, self).mouseMoveEvent(event)
 
     def paintEvent(self, event):
diff --git a/QStackedWidget/LeftTabStacked.py b/QStackedWidget/LeftTabStacked.py
index cc5d761e..6c8257d0 100644
--- a/QStackedWidget/LeftTabStacked.py
+++ b/QStackedWidget/LeftTabStacked.py
@@ -1,24 +1,27 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from random import randint
 
-from PyQt5.QtCore import Qt, QSize
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QWidget, QListWidget, QStackedWidget, QHBoxLayout,\
-    QListWidgetItem, QLabel
+"""
+Created on 2018年5月29日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: LeftTabWidget
+@description:
+"""
 
+from random import randint
 
-# Created on 2018年5月29日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: LeftTabWidget
-# description:
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt, QSize
+    from PyQt5.QtGui import QIcon
+    from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QStackedWidget, QHBoxLayout, \
+        QListWidgetItem, QLabel
+except ImportError:
+    from PySide2.QtCore import Qt, QSize
+    from PySide2.QtGui import QIcon
+    from PySide2.QtWidgets import QApplication, QWidget, QListWidget, QStackedWidget, QHBoxLayout, \
+        QListWidgetItem, QLabel
 
 
 class LeftTabWidget(QWidget):
@@ -26,7 +29,7 @@ class LeftTabWidget(QWidget):
     def __init__(self, *args, **kwargs):
         super(LeftTabWidget, self).__init__(*args, **kwargs)
         self.resize(800, 600)
-        #左右布局(左边一个QListWidget + 右边QStackedWidget)
+        # 左右布局(左边一个QListWidget + 右边QStackedWidget)
         layout = QHBoxLayout(self, spacing=0)
         layout.setContentsMargins(0, 0, 0, 0)
         # 左侧列表
@@ -102,7 +105,7 @@ def initUi(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyleSheet(Stylesheet)
     w = LeftTabWidget()
diff --git a/QStackedWidget/README.md b/QStackedWidget/README.md
index 2329fc3e..58996c68 100644
--- a/QStackedWidget/README.md
+++ b/QStackedWidget/README.md
@@ -4,6 +4,7 @@
   - [左侧选项卡](#1左侧选项卡)
 
 ## 1、左侧选项卡
+
 [运行 LeftTabStacked.py](LeftTabStacked.py)
 
 本来使用`QTabWidget`可以实现多标签页面,但是当标签在左侧时会出现文字方向不对的问题,
@@ -14,7 +15,7 @@
 2. 右侧添加`QWidget`的时候有两种方案
     1. 左侧list根据序号来索引,右侧添加widget时给定带序号的变量名,如widget_0,widget_1,widget_2之类的,这样可以直接根据`QListWidget`的序号关联起来
     2. 左侧list添加item时给定右侧对应的widget变量值
-    
-PS: 用设计设的做法 : https://www.jianshu.com/p/dac62b5c225c
+
+PS: 用设计设的做法 : 
 
 
diff --git a/QSystemTrayIcon/MinimizeToTray.py b/QSystemTrayIcon/MinimizeToTray.py
index 24382152..f4325838 100755
--- a/QSystemTrayIcon/MinimizeToTray.py
+++ b/QSystemTrayIcon/MinimizeToTray.py
@@ -1,11 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
 import sys
 
-from PyQt5.QtWidgets import (
-    QApplication, QMainWindow,
-    QLabel, QGridLayout, QWidget,
-    QCheckBox, QSystemTrayIcon,
-    QSpacerItem, QSizePolicy, QMenu, QAction, QStyle, qApp)
-from PyQt5.QtCore import QSize
+try:
+    from PyQt5.QtCore import QSize
+    from PyQt5.QtWidgets import (
+        QApplication, QMainWindow,
+        QLabel, QGridLayout, QWidget,
+        QCheckBox, QSystemTrayIcon,
+        QSpacerItem, QSizePolicy, QMenu, QAction, QStyle)
+except ImportError:
+    from PySide2.QtCore import QSize
+    from PySide2.QtWidgets import (
+        QApplication, QMainWindow,
+        QLabel, QGridLayout, QWidget,
+        QCheckBox, QSystemTrayIcon,
+        QSpacerItem, QSizePolicy, QMenu, QAction, QStyle)
 
 
 class MainWindow(QMainWindow):
@@ -21,14 +32,14 @@ def __init__(self):
         # Be sure to call the super class method
         QMainWindow.__init__(self)
 
-        self.setMinimumSize(QSize(480, 80))             # Set sizes
+        self.setMinimumSize(QSize(480, 80))  # Set sizes
         self.setWindowTitle("System Tray Application")  # Set a title
         # Create a central widget
         central_widget = QWidget(self)
         # Set the central widget
         self.setCentralWidget(central_widget)
 
-        grid_layout = QGridLayout(self)         # Create a QGridLayout
+        grid_layout = QGridLayout(self)  # Create a QGridLayout
         # Set the layout into the central widget
         central_widget.setLayout(grid_layout)
         grid_layout.addWidget(
@@ -56,7 +67,7 @@ def __init__(self):
         hide_action = QAction("Hide", self)
         show_action.triggered.connect(self.show)
         hide_action.triggered.connect(self.hide)
-        quit_action.triggered.connect(qApp.quit)
+        quit_action.triggered.connect(QApplication.instance().quit)
         tray_menu = QMenu()
         tray_menu.addAction(show_action)
         tray_menu.addAction(hide_action)
@@ -82,4 +93,4 @@ def closeEvent(self, event):
     app = QApplication(sys.argv)
     mw = MainWindow()
     mw.show()
-    sys.exit(app.exec())
+    sys.exit(app.exec_())
diff --git a/QSystemTrayIcon/README.md b/QSystemTrayIcon/README.md
index 6e14cf6a..268c3036 100755
--- a/QSystemTrayIcon/README.md
+++ b/QSystemTrayIcon/README.md
@@ -2,6 +2,7 @@
 
 - 目录
   - [最小化到系统托盘](#1最小化到系统托盘)
+  - [系统托盘闪烁](#2系统托盘闪烁)
 
 ## 1、最小化到系统托盘
 
@@ -9,9 +10,12 @@
 
 选择 Minimize to Tray 在关闭窗口时最小化到系统托盘。
 
-> Reference: https://evileg.com/en/post/68/
+> Reference: 
'  # 方式一直接加载本地图片
+            '
'  # 方式二通过addResource添加资源
+            '
'  # 方式三定义自定义的协议头通过loadResource动态加载
+            '
')
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QTextBrowser/README.md b/QTextBrowser/README.md
index e69de29b..dfe873bf 100644
--- a/QTextBrowser/README.md
+++ b/QTextBrowser/README.md
@@ -0,0 +1,15 @@
+# QTextBrowser
+
+- 目录
+  - [动态加载图片](#1动态加载图片)
+
+## 1、动态加载图片
+
+[运行 DynamicRes.py](DynamicRes.py)
+
+动态加载资源有多种方式,这里主要介绍 [addResource](https://doc.qt.io/qt-5/qtextdocument.html#addResource) 和 [loadResource](https://doc.qt.io/qt-5/qtextbrowser.html#loadResource) 函数
+
+1、通过 `self.textBrowser.document().addResource(QTextDocument.ImageResource, QUrl('dynamic:/images/weixin.png'), img)` 向文档中注册新的资源索引,类似QRC
+2、通过重载 `loadResource` 函数可以监听到所有的资源加载,然后动态返回内容
+
+
diff --git a/QTextBrowser/ScreenShot/DynamicRes.gif b/QTextBrowser/ScreenShot/DynamicRes.gif
new file mode 100644
index 00000000..9c923b58
Binary files /dev/null and b/QTextBrowser/ScreenShot/DynamicRes.gif differ
diff --git a/QTextEdit/HighlightText.py b/QTextEdit/HighlightText.py
index d1c8c485..ca7398fd 100644
--- a/QTextEdit/HighlightText.py
+++ b/QTextEdit/HighlightText.py
@@ -1,7 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年5月22日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file:
+@description:
+"""
+
 import sys
-from PyQt5.QtGui import QTextCharFormat, QTextDocument, QTextCursor
-from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit,
-                             QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget)
+
+try:
+    from PyQt5.QtCore import QRegExp
+    from PyQt5.QtGui import QTextCharFormat, QTextCursor
+    from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit,
+                                 QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget)
+except ImportError:
+    from PySide2.QtCore import QRegExp
+    from PySide2.QtGui import QTextCharFormat, QTextCursor
+    from PySide2.QtWidgets import (QApplication, QMainWindow, QTextEdit,
+                                   QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget)
 
 
 class TextEdit(QMainWindow):
@@ -22,30 +42,46 @@ def __init__(self, parent=None):
 
         tb = QToolBar(self)
         tb.addWidget(widget)
+        self.addToolBar(tb)
 
     def setText(self, text):
         self.textEdit.setPlainText(text)
 
-    def mergeFormatOnWordOrSelection(self, format):
-        cursor = self.textEdit.textCursor()
-        if not cursor.hasSelection():
-            cursor.select(QTextCursor.WordUnderCursor)
-        cursor.mergeCharFormat(format)
-        self.textEdit.mergeCurrentCharFormat(format)
-
     def highlight(self):
         text = self.findText.text()  # 输入框中的文字
         if not text:
             return
+
         col = QColorDialog.getColor(self.textEdit.textColor(), self)
         if not col.isValid():
             return
+
+        # 恢复默认的颜色
+        cursor = self.textEdit.textCursor()
+        cursor.select(QTextCursor.Document)
+        cursor.setCharFormat(QTextCharFormat())
+        cursor.clearSelection()
+        self.textEdit.setTextCursor(cursor)
+
+        # 文字颜色
         fmt = QTextCharFormat()
         fmt.setForeground(col)
-        # 先把光标移动到开头
+
+        # 正则
+        expression = QRegExp(text)
         self.textEdit.moveCursor(QTextCursor.Start)
-        while self.textEdit.find(text, QTextDocument.FindWholeWords):  # 查找所有文字
-            self.mergeFormatOnWordOrSelection(fmt)
+        cursor = self.textEdit.textCursor()
+
+        # 循环查找设置颜色
+        pos = 0
+        index = expression.indexIn(self.textEdit.toPlainText(), pos)
+        while index >= 0:
+            cursor.setPosition(index)
+            cursor.movePosition(QTextCursor.Right,
+                                QTextCursor.KeepAnchor, len(text))
+            cursor.mergeCharFormat(fmt)
+            pos = index + expression.matchedLength()
+            index = expression.indexIn(self.textEdit.toPlainText(), pos)
 
 
 if __name__ == '__main__':
diff --git a/QTextEdit/README.md b/QTextEdit/README.md
index 98bec9d8..f6a4a672 100644
--- a/QTextEdit/README.md
+++ b/QTextEdit/README.md
@@ -4,8 +4,9 @@
   - [文本查找高亮](#1文本查找高亮)
 
 ## 1、文本查找高亮
+
 [运行 HighlightText.py](HighlightText.py)
 
 主要用到`mergeCurrentCharFormat`函数
 
-
\ No newline at end of file
+
diff --git a/QThread/InheritQThread.py b/QThread/InheritQThread.py
index ea108018..051f5f6b 100644
--- a/QThread/InheritQThread.py
+++ b/QThread/InheritQThread.py
@@ -4,27 +4,28 @@
 """
 Created on 2018年3月9日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: InheritQThread
 @description: 继承QThread
 """
-from PyQt5.QtCore import QThread, pyqtSignal
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton
 
-
-__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com'
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QThread, pyqtSignal
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton
+except ImportError:
+    from PySide2.QtCore import QThread, Signal as pyqtSignal
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton
 
 
 class Worker(QThread):
-
     valueChanged = pyqtSignal(int)  # 值变化信号
 
     def run(self):
-        print('thread id', int(QThread.currentThreadId()))
+        print('thread id', QThread.currentThread())
         for i in range(1, 101):
+            if self.isInterruptionRequested():
+                break
             print('value', i)
             self.valueChanged.emit(i)
             QThread.sleep(1)
@@ -41,7 +42,7 @@ def __init__(self, *args, **kwargs):
         layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart))
 
         # 当前线程id
-        print('main id', int(QThread.currentThreadId()))
+        print('main id', QThread.currentThread())
 
         # 子线程
         self._thread = Worker(self)
@@ -49,21 +50,26 @@ def __init__(self, *args, **kwargs):
         self._thread.valueChanged.connect(self.progressBar.setValue)
 
     def onStart(self):
-        print('main id', int(QThread.currentThreadId()))
-        self._thread.start()  # 启动线程
+        if not self._thread.isRunning():
+            print('main id', QThread.currentThread())
+            self._thread.start()  # 启动线程
 
     def closeEvent(self, event):
         if self._thread.isRunning():
+            self._thread.requestInterruption()
             self._thread.quit()
+            self._thread.wait()
             # 强制
             # self._thread.terminate()
-        del self._thread
+        self._thread.deleteLater()
         super(Window, self).closeEvent(event)
 
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+    import cgitb
+
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QThread/QuitThread.py b/QThread/QuitThread.py
new file mode 100644
index 00000000..35b7a8e9
--- /dev/null
+++ b/QThread/QuitThread.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/11/27
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QuitThread
+@description: 
+"""
+
+import sys
+from time import time
+
+try:
+    from PyQt5.QtCore import QThread, QCoreApplication, QTimer
+except ImportError:
+    from PySide2.QtCore import QThread, QCoreApplication, QTimer
+
+
+class Thread(QThread):
+
+    def run(self):
+        print('thread id', QThread.currentThread())
+        i = 0
+        while i < 101 and not self.isInterruptionRequested():
+            print('value', i, time())
+            i += 1
+            QThread.msleep(500)
+        print('thread quit')
+
+
+if __name__ == '__main__':
+    app = QCoreApplication(sys.argv)
+    t = Thread()
+    t.finished.connect(app.quit)
+    t.start()
+    # 3秒后退出
+    print('will quit 3s latter')
+    QTimer.singleShot(3000, t.requestInterruption)
+    sys.exit(app.exec_())
diff --git a/QThread/README.md b/QThread/README.md
index ef1ee88c..e1068ded 100644
--- a/QThread/README.md
+++ b/QThread/README.md
@@ -2,21 +2,25 @@
 
 - 目录
   - [继承QThread](#1继承QThread)
-  - [moveToThread](#2moveToThread)
+  - [moveToThread](#2movetothread)
   - [线程挂起恢复](#3线程挂起恢复)
   - [线程休眠唤醒](#4线程休眠唤醒)
+  - [线程退出](#5线程退出)
 
 ## 1、继承QThread
+
 [运行 InheritQThread.py](InheritQThread.py)
 
 
 
 ## 2、moveToThread
+
 [运行 moveToThread.py](moveToThread.py)
 
 
 
 ## 3、线程挂起恢复
+
 [运行 SuspendThread.py](SuspendThread.py)
 
 注意,这里只是简单演示,在应用这些代码时要小心
@@ -30,8 +34,17 @@
 
 
 ## 4、线程休眠唤醒
+
 [运行 WakeupThread.py](WakeupThread.py)
 
 使用 `QWaitCondition` 的 `wait` 和 `wakeAll` 方法
 
-
\ No newline at end of file
+
+
+## 5、线程退出
+
+[运行 QuitThread.py](QuitThread.py)
+
+`isInterruptionRequested` 和 `requestInterruption` 函数作为退出标识调用
+
+
diff --git a/QThread/ScreenShot/QuitThread.jpg b/QThread/ScreenShot/QuitThread.jpg
new file mode 100644
index 00000000..2c96dee2
Binary files /dev/null and b/QThread/ScreenShot/QuitThread.jpg differ
diff --git a/QThread/SuspendThread.py b/QThread/SuspendThread.py
index 707324e6..32c36018 100644
--- a/QThread/SuspendThread.py
+++ b/QThread/SuspendThread.py
@@ -1,26 +1,24 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
+
+"""
+Created on 2018年3月13日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file:
+@description:
+"""
+
 import ctypes
 
+import win32con
 from PyQt5.QtCore import QThread, pyqtSignal
 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton
-import win32con
 from win32process import SuspendThread, ResumeThread
 
 
-# Created on 2018年3月13日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file: 多线程使用.a
-# description:
-__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com'
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
-
 class Worker(QThread):
-
     valueChanged = pyqtSignal(int)  # 值变化信号
     handle = -1
 
@@ -109,8 +107,10 @@ def closeEvent(self, event):
 if __name__ == '__main__':
     import sys
     import os
+
     print('pid', os.getpid())
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QThread/WakeupThread.py b/QThread/WakeupThread.py
index 2f11a806..e9350371 100644
--- a/QThread/WakeupThread.py
+++ b/QThread/WakeupThread.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年11月11日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 
 @description: 
@@ -13,15 +13,7 @@
 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QProgressBar
 
 
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
-
-
 class Thread(QThread):
-
     valueChange = pyqtSignal(int)
 
     def __init__(self, *args, **kwargs):
@@ -75,8 +67,10 @@ def doWake(self):
 if __name__ == '__main__':
     import sys
     import cgitb
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+
+    cgitb.enable(format='text')
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QThread/moveToThread.py b/QThread/moveToThread.py
index ea1602de..77214df1 100644
--- a/QThread/moveToThread.py
+++ b/QThread/moveToThread.py
@@ -4,27 +4,28 @@
 """
 Created on 2018年3月9日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: moveToThread
 @description: moveToThread
 """
-from PyQt5.QtCore import QObject, pyqtSignal, QThread
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton
 
-
-__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com'
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import QObject, pyqtSignal, QThread
+    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton
+except ImportError:
+    from PySide2.QtCore import QObject, Signal as pyqtSignal, QThread
+    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton
 
 
 class Worker(QObject):
-
     valueChanged = pyqtSignal(int)  # 值变化信号
 
     def run(self):
-        print('thread id', int(QThread.currentThreadId()))
+        print('thread id', )
         for i in range(1, 101):
+            if QThread.currentThread().isInterruptionRequested():
+                break
             print('value', i)
             self.valueChanged.emit(i)
             QThread.sleep(1)
@@ -41,33 +42,38 @@ def __init__(self, *args, **kwargs):
         layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart))
 
         # 当前线程id
-        print('main id', int(QThread.currentThreadId()))
+        print('main id', QThread.currentThread())
 
         # 启动线程更新进度条值
         self._thread = QThread(self)
         self._worker = Worker()
         self._worker.moveToThread(self._thread)  # 移动到线程中执行
         self._thread.finished.connect(self._worker.deleteLater)
+        self._thread.started.connect(self._worker.run)
         self._worker.valueChanged.connect(self.progressBar.setValue)
 
     def onStart(self):
-        print('main id', int(QThread.currentThreadId()))
-        self._thread.started.connect(self._worker.run)
-        self._thread.start()  # 启动线程
+        if not self._thread.isRunning():
+            print('main id', QThread.currentThread())
+            self._thread.start()  # 启动线程
 
     def closeEvent(self, event):
         if self._thread.isRunning():
+            self._thread.requestInterruption()
             self._thread.quit()
+            self._thread.wait()
             # 强制
             # self._thread.terminate()
-        del self._thread
-        del self._worker
+        self._thread.deleteLater()
         super(Window, self).closeEvent(event)
 
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+    import cgitb
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QTreeView/Data/serializewidget.ui b/QTreeView/Data/serializewidget.ui
new file mode 100644
index 00000000..9204a9e3
--- /dev/null
+++ b/QTreeView/Data/serializewidget.ui
@@ -0,0 +1,271 @@
+
+
+ SerializeWidget 
+ 
+  
+   
+    0 
+    0 
+    800 
+    600 
+    
+   
+  
+   TestSerialize 
+   
+  
+   - 
+    
+     
+      Qt::Horizontal +
 +
+      false +
 +
+      
+       
+        800 +16777215 +
 +
 +
+       Json View +
 +
+       - 
+        +- 
+        + +
 +
+      
+       Widget View +
 +
+       - 
+        
+         
+          0 +
 +
+          
+           Input +
 +
+           - 
+            +- 
+            
+             
+              CheckBox +
 +
 +
+- 
+            
+             
+              RadioButton +
 +
 +
+- 
+            +- 
+            +- 
+            +- 
+            +- 
+            +- 
+            +- 
+            
+             
+              Qt::Horizontal +
 +
+              
+               40 +20 +
 +
 +
 +
+- 
+            
+             
+              Qt::Vertical +
 +
+              
+               20 +40 +
 +
 +
 +
+ +
 +
+          
+           Edit +
 +
+           - 
+            +- 
+            + +
 +
+          
+           Correlation +
 +
+           - 
+            
+             
+              Qt::Horizontal +
 +
 +
+- 
+            +- 
+            
+             
+              Qt::Vertical +
 +
+              
+               20 +40 +
 +
 +
 +
+- 
+            
+             
+              0 +
 +
+              Qt::Vertical +
 +
 +
+- 
+            
+             
+              Qt::Vertical +
 +
 +
+ +
 +
+          
+           View +
 +
+           - 
+            
+             
+              ListView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+- 
+            
+             
+              TreeView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+- 
+            
+             
+              TableView +
 +
+              
+               2 +
 +
+               2 +
 +
+               2 +
 +
+               2 +
 +
- 
+               + +
 +
+ +
 +
 +
+ +
 +
 +
+ 
+  
+  
diff --git a/QTreeView/Lib/qjsonmodel.py b/QTreeView/Lib/qjsonmodel.py
new file mode 100644
index 00000000..336ddbd6
--- /dev/null
+++ b/QTreeView/Lib/qjsonmodel.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/05
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: qjsonmodel.py
+@description:
+"""
+
+import json
+from functools import reduce
+from typing import Any, List, Union
+
+try:
+    from PyQt5.QtCore import QObject, Qt
+    from PyQt5.QtGui import QStandardItem, QStandardItemModel
+except ImportError:
+    from PySide2.QtCore import QObject, Qt
+    from PySide2.QtGui import QStandardItem, QStandardItemModel
+
+
+class QJsonItem(QStandardItem):
+    Sep = "/"
+    EditRole = Qt.EditRole
+    KeyRole = Qt.UserRole + 1
+    ValueRole = Qt.UserRole + 2
+    DatasRole = Qt.UserRole + 3
+    PathRole = Qt.UserRole + 4
+
+    def __init__(
+        self,
+        *args,
+        key: Union["QJsonItem", None] = None,  # 上一级key item
+        value: Union["QJsonItem", None] = None,  # 当前value item
+        editAble: bool = True,
+        role: int = Qt.UserRole + 1,
+    ):
+        super().__init__(*args)
+        self.setInfo(key, value, None, editAble, role)
+
+    def clear(self):
+        for _ in range(self.rowCount()):
+            self.removeRow(0)
+
+    def setInfo(
+        self,
+        key: Union["QJsonItem", None] = None,
+        value: Union["QJsonItem", None] = None,
+        datas: Any = None,
+        editAble: bool = True,
+        role: int = Qt.UserRole + 1,
+        edit=None,
+    ):
+        self.setEditable(editAble)
+        self._role = role
+        self.keyItem = key
+        self.valueItem = value
+        self.edit = edit
+        self.datas = datas
+
+    @property
+    def keyItem(self) -> Union["QJsonItem", None]:
+        # 上一级key item
+        item = self.data(self.KeyRole)
+        if not isinstance(item, QJsonItem):
+            return None
+        return item
+
+    @keyItem.setter
+    def keyItem(self, value: "QJsonItem") -> None:
+        self.setData(value, self.KeyRole)
+
+    @property
+    def valueItem(self) -> "QJsonItem":
+        # 当前value item
+        return self.data(self.ValueRole)
+
+    @valueItem.setter
+    def valueItem(self, value: "QJsonItem") -> None:
+        self.setData(value, self.ValueRole)
+
+    @property
+    def edit(self) -> Any:
+        # 设置角色为EditRole的值
+        return self.data(self.EditRole)
+
+    @edit.setter
+    def edit(self, value: Any) -> None:
+        if value is None:
+            return
+        self.setData(value, self.EditRole)
+
+    @property
+    def datas(self) -> Any:
+        return self.data(self.DatasRole)
+
+    @datas.setter
+    def datas(self, value: Any) -> None:
+        # key item中的原始数据设置
+        self.clear()
+        self.setData(value, self.DatasRole)
+        if self._role == QJsonItem.ValueRole:
+            if not isinstance(value, (list, tuple, dict)):
+                self.edit = value
+            return
+        self.__loadData(value)
+
+    @property
+    def type(self) -> Any:
+        return type(self.edit)
+
+    @property
+    def role(self) -> int:
+        return self._role
+
+    @property
+    def path(self) -> str:
+        item = self.keyItem if self._role == self.ValueRole else self
+        paths = []
+
+        while item:
+            paths.append(item.text())
+            item = item.keyItem
+
+        paths.reverse()
+        return QJsonItem.Sep.join(paths)
+
+    def data(self, role: int = Qt.DisplayRole) -> Any:
+        if role == self.PathRole:
+            return self.path
+
+        return super().data(role)
+
+    def setData(self, data: Any, role: int = Qt.EditRole):
+        super().setData(data, role)
+
+    def __loadData(self, data: Any) -> None:
+        # 数组类型的key是索引, 不允许修改
+        isArray = isinstance(data, (list, tuple))
+        keyEditAble = not isArray
+        datas = (
+            data.items()
+            if isinstance(data, dict)
+            else enumerate(data)
+            if isArray
+            else []
+        )
+
+        for key, value in datas:
+            itemKey = QJsonItem(str(key))
+            itemValue = QJsonItem()
+
+            # 先添加items
+            self.appendRow([itemKey, itemValue])
+
+            itemKey.setInfo(self, itemValue, value, keyEditAble, edit=key)
+            # key对应的后面的空列不允许修改
+            valueEditAble = not isinstance(value, (list, tuple, dict))
+            itemValue.setInfo(itemKey, None, value, valueEditAble, QJsonItem.ValueRole)
+
+    def updateValue(self, value: Any) -> bool:
+        itemValue: Union[QJsonItem, None] = self.valueItem
+        if itemValue is None:
+            return False
+        if itemValue.datas == value:
+            return True
+
+        self.datas = value
+        # key对应的后面的空列不允许修改
+        valueEditAble = not isinstance(value, (list, tuple, dict))
+        itemValue.setInfo(self, None, value, valueEditAble, QJsonItem.ValueRole)
+
+        return True
+
+    def toObject(self) -> Any:
+        datas = self.datas
+        typ = type(datas)
+
+        if typ is dict:
+            return {
+                self.child(i, 0).text(): self.child(i, 1).toObject()
+                for i in range(self.rowCount())
+            }
+        elif typ is list:
+            return [self.child(i, 0).toObject() for i in range(self.rowCount())]
+        elif self.valueItem:
+            return self.valueItem.toObject()
+
+        return self.edit
+
+
+class QJsonModel(QStandardItemModel):
+    def __init__(
+        self,
+        parent: QObject = None,
+        data: dict = None,
+    ) -> None:
+        super().__init__(parent)
+        self.setHorizontalHeaderLabels(["Key", "Value"])
+        self.loadData(data)
+
+    def loadFile(
+        self, file: str, encoding: str = "utf-8", errors: str = "ignore"
+    ) -> bool:
+        with open(file, "rb") as f:
+            return self.loadJson(f.read().decode(encoding=encoding, errors=errors))
+
+    def loadJson(self, string: str) -> bool:
+        return self.loadData(json.loads(string))
+
+    def loadData(self, data: dict, force: bool = False) -> bool:
+        if isinstance(data, dict):
+            if force:
+                self.clear()
+            self.__loadData(data)
+            return True
+
+        return False
+
+    def horizontalHeaderLabels(self) -> List[str]:
+        return [self.horizontalHeaderItem(i).text() for i in range(self.columnCount())]
+
+    def clear(self):
+        headers = self.horizontalHeaderLabels()
+        super().clear()
+        self.setHorizontalHeaderLabels(headers)
+
+    def findPath(
+        self,
+        path: str,
+        flags=Qt.MatchFixedString
+        | Qt.MatchCaseSensitive
+        | Qt.MatchWrap
+        | Qt.MatchRecursive,
+    ) -> Union[QJsonItem, None]:
+        indexes = self.match(self.index(0, 0), QJsonItem.PathRole, path, -1, flags)
+        indexes = [index for index in indexes if index.isValid()]
+        return self.itemFromIndex(indexes[0]) if indexes else None  # type: ignore
+
+    def updateValue(
+        self, path: str, value: Any, item: Union[QJsonItem, None] = None
+    ) -> bool:
+        item = item or self.findPath(path)
+        if item is None:
+            keys = path.split(QJsonItem.Sep)
+            self.__loadData(reduce(lambda val, key: {key: val}, reversed(keys), value))
+            return True
+
+        return item.updateValue(value)
+
+    def __findItem(
+        self, key: str, parent=None
+    ) -> Union[QStandardItem, "QJsonItem", None]:
+        parent = parent or self.invisibleRootItem()
+        for row in range(parent.rowCount()):
+            item = parent.child(row)
+            if item.text() == key:
+                return item
+
+        return None
+
+    def __createItem(self, key: str, value: Any, parent=None):
+        parent = parent or self.invisibleRootItem()
+
+        itemKey = QJsonItem(str(key))
+        itemValue = QJsonItem()
+        parent.appendRow([itemKey, itemValue])
+
+        itemKey.setInfo(parent, itemValue, value, edit=key)
+        # key对应的后面的空列不允许修改
+        valueEditAble = not isinstance(value, (list, tuple, dict))
+        itemValue.setInfo(itemKey, None, value, valueEditAble, QJsonItem.ValueRole)
+
+    def __loadData(self, data: Any, parent=None):
+        if not isinstance(data, dict):  # 更新值
+            itemKey = parent
+            if itemKey:
+                itemKey.updateValue(data)
+            return
+
+        parent = parent or self.invisibleRootItem()
+
+        for key, value in data.items():
+            key = str(key)
+            itemKey = self.__findItem(key, parent)
+            if not itemKey:
+                self.__createItem(key, value, parent)
+            else:
+                self.__loadData(value, itemKey)
+
+    def toDict(self) -> dict:
+        item = self.invisibleRootItem()
+
+        return {
+            item.child(i).text(): item.child(i).toObject()
+            for i in range(item.rowCount())
+        }
+
+    def toJson(self, ensure_ascii=False, indent=None, **kwargs) -> str:
+        return json.dumps(
+            self.toDict(), ensure_ascii=ensure_ascii, indent=indent, **kwargs
+        )
diff --git a/QTreeView/Lib/qmodelmapper.py b/QTreeView/Lib/qmodelmapper.py
new file mode 100644
index 00000000..9d1dc854
--- /dev/null
+++ b/QTreeView/Lib/qmodelmapper.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/08
+@file: qmodelmapper.py
+@description:
+"""
+
+from copy import deepcopy
+from typing import Any, List, Union
+
+from Lib.qjsonmodel import QJsonModel
+
+try:
+    from PyQt5.QtCore import (
+        QDateTime,
+        QMetaProperty,
+        QModelIndex,
+        QObject,
+        Qt,
+        QTimer,
+    )
+    from PyQt5.QtCore import pyqtSignal as Signal
+    from PyQt5.QtWidgets import QWidget
+except ImportError:
+    from PySide2.QtCore import (
+        QDateTime,
+        QMetaProperty,
+        QModelIndex,
+        QObject,
+        Qt,
+        QTimer,
+        Signal,
+    )
+    from PySide2.QtWidgets import QWidget
+
+
+class QModelMapper(QObject):
+    Debug = False
+    valueChanged = Signal()
+    Signal = (
+        "dateTimeChanged",
+        "currentTextChanged",
+        "valueChanged",
+        "toggled",
+        "textChanged",
+    )
+    Props = (
+        "dateTime",
+        "html",
+        "plainText",
+        "currentText",
+        "checked",
+        "value",
+        "text",
+    )
+
+    def __init__(self, *args, **kwargs):
+        self._delay = kwargs.pop("delay", 50)
+        data = kwargs.pop("data", {})
+        super().__init__(*args, **kwargs)
+        self._old = deepcopy(data)
+        self._widgetkey = {}
+        self._keywidget = {}
+        self._timer = QTimer(self)
+        self._timer.setSingleShot(True)
+        self._timer.timeout.connect(self.valueChanged.emit)
+        self._model = QJsonModel(self.parent(), data=data)
+        self._model.dataChanged.connect(self.onItemDataChanged)
+
+    def bind(self, widget: QWidget, key: str, default: Any = None, prop: str = ""):
+        if widget not in self._widgetkey:
+            self._widgetkey[widget] = {
+                "key": key,
+                "prop": self.getProperty(widget, prop),
+            }
+            self._setValue(widget, key, default)
+
+        # record all widgets for key
+        if key not in self._keywidget:
+            self._keywidget[key] = set()
+        self._keywidget[key].add(widget)
+
+        for signal in self.Signal:
+            signal = getattr(widget, signal, None)
+            if signal:
+                signal.connect(self._setData)
+                break
+
+    def isModify(self) -> bool:
+        return self._model.toDict() != self._old
+
+    def getModel(self) -> QJsonModel:
+        return self._model
+
+    def getData(self) -> dict:
+        return self._model.toDict()
+
+    def getJson(self, ensure_ascii=False, indent=None, **kwargs) -> str:
+        return self._model.toJson(ensure_ascii=ensure_ascii, indent=indent, **kwargs)
+
+    def setData(self, data: dict, force: bool = False):
+        if force:
+            self._old = deepcopy(data)
+        else:
+            self._old.update(data)
+
+        if force:
+            self._model.blockSignals(True)
+        self._model.loadData(data, force)
+        if force:
+            self._model.blockSignals(False)
+
+    def getProperty(
+        self, widget: QWidget, prop: str = ""
+    ) -> Union[QMetaProperty, None]:
+        qmo = widget.metaObject()
+        props = [prop] if prop else self.Props
+        widgetProps = (
+            qmo.property(i)
+            for i in range(
+                QWidget.staticMetaObject.propertyCount(), qmo.propertyCount()
+            )
+        )
+        widgetProps = [
+            p for p in widgetProps if p and p.isReadable() and p.isWritable()
+        ]
+
+        rets = [p for prop in props for p in widgetProps if p.name() == prop]
+        return rets[0] if rets else None
+
+    def _getDefault(self, widget: QWidget, default: Any = None):
+        if default:
+            return default
+
+        prop: Union[QMetaProperty, None] = self._widgetkey[widget]["prop"]
+        if prop is None:
+            return None
+
+        value = prop.read(widget)
+        if value is not None:
+            if prop.name() == "dateTime":
+                return value.toString("yyyy-MM-dd HH:mm:ss")
+            elif prop.name() == "html" and len(widget.property("plainText")) == 0:
+                return ""
+            return value
+
+        return None
+
+    def _setValue(
+        self,
+        widget: QWidget,
+        key: str,
+        default: Any = None,
+        updated: bool = False,
+        block=False,
+    ):
+        created = False
+        value: Any = default
+
+        if not updated:
+            item = self._model.findPath(key)
+            if item is None:
+                value = self._getDefault(widget, default)
+                created = self._model.updateValue(key, value, item)
+            else:
+                # value from model
+                if item.valueItem:
+                    value = item.valueItem.edit
+                    created = default != value
+                block = True
+            if value is None:
+                return
+
+        if block:
+            widget.blockSignals(True)
+
+        prop: Union[QMetaProperty, None] = self._widgetkey[widget]["prop"]
+        if prop is not None:
+            if prop.name() == "dateTime" and isinstance(value, str):
+                value = QDateTime.fromString(value, Qt.ISODate)
+            prop.write(widget, value)
+            if created:
+                self._timer.start(self._delay)
+
+        if block:
+            widget.blockSignals(False)
+
+    def _setData(self, *args, **kwargs):
+        sender = self.sender()
+        info = self._widgetkey.get(sender, {})
+        key = info.get("key", None)
+        prop: Union[QMetaProperty, None] = info.get("prop", None)
+        if key is None or prop is None:
+            return
+
+        value = prop.read(sender)
+        if value is not None:
+            if prop.name() == "dateTime":
+                value = value.toString("yyyy-MM-dd HH:mm:ss")
+            elif prop.name() == "html" and len(sender.property("plainText")) == 0:
+                value = ""
+
+        if value is None:
+            return
+
+        self._timer.start(self._delay)
+
+        # 更新关联的widget
+        for widget in self._keywidget.get(key, []):
+            if widget != sender:
+                if self.Debug:
+                    print(f"view to view({widget}) = {value}")
+                self._setValue(widget, key, value, updated=True, block=True)
+
+        # 更新关联的model
+        if self.Debug:
+            print(f"view({sender}) to model = {value}")
+        if not self._model.updateValue(key, value):
+            return
+
+    def onItemDataChanged(
+        self, topLeft: QModelIndex, bottomRight: QModelIndex, roles: List[int]
+    ):
+        item = self._model.itemFromIndex(topLeft)
+        if not item or Qt.EditRole not in roles or item.valueItem:
+            return
+
+        path = getattr(item, "path", None)
+        if not path:
+            return
+
+        # 更新关联的widget
+        value = item.edit
+        for widget in self._keywidget.get(path, []):
+            if self.Debug:
+                print(f"model to view({widget}) = {value}")
+            self._setValue(widget, path, value, updated=True, block=True)
diff --git a/QTreeView/Lib/serializewidget.py b/QTreeView/Lib/serializewidget.py
new file mode 100644
index 00000000..fcd98edb
--- /dev/null
+++ b/QTreeView/Lib/serializewidget.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+try:
+    from PyQt5 import QtCore, QtGui, QtWidgets
+except ImportError:
+    from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_SerializeWidget(object):
+    def setupUi(self, SerializeWidget):
+        SerializeWidget.setObjectName("SerializeWidget")
+        SerializeWidget.resize(800, 600)
+        self.verticalLayout = QtWidgets.QVBoxLayout(SerializeWidget)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.splitter = QtWidgets.QSplitter(SerializeWidget)
+        self.splitter.setOrientation(QtCore.Qt.Horizontal)
+        self.splitter.setChildrenCollapsible(False)
+        self.splitter.setObjectName("splitter")
+        self.groupBox = QtWidgets.QGroupBox(self.splitter)
+        self.groupBox.setMaximumSize(QtCore.QSize(800, 16777215))
+        self.groupBox.setObjectName("groupBox")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.treeJsonView = QtWidgets.QTreeView(self.groupBox)
+        self.treeJsonView.setObjectName("treeJsonView")
+        self.horizontalLayout.addWidget(self.treeJsonView)
+        self.editJsonView = QtWidgets.QTextEdit(self.groupBox)
+        self.editJsonView.setObjectName("editJsonView")
+        self.horizontalLayout.addWidget(self.editJsonView)
+        self.groupBox_2 = QtWidgets.QGroupBox(self.splitter)
+        self.groupBox_2.setObjectName("groupBox_2")
+        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2)
+        self.verticalLayout_3.setObjectName("verticalLayout_3")
+        self.tabWidget = QtWidgets.QTabWidget(self.groupBox_2)
+        self.tabWidget.setObjectName("tabWidget")
+        self.tab = QtWidgets.QWidget()
+        self.tab.setObjectName("tab")
+        self.gridLayout = QtWidgets.QGridLayout(self.tab)
+        self.gridLayout.setObjectName("gridLayout")
+        self.doubleSpinBox = QtWidgets.QDoubleSpinBox(self.tab)
+        self.doubleSpinBox.setObjectName("doubleSpinBox")
+        self.gridLayout.addWidget(self.doubleSpinBox, 2, 1, 1, 1)
+        self.checkBox = QtWidgets.QCheckBox(self.tab)
+        self.checkBox.setObjectName("checkBox")
+        self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1)
+        self.radioButton = QtWidgets.QRadioButton(self.tab)
+        self.radioButton.setObjectName("radioButton")
+        self.gridLayout.addWidget(self.radioButton, 0, 0, 1, 1)
+        self.spinBox = QtWidgets.QSpinBox(self.tab)
+        self.spinBox.setObjectName("spinBox")
+        self.gridLayout.addWidget(self.spinBox, 2, 0, 1, 1)
+        self.lineEdit = QtWidgets.QLineEdit(self.tab)
+        self.lineEdit.setObjectName("lineEdit")
+        self.gridLayout.addWidget(self.lineEdit, 5, 0, 1, 2)
+        self.dateTimeEdit = QtWidgets.QDateTimeEdit(self.tab)
+        self.dateTimeEdit.setObjectName("dateTimeEdit")
+        self.gridLayout.addWidget(self.dateTimeEdit, 4, 0, 1, 2)
+        self.comboBox = QtWidgets.QComboBox(self.tab)
+        self.comboBox.setObjectName("comboBox")
+        self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
+        self.timeEdit = QtWidgets.QTimeEdit(self.tab)
+        self.timeEdit.setObjectName("timeEdit")
+        self.gridLayout.addWidget(self.timeEdit, 3, 0, 1, 1)
+        self.dateEdit = QtWidgets.QDateEdit(self.tab)
+        self.dateEdit.setObjectName("dateEdit")
+        self.gridLayout.addWidget(self.dateEdit, 3, 1, 1, 1)
+        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.gridLayout.addItem(spacerItem, 0, 3, 1, 1)
+        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.gridLayout.addItem(spacerItem1, 6, 0, 1, 1)
+        self.tabWidget.addTab(self.tab, "")
+        self.tab_2 = QtWidgets.QWidget()
+        self.tab_2.setObjectName("tab_2")
+        self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_2)
+        self.verticalLayout_4.setObjectName("verticalLayout_4")
+        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
+        self.plainTextEdit.setObjectName("plainTextEdit")
+        self.verticalLayout_4.addWidget(self.plainTextEdit)
+        self.textEdit = QtWidgets.QTextEdit(self.tab_2)
+        self.textEdit.setObjectName("textEdit")
+        self.verticalLayout_4.addWidget(self.textEdit)
+        self.tabWidget.addTab(self.tab_2, "")
+        self.tab_3 = QtWidgets.QWidget()
+        self.tab_3.setObjectName("tab_3")
+        self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_3)
+        self.gridLayout_2.setObjectName("gridLayout_2")
+        self.horizontalSlider = QtWidgets.QSlider(self.tab_3)
+        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
+        self.horizontalSlider.setObjectName("horizontalSlider")
+        self.gridLayout_2.addWidget(self.horizontalSlider, 1, 0, 1, 1)
+        self.spinBox_2 = QtWidgets.QSpinBox(self.tab_3)
+        self.spinBox_2.setObjectName("spinBox_2")
+        self.gridLayout_2.addWidget(self.spinBox_2, 0, 0, 1, 1)
+        spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        self.gridLayout_2.addItem(spacerItem2, 2, 0, 1, 1)
+        self.progressBar = QtWidgets.QProgressBar(self.tab_3)
+        self.progressBar.setProperty("value", 0)
+        self.progressBar.setOrientation(QtCore.Qt.Vertical)
+        self.progressBar.setObjectName("progressBar")
+        self.gridLayout_2.addWidget(self.progressBar, 0, 1, 3, 1)
+        self.verticalSlider = QtWidgets.QSlider(self.tab_3)
+        self.verticalSlider.setOrientation(QtCore.Qt.Vertical)
+        self.verticalSlider.setObjectName("verticalSlider")
+        self.gridLayout_2.addWidget(self.verticalSlider, 0, 2, 3, 1)
+        self.tabWidget.addTab(self.tab_3, "")
+        self.tab_4 = QtWidgets.QWidget()
+        self.tab_4.setObjectName("tab_4")
+        self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_4)
+        self.gridLayout_3.setObjectName("gridLayout_3")
+        self.groupBox_3 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_3.setObjectName("groupBox_3")
+        self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_3)
+        self.verticalLayout_6.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_6.setObjectName("verticalLayout_6")
+        self.listView = QtWidgets.QListView(self.groupBox_3)
+        self.listView.setObjectName("listView")
+        self.verticalLayout_6.addWidget(self.listView)
+        self.gridLayout_3.addWidget(self.groupBox_3, 0, 0, 1, 1)
+        self.groupBox_4 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_4.setObjectName("groupBox_4")
+        self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_4)
+        self.verticalLayout_7.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_7.setObjectName("verticalLayout_7")
+        self.treeView = QtWidgets.QTreeView(self.groupBox_4)
+        self.treeView.setObjectName("treeView")
+        self.verticalLayout_7.addWidget(self.treeView)
+        self.gridLayout_3.addWidget(self.groupBox_4, 0, 1, 1, 1)
+        self.groupBox_5 = QtWidgets.QGroupBox(self.tab_4)
+        self.groupBox_5.setObjectName("groupBox_5")
+        self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_5)
+        self.verticalLayout_5.setContentsMargins(2, 2, 2, 2)
+        self.verticalLayout_5.setObjectName("verticalLayout_5")
+        self.tableView = QtWidgets.QTableView(self.groupBox_5)
+        self.tableView.setObjectName("tableView")
+        self.verticalLayout_5.addWidget(self.tableView)
+        self.gridLayout_3.addWidget(self.groupBox_5, 1, 0, 1, 2)
+        self.tabWidget.addTab(self.tab_4, "")
+        self.verticalLayout_3.addWidget(self.tabWidget)
+        self.verticalLayout.addWidget(self.splitter)
+
+        self.retranslateUi(SerializeWidget)
+        self.tabWidget.setCurrentIndex(0)
+        QtCore.QMetaObject.connectSlotsByName(SerializeWidget)
+
+    def retranslateUi(self, SerializeWidget):
+        _translate = QtCore.QCoreApplication.translate
+        SerializeWidget.setWindowTitle(_translate("SerializeWidget", "TestSerialize"))
+        self.groupBox.setTitle(_translate("SerializeWidget", "Json View"))
+        self.groupBox_2.setTitle(_translate("SerializeWidget", "Widget View"))
+        self.checkBox.setText(_translate("SerializeWidget", "CheckBox"))
+        self.radioButton.setText(_translate("SerializeWidget", "RadioButton"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("SerializeWidget", "Input"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("SerializeWidget", "Edit"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("SerializeWidget", "Correlation"))
+        self.groupBox_3.setTitle(_translate("SerializeWidget", "ListView"))
+        self.groupBox_4.setTitle(_translate("SerializeWidget", "TreeView"))
+        self.groupBox_5.setTitle(_translate("SerializeWidget", "TableView"))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("SerializeWidget", "View"))
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    SerializeWidget = QtWidgets.QWidget()
+    ui = Ui_SerializeWidget()
+    ui.setupUi(SerializeWidget)
+    SerializeWidget.show()
+    sys.exit(app.exec_())
diff --git a/QTreeView/README.md b/QTreeView/README.md
index e69de29b..79327648 100644
--- a/QTreeView/README.md
+++ b/QTreeView/README.md
@@ -0,0 +1,24 @@
+# QTreeView
+
+- 目录
+  - [通过json数据生成树形结构](#1通过json数据生成树形结构)
+  - [json树形结构查询和修改](#2json树形结构查询和修改)
+  - [json数据绑定](#3json数据绑定)
+
+## 1、通过json数据生成树形结构
+
+[运行 TestJsonModel.py](TestJsonModel.py)
+
+
+
+## 2、json树形结构查询和修改
+
+[运行 TestModelModify.py](TestModelModify.py)
+
+
+
+## 3、json数据绑定
+
+[运行 TestSerializeModel.py](TestSerializeModel.py)
+
+
diff --git a/QTreeView/ScreenShot/TestJsonModel.gif b/QTreeView/ScreenShot/TestJsonModel.gif
new file mode 100644
index 00000000..d91b7176
Binary files /dev/null and b/QTreeView/ScreenShot/TestJsonModel.gif differ
diff --git a/QTreeView/ScreenShot/TestModelModify.gif b/QTreeView/ScreenShot/TestModelModify.gif
new file mode 100644
index 00000000..834dc413
Binary files /dev/null and b/QTreeView/ScreenShot/TestModelModify.gif differ
diff --git a/QTreeView/ScreenShot/TestSerializeModel.gif b/QTreeView/ScreenShot/TestSerializeModel.gif
new file mode 100644
index 00000000..ca54f485
Binary files /dev/null and b/QTreeView/ScreenShot/TestSerializeModel.gif differ
diff --git a/QTreeView/TestJsonModel.py b/QTreeView/TestJsonModel.py
new file mode 100644
index 00000000..febf6fe7
--- /dev/null
+++ b/QTreeView/TestJsonModel.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/05
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TestJsonModel.py
+@description:
+"""
+
+import sys
+
+from Lib.qjsonmodel import QJsonItem, QJsonModel
+
+try:
+    from PyQt5.QtCore import QModelIndex, QSortFilterProxyModel, Qt
+    from PyQt5.QtWidgets import (
+        QAction,
+        QApplication,
+        QCheckBox,
+        QGridLayout,
+        QLineEdit,
+        QPlainTextEdit,
+        QTreeView,
+        QWidget,
+    )
+except ImportError:
+    from PySide2.QtCore import QModelIndex, QSortFilterProxyModel, Qt
+    from PySide2.QtWidgets import (
+        QAction,
+        QApplication,
+        QCheckBox,
+        QGridLayout,
+        QLineEdit,
+        QPlainTextEdit,
+        QTreeView,
+        QWidget,
+    )
+
+
+class TestWindow(QWidget):
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+        self.resize(800, 600)
+        layout = QGridLayout(self)
+        self.filterPath = QCheckBox("Path Filter?", self)
+        self.filterEdit = QLineEdit(self)
+        self.treeView = QTreeView(self)
+        self.widgetEdit = QPlainTextEdit(self)
+        layout.addWidget(self.filterPath, 0, 0, 1, 1)
+        layout.addWidget(self.filterEdit, 0, 1, 1, 1)
+        layout.addWidget(self.treeView, 1, 0, 1, 2)
+        layout.addWidget(self.widgetEdit, 0, 2, 2, 1)
+
+        self.filterPath.toggled.connect(self.onFilterPathToggled)
+        self.filterEdit.setPlaceholderText("过滤条件")
+        self.filterEdit.textChanged.connect(self.onFilterTextChanged)
+
+        self.model = QJsonModel(self)
+        self.model.itemChanged.connect(self.onItemChanged)
+        self.model.rowsRemoved.connect(self.onRowsRemoved)
+        self.treeView.clicked.connect(self.onItemChanged)
+        action = QAction("Delete", self.treeView)
+        action.triggered.connect(self.deleteItem)
+        self.treeView.setContextMenuPolicy(Qt.ActionsContextMenu)
+        self.treeView.addAction(action)
+
+        # setup model data
+        self.setupModel()
+
+        # filter model
+        self.fmodel = QSortFilterProxyModel(self)
+        self.fmodel.setSourceModel(self.model)
+        self.fmodel.setRecursiveFilteringEnabled(True)
+        self.treeView.setModel(self.fmodel)
+        self.treeView.expandAll()
+
+    def setupModel(self):
+        data = {
+            "name": "Irony",
+            "age": 33,
+            "address": {
+                "country": "China",
+                "city": "Chengdu",
+            },
+            "phone": [
+                {"type": "home", "number": "123456789"},
+                {"type": "fax", "number": "6987654321"},
+            ],
+            "marriage": False,
+            "salary": 6666.6,
+            "skills": [
+                "C++",
+                "Python",
+                "Java",
+                "JavaScript",
+                "Shell",
+                "Android",
+            ],
+            "others": [
+                [1, 2, 3],
+                [4, 5, 6],
+                [7, 8, 9],
+            ],
+        }
+        self.model.blockSignals(True)
+        self.model.loadData(data)
+        self.model.blockSignals(False)
+
+        self.treeView.expandAll()
+        self.doExport()
+
+    def doExport(self):
+        self.widgetEdit.setPlainText(self.model.toJson(ensure_ascii=False, indent=4))
+
+    def deleteItem(self, _):
+        indexes = self.treeView.selectedIndexes()
+        if not indexes:
+            return
+
+        index = indexes[0]
+        print(index.row(), index.column(), index.parent())
+        self.model.removeRow(index.row(), index.parent())
+
+    def onRowsRemoved(self, parent, first, last):
+        print("onRowsRemoved", parent, first, last)
+        self.doExport()
+
+    def onItemChanged(self, item):
+        if self.sender() == self.model:
+            self.doExport()
+
+        if isinstance(item, QModelIndex):
+            item = self.model.itemFromIndex(item)
+
+        if not item:
+            return
+        keyInfo = ""
+        if item.keyItem:
+            keyInfo = f"row={item.keyItem.row()}, col={item.keyItem.column()}, text={item.keyItem.text()}"
+        print(
+            f"row={item.row()}, col={item.column()}, text={item.text()}, value={str(item.data(Qt.EditRole))}, key=({keyInfo})\n\trowCount={item.rowCount()}\n\tpath={item.path}"
+        )
+
+    def onFilterPathToggled(self, checked):
+        if checked:
+            self.fmodel.setFilterRole(QJsonItem.PathRole)
+        else:
+            self.fmodel.setFilterRole(Qt.DisplayRole)
+
+    def onFilterTextChanged(self, text):
+        self.fmodel.setFilterFixedString(text)
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QTreeView/TestModelModify.py b/QTreeView/TestModelModify.py
new file mode 100644
index 00000000..b62b0976
--- /dev/null
+++ b/QTreeView/TestModelModify.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/05
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TestModelModify.py
+@description:
+"""
+
+import json
+import sys
+
+from Lib.qjsonmodel import QJsonItem, QJsonModel
+
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import (
+        QApplication,
+        QFormLayout,
+        QHBoxLayout,
+        QLineEdit,
+        QPlainTextEdit,
+        QPushButton,
+        QTreeView,
+        QWidget,
+    )
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import (
+        QApplication,
+        QFormLayout,
+        QHBoxLayout,
+        QLineEdit,
+        QPlainTextEdit,
+        QPushButton,
+        QTreeView,
+        QWidget,
+    )
+
+
+class TestWindow(QWidget):
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        self.treeView = QTreeView(self)
+        self.widgetEdit = QWidget(self)
+        layout.addWidget(self.treeView)
+        layout.addWidget(self.widgetEdit)
+
+        layout = QFormLayout(self.widgetEdit)
+        layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
+        layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
+        self.editPath = QLineEdit(f"address{QJsonItem.Sep}city", self.widgetEdit)
+        self.editPath.setPlaceholderText(
+            f"请输入查询内容,支持路径查询,比如:aaa{QJsonItem.Sep}bbb{QJsonItem.Sep}ccc"
+        )
+        layout.addRow("Path:", self.editPath)
+        self.editValue = QLineEdit("SiChuan", self.widgetEdit)
+        self.editValue.setPlaceholderText("请输入修改值,只支持路径匹配")
+        layout.addRow("Value:", self.editValue)
+        self.buttonQuery = QPushButton("查询", self.widgetEdit)
+        self.buttonModify = QPushButton("修改", self.widgetEdit)
+        self.buttonExport = QPushButton("获取Json", self.widgetEdit)
+        layout.addRow("", self.buttonQuery)
+        layout.addRow("", self.buttonModify)
+        layout.addRow("", self.buttonExport)
+        self.editText = QPlainTextEdit(self.widgetEdit)
+        self.editText.setReadOnly(True)
+        layout.addRow("", self.editText)
+        self.buttonQuery.clicked.connect(self.doQuery)
+        self.buttonModify.clicked.connect(self.doModify)
+        self.buttonExport.clicked.connect(self.doExport)
+
+        self.model = QJsonModel(self)
+        self.model.itemChanged.connect(self.onItemChanged)
+        self.treeView.setModel(self.model)
+
+        self.setupModel()
+
+    def doQuery(self):
+        self.editText.clear()
+        path = self.editPath.text().strip()
+        if not path:
+            return
+
+        item = self.model.findPath(path)
+
+        if item:
+            self.editText.appendPlainText(
+                str(
+                    {
+                        "path": item.path,
+                        "text": item.text(),
+                        "value": item.valueItem.edit,
+                    }
+                )
+            )
+
+    def doModify(self):
+        self.editText.clear()
+        path = self.editPath.text().strip()
+        value = self.editValue.text().strip()
+        if not path or not value:
+            return
+
+        try:
+            value = json.loads(value)
+        except Exception:
+            pass
+
+        ret = self.model.updateValue(path, value)
+        self.editText.setPlainText(f"change ret: {ret}")
+
+    def doExport(self):
+        self.editText.setPlainText(self.model.toJson(ensure_ascii=False, indent=4))
+
+    def setupModel(self):
+        data = {
+            "name": "Irony",
+            "age": 33,
+            "address": {
+                "country": "China",
+                "city": "Chengdu",
+            },
+            "phone": [
+                {"type": "home", "number": "123456789"},
+                {"type": "fax", "number": "6987654321"},
+            ],
+            "marriage": False,
+            "salary": 6666.6,
+            "skills": [
+                "C++",
+                "Python",
+                "Java",
+                "JavaScript",
+                "Shell",
+                "Android",
+            ],
+            "others": [
+                [1, 2, 3],
+                [4, 5, 6],
+                [7, 8, 9],
+            ],
+        }
+        self.model.blockSignals(True)
+        self.model.loadData(data)
+        self.model.blockSignals(False)
+
+        self.treeView.expandAll()
+        self.doExport()
+
+    def onItemChanged(self, item):
+        self.doExport()
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QTreeView/TestSerializeModel.py b/QTreeView/TestSerializeModel.py
new file mode 100644
index 00000000..20b0b62b
--- /dev/null
+++ b/QTreeView/TestSerializeModel.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2025/08/08
+@file: TestSerializeModel.py
+@description:
+"""
+
+import json
+import sys
+from datetime import datetime
+
+from Lib.qmodelmapper import QModelMapper
+from Lib.serializewidget import Ui_SerializeWidget
+
+try:
+    from PyQt5.QtCore import QRegExp, Qt
+    from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat
+    from PyQt5.QtWidgets import QApplication, QWidget
+except ImportError:
+    from PySide2.QtCore import QRegExp, Qt
+    from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat
+    from PySide2.QtWidgets import QApplication, QWidget
+
+
+class HighlightingRule:
+    def __init__(self, pattern, color):
+        self.pattern = QRegExp(pattern)
+        self.format = QTextCharFormat()
+        self.format.setForeground(color)
+
+
+class JsonHighlighter(QSyntaxHighlighter):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._rules = [
+            # numbers
+            HighlightingRule(QRegExp('([-0-9.]+)(?!([^"]*"[\\s]*\\:))'), Qt.darkRed),
+            # key
+            HighlightingRule(QRegExp('("[^"]*")\\s*\\:'), Qt.darkBlue),
+            # value
+            HighlightingRule(QRegExp(':+(?:[: []*)("[^"]*")'), Qt.darkGreen),
+        ]
+
+    def highlightBlock(self, text: str) -> None:
+        for rule in self._rules:
+            index = rule.pattern.indexIn(text)
+            while index >= 0:
+                length = rule.pattern.matchedLength()
+                self.setFormat(index, length, rule.format)
+                index = rule.pattern.indexIn(text, index + length)
+
+
+class TestWindow(QWidget, Ui_SerializeWidget):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.setupUi(self)
+        self.resize(1200, 600)
+
+        # json view
+        self.highlighter = JsonHighlighter(self.editJsonView.document())
+        self.editJsonView.textChanged.connect(self.onJsonChanged)
+
+        # serialize model
+        QModelMapper.Debug = True
+        self.mapper = QModelMapper(self)
+        self.mapper.setData(
+            {
+                "input": {
+                    "radioButton": True,
+                    "checkBox": False,
+                },
+                "name": "Irony",
+            }
+        )
+        self.mapper.valueChanged.connect(self.onModelChanged)
+        self.treeJsonView.setModel(self.mapper.getModel())
+        self.treeJsonView.expandAll()
+
+        # comboBox
+        self.comboBox.addItems([f"Item {i}" for i in range(10)])
+
+        self.mapper.bind(self.radioButton, "input/radioButton")
+        self.mapper.bind(self.checkBox, "input/checkBox", True)
+        self.mapper.bind(self.comboBox, "input/comboBox")
+        self.mapper.bind(self.spinBox, "age")
+        self.mapper.bind(self.doubleSpinBox, "money")
+        self.mapper.bind(self.lineEdit, "name")
+
+        # date and time
+        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.mapper.bind(self.timeEdit, "input/time", now)
+        self.mapper.bind(self.dateEdit, "input/date", now)
+        self.mapper.bind(self.dateTimeEdit, "input/dateTime", now)
+
+        # edit
+        self.mapper.bind(self.plainTextEdit, "desc/desc")
+        self.mapper.bind(self.textEdit, "desc/text")
+
+        # Correlation
+        self.mapper.bind(self.spinBox_2, "correlation/slider")
+        self.mapper.bind(self.horizontalSlider, "correlation/slider")
+        self.mapper.bind(self.progressBar, "correlation/progress")
+        self.mapper.bind(self.verticalSlider, "correlation/progress")
+        self.treeJsonView.expandAll()
+
+    def onModelChanged(self):
+        data = self.mapper.getJson(indent=2)
+        self.editJsonView.blockSignals(True)
+        self.editJsonView.setPlainText(data)
+        self.editJsonView.blockSignals(False)
+
+    def onJsonChanged(self):
+        text = self.editJsonView.toPlainText().strip()
+        try:
+            data = json.loads(text)
+            self.mapper.setData(data)
+        except Exception:
+            pass
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QTreeWidget/Lib/testTree.py b/QTreeWidget/Lib/testTree.py
index 647f0055..2745b729 100644
--- a/QTreeWidget/Lib/testTree.py
+++ b/QTreeWidget/Lib/testTree.py
@@ -6,7 +6,8 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt5 import QtCore, QtWidgets
+
 
 class Ui_Form(object):
     def setupUi(self, Form):
@@ -48,10 +49,10 @@ def retranslateUi(self, Form):
 
 if __name__ == "__main__":
     import sys
+
     app = QtWidgets.QApplication(sys.argv)
     Form = QtWidgets.QWidget()
     ui = Ui_Form()
     ui.setupUi(Form)
     Form.show()
     sys.exit(app.exec_())
-
diff --git a/QTreeWidget/ParentNodeForbid.py b/QTreeWidget/ParentNodeForbid.py
index 18759dd2..1a139e7b 100644
--- a/QTreeWidget/ParentNodeForbid.py
+++ b/QTreeWidget/ParentNodeForbid.py
@@ -4,19 +4,20 @@
 """
 Created on 2019年11月8日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QTreeWidget.ParentNodeForbid
 @description: 父节点不可选中
 """
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QStyledItemDelegate,\
-    QStyle
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-__Version__ = 1.0
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QStyledItemDelegate, \
+        QStyle
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QStyledItemDelegate, \
+        QStyle
 
 
 class NoColorItemDelegate(QStyledItemDelegate):
@@ -84,8 +85,9 @@ def onItemPressed(self, item, column):
 if __name__ == '__main__':
     import sys
     import cgitb
-    cgitb.enable(1, None, 5, '')
-    from PyQt5.QtWidgets import QApplication
+
+    cgitb.enable(format='text')
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QTreeWidget/ParsingJson.py b/QTreeWidget/ParsingJson.py
index dbbc088c..c32480ce 100644
--- a/QTreeWidget/ParsingJson.py
+++ b/QTreeWidget/ParsingJson.py
@@ -4,7 +4,7 @@
 """
 Created on 2018年4月8日
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ParsingJson
 @description: 
@@ -12,18 +12,18 @@
 import json
 import webbrowser
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget,\
-    QLabel, QSpacerItem, QSizePolicy, QHBoxLayout
 import chardet
 
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = "Copyright (c) 2018 Irony"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QIcon
+    from PyQt5.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QWidget, \
+        QLabel, QSpacerItem, QSizePolicy, QHBoxLayout
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QIcon
+    from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QWidget, \
+        PySide2, QSpacerItem, QSizePolicy, QHBoxLayout
 
 
 class ItemWidget(QWidget):
@@ -94,7 +94,7 @@ def loadData(self, path):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     app.setStyleSheet("""QTreeView {
     outline: 0px;
diff --git a/QTreeWidget/README.md b/QTreeWidget/README.md
index a1afc7d0..1ecaf373 100644
--- a/QTreeWidget/README.md
+++ b/QTreeWidget/README.md
@@ -6,24 +6,26 @@
   - [禁止父节点/禁止父节点](#3禁止父节点)
 
 ## 1、通过json数据生成树形结构
+
 [运行 ParsingJson.py](ParsingJson.py)
 
 解析每一层json数据中的list
 
-
 
 
 ## 2、点击父节点全选/取消全选子节点
-[运行 testTreeWidget.py](testTreeWidget.py)
+
+[运行 testTreeWidget.py](testTreeWidget.py) | [查看 testTree.ui](Data/testTree.ui)
 
 点击父节点全选/取消全选子节点
 
 
 
 ## 3、禁止父节点
+
 [运行 ParentNodeForbid.py](ParentNodeForbid.py)
 
  1. 父节点通过设置`pitem1.setFlags(pitem1.flags() & ~Qt.ItemIsSelectable)`为不可选
  2. 完全禁用点击等需要重写`mousePressEvent`事件并结合item的标志来判断
 
-
\ No newline at end of file
+
diff --git a/QTreeWidget/testTreeWidget.py b/QTreeWidget/testTreeWidget.py
index 39aecad0..3bdb9568 100644
--- a/QTreeWidget/testTreeWidget.py
+++ b/QTreeWidget/testTreeWidget.py
@@ -1,13 +1,13 @@
 #!/usr/bin/env python
 # encoding: utf-8
-'''
+"""
 Created on 2017年4月20日
 @author: weike32
-@site: https://pyqt5.com , https://github.com/weike32
+@site: https://pyqt.site , https://github.com/weike32
 @email: 394967319@qq.com
 @file: CopyContent
 @description:
-'''
+"""
 import sys
 
 from PyQt5.QtCore import Qt
diff --git a/QVBoxLayout/Data/BaseVerticalLayout.ui b/QVBoxLayout/Data/BaseVerticalLayout.ui
new file mode 100644
index 00000000..77c5b148
--- /dev/null
+++ b/QVBoxLayout/Data/BaseVerticalLayout.ui
@@ -0,0 +1,38 @@
+
+
+ BaseVerticalLayout 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   - 
+    
+     
+      通过 右键 -> 布局 -> 垂直布局 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      底部按钮 +
 +
 +
+ 
+  
+  
diff --git a/QVBoxLayout/Data/VerticalLayoutMargin.ui b/QVBoxLayout/Data/VerticalLayoutMargin.ui
new file mode 100644
index 00000000..6045d809
--- /dev/null
+++ b/QVBoxLayout/Data/VerticalLayoutMargin.ui
@@ -0,0 +1,62 @@
+
+
+ VerticalLayoutMargin 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   #VerticalLayoutMargin {
+    background: #5aaadb;
+}
+#label {
+    background: #85c440;
+} 
+   
+  
+   
+    50 
+    
+   
+    20 
+    
+   
+    20 
+    
+   - 
+    
+     
+      通过设置Margin和Spacing设置边距以及两个控件之间的间隔距离 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      Spcasing 为 50 +
 +
 +
+- 
+    
+     
+      Spcasing 为 50 +
 +
 +
+ 
+  
+  
diff --git a/QVBoxLayout/Data/VerticalLayoutStretch.ui b/QVBoxLayout/Data/VerticalLayoutStretch.ui
new file mode 100644
index 00000000..bf198c9f
--- /dev/null
+++ b/QVBoxLayout/Data/VerticalLayoutStretch.ui
@@ -0,0 +1,62 @@
+
+
+ VerticalLayoutStretch 
+ 
+  
+   
+    0 
+    0 
+    400 
+    300 
+    
+   
+  
+   Form 
+   
+  
+   #label {
+    background: #5aaadb;
+}
+#label_2 {
+    background: #85c440;
+}
+#label_3 {
+    background: #f2b63c;
+} 
+   
+  
+   - 
+    
+     
+      通过设置Stretch设置每部分的占比(1,2,3) 1/6 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      2/6 +
 +
+      Qt::AlignCenter +
 +
 +
+- 
+    
+     
+      3/6 +
 +
+      Qt::AlignCenter +
 +
 +
+ 
+  
+  
diff --git a/QVBoxLayout/README.md b/QVBoxLayout/README.md
index e69de29b..8d9c67c5 100644
--- a/QVBoxLayout/README.md
+++ b/QVBoxLayout/README.md
@@ -0,0 +1,35 @@
+# QVBoxLayout
+
+- 目录
+  - [垂直布局](#1垂直布局)
+  - [边距和间隔](#2边距和间隔)
+  - [比例分配](#3比例分配)
+
+## 1、垂直布局
+
+[查看 BaseVerticalLayout.ui](Data/BaseVerticalLayout.ui)
+
+
+
+## 2、边距和间隔
+
+[查看 VerticalLayoutMargin.ui](Data/VerticalLayoutMargin.ui)
+
+1. 通过`setContentsMargins(20, 20, -1, -1)`设置左上右下的边距,-1表示默认值
+2. 通过`setSpacing`设置控件之间的间隔
+
+
+
+## 3、比例分配
+
+[查看 VerticalLayoutStretch.ui](Data/VerticalLayoutStretch.ui)
+
+通过`setStretch`设置各个部分的占比 分别为:1/6 2/6 3/6
+
+```python
+self.verticalLayout.setStretch(0, 1)
+self.verticalLayout.setStretch(1, 2)
+self.verticalLayout.setStretch(2, 3)
+```
+
+
diff --git a/QVBoxLayout/ScreenShot/BaseVerticalLayout.png b/QVBoxLayout/ScreenShot/BaseVerticalLayout.png
new file mode 100644
index 00000000..944a73a7
Binary files /dev/null and b/QVBoxLayout/ScreenShot/BaseVerticalLayout.png differ
diff --git a/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png b/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png
new file mode 100644
index 00000000..2208490c
Binary files /dev/null and b/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png differ
diff --git a/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png b/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png
new file mode 100644
index 00000000..ff357323
Binary files /dev/null and b/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png differ
diff --git a/QWebChannel/CallEachWithJs.py b/QWebChannel/CallEachWithJs.py
new file mode 100644
index 00000000..238b0625
--- /dev/null
+++ b/QWebChannel/CallEachWithJs.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2021/12/15
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CallEachWithJs.py
+@description: 与JS之间的互相调用
+"""
+
+import os
+
+from PyQt5.QtCore import QUrl, pyqtSlot
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtWidgets import (QApplication, QLineEdit, QPushButton, QVBoxLayout,
+                             QWidget)
+
+from Lib.WebChannelObject import WebChannelObject
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.m_obj = WebChannelObject(self)
+        # 注册该窗口,可以访问该窗口的属性,槽函数,信号
+        # https://doc.qt.io/qt-5/qwidget.html#properties
+        # https://doc.qt.io/qt-5/qwidget.html#signals
+        # https://doc.qt.io/qt-5/qwidget.html#public-slots
+        self.m_obj.registerObject('qtwindow', self)
+        self.m_obj.start()
+
+        layout = QVBoxLayout(self)
+        self.editTitle = QLineEdit(self, placeholderText='输入标题')
+        layout.addWidget(self.editTitle)
+        layout.addWidget(QPushButton('修改标题', self, clicked=self.onChangeTitle))
+
+        QDesktopServices.openUrl(
+            QUrl.fromLocalFile(
+                os.path.join(os.path.dirname(sys.argv[0] or __file__),
+                             'Data/CallEachWithJs.html')))
+
+    def onChangeTitle(self):
+        self.setWindowTitle(self.editTitle.text())
+
+    # ------- 把非槽函数通过pyqtSlot重新暴露 -------
+    @pyqtSlot(int, int)
+    def resize(self, width, height):
+        super().resize(width, height)
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QWebChannel/Data/CallEachWithJs.html b/QWebChannel/Data/CallEachWithJs.html
new file mode 100644
index 00000000..fb5752be
--- /dev/null
+++ b/QWebChannel/Data/CallEachWithJs.html
@@ -0,0 +1,76 @@
+
+
+
+
+    输出: 
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebChannel module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+"use strict";
+
+var QWebChannelMessageTypes = {
+    signal: 1,
+    propertyUpdate: 2,
+    init: 3,
+    idle: 4,
+    debug: 5,
+    invokeMethod: 6,
+    connectToSignal: 7,
+    disconnectFromSignal: 8,
+    setProperty: 9,
+    response: 10,
+};
+
+var QWebChannel = function(transport, initCallback)
+{
+    if (typeof transport !== "object" || typeof transport.send !== "function") {
+        console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
+                      " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
+        return;
+    }
+
+    var channel = this;
+    this.transport = transport;
+
+    this.send = function(data)
+    {
+        if (typeof(data) !== "string") {
+            data = JSON.stringify(data);
+        }
+        channel.transport.send(data);
+    }
+
+    this.transport.onmessage = function(message)
+    {
+        var data = message.data;
+        if (typeof data === "string") {
+            data = JSON.parse(data);
+        }
+        switch (data.type) {
+            case QWebChannelMessageTypes.signal:
+                channel.handleSignal(data);
+                break;
+            case QWebChannelMessageTypes.response:
+                channel.handleResponse(data);
+                break;
+            case QWebChannelMessageTypes.propertyUpdate:
+                channel.handlePropertyUpdate(data);
+                break;
+            default:
+                console.error("invalid message received:", message.data);
+                break;
+        }
+    }
+
+    this.execCallbacks = {};
+    this.execId = 0;
+    this.exec = function(data, callback)
+    {
+        if (!callback) {
+            // if no callback is given, send directly
+            channel.send(data);
+            return;
+        }
+        if (channel.execId === Number.MAX_VALUE) {
+            // wrap
+            channel.execId = Number.MIN_VALUE;
+        }
+        if (data.hasOwnProperty("id")) {
+            console.error("Cannot exec message with property id: " + JSON.stringify(data));
+            return;
+        }
+        data.id = channel.execId++;
+        channel.execCallbacks[data.id] = callback;
+        channel.send(data);
+    };
+
+    this.objects = {};
+
+    this.handleSignal = function(message)
+    {
+        var object = channel.objects[message.object];
+        if (object) {
+            object.signalEmitted(message.signal, message.args);
+        } else {
+            console.warn("Unhandled signal: " + message.object + "::" + message.signal);
+        }
+    }
+
+    this.handleResponse = function(message)
+    {
+        if (!message.hasOwnProperty("id")) {
+            console.error("Invalid response message received: ", JSON.stringify(message));
+            return;
+        }
+        channel.execCallbacks[message.id](message.data);
+        delete channel.execCallbacks[message.id];
+    }
+
+    this.handlePropertyUpdate = function(message)
+    {
+        message.data.forEach(data => {
+            var object = channel.objects[data.object];
+            if (object) {
+                object.propertyUpdate(data.signals, data.properties);
+            } else {
+                console.warn("Unhandled property update: " + data.object + "::" + data.signal);
+            }
+        });
+        channel.exec({type: QWebChannelMessageTypes.idle});
+    }
+
+    this.debug = function(message)
+    {
+        channel.send({type: QWebChannelMessageTypes.debug, data: message});
+    };
+
+    channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
+        for (const objectName of Object.keys(data)) {
+            new QObject(objectName, data[objectName], channel);
+        }
+
+        // now unwrap properties, which might reference other registered objects
+        for (const objectName of Object.keys(channel.objects)) {
+            channel.objects[objectName].unwrapProperties();
+        }
+
+        if (initCallback) {
+            initCallback(channel);
+        }
+        channel.exec({type: QWebChannelMessageTypes.idle});
+    });
+};
+
+function QObject(name, data, webChannel)
+{
+    this.__id__ = name;
+    webChannel.objects[name] = this;
+
+    // List of callbacks that get invoked upon signal emission
+    this.__objectSignals__ = {};
+
+    // Cache of all properties, updated when a notify signal is emitted
+    this.__propertyCache__ = {};
+
+    var object = this;
+
+    // ----------------------------------------------------------------------
+
+    this.unwrapQObject = function(response)
+    {
+        if (response instanceof Array) {
+            // support list of objects
+            return response.map(qobj => object.unwrapQObject(qobj))
+        }
+        if (!(response instanceof Object))
+            return response;
+
+        if (!response["__QObject*__"] || response.id === undefined) {
+            var jObj = {};
+            for (const propName of Object.keys(response)) {
+                jObj[propName] = object.unwrapQObject(response[propName]);
+            }
+            return jObj;
+        }
+
+        var objectId = response.id;
+        if (webChannel.objects[objectId])
+            return webChannel.objects[objectId];
+
+        if (!response.data) {
+            console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
+            return;
+        }
+
+        var qObject = new QObject( objectId, response.data, webChannel );
+        qObject.destroyed.connect(function() {
+            if (webChannel.objects[objectId] === qObject) {
+                delete webChannel.objects[objectId];
+                // reset the now deleted QObject to an empty {} object
+                // just assigning {} though would not have the desired effect, but the
+                // below also ensures all external references will see the empty map
+                // NOTE: this detour is necessary to workaround QTBUG-40021
+                Object.keys(qObject).forEach(name => delete qObject[name]);
+            }
+        });
+        // here we are already initialized, and thus must directly unwrap the properties
+        qObject.unwrapProperties();
+        return qObject;
+    }
+
+    this.unwrapProperties = function()
+    {
+        for (const propertyIdx of Object.keys(object.__propertyCache__)) {
+            object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
+        }
+    }
+
+    function addSignal(signalData, isPropertyNotifySignal)
+    {
+        var signalName = signalData[0];
+        var signalIndex = signalData[1];
+        object[signalName] = {
+            connect: function(callback) {
+                if (typeof(callback) !== "function") {
+                    console.error("Bad callback given to connect to signal " + signalName);
+                    return;
+                }
+
+                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+                object.__objectSignals__[signalIndex].push(callback);
+
+                // only required for "pure" signals, handled separately for properties in propertyUpdate
+                if (isPropertyNotifySignal)
+                    return;
+
+                // also note that we always get notified about the destroyed signal
+                if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)")
+                    return;
+
+                // and otherwise we only need to be connected only once
+                if (object.__objectSignals__[signalIndex].length == 1) {
+                    webChannel.exec({
+                        type: QWebChannelMessageTypes.connectToSignal,
+                        object: object.__id__,
+                        signal: signalIndex
+                    });
+                }
+            },
+            disconnect: function(callback) {
+                if (typeof(callback) !== "function") {
+                    console.error("Bad callback given to disconnect from signal " + signalName);
+                    return;
+                }
+                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+                var idx = object.__objectSignals__[signalIndex].indexOf(callback);
+                if (idx === -1) {
+                    console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
+                    return;
+                }
+                object.__objectSignals__[signalIndex].splice(idx, 1);
+                if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
+                    // only required for "pure" signals, handled separately for properties in propertyUpdate
+                    webChannel.exec({
+                        type: QWebChannelMessageTypes.disconnectFromSignal,
+                        object: object.__id__,
+                        signal: signalIndex
+                    });
+                }
+            }
+        };
+    }
+
+    /**
+     * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
+     */
+    function invokeSignalCallbacks(signalName, signalArgs)
+    {
+        var connections = object.__objectSignals__[signalName];
+        if (connections) {
+            connections.forEach(function(callback) {
+                callback.apply(callback, signalArgs);
+            });
+        }
+    }
+
+    this.propertyUpdate = function(signals, propertyMap)
+    {
+        // update property cache
+        for (const propertyIndex of Object.keys(propertyMap)) {
+            var propertyValue = propertyMap[propertyIndex];
+            object.__propertyCache__[propertyIndex] = this.unwrapQObject(propertyValue);
+        }
+
+        for (const signalName of Object.keys(signals)) {
+            // Invoke all callbacks, as signalEmitted() does not. This ensures the
+            // property cache is updated before the callbacks are invoked.
+            invokeSignalCallbacks(signalName, signals[signalName]);
+        }
+    }
+
+    this.signalEmitted = function(signalName, signalArgs)
+    {
+        invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));
+    }
+
+    function addMethod(methodData)
+    {
+        var methodName = methodData[0];
+        var methodIdx = methodData[1];
+
+        // Fully specified methods are invoked by id, others by name for host-side overload resolution
+        var invokedMethod = methodName[methodName.length - 1] === ')' ? methodIdx : methodName
+
+        object[methodName] = function() {
+            var args = [];
+            var callback;
+            var errCallback;
+            for (var i = 0; i < arguments.length; ++i) {
+                var argument = arguments[i];
+                if (typeof argument === "function")
+                    callback = argument;
+                else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)
+                    args.push({
+                        "id": argument.__id__
+                    });
+                else
+                    args.push(argument);
+            }
+
+            var result;
+            // during test, webChannel.exec synchronously calls the callback
+            // therefore, the promise must be constucted before calling
+            // webChannel.exec to ensure the callback is set up
+            if (!callback && (typeof(Promise) === 'function')) {
+              result = new Promise(function(resolve, reject) {
+                callback = resolve;
+                errCallback = reject;
+              });
+            }
+
+            webChannel.exec({
+                "type": QWebChannelMessageTypes.invokeMethod,
+                "object": object.__id__,
+                "method": invokedMethod,
+                "args": args
+            }, function(response) {
+                if (response !== undefined) {
+                    var result = object.unwrapQObject(response);
+                    if (callback) {
+                        (callback)(result);
+                    }
+                } else if (errCallback) {
+                  (errCallback)();
+                }
+            });
+
+            return result;
+        };
+    }
+
+    function bindGetterSetter(propertyInfo)
+    {
+        var propertyIndex = propertyInfo[0];
+        var propertyName = propertyInfo[1];
+        var notifySignalData = propertyInfo[2];
+        // initialize property cache with current value
+        // NOTE: if this is an object, it is not directly unwrapped as it might
+        // reference other QObject that we do not know yet
+        object.__propertyCache__[propertyIndex] = propertyInfo[3];
+
+        if (notifySignalData) {
+            if (notifySignalData[0] === 1) {
+                // signal name is optimized away, reconstruct the actual name
+                notifySignalData[0] = propertyName + "Changed";
+            }
+            addSignal(notifySignalData, true);
+        }
+
+        Object.defineProperty(object, propertyName, {
+            configurable: true,
+            get: function () {
+                var propertyValue = object.__propertyCache__[propertyIndex];
+                if (propertyValue === undefined) {
+                    // This shouldn't happen
+                    console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
+                }
+
+                return propertyValue;
+            },
+            set: function(value) {
+                if (value === undefined) {
+                    console.warn("Property setter for " + propertyName + " called with undefined value!");
+                    return;
+                }
+                object.__propertyCache__[propertyIndex] = value;
+                var valueToSend = value;
+                if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)
+                    valueToSend = { "id": valueToSend.__id__ };
+                webChannel.exec({
+                    "type": QWebChannelMessageTypes.setProperty,
+                    "object": object.__id__,
+                    "property": propertyIndex,
+                    "value": valueToSend
+                });
+            }
+        });
+
+    }
+
+    // ----------------------------------------------------------------------
+
+    data.methods.forEach(addMethod);
+
+    data.properties.forEach(bindGetterSetter);
+
+    data.signals.forEach(function(signal) { addSignal(signal, false); });
+
+    Object.assign(object, data.enums);
+}
+
+//required for use with nodejs
+if (typeof module === 'object') {
+    module.exports = {
+        QWebChannel: QWebChannel
+    };
+}
diff --git a/QWebChannel/Lib/WebChannelObject.py b/QWebChannel/Lib/WebChannelObject.py
new file mode 100644
index 00000000..7fe5e9ed
--- /dev/null
+++ b/QWebChannel/Lib/WebChannelObject.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2021/12/15
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: WebChannelObject.py
+@description: 交互对象,需要继承QObject并暴露接口
+"""
+
+from PyQt5.QtCore import (QJsonDocument, QJsonParseError, QObject,
+                          pyqtProperty, pyqtSlot)
+from PyQt5.QtNetwork import QHostAddress
+from PyQt5.QtWebChannel import QWebChannel, QWebChannelAbstractTransport
+from PyQt5.QtWebSockets import QWebSocketServer
+
+
+class WebSocketTransport(QWebChannelAbstractTransport):
+
+    def __init__(self, socket, *args, **kwargs):
+        super(WebSocketTransport, self).__init__(*args, **kwargs)
+        self.m_socket = socket
+        self.m_socket.textMessageReceived.connect(self.textMessageReceived)
+        self.m_socket.disconnected.connect(self.deleteLater)
+
+    def sendMessage(self, message):
+        print('sendMessage:', message)
+        self.m_socket.sendTextMessage(
+            QJsonDocument(message).toJson(QJsonDocument.Compact).data().decode(
+                'utf-8', errors='ignore'))
+
+    def textMessageReceived(self, message):
+        print('textMessageReceived:', message)
+        error = QJsonParseError()
+        json = QJsonDocument.fromJson(message.encode('utf-8', errors='ignore'),
+                                      error)
+        if error.error:
+            print('Failed to parse message:{}, Error is:{}'.format(
+                message, error.errorString()))
+            return
+        if not json.isObject():
+            print('Received JSON message that is not an object:{}'.format(
+                message))
+            return
+        self.messageReceived.emit(json.object(), self)
+
+
+class WebChannelObject(QObject):
+
+    def __init__(self, *args, **kwargs):
+        super(WebChannelObject, self).__init__(*args, **kwargs)
+        # 内部属性供外部调用
+        self._intValue = 0
+        self._floatValue = 0.0
+        self._boolValue = False
+        self._strValue = ''
+        # 设置数组或者字典有一定问题
+        # self._listValue = []
+        # self._mapValue = {}
+
+        # webchannel对象
+        self.m_webchannel = QWebChannel(self)
+        # 这里默认注册自己,这里使用了类名作为名称
+        self.registerObject(self.__class__.__name__, self)
+        # websocket服务
+        self.m_clients = {}
+        self.m_server = QWebSocketServer(self.__class__.__name__,
+                                         QWebSocketServer.NonSecureMode, self)
+
+    def registerObject(self, name, obj):
+        """注册对象
+        @param name: 名称
+        @type name: str
+        @param obj: 对象
+        @type obj: QObject
+        """
+        self.m_webchannel.registerObject(name, obj)
+
+    def registerObjects(self, objects):
+        """注册多个对象
+        @param objects: 对象列表
+        @type objects: list
+        """
+        for name, obj in objects:
+            self.registerObject(name, obj)
+
+    def deregisterObject(self, obj):
+        """注销对象
+        @param obj: 对象
+        @type obj: QObject
+        """
+        self.m_webchannel.deregisterObject(obj)
+
+    def deregisterObjects(self, objects):
+        """注销多个对象
+        @param objects: 对象列表
+        @type objects: list
+        """
+        for obj in objects:
+            self.deregisterObject(obj)
+
+    def start(self, port=12345):
+        """启动服务
+        @param port: 端口
+        @type port: int
+        """
+        if not self.m_server.listen(QHostAddress.Any, port):
+            raise Exception(
+                'Failed to create WebSocket server on port {}'.format(port))
+
+        print('WebSocket server listening on port {}'.format(port))
+        # 新连接信号
+        self.m_server.newConnection.connect(self._handleNewConnection)
+
+    def stop(self):
+        """停止服务"""
+        self.m_server.close()
+
+    def _handleNewConnection(self):
+        """新连接"""
+        socket = self.m_server.nextPendingConnection()
+        print('New WebSocket connection from {}'.format(
+            socket.peerAddress().toString()))
+        # 连接关闭信号
+        socket.disconnected.connect(self._handleDisconnected)
+        transport = WebSocketTransport(socket)
+        self.m_clients[socket] = transport
+        self.m_webchannel.connectTo(transport)
+
+    def _handleDisconnected(self):
+        """连接关闭"""
+        socket = self.sender()
+        print('WebSocket connection from {} closed'.format(
+            socket.peerAddress()))
+        if socket in self.m_clients:
+            self.m_clients.pop(socket)
+        socket.deleteLater()
+
+    # ------- 下面是注册属性的方法 -------
+
+    @pyqtProperty(int)
+    def intValue(self):
+        return self._intValue
+
+    @intValue.setter
+    def intValue(self, value):
+        self._intValue = value
+
+    @pyqtProperty(float)
+    def floatValue(self):
+        return self._floatValue
+
+    @floatValue.setter
+    def floatValue(self, value):
+        self._floatValue = value
+
+    @pyqtProperty(bool)
+    def boolValue(self):
+        return self._boolValue
+
+    @boolValue.setter
+    def boolValue(self, value):
+        self._boolValue = value
+
+    @pyqtProperty(str)
+    def strValue(self):
+        return self._strValue
+
+    @strValue.setter
+    def strValue(self, value):
+        self._strValue = value
+
+    # @pyqtProperty(list)
+    # def listValue(self):
+    #     return self._listValue
+
+    # @listValue.setter
+    # def listValue(self, value):
+    #     self._listValue = value
+
+    # @pyqtProperty(dict)
+    # def mapValue(self):
+    #     return self._mapValue
+
+    # @mapValue.setter
+    # def mapValue(self, value):
+    #     self._mapValue = value
+
+    # ------- 下面是注册函数的方法 -------
+    # ------- 如果有返回值一定要注明 result=返回类型 -------
+
+    @pyqtSlot(int, int, result=int)
+    def testAdd(self, a, b):
+        return a + b
diff --git a/QWebChannel/Lib/__init__.py b/QWebChannel/Lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/QWebChannel/README.en.md b/QWebChannel/README.en.md
new file mode 100644
index 00000000..e69de29b
diff --git a/QWebChannel/README.md b/QWebChannel/README.md
new file mode 100644
index 00000000..4bcdb07e
--- /dev/null
+++ b/QWebChannel/README.md
@@ -0,0 +1,13 @@
+# QWebChannel
+
+- 目录
+  - [和Js互相调用](#1和Js互相调用)
+
+## 1、和Js互相调用
+
+[运行 CallEachWithJs.py](CallEachWithJs.py)
+
+通过`qwebchannel.js`和`QWebChannel.registerObject`通过中间件`WebSocket`进行对象和Javascript的交互(类似于json rpc)
+该方法类似与`QWebEngineView`中的例子,同时该demo也适用与nodejs。
+
+
diff --git a/QWebChannel/ScreenShot/CallEachWithJs.gif b/QWebChannel/ScreenShot/CallEachWithJs.gif
new file mode 100644
index 00000000..8ff3a88e
Binary files /dev/null and b/QWebChannel/ScreenShot/CallEachWithJs.gif differ
diff --git a/QWebEngineView/BlockRequest.py b/QWebEngineView/BlockRequest.py
index e46744e4..27b86d78 100644
--- a/QWebEngineView/BlockRequest.py
+++ b/QWebEngineView/BlockRequest.py
@@ -4,19 +4,22 @@
 """
 Created on 2019年9月24日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: QWebEngineView.BlockAds
+@file: BlockRequest
 @description: 拦截请求
 """
-from PyQt5.QtCore import QUrl
-from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-__Version__ = 'Version 1.0'
+try:
+    from PyQt5.QtCore import QUrl
+    from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import QUrl
+    from PySide2.QtWebEngineCore import QWebEngineUrlRequestInterceptor
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+    from PySide2.QtWidgets import QApplication
 
 
 class RequestInterceptor(QWebEngineUrlRequestInterceptor):
@@ -39,7 +42,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebEngineView/BlockRequestData.py b/QWebEngineView/BlockRequestData.py
index 00da4fb8..eea7a024 100644
--- a/QWebEngineView/BlockRequestData.py
+++ b/QWebEngineView/BlockRequestData.py
@@ -4,20 +4,24 @@
 """
 Created on 2020年2月18日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
-@file: QWebEngineView.BlockRequestData
+@file: BlockRequestData
 @description: 拦截请求内容
 """
-from PyQt5.QtCore import QUrl, QFile, QIODevice
-from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler,\
-    QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme  # @UnresolvedImport
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
 
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-__Version__ = 'Version 1.0'
+try:
+    from PyQt5.QtCore import QUrl, QFile, QIODevice, QByteArray
+    from PyQt5.QtWidgets import QApplication
+    from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, \
+        QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme  # @UnresolvedImport
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+except ImportError:
+    from PySide2.QtCore import QUrl, QFile, QIODevice, QByteArray
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtWebEngineCore import QWebEngineUrlSchemeHandler, \
+        QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme  # @UnresolvedImport
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
 
 
 # 自定义url协议头
@@ -30,6 +34,7 @@ def requestStarted(self, job):
             file.open(QIODevice.ReadOnly)
             job.reply(b'image/png', file)
 
+
 # 请求拦截器
 
 
@@ -50,8 +55,8 @@ def __init__(self, *args, **kwargs):
         self.resize(800, 600)
 
         # 首先获取默认的url协议
-        h1 = QWebEngineUrlScheme.schemeByName(b'http')
-        h2 = QWebEngineUrlScheme.schemeByName(b'https')
+        h1 = QWebEngineUrlScheme.schemeByName(QByteArray(b'http'))
+        h2 = QWebEngineUrlScheme.schemeByName(QByteArray(b'https'))
 
         # 这里需要修改增加本地文件和跨域支持
         CorsEnabled = 0x80  # 5.14才增加
@@ -69,12 +74,12 @@ def __init__(self, *args, **kwargs):
         # 安装url拦截器和自定义url协议处理
         de = QWebEngineProfile.defaultProfile()  # @UndefinedVariable
         de.setRequestInterceptor(RequestInterceptor(self))
-        de.installUrlSchemeHandler(b'myurl', UrlSchemeHandler(self))
+        de.installUrlSchemeHandler(QByteArray(b'myurl'), UrlSchemeHandler(self))
 
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebEngineView/GetCookie.py b/QWebEngineView/GetCookie.py
index 81aa2592..f74eae8b 100644
--- a/QWebEngineView/GetCookie.py
+++ b/QWebEngineView/GetCookie.py
@@ -1,28 +1,28 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月10日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: GetCookie
 @description: 
-'''
-import sys
-
-from PyQt5.QtCore import QUrl, QByteArray
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
-from PyQt5.QtWidgets import QApplication, QTextEdit
+"""
 
+import sys
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QUrl, QByteArray
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+    from PyQt5.QtWidgets import QApplication, QTextEdit
+except ImportError:
+    from PySide2.QtCore import QUrl, QByteArray
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+    from PySide2.QtWidgets import QApplication, QTextEdit
 
 
 class WebEngineView(QWebEngineView):
-
     DomainCookies = {}  # 存放domain的key-value
     PathCookies = {}  # 存放domain+path的key-value
 
@@ -63,9 +63,9 @@ def onLoadFinished(self):
             "AllPathCookies: " + self.bytestostr(self.getAllPathCookies()))
         self.cookieView.append('')
 
-        print("*****pyqt5.com cookie:", self.getDomainCookies(".pyqt5.com"))
-        print("*****pyqt5.com / path cookie:",
-              self.getPathCookies(".pyqt5.com/"))
+        print("*****pyqt.site cookie:", self.getDomainCookies(".pyqt.site"))
+        print("*****pyqt.site / path cookie:",
+              self.getPathCookies(".pyqt.site/"))
 
     def getAllDomainCookies(self):
         return self.DomainCookies
@@ -80,9 +80,9 @@ def getPathCookies(self, dpath):
         return self.PathCookies.get(dpath, {})
 
     def onCookieAdd(self, cookie):
-        '''
+        """
         :param cookie: QNetworkCookie
-        '''
+        """
         domain = cookie.domain()
         path = cookie.path()
         name = cookie.name().data()
@@ -98,6 +98,8 @@ def onCookieAdd(self, cookie):
             _cookie[name] = value
         else:
             self.PathCookies[domain_path] = {name: value}
+
+
 #         print("add cookie:", cookie.domain(),
 #               cookie.path(), cookie.name(), cookie.value())
 
@@ -106,5 +108,5 @@ def onCookieAdd(self, cookie):
     app = QApplication(sys.argv)
     w = WebEngineView()
     w.show()
-    w.load(QUrl("https://pyqt5.com/"))
+    w.load(QUrl("https://pyqt.site/"))
     sys.exit(app.exec_())
diff --git a/QWebEngineView/GetRequestInfo.py b/QWebEngineView/GetRequestInfo.py
new file mode 100644
index 00000000..a86d810a
--- /dev/null
+++ b/QWebEngineView/GetRequestInfo.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年9月24日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: QWebEngineView.BlockAds
+@description: 拦截请求
+"""
+
+try:
+    from PyQt5.QtCore import QUrl, QByteArray
+    from PyQt5.QtWidgets import QApplication
+    from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlScheme, \
+        QWebEngineUrlRequestInterceptor
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+except ImportError:
+    from PySide2.QtCore import QUrl, QByteArray
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
+    from PySide2.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlScheme, \
+        QWebEngineUrlRequestInterceptor
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
+
+
+class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
+    AttrType = QNetworkRequest.User + 1
+
+    def __init__(self, *args, **kwargs):
+        super(UrlSchemeHandler, self).__init__(*args, **kwargs)
+        self._manager = QNetworkAccessManager(self)
+        self._manager.finished.connect(self.onFinished)
+
+    def requestStarted(self, request):
+        # 拦截
+        # request.fail(QWebEngineUrlRequestJob.RequestDenied)
+        # print('initiator:', request.initiator())
+        print('requestMethod:', request.requestMethod())
+        print('requestHeaders:', request.requestHeaders())
+        url = request.requestUrl()
+        if url.scheme().startswith('myurl'):
+            url.setScheme(url.scheme().replace('myurl', 'http'))
+        print('requestUrl:', url)
+
+        # 构造真实请求
+        req = QNetworkRequest(url)
+        req.setAttribute(self.AttrType, request)  # 记录
+        for headerName, headerValue in request.requestHeaders().items():
+            req.setRawHeader(headerName, headerValue)
+        method = request.requestMethod()
+
+        # TODO: 这里需要把浏览器内部的cookie获取出来重新设置
+        if method == b'GET':
+            self._manager.get(req)
+        # TODO: 这里貌似没法得到POST的数据,ajax的请求貌似也有问题
+        elif method == b'POST':
+            self._manager.post(req)
+
+    def onFinished(self, reply):
+        req = reply.request()  # 获取请求
+        o_req = req.attribute(self.AttrType, None)
+        if o_req:
+            # Notice: 这里可以对数据做修改再返回
+            # TODO: 可能还存在 QNetworkAccessManager 与浏览器之间的 cookie 同步问题
+            o_req.reply(req.header(QNetworkRequest.ContentTypeHeader) or b'text/html', reply)
+            o_req.destroyed.connect(reply.deleteLater)
+
+
+# 把所有请求重定向到myurl
+class RequestInterceptor(QWebEngineUrlRequestInterceptor):
+
+    def interceptRequest(self, info):
+        url = info.requestUrl()
+        if url.scheme() == 'http':
+            # 重定向
+            url.setScheme('myurl')
+            info.redirect(url)
+        elif url.scheme() == 'https':
+            # 重定向
+            url.setScheme('myurls')
+            info.redirect(url)
+
+
+class Window(QWebEngineView):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.resize(800, 600)
+        profile = QWebEngineProfile.defaultProfile()
+
+        # 首先获取默认的url协议
+        o_http = QWebEngineUrlScheme.schemeByName(QByteArray(b'http'))
+        o_https = QWebEngineUrlScheme.schemeByName(QByteArray(b'https'))
+        print('scheme:', o_http, o_https)
+
+        # 这里需要修改增加本地文件和跨域支持
+        CorsEnabled = 0x80  # 5.14才增加
+        o_http.setFlags(o_http.flags() |
+                        QWebEngineUrlScheme.SecureScheme |
+                        QWebEngineUrlScheme.LocalScheme |
+                        QWebEngineUrlScheme.LocalAccessAllowed |
+                        CorsEnabled)
+        o_https.setFlags(o_https.flags() |
+                         QWebEngineUrlScheme.SecureScheme |
+                         QWebEngineUrlScheme.LocalScheme |
+                         QWebEngineUrlScheme.LocalAccessAllowed |
+                         CorsEnabled)
+
+        # 安装url拦截器和自定义url协议处理
+        de = QWebEngineProfile.defaultProfile()  # @UndefinedVariable
+        de.setRequestInterceptor(RequestInterceptor(self))
+        self.urlSchemeHandler = UrlSchemeHandler(self)
+        de.installUrlSchemeHandler(QByteArray(b'myurl'), self.urlSchemeHandler)  # for http
+        de.installUrlSchemeHandler(QByteArray(b'myurls'), self.urlSchemeHandler)  # for https
+
+
+if __name__ == '__main__':
+    import sys
+    import os
+    import webbrowser
+    import cgitb
+
+    cgitb.enable(format='text')
+
+    app = QApplication(sys.argv)
+    # 开启F12 控制台功能,需要单独通过浏览器打开这个页面
+    # 这里可以做个保护, 发布软件,启动时把这个环境变量删掉。防止他人通过环境变量开启
+    os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '9966'
+    # 打开调试页面
+    webbrowser.open_new_tab('http://127.0.0.1:9966/')
+
+    w = Window()
+    w.show()
+    w.load(QUrl('https://pyqt.site/'))
+    sys.exit(app.exec_())
diff --git a/QWebEngineView/JsSignals.py b/QWebEngineView/JsSignals.py
index 725ea901..aac94019 100644
--- a/QWebEngineView/JsSignals.py
+++ b/QWebEngineView/JsSignals.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年4月27日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QWebEngineView.JsSignals
 @description: 
@@ -12,18 +12,19 @@
 import os
 from time import time
 
-from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal
-from PyQt5.QtWebChannel import QWebChannel
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
-from PyQt5.QtWidgets import QMessageBox, QWidget, QVBoxLayout, QPushButton
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
+try:
+    from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal
+    from PyQt5.QtWebChannel import QWebChannel
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
+    from PyQt5.QtWidgets import QMessageBox, QWidget, QVBoxLayout, QPushButton
+except ImportError:
+    from PySide2.QtCore import QUrl, Slot as pyqtSlot, Signal as pyqtSignal
+    from PySide2.QtWebChannel import QWebChannel
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
+    from PySide2.QtWidgets import QMessageBox, QWidget, QVBoxLayout, QPushButton
 
 
 class WebEngineView(QWebEngineView):
-
     customSignal = pyqtSignal(str)
 
     def __init__(self, *args, **kwargs):
@@ -38,13 +39,13 @@ def __init__(self, *args, **kwargs):
         # START #####以下代码可能是在5.6 QWebEngineView刚出来时的bug,必须在每次加载页面的时候手动注入
         #### 也有可能是跳转页面后就失效了,需要手动注入,有没有修复具体未测试
 
-#         self.page().loadStarted.connect(self.onLoadStart)
-#         self._script = open('Data/qwebchannel.js', 'rb').read().decode()
+    #         self.page().loadStarted.connect(self.onLoadStart)
+    #         self._script = open('Data/qwebchannel.js', 'rb').read().decode()
 
-#     def onLoadStart(self):
-#         self.page().runJavaScript(self._script)
+    #     def onLoadStart(self):
+    #         self.page().runJavaScript(self._script)
 
-        # END ###########################
+    # END ###########################
 
     # 注意pyqtSlot用于把该函数暴露给js可以调用
     @pyqtSlot(str)
@@ -58,16 +59,16 @@ def sendCustomSignal(self):
     @pyqtSlot(str)
     @pyqtSlot(QUrl)
     def load(self, url):
-        '''
-        eg: load("https://pyqt5.com/")
+        """
+        eg: load("https://pyqt.site/")
         :param url: 网址
-        '''
+        """
         return super(WebEngineView, self).load(QUrl(url))
 
     def initSettings(self):
-        '''
+        """
         eg: 初始化设置
-        '''
+        """
         # 获取浏览器默认设置
         settings = QWebEngineSettings.globalSettings()
         # 设置默认编码utf8
@@ -133,9 +134,10 @@ def __init__(self, *args, **kwargs):
             os.path.abspath('Data/JsSignals.html')))
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     from PyQt5.QtWidgets import QApplication
     import sys
+
     # 开启F12 控制台功能,需要单独通过浏览器打开这个页面
     # 这里可以做个保护, 发布软件,启动时把这个环境变量删掉。防止他人通过环境变量开启
     os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '9966'
diff --git a/QWebEngineView/README.md b/QWebEngineView/README.md
index 533876a3..4a26699a 100644
--- a/QWebEngineView/README.md
+++ b/QWebEngineView/README.md
@@ -7,8 +7,10 @@
   - [同网站不同用户](#4同网站不同用户)
   - [拦截请求](#5拦截请求)
   - [拦截请求内容](#6拦截请求内容)
+  - [设置Cookie](#7设置Cookie)
 
 ## 1、获取Cookie
+
 [运行 GetCookie.py](GetCookie.py)
 
 通过`QWebEngineProfile`中得到的`cookieStore`并绑定它的`cookieAdded`信号来得到Cookie
@@ -16,6 +18,7 @@
 
 
 ## 2、和Js交互操作
+
 [运行 JsSignals.py](JsSignals.py)
 
 通过`qwebchannel.js`和`QWebChannel.registerObject`进行Python对象和Javascript的交互
@@ -25,6 +28,7 @@
 
 
 ## 3、网页整体截图
+
 [运行 ScreenShotPage.py](ScreenShotPage.py)
 
 1. 方式1:目前通过不完美方法(先调整`QWebEngineView`的大小为`QWebEnginePage`的内容大小,等待一定时间后截图再还原大小)
@@ -33,6 +37,7 @@
 
 
 ## 4、同网站不同用户
+
 [运行 SiteDiffUser.py](SiteDiffUser.py)
 
 原理是为每个`QWebEngineView`创建一个`QWebEnginePage`,且使用独立的`QWebEngineProfile`,并配置`persistentStoragePath`不同路径
@@ -40,6 +45,7 @@
 
 
 ## 5、拦截请求
+
 [运行 BlockRequest.py](BlockRequest.py)
 
 通过`QWebEngineUrlRequestInterceptor`中的`interceptRequest`方法对每个请求做拦截过滤
@@ -47,9 +53,19 @@
 
 
 ## 6、拦截请求内容
+
 [运行 BlockRequestData.py](BlockRequestData.py)
 
 这里用了一个投巧的办法,原理是先通过`QWebEngineUrlRequestInterceptor`中的`interceptRequest`方法对每个请求做拦截过滤,
 发现目标url后重定向到`QWebEngineUrlSchemeHandler`实现的自定义协议头返回数据
 
-
\ No newline at end of file
+
+
+## 7、设置Cookie
+
+[运行 SetCookies.py](SetCookies.py)
+
+通过`QWebEngineProfile`中得到的`cookieStore`来添加`QNetworkCookie`对象实现,
+需要注意的是httpOnly=true时,通过js无法获取
+
+
diff --git a/QWebEngineView/ScreenShot/SetCookies.png b/QWebEngineView/ScreenShot/SetCookies.png
new file mode 100644
index 00000000..01c98c1d
Binary files /dev/null and b/QWebEngineView/ScreenShot/SetCookies.png differ
diff --git a/QWebEngineView/ScreenShotPage.py b/QWebEngineView/ScreenShotPage.py
index ad828219..bef64383 100644
--- a/QWebEngineView/ScreenShotPage.py
+++ b/QWebEngineView/ScreenShotPage.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年7月8日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ScreenShotPage
 @description: 网页整体截图
@@ -14,18 +14,22 @@
 import os
 import sys
 
-from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QSize, QTimer
-from PyQt5.QtGui import QImage, QPainter, QIcon, QPixmap
-from PyQt5.QtWebChannel import QWebChannel
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton,\
-    QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem,\
-    QProgressDialog
-
-
-__Author__ = "Irony"
-__Copyright__ = "Copyright (c) 2019"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QSize, QTimer, QPoint
+    from PyQt5.QtGui import QImage, QPainter, QIcon, QPixmap
+    from PyQt5.QtWebChannel import QWebChannel
+    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
+    from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton, \
+        QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem, \
+        QProgressDialog
+except ImportError:
+    from PySide2.QtCore import QUrl, Qt, Slot as pyqtSlot, QSize, QTimer, QPoint
+    from PySide2.QtGui import QImage, QPainter, QIcon, QPixmap
+    from PySide2.QtWebChannel import QWebChannel
+    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
+    from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton, \
+        QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem, \
+        QProgressDialog
 
 # 对部分内容进行截图
 CODE = """
@@ -116,8 +120,8 @@ def onLoadFinished(self, finished):
             open('Data/qwebchannel.js', 'rb').read().decode())
         page.runJavaScript(
             open('Data/jquery.js', 'rb').read().decode())
-#         page.runJavaScript(
-#             open('Data/promise-7.0.4.min.js', 'rb').read().decode())
+        #         page.runJavaScript(
+        #             open('Data/promise-7.0.4.min.js', 'rb').read().decode())
         page.runJavaScript(
             open('Data/html2canvas.min.js', 'rb').read().decode())
         page.runJavaScript(CreateBridge)
@@ -143,7 +147,7 @@ def doScreenShot():
             painter.setRenderHint(QPainter.TextAntialiasing, True)
             painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
 
-            self.webView.render(painter)
+            self.webView.render(painter, QPoint())
             painter.end()
             self.webView.resize(oldSize)
 
@@ -181,11 +185,11 @@ def saveImage(self, image):
         item.setData(Qt.UserRole + 1, image)
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     # 开启F12 控制台功能,需要单独通过浏览器打开这个页面
     # 这里可以做个保护, 发布软件,启动时把这个环境变量删掉。防止他人通过环境变量开启
     os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '9966'
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebEngineView/SetCookies.py b/QWebEngineView/SetCookies.py
new file mode 100644
index 00000000..8a858378
--- /dev/null
+++ b/QWebEngineView/SetCookies.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2022/05/12
+@author: Irony
+@site: https://pyqt.site https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: SetCookies.py
+@description: 主动设置Cookie
+"""
+
+try:
+    from PyQt5.QtCore import QDateTime, Qt, QUrl
+    from PyQt5.QtWebEngineWidgets import QWebEngineProfile, QWebEngineView
+    from PyQt5.QtWidgets import QApplication
+    from PyQt5.QtNetwork import QNetworkCookie
+except ImportError:
+    from PySide2.QtCore import QDateTime, Qt, QUrl
+    from PySide2.QtWebEngineWidgets import QWebEngineProfile, QWebEngineView
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtNetwork import QNetworkCookie
+
+cookies = [{
+    "domain": "pyqt.site",
+    "expirationDate": 1714906174.734258,
+    "hostOnly": True,
+    "httpOnly": False,
+    "name": "snexid",
+    "path": "/",
+    "sameSite": None,
+    "secure": False,
+    "session": False,
+    "storeId": None,
+    "value": "1-22-333-4444-55555"
+}, {
+    "domain": "pyqt.site",
+    "expirationDate": 1714906174.734258,
+    "hostOnly": True,
+    "httpOnly": True,
+    "name": "testonly",
+    "path": "/",
+    "secure": True,
+    "value": "testonly"
+}, {
+    "domain": "pyqt.site",
+    "hostOnly": True,
+    "httpOnly": False,
+    "name": "test",
+    "path": "/",
+    "secure": False,
+    "value": "test"
+}]
+
+
+class Window(QWebEngineView):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        # global
+        # self.cookieStore = QWebEngineProfile.defaultProfile().cookieStore()
+
+        # current
+        self.cookieStore = self.page().profile().cookieStore()
+        self.initCookies()
+        self.loadProgress.connect(self.onLoadProgress)
+        self.load(QUrl('https://pyqt.site/'))
+
+    def onLoadProgress(self, progress):
+        if progress == 100:
+            # 测试获取cookie
+            self.page().runJavaScript('alert(document.cookie);')
+
+    def initCookies(self):
+        for cookie in cookies:
+            qcookie = QNetworkCookie()
+            qcookie.setName(cookie.get('name', '').encode())
+            qcookie.setValue(cookie.get('value', '').encode())
+            qcookie.setDomain(cookie.get('domain', ''))
+            qcookie.setPath(cookie.get('path', ''))
+            qcookie.setExpirationDate(
+                QDateTime.fromString(str(cookie.get('expirationDate', 0)),
+                                     Qt.ISODate))
+            qcookie.setHttpOnly(cookie.get('httpOnly', False))
+            qcookie.setSecure(cookie.get('secure', False))
+            # 注意可以设置具体的url
+            self.cookieStore.setCookie(qcookie, QUrl())
+
+
+if __name__ == '__main__':
+    import cgitb
+    import sys
+
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QWebEngineView/SiteDiffUser.py b/QWebEngineView/SiteDiffUser.py
index 89d096a0..3b30b642 100644
--- a/QWebEngineView/SiteDiffUser.py
+++ b/QWebEngineView/SiteDiffUser.py
@@ -1,23 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
 """
 Created on 2019年8月23日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QWebEngineView.SiteDiffUser
 @description: 同个网站不同用户
 """
-from PyQt5.QtCore import QUrl
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage,\
-    QWebEngineProfile
-from PyQt5.QtWidgets import QTabWidget
 
+import os
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-__Version__ = 'Version 1.0'
+try:
+    from PyQt5.QtCore import QUrl
+    from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineProfile,
+                                          QWebEngineView)
+    from PyQt5.QtWidgets import QApplication, QTabWidget
+except ImportError:
+    from PySide2.QtCore import QUrl
+    from PySide2.QtWebEngineWidgets import (QWebEnginePage, QWebEngineProfile,
+                                            QWebEngineView)
+    from PySide2.QtWidgets import QApplication, QTabWidget
 
 
 class Window(QTabWidget):
@@ -28,7 +31,9 @@ def __init__(self, *args, **kwargs):
         # 用户1
         self.webView1 = QWebEngineView(self)
         profile1 = QWebEngineProfile('storage1', self.webView1)
-        profile1.setPersistentStoragePath('Tmp/Storage1')
+        # 要设置绝对路径,否则会出现部分问题
+        # 比如 reCaptcha 不能加载,见 https://github.com/PyQt5/PyQt/issues/125
+        profile1.setPersistentStoragePath(os.path.abspath('Tmp/Storage1'))
         print(profile1.cookieStore())
         # 如果要清除cookie
         # cookieStore = profile1.cookieStore()
@@ -41,7 +46,7 @@ def __init__(self, *args, **kwargs):
         # 用户2
         self.webView2 = QWebEngineView(self)
         profile2 = QWebEngineProfile('storage2', self.webView2)
-        profile2.setPersistentStoragePath('Tmp/Storage2')
+        profile2.setPersistentStoragePath(os.path.abspath('Tmp/Storage2'))
         print(profile2.cookieStore())
         page2 = QWebEnginePage(profile2, self.webView2)
         self.webView2.setPage(page2)
@@ -53,7 +58,7 @@ def __init__(self, *args, **kwargs):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebView/BlockRequest.py b/QWebView/BlockRequest.py
index 3db7d0d1..dd50c6ec 100644
--- a/QWebView/BlockRequest.py
+++ b/QWebView/BlockRequest.py
@@ -4,21 +4,16 @@
 """
 Created on 2019年9月24日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QWebView.BlockAds
 @description: 拦截请求
 """
-from PyQt5.QtCore import QUrl, QBuffer, QByteArray
+from PyQt5.QtCore import QUrl, QBuffer
 from PyQt5.QtNetwork import QNetworkAccessManager
 from PyQt5.QtWebKitWidgets import QWebView
 
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-__Version__ = 'Version 1.0'
-
-
 class RequestInterceptor(QNetworkAccessManager):
 
     def createRequest(self, op, originalReq, outgoingData):
@@ -55,6 +50,7 @@ def __init__(self, *args, **kwargs):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebView/DreamTree.py b/QWebView/DreamTree.py
index 8263b01a..071e2f1e 100644
--- a/QWebView/DreamTree.py
+++ b/QWebView/DreamTree.py
@@ -1,14 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年4月6日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: DreamTree
 @description: 
-'''
+"""
 
 import sys
 
@@ -17,12 +17,8 @@
 from PyQt5.QtWebKitWidgets import QWebView
 from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
 
-from Lib import data_rc  # @UnusedImport @UnresolvedImport
 
 # from PyQt5.QtWebKit import QWebSettings
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
 
 
 # 要实现透明的webview,需要先用一个QWidget作为父控件
diff --git a/QWebView/GetCookie.py b/QWebView/GetCookie.py
index 090b4f6e..1420837e 100644
--- a/QWebView/GetCookie.py
+++ b/QWebView/GetCookie.py
@@ -1,14 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月10日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: GetCookie
 @description: 
-'''
+"""
 import cgitb
 import sys
 
@@ -17,11 +17,6 @@
 from PyQt5.QtWidgets import QApplication, QTextEdit
 
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-
 class WebView(QWebView):
 
     def __init__(self, *args, **kwargs):
@@ -52,7 +47,7 @@ def onLoadFinished(self):
         allCookies = self.page().networkAccessManager().cookieJar().allCookies()
         print("allCookies:", allCookies)
         for cookie in allCookies:
-            # if cookie.domain() == ".pyqt5.com":
+            # if cookie.domain() == ".pyqt.site":
             self.cookieView.append(
                 "domain: " + self.bytestostr(cookie.domain()))
             self.cookieView.append("path:   " + self.bytestostr(cookie.path()))
@@ -68,9 +63,9 @@ def onLoadFinished(self):
 
 
 if __name__ == "__main__":
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = WebView()
     w.show()
-    w.load(QUrl("https://pyqt5.com/"))
+    w.load(QUrl("https://pyqt.site/"))
     sys.exit(app.exec_())
diff --git a/QWebView/JsSignals.py b/QWebView/JsSignals.py
index 878e61b6..6a3d7a55 100644
--- a/QWebView/JsSignals.py
+++ b/QWebView/JsSignals.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年4月27日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QWebEngineView.JsSignals
 @description: 
@@ -18,12 +18,7 @@
 from PyQt5.QtWidgets import QMessageBox, QWidget, QVBoxLayout, QPushButton
 
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-
-
 class WebView(QWebView):
-
     customSignal = pyqtSignal(str)
 
     def __init__(self, *args, **kwargs):
@@ -50,7 +45,7 @@ def sendCustomSignal(self):
     @pyqtSlot(QUrl)
     def load(self, url):
         '''
-        eg: load("https://pyqt5.com/")
+        eg: load("https://pyqt.site/")
         :param url: 网址
         '''
         return super(WebView, self).load(QUrl(url))
@@ -85,6 +80,7 @@ def __init__(self, *args, **kwargs):
 if __name__ == "__main__":
     from PyQt5.QtWidgets import QApplication
     import sys
+
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWebView/Lib/data_rc.py b/QWebView/Lib/data_rc.py
index 59b9117a..17c0cc24 100644
--- a/QWebView/Lib/data_rc.py
+++ b/QWebView/Lib/data_rc.py
@@ -6216,10 +6216,13 @@
 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
 "
 
+
 def qInitResources():
     QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
 
+
 def qCleanupResources():
     QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
 
+
 qInitResources()
diff --git a/QWebView/PlayFlash.py b/QWebView/PlayFlash.py
index dc992f57..15da61d0 100644
--- a/QWebView/PlayFlash.py
+++ b/QWebView/PlayFlash.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年9月18日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QWebView.PlayFlash
 @description: 播放Flash
@@ -19,11 +19,6 @@
 from PyQt5.QtWidgets import QApplication
 
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
-
-
 class Window(QWebView):
 
     def __init__(self, *args, **kwargs):
@@ -53,5 +48,6 @@ def handleSslErrors(self, reply, errors):
     app = QApplication(sys.argv)
     w = Window()
     w.show()
-    w.load(QUrl('https://www.17sucai.com/preview/8825/2013-07-07/%E4%B8%80%E6%AC%BE%E4%BA%BA%E5%BD%A2%E5%8A%A8%E4%BD%9C%E6%98%BE%E7%A4%BA%E7%9A%84flash%E6%97%B6%E9%97%B4/index.html'))
+    w.load(QUrl(
+        'https://www.17sucai.com/preview/8825/2013-07-07/%E4%B8%80%E6%AC%BE%E4%BA%BA%E5%BD%A2%E5%8A%A8%E4%BD%9C%E6%98%BE%E7%A4%BA%E7%9A%84flash%E6%97%B6%E9%97%B4/index.html'))
     sys.exit(app.exec_())
diff --git a/QWebView/README.md b/QWebView/README.md
index 557b5faa..f9b509e2 100644
--- a/QWebView/README.md
+++ b/QWebView/README.md
@@ -9,6 +9,7 @@
   - [拦截请求](#6拦截请求)
 
 ## 1、梦幻树
+
 [运行 DreamTree.py](DreamTree.py)
 
 在桌面上显示透明html效果,使用`QWebkit`加载html实现,采用窗口背景透明和穿透方式
@@ -16,6 +17,7 @@
 
 
 ## 2、获取Cookie
+
 [运行 GetCookie.py](GetCookie.py)
 
 从`page()`中得到`QNetworkAccessManager`,在从中得到`QNetworkCookieJar`,
@@ -24,6 +26,7 @@
 
 
 ## 3、和Js交互操作
+
 [运行 JsSignals.py](JsSignals.py)
 
 通过`QWebFrame`的`addToJavaScriptWindowObject`函数提供进行Python对象和Javascript的交互
@@ -33,6 +36,7 @@
 
 
 ## 4、网页整体截图
+
 [运行 ScreenShotPage.py](ScreenShotPage.py)
 
 1. 方式1:原理是通过`QWebView.QWebPage.QWebFrame`得到内容的高度,然后设置`QWebPage.setViewportSize`的大小,
@@ -42,6 +46,7 @@
 
 
 ## 5、播放Flash
+
 [运行 PlayFlash.py](PlayFlash.py)
 
 1. 重点在于设置 `os.environ['QTWEBKIT_PLUGIN_PATH'] = os.path.abspath('Data')` ,非常重要,设置为NPSWF32.dll文件所在目录
@@ -50,8 +55,9 @@
 
 
 ## 6、拦截请求
+
 [运行 BlockRequest.py](BlockRequest.py)
 
 通过`QNetworkAccessManager`中的`createRequest`方法对每个请求做拦截过滤
 
-
\ No newline at end of file
+
diff --git a/QWebView/ScreenShotPage.py b/QWebView/ScreenShotPage.py
index 5569a545..6a00fc9a 100644
--- a/QWebView/ScreenShotPage.py
+++ b/QWebView/ScreenShotPage.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年7月8日
 @author: Irony
-@site: https://pyqt5.com https://github.com/PyQt5
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ScreenShotPage
 @description: 网页整体截图
@@ -17,15 +17,10 @@
 from PyQt5.QtGui import QImage, QPainter, QIcon, QPixmap
 from PyQt5.QtWebKit import QWebSettings
 from PyQt5.QtWebKitWidgets import QWebView
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton,\
-    QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem,\
+from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton, \
+    QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem, \
     QProgressDialog
 
-
-__Author__ = "Irony"
-__Copyright__ = "Copyright (c) 2019"
-__Version__ = "Version 1.0"
-
 # 对部分内容进行截图
 CODE = """
 var el = $("%s");
@@ -80,7 +75,7 @@ def __init__(self, *args, **kwargs):
             QWebSettings.DeveloperExtrasEnabled, True)
         self.webView.loadStarted.connect(self.onLoadStarted)
         self.webView.loadFinished.connect(self.onLoadFinished)
-        self.webView.load(QUrl("https://pyqt5.com/"))
+        self.webView.load(QUrl("https://pyqt.site/"))
 
         # 暴露接口和加载完成后执行jquery等一些库文件
         self.webView.page().mainFrame().javaScriptWindowObjectCleared.connect(
@@ -165,7 +160,7 @@ def saveImage(self, image):
 
 
 if __name__ == "__main__":
-    sys.excepthook = cgitb.enable(1, None, 5, '')
+    cgitb.enable(format='text')
     app = QApplication(sys.argv)
     w = Window()
     w.show()
diff --git a/QWidget/Lib/CustomPaintWidget.py b/QWidget/Lib/CustomPaintWidget.py
index 4d5d4ed6..f006d0a6 100644
--- a/QWidget/Lib/CustomPaintWidget.py
+++ b/QWidget/Lib/CustomPaintWidget.py
@@ -1,21 +1,21 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月10日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CustomPaintWidget
 @description: 
-'''
-from PyQt5.QtGui import QPainter
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QStyleOption, QStyle
+"""
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QStyleOption, QStyle
+except ImportError:
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QStyleOption, QStyle
 
 
 class CustomPaintWidget(QWidget):
diff --git a/QWidget/Lib/CustomWidget.py b/QWidget/Lib/CustomWidget.py
index 11edcfb3..0231fc94 100644
--- a/QWidget/Lib/CustomWidget.py
+++ b/QWidget/Lib/CustomWidget.py
@@ -1,20 +1,19 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月10日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CustomWidget
 @description: 
-'''
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
+"""
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
+except ImportError:
+    from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel
 
 
 class CustomWidget(QWidget):
@@ -22,4 +21,4 @@ class CustomWidget(QWidget):
     def __init__(self, *args, **kwargs):
         super(CustomWidget, self).__init__(*args, **kwargs)
         layout = QVBoxLayout(self)
-        layout.addWidget(QLabel("我是自定义CustomWidget", self))
\ No newline at end of file
+        layout.addWidget(QLabel("我是自定义CustomWidget", self))
diff --git a/QWidget/README.md b/QWidget/README.md
index d5b6c1c9..0d86518e 100644
--- a/QWidget/README.md
+++ b/QWidget/README.md
@@ -4,9 +4,10 @@
   - [样式表测试](#1样式表测试)
 
 ## 1、样式表测试
+
 [运行 WidgetStyle.py](WidgetStyle.py)
 
 1. 一种是重写 `paintEvent`
 2. 设置 `Qt.WA_StyledBackground` 后可以通过QSS增加背景等
 
-
\ No newline at end of file
+
diff --git a/QWidget/WidgetStyle.py b/QWidget/WidgetStyle.py
index 945407c6..842a29a6 100644
--- a/QWidget/WidgetStyle.py
+++ b/QWidget/WidgetStyle.py
@@ -1,28 +1,28 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月10日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: test
 @description: 
-'''
+"""
+
 import sys
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
+try:
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout
 
 from Lib.CustomPaintWidget import CustomPaintWidget  # @UnresolvedImport
 from Lib.CustomWidget import CustomWidget  # @UnresolvedImport
 
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-
 class Window(QWidget):
 
     def __init__(self, *args, **kwargs):
@@ -31,9 +31,9 @@ def __init__(self, *args, **kwargs):
         layout.addWidget(CustomPaintWidget(self))
         layout.addWidget(CustomWidget(self))
         # 注意
-        w = CustomWidget(self)
-        w.setAttribute(Qt.WA_StyledBackground)  # 很重要
-        layout.addWidget(w)
+        wc = CustomWidget(self)
+        wc.setAttribute(Qt.WA_StyledBackground)  # 很重要
+        layout.addWidget(wc)
 
 
 if __name__ == "__main__":
diff --git a/QtChart/AreaChart.py b/QtChart/AreaChart.py
index 9e2d022f..80b88682 100644
--- a/QtChart/AreaChart.py
+++ b/QtChart/AreaChart.py
@@ -4,14 +4,27 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: AreaChart
 @description: 区域图表
 """
-from PyQt5.QtChart import QChartView, QChart, QLineSeries, QAreaSeries
-from PyQt5.QtCore import QPointF
-from PyQt5.QtGui import QColor, QGradient, QLinearGradient, QPainter, QPen
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QLineSeries, QAreaSeries
+    from PyQt5.QtCore import QPointF
+    from PyQt5.QtGui import QColor, QGradient, QLinearGradient, QPainter, QPen
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import QPointF
+    from PySide2.QtGui import QColor, QGradient, QLinearGradient, QPainter, QPen
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
+    QAreaSeries = QtCharts.QAreaSeries
 
 
 class Window(QChartView):
@@ -42,9 +55,9 @@ def getSeries(self):
 
         # 添加数据
         series0 << QPointF(1, 5) << QPointF(3, 7) << QPointF(7, 6) << QPointF(9, 7) \
-                << QPointF(12, 6) << QPointF(16, 7) << QPointF(18, 5)
+        << QPointF(12, 6) << QPointF(16, 7) << QPointF(18, 5)
         series1 << QPointF(1, 3) << QPointF(3, 4) << QPointF(7, 3) << QPointF(8, 2) \
-                << QPointF(12, 3) << QPointF(16, 4) << QPointF(18, 3)
+        << QPointF(12, 3) << QPointF(16, 4) << QPointF(18, 3)
 
         # 创建区域图
         series = QAreaSeries(series0, series1)
@@ -67,7 +80,6 @@ def getSeries(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/BarChart.py b/QtChart/BarChart.py
index 73bd762b..fadcbdb0 100644
--- a/QtChart/BarChart.py
+++ b/QtChart/BarChart.py
@@ -4,14 +4,28 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: BarChart
 @description: 柱状图表
 """
-from PyQt5.QtChart import QChartView, QChart, QBarSet, QBarSeries, QBarCategoryAxis
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPainter
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QBarSet, QBarSeries, QBarCategoryAxis
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QBarSet = QtCharts.QBarSet
+    QBarSeries = QtCharts.QBarSeries
+    QBarCategoryAxis = QtCharts.QBarCategoryAxis
 
 
 class Window(QChartView):
@@ -72,7 +86,6 @@ def getSeries(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/BarStack.py b/QtChart/BarStack.py
index 6267f422..f0ea25cc 100644
--- a/QtChart/BarStack.py
+++ b/QtChart/BarStack.py
@@ -1,27 +1,36 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: charts.bar.BarStack
 @description: like http://echarts.baidu.com/demo.html#bar-stack
-'''
+"""
 
 import sys
 from random import randint
 
-from PyQt5.QtChart import QChartView, QChart, QBarSeries, QBarSet, QBarCategoryAxis
-from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
-from PyQt5.QtGui import QPainter, QPen
-from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
-    QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtChart import QChartView, QChart, QBarSeries, QBarSet, QBarCategoryAxis
+    from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
+    from PyQt5.QtGui import QPainter, QPen
+    from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
+        QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
+except ImportError:
+    from PySide2.QtCore import Qt, QPointF, QRectF, QPoint
+    from PySide2.QtGui import QPainter, QPen
+    from PySide2.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
+        QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QBarSeries = QtCharts.QBarSeries
+    QBarSet = QtCharts.QBarSet
+    QBarCategoryAxis = QtCharts.QBarCategoryAxis
 
 
 class ToolTipItem(QWidget):
@@ -44,7 +53,6 @@ def setText(self, text):
 
 
 class ToolTipWidget(QWidget):
-
     Cache = {}
 
     def __init__(self, *args, **kwargs):
@@ -134,7 +142,7 @@ def mouseMoveEvent(self, event):
         serie = self._chart.series()[0]
         bars = [(bar, bar.at(index))
                 for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
-#         print(bars)
+        #         print(bars)
         if bars:
             right_top = self._chart.mapToPosition(
                 QPointF(self.max_x, self.max_y))
@@ -153,10 +161,10 @@ def mouseMoveEvent(self, event):
             t_height = self.toolTipWidget.height()
             # 如果鼠标位置离右侧的距离小于tip宽度
             x = pos.x() - t_width if self.width() - \
-                pos.x() - 20 < t_width else pos.x()
+                                     pos.x() - 20 < t_width else pos.x()
             # 如果鼠标位置离底部的高度小于tip高度
             y = pos.y() - t_height if self.height() - \
-                pos.y() - 20 < t_height else pos.y()
+                                      pos.y() - 20 < t_height else pos.y()
             self.toolTipWidget.show(
                 title, bars, QPoint(x, y))
         else:
diff --git a/QtChart/ChartThemes.py b/QtChart/ChartThemes.py
index 38704012..059e7f15 100644
--- a/QtChart/ChartThemes.py
+++ b/QtChart/ChartThemes.py
@@ -4,7 +4,7 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ChartThemes
 @description: 图表主题动画等
@@ -29,13 +29,30 @@
 
 import random
 
-from PyQt5.QtChart import (QAreaSeries, QBarSet, QChart, QChartView,
-                           QLineSeries, QPieSeries, QScatterSeries, QSplineSeries,
-                           QStackedBarSeries)
-from PyQt5.QtCore import pyqtSlot, QPointF, Qt
-from PyQt5.QtGui import QColor, QPainter, QPalette
-from PyQt5.QtWidgets import (QCheckBox, QComboBox, QGridLayout, QHBoxLayout,
-                             QLabel, QSizePolicy, QWidget)
+try:
+    from PyQt5.QtChart import (QAreaSeries, QBarSet, QChart, QChartView,
+                               QLineSeries, QPieSeries, QScatterSeries, QSplineSeries,
+                               QStackedBarSeries)
+    from PyQt5.QtCore import pyqtSlot, QPointF, Qt
+    from PyQt5.QtGui import QColor, QPainter, QPalette
+    from PyQt5.QtWidgets import QApplication, QMainWindow, QCheckBox, QComboBox, QGridLayout, QHBoxLayout, \
+        QLabel, QSizePolicy, QWidget
+except ImportError:
+    from PySide2.QtCore import Slot as pyqtSlot, QPointF, Qt
+    from PySide2.QtGui import QColor, QPainter, QPalette
+    from PySide2.QtWidgets import QApplication, QMainWindow, QCheckBox, QComboBox, QGridLayout, QHBoxLayout, \
+        QLabel, QSizePolicy, QWidget
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QAreaSeries = QtCharts.QAreaSeries
+    QBarSet = QtCharts.QBarSet
+    QLineSeries = QtCharts.QLineSeries
+    QPieSeries = QtCharts.QPieSeries
+    QScatterSeries = QtCharts.QScatterSeries
+    QSplineSeries = QtCharts.QSplineSeries
+    QStackedBarSeries = QtCharts.QStackedBarSeries
 
 
 class ThemeWidget(QWidget):
@@ -238,7 +255,7 @@ def createPieChart(self):
             series = QPieSeries(chart)
             for value, label in data_list:
                 slice = series.append(label, value.y())
-                if len(series) == 1:
+                if series.count() == 1:
                     slice.setLabelVisible()
                     slice.setExploded()
 
@@ -290,7 +307,7 @@ def updateUI(self):
 
         if self.m_charts[0].chart().theme() != theme:
             for chartView in self.m_charts:
-                chartView.chart().setTheme(theme)
+                chartView.chart().setTheme(QChart.ChartTheme(theme))
 
             pal = self.window().palette()
 
@@ -349,8 +366,6 @@ def updateUI(self):
 if __name__ == '__main__':
     import sys
 
-    from PyQt5.QtWidgets import QApplication, QMainWindow
-
     app = QApplication(sys.argv)
 
     window = QMainWindow()
diff --git a/QtChart/CpuLineChart.py b/QtChart/CpuLineChart.py
new file mode 100644
index 00000000..17b521e5
--- /dev/null
+++ b/QtChart/CpuLineChart.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2021/5/13
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CpuLineChart
+@description: 
+"""
+
+import sys
+
+from PyQt5.QtChart import QChartView, QChart, QSplineSeries, QDateTimeAxis, QValueAxis
+from PyQt5.QtCore import Qt, QTimer, QDateTime, QPointF
+from PyQt5.QtGui import QPainter, QPen, QColor
+from PyQt5.QtWidgets import QApplication
+from psutil import cpu_percent
+
+
+class CpuLineChart(QChart):
+
+    def __init__(self, *args, **kwargs):
+        super(CpuLineChart, self).__init__(*args, **kwargs)
+        self.m_count = 10
+        # 隐藏图例
+        self.legend().hide()
+        self.m_series = QSplineSeries(self)
+        # 设置画笔
+        self.m_series.setPen(QPen(QColor('#3B8CFF'), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+        self.addSeries(self.m_series)
+        # x轴
+        self.m_axisX = QDateTimeAxis(self)
+        self.m_axisX.setTickCount(self.m_count + 1)  # 设置刻度数量
+        self.m_axisX.setFormat('hh:mm:ss')  # 设置时间显示格式
+        now = QDateTime.currentDateTime()  # 前10秒到现在
+        self.m_axisX.setRange(now.addSecs(-self.m_count), now)
+        self.addAxis(self.m_axisX, Qt.AlignBottom)
+        self.m_series.attachAxis(self.m_axisX)
+        # y轴
+        self.m_axisY = QValueAxis(self)
+        self.m_axisY.setLabelFormat('%d')  # 设置文本格式
+        self.m_axisY.setMinorTickCount(4)  # 设置小刻度线的数目
+        self.m_axisY.setTickCount(self.m_count + 1)
+        self.m_axisY.setRange(0, 100)
+        self.addAxis(self.m_axisY, Qt.AlignLeft)
+        self.m_series.attachAxis(self.m_axisY)
+
+        # 填充11个初始点,注意x轴 需要转为秒的时间戳
+        self.m_series.append(
+            [QPointF(now.addSecs(-i).toMSecsSinceEpoch(), 0) for i in range(self.m_count, -1, -1)])
+
+        # 定时器获取数据
+        self.m_timer = QTimer()
+        self.m_timer.timeout.connect(self.update_data)
+        self.m_timer.start(1000)
+
+    def update_data(self):
+        value = cpu_percent()
+        now = QDateTime.currentDateTime()
+        self.m_axisX.setRange(now.addSecs(-self.m_count), now)  # 重新调整x轴的时间范围
+        # 获取原来的所有点,去掉第一个并追加新的一个
+        points = self.m_series.pointsVector()
+        points.pop(0)
+        points.append(QPointF(now.toMSecsSinceEpoch(), value))
+        # 替换法速度更快
+        self.m_series.replace(points)
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    chart = CpuLineChart()
+    chart.setTitle('cpu')
+    # chart.setAnimationOptions(QChart.SeriesAnimations)
+
+    view = QChartView(chart)
+    view.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
+    view.resize(800, 600)
+    view.show()
+    sys.exit(app.exec_())
diff --git a/QtChart/CustomXYaxis.py b/QtChart/CustomXYaxis.py
index 9ab84fbe..014ab6bc 100644
--- a/QtChart/CustomXYaxis.py
+++ b/QtChart/CustomXYaxis.py
@@ -1,22 +1,30 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月19日
-@author: Irony."[讽刺]
-@site: http://alyl.vip, http://orzorz.vip, http://coding.net/u/892768447, http://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: CustomXYaxis
 @description: 
-'''
+"""
 import random
 import sys
 
-from PyQt5.QtChart import QChartView, QLineSeries, QChart, QCategoryAxis
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
-
-__version__ = "0.0.1"
+try:
+    from PyQt5.QtChart import QChartView, QLineSeries, QChart, QCategoryAxis
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtWidgets import QApplication, QWidget, QHBoxLayout
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
+    QCategoryAxis = QtCharts.QCategoryAxis
 
 m_listCount = 3
 m_valueMax = 10
diff --git a/QtChart/DynamicSpline.py b/QtChart/DynamicSpline.py
index 3ed3b85a..b6bed0e0 100644
--- a/QtChart/DynamicSpline.py
+++ b/QtChart/DynamicSpline.py
@@ -10,12 +10,21 @@
 """
 import sys
 
-from PyQt5.QtChart import QChartView, QChart, QSplineSeries, QValueAxis
-from PyQt5.QtCore import Qt, QTimer, QRandomGenerator
-from PyQt5.QtGui import QPainter, QPen
-from PyQt5.QtWidgets import QApplication
+try:
+    from PyQt5.QtChart import QChartView, QChart, QSplineSeries, QValueAxis
+    from PyQt5.QtCore import Qt, QTimer, QRandomGenerator
+    from PyQt5.QtGui import QPainter, QPen
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import Qt, QTimer, QRandomGenerator
+    from PySide2.QtGui import QPainter, QPen
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
 
-__version__ = "0.0.1"
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QSplineSeries = QtCharts.QSplineSeries
+    QValueAxis = QtCharts.QValueAxis
 
 
 class DynamicSpline(QChart):
diff --git a/QtChart/HorizontalBarChart.py b/QtChart/HorizontalBarChart.py
index 1f4000da..6018cefb 100644
--- a/QtChart/HorizontalBarChart.py
+++ b/QtChart/HorizontalBarChart.py
@@ -4,14 +4,28 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: HorizontalBarChart
 @description: 横向柱状图表
 """
-from PyQt5.QtChart import QChartView, QChart, QBarSet, QHorizontalBarSeries, QBarCategoryAxis
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPainter
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QBarSet, QHorizontalBarSeries, QBarCategoryAxis
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QBarSet = QtCharts.QBarSet
+    QHorizontalBarSeries = QtCharts.QHorizontalBarSeries
+    QBarCategoryAxis = QtCharts.QBarCategoryAxis
 
 
 class Window(QChartView):
@@ -72,7 +86,6 @@ def getSeries(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/HorizontalPercentBarChart.py b/QtChart/HorizontalPercentBarChart.py
index 6f807e21..785efc9f 100644
--- a/QtChart/HorizontalPercentBarChart.py
+++ b/QtChart/HorizontalPercentBarChart.py
@@ -4,14 +4,26 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: HorizontalPercentBarChart
 @description: 横向百分比柱状图表
 """
-from PyQt5.QtChart import QChartView, QChart, QBarSet, QHorizontalPercentBarSeries, QBarCategoryAxis
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPainter
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QBarSet, QHorizontalPercentBarSeries, QBarCategoryAxis
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPainter
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPainter
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QBarSet = QtCharts.QBarSet
+    QHorizontalPercentBarSeries = QtCharts.QHorizontalPercentBarSeries
+    QBarCategoryAxis = QtCharts.QBarCategoryAxis
 
 
 class Window(QChartView):
diff --git a/QtChart/LineChart.py b/QtChart/LineChart.py
index aa2709c4..9386a99c 100644
--- a/QtChart/LineChart.py
+++ b/QtChart/LineChart.py
@@ -1,27 +1,33 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月19日
-@author: Irony."[讽刺]
-@site: http://alyl.vip, http://orzorz.vip, http://coding.net/u/892768447, http://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: LineChart
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtChart import QChartView, QLineSeries, QChart
-from PyQt5.QtGui import QPainter
-from PyQt5.QtWidgets import QApplication
+try:
+    from PyQt5.QtChart import QChartView, QLineSeries, QChart
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
 
-__version__ = "0.0.1"
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
 
-
-if __name__ == "__main__":
+if __name__ == '__main__':
     app = QApplication(sys.argv)
     chart = QChart()
-    chart.setTitle("Line Chart 1")
+    chart.setTitle('Line Chart 1')
     series = QLineSeries(chart)
     series.append(0, 6)
     series.append(2, 4)
diff --git a/QtChart/LineStack.py b/QtChart/LineStack.py
index 7a06c813..fb03eaf1 100644
--- a/QtChart/LineStack.py
+++ b/QtChart/LineStack.py
@@ -1,27 +1,36 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: charts.line.LineStack
 @description: like http://echarts.baidu.com/demo.html#line-stack
-'''
+"""
 
 import sys
 
-from PyQt5.QtChart import QChartView, QChart, QLineSeries, QLegend, \
-    QCategoryAxis
-from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
-from PyQt5.QtGui import QPainter, QPen
-from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
-    QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtChart import QChartView, QChart, QLineSeries, QLegend, \
+        QCategoryAxis
+    from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
+    from PyQt5.QtGui import QPainter, QPen
+    from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
+        QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
+except ImportError:
+    from PySide2.QtCore import Qt, QPointF, QRectF, QPoint
+    from PySide2.QtGui import QPainter, QPen
+    from PySide2.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
+        QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
+    QLegend = QtCharts.QLegend
+    QCategoryAxis = QtCharts.QCategoryAxis
 
 
 class ToolTipItem(QWidget):
@@ -44,7 +53,6 @@ def setText(self, text):
 
 
 class ToolTipWidget(QWidget):
-
     Cache = {}
 
     def __init__(self, *args, **kwargs):
@@ -130,7 +138,7 @@ def resizeEvent(self, event):
         self.point_bottom = self._chart.mapToPosition(
             QPointF(self.min_x, self.min_y))
         self.step_x = (self.max_x - self.min_x) / \
-            (self._chart.axisX().tickCount() - 1)
+                      (self._chart.axisX().tickCount() - 1)
 
     def mouseMoveEvent(self, event):
         super(ChartView, self).mouseMoveEvent(event)
@@ -158,10 +166,10 @@ def mouseMoveEvent(self, event):
             t_height = self.toolTipWidget.height()
             # 如果鼠标位置离右侧的距离小于tip宽度
             x = pos.x() - t_width if self.width() - \
-                pos.x() - 20 < t_width else pos.x()
+                                     pos.x() - 20 < t_width else pos.x()
             # 如果鼠标位置离底部的高度小于tip高度
             y = pos.y() - t_height if self.height() - \
-                pos.y() - 20 < t_height else pos.y()
+                                      pos.y() - 20 < t_height else pos.y()
             self.toolTipWidget.show(
                 title, points, QPoint(x, y))
         else:
@@ -173,7 +181,7 @@ def handleMarkerClicked(self):
         if not marker:
             return
         visible = not marker.series().isVisible()
-#         # 隐藏或显示series
+        #         # 隐藏或显示series
         marker.series().setVisible(visible)
         marker.setVisible(True)  # 要保证marker一直显示
         # 透明度
diff --git a/QtChart/PercentBarChart.py b/QtChart/PercentBarChart.py
index 1cd4d066..dcbf8837 100644
--- a/QtChart/PercentBarChart.py
+++ b/QtChart/PercentBarChart.py
@@ -4,14 +4,28 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PercentBarChart
 @description: 百分比柱状图表
 """
-from PyQt5.QtChart import QChartView, QChart, QBarSet, QPercentBarSeries, QBarCategoryAxis
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPainter
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QBarSet, QPercentBarSeries, QBarCategoryAxis
+    from PyQt5.QtCore import Qt
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import Qt
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QBarSet = QtCharts.QBarSet
+    QPercentBarSeries = QtCharts.QPercentBarSeries
+    QBarCategoryAxis = QtCharts.QBarCategoryAxis
 
 
 class Window(QChartView):
@@ -72,7 +86,6 @@ def getSeries(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/PieChart.py b/QtChart/PieChart.py
index f3bb7a0e..57fa5bde 100644
--- a/QtChart/PieChart.py
+++ b/QtChart/PieChart.py
@@ -4,13 +4,24 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: PieChart
 @description: 饼状图表
 """
-from PyQt5.QtChart import QChartView, QChart, QPieSeries
-from PyQt5.QtGui import QPainter, QColor
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QPieSeries
+    from PyQt5.QtGui import QPainter, QColor
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtGui import QPainter, QColor
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QPieSeries = QtCharts.QPieSeries
 
 
 class Window(QChartView):
@@ -47,7 +58,6 @@ def getSeries(self):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/README.md b/QtChart/README.md
index 8330a7a3..80f9a1ff 100644
--- a/QtChart/README.md
+++ b/QtChart/README.md
@@ -16,13 +16,16 @@
   - [横向百分比柱状图表](#13横向百分比柱状图表)
   - [散点图表](#14散点图表)
   - [图表主题动画](#15图表主题动画)
+  - [CPU动态折线图](#16CPU动态折线图)
 
 ## 1、折线图
+
 [运行 LineChart.py](LineChart.py)
 
 
 
 ## 2、折线堆叠图
+
 [运行 LineStack.py](LineStack.py)
 
 仿照 [line-stack](http://echarts.baidu.com/demo.html#line-stack)
@@ -30,6 +33,7 @@
 
 
 ## 3、柱状堆叠图
+
 [运行 BarStack.py](BarStack.py)
 
 仿照 [bar-stack](http://echarts.baidu.com/demo.html#bar-stack)
@@ -37,61 +41,81 @@
 
 
 ## 4、LineChart自定义xy轴
+
 [运行 CustomXYaxis.py](CustomXYaxis.py)
 
 
 
 ## 5、ToolTip提示
-[运行 ToolTip.py](ToolTip.py) | [运行 ToolTip2.py](ToolTip2.py) 
+
+[运行 ToolTip.py](ToolTip.py) | [运行 ToolTip2.py](ToolTip2.py)
 
  
 
 ## 6、动态曲线图
+
 [运行 DynamicSpline.py](DynamicSpline.py)
 
 
 
 ## 7、区域图表
+
 [运行 AreaChart.py](AreaChart.py)
 
 
 
 ## 8、柱状图表
+
 [运行 BarChart.py](BarChart.py)
 
 
 
 ## 9、饼状图表
+
 [运行 PieChart.py](PieChart.py)
 
 
 
 ## 10、样条图表
+
 [运行 SplineChart.py](SplineChart.py)
 
 
 
 ## 11、百分比柱状图表
+
 [运行 PercentBarChart.py](PercentBarChart.py)
 
 
 
 ## 12、横向柱状图表
+
 [运行 HorizontalBarChart.py](HorizontalBarChart.py)
 
 
 
 ## 13、横向百分比柱状图表
+
 [运行 HorizontalPercentBarChart.py](HorizontalPercentBarChart.py)
 
 
 
 ## 14、散点图表
+
 [运行 ScatterChart.py](ScatterChart.py)
 
 
 
 ## 15、图表主题动画
+
 [运行 ChartThemes.py](ChartThemes.py)
 
-
\ No newline at end of file
+
+
+## 16、CPU动态折线图
+
+[运行 CpuLineChart.py](CpuLineChart.py)
+
+通过设置x轴的时间范围并替换y点达到动态移动效果
+
+
diff --git a/QtChart/ScatterChart.py b/QtChart/ScatterChart.py
index e6d7b0a6..d523e83c 100644
--- a/QtChart/ScatterChart.py
+++ b/QtChart/ScatterChart.py
@@ -4,16 +4,27 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ScatterChart
 @description: 散点图表
 """
 import random
 
-from PyQt5.QtChart import QChartView, QChart, QScatterSeries
-from PyQt5.QtCore import QPointF
-from PyQt5.QtGui import QPainter
+try:
+    from PyQt5.QtChart import QChartView, QChart, QScatterSeries
+    from PyQt5.QtCore import QPointF
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import QPointF
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QScatterSeries = QtCharts.QScatterSeries
 
 
 class Window(QChartView):
@@ -70,7 +81,6 @@ def generateRandomData(self, listCount, valueMax, valueCount):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/ScreenShot/CpuLineChart.png b/QtChart/ScreenShot/CpuLineChart.png
new file mode 100644
index 00000000..06cb3b2c
Binary files /dev/null and b/QtChart/ScreenShot/CpuLineChart.png differ
diff --git a/QtChart/SplineChart.py b/QtChart/SplineChart.py
index 69a54361..9fe2eaf8 100644
--- a/QtChart/SplineChart.py
+++ b/QtChart/SplineChart.py
@@ -4,14 +4,26 @@
 """
 Created on 2019/10/2
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: SplineChart
 @description: 样条图表
 """
-from PyQt5.QtChart import QChartView, QChart, QSplineSeries
-from PyQt5.QtCore import QPointF
-from PyQt5.QtGui import QPainter
+
+try:
+    from PyQt5.QtChart import QChartView, QChart, QSplineSeries
+    from PyQt5.QtCore import QPointF
+    from PyQt5.QtGui import QPainter
+    from PyQt5.QtWidgets import QApplication
+except ImportError:
+    from PySide2.QtCore import QPointF
+    from PySide2.QtGui import QPainter
+    from PySide2.QtWidgets import QApplication
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QSplineSeries = QtCharts.QSplineSeries
 
 
 class Window(QChartView):
@@ -49,7 +61,6 @@ def getSeries(self, chart):
 
 if __name__ == '__main__':
     import sys
-    from PyQt5.QtWidgets import QApplication
 
     app = QApplication(sys.argv)
     w = Window()
diff --git a/QtChart/ToolTip.py b/QtChart/ToolTip.py
index dc34a596..397254b1 100644
--- a/QtChart/ToolTip.py
+++ b/QtChart/ToolTip.py
@@ -1,27 +1,34 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月23日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ToolTip
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtChart import QChartView, QChart, QLineSeries
-from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF
-from PyQt5.QtGui import QPainter, QCursor
-from PyQt5.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
-    QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-'''
+try:
+    from PyQt5.QtChart import QChartView, QChart, QLineSeries
+    from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF
+    from PyQt5.QtGui import QPainter, QCursor
+    from PyQt5.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
+        QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
+except ImportError:
+    from PySide2.QtCore import Qt, QRectF, QPoint, QPointF
+    from PySide2.QtGui import QPainter, QCursor
+    from PySide2.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
+        QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
+
+"""
 class CircleWidget(QGraphicsProxyWidget):
 
     def __init__(self, color, *args, **kwargs):
@@ -57,7 +64,7 @@ def show(self, pos):
         self.setGeometry(pos.x(), pos.y(), self.size().width(),
                          self.size().height())
         super(GraphicsWidget, self).show()
-'''
+"""
 
 
 class ToolTipItem(QWidget):
@@ -161,7 +168,8 @@ def mouseMoveEvent(self, event):
         #         print(x, pos_x, index, index * self.step_x + self.min_x)
         # 得到在坐标系中的所有series的类型和点
         points = [(serie, serie.at(index))
-                  for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
+                  for serie in self._chart.series() if
+                  self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
         if points:
             # 永远在轴上的黑线条
             self.lineItem.setLine(pos_x.x(), self.point_top.y(),
diff --git a/QtChart/ToolTip2.py b/QtChart/ToolTip2.py
index 349473ed..3a8ca3df 100644
--- a/QtChart/ToolTip2.py
+++ b/QtChart/ToolTip2.py
@@ -1,27 +1,34 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月23日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ToolTip2
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtChart import QChartView, QChart, QLineSeries
-from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF
-from PyQt5.QtGui import QPainter, QCursor
-from PyQt5.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
-    QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-'''
+try:
+    from PyQt5.QtChart import QChartView, QChart, QLineSeries
+    from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF
+    from PyQt5.QtGui import QPainter, QCursor
+    from PyQt5.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
+        QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
+except ImportError:
+    from PySide2.QtCore import Qt, QRectF, QPoint, QPointF
+    from PySide2.QtGui import QPainter, QCursor
+    from PySide2.QtWidgets import QApplication, QGraphicsProxyWidget, QLabel, \
+        QWidget, QHBoxLayout, QVBoxLayout, QToolTip, QGraphicsLineItem
+    from PySide2.QtCharts import QtCharts
+
+    QChartView = QtCharts.QChartView
+    QChart = QtCharts.QChart
+    QLineSeries = QtCharts.QLineSeries
+
+"""
 class CircleWidget(QGraphicsProxyWidget):
 
     def __init__(self, color, *args, **kwargs):
@@ -57,7 +64,7 @@ def show(self, pos):
         self.setGeometry(pos.x(), pos.y(), self.size().width(),
                          self.size().height())
         super(GraphicsWidget, self).show()
-'''
+"""
 
 
 class ToolTipItem(QWidget):
@@ -159,7 +166,8 @@ def mouseMoveEvent(self, event):
         #         print(x, pos_x, index, index * self.step_x + self.min_x)
         # 得到在坐标系中的所有series的类型和点
         points = [(serie, serie.at(index))
-                  for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
+                  for serie in self._chart.series() if
+                  self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
         if points:
             # 跟随鼠标的黑线条
             self.lineItem.setLine(event.pos().x(), self.point_top.y(),
diff --git a/QtDataVisualization/BarsVisualization.py b/QtDataVisualization/BarsVisualization.py
index 6808b09a..7fe583dd 100644
--- a/QtDataVisualization/BarsVisualization.py
+++ b/QtDataVisualization/BarsVisualization.py
@@ -4,7 +4,7 @@
 """
 Created on 2019/10/4
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: BarsVisualization
 @description: 柱状图
diff --git a/QtDataVisualization/MagneticOfSun.py b/QtDataVisualization/MagneticOfSun.py
index d4562f56..da9fa0d1 100644
--- a/QtDataVisualization/MagneticOfSun.py
+++ b/QtDataVisualization/MagneticOfSun.py
@@ -4,7 +4,7 @@
 """
 Created on 2019/10/4
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: MagneticOfSun
 @description: 
diff --git a/QtDataVisualization/README.md b/QtDataVisualization/README.md
index 9366e669..bcd8a75c 100644
--- a/QtDataVisualization/README.md
+++ b/QtDataVisualization/README.md
@@ -6,16 +6,19 @@
   - [余弦波3D](#3余弦波3D)
 
 ## 1、柱状图3D
+
 [运行 BarsVisualization.py](BarsVisualization.py)
 
 
 
 ## 2、太阳磁场线
+
 [运行 MagneticOfSun.py](MagneticOfSun.py)
 
 
 
 ## 3、余弦波3D
+
 [运行 ScatterVisualization.py](ScatterVisualization.py)
 
-
\ No newline at end of file
+
diff --git a/QtDataVisualization/ScatterVisualization.py b/QtDataVisualization/ScatterVisualization.py
index eff7fe0c..69eeeb82 100644
--- a/QtDataVisualization/ScatterVisualization.py
+++ b/QtDataVisualization/ScatterVisualization.py
@@ -4,7 +4,7 @@
 """
 Created on 2019/10/4
 @author: Irony
-@site: https://pyqt5.com , https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: ScatterVisualization
 @description:
diff --git a/QtQuick/FlatStyle.py b/QtQuick/FlatStyle.py
index c4e42b92..e11e984d 100644
--- a/QtQuick/FlatStyle.py
+++ b/QtQuick/FlatStyle.py
@@ -4,7 +4,7 @@
 """
 Created on 2019年2月2日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtQuick.FlatStyle
 @description: 
@@ -12,14 +12,14 @@
 import os
 import sys
 
-from PyQt5.QtCore import QCoreApplication, Qt, QUrl
-from PyQt5.QtQml import QQmlApplicationEngine
-from PyQt5.QtWidgets import QApplication, QMessageBox
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
-
+try:
+    from PyQt5.QtCore import QCoreApplication, Qt, QUrl
+    from PyQt5.QtQml import QQmlApplicationEngine
+    from PyQt5.QtWidgets import QApplication, QMessageBox
+except ImportError:
+    from PySide2.QtCore import QCoreApplication, Qt, QUrl
+    from PySide2.QtQml import QQmlApplicationEngine
+    from PySide2.QtWidgets import QApplication, QMessageBox
 
 if __name__ == '__main__':
     try:
@@ -32,7 +32,8 @@
     app = QApplication(sys.argv)
     engine = QQmlApplicationEngine()
     engine.objectCreated.connect(
-        lambda obj, _: (QMessageBox.critical(None, '错误', '运行失败,可能是当前PyQt版本不支持'), engine.quit) if not obj else 0)
+        lambda obj, _: (
+            QMessageBox.critical(None, '错误', '运行失败,可能是当前PyQt版本不支持'), engine.quit) if not obj else 0)
     engine.addImportPath('imports')
     engine.load(QUrl('flatstyle.qml'))
 
diff --git a/QtQuick/README.md b/QtQuick/README.md
index 253ed416..879091c9 100644
--- a/QtQuick/README.md
+++ b/QtQuick/README.md
@@ -5,11 +5,13 @@
   - [QML与Python交互](#2QML与Python交互)
 
 ## 1、Flat样式
+
 [运行 FlatStyle.py](FlatStyle.py)
 
 
 
 ## 2、QML与Python交互
+
 [运行 Signals.py](Signals.py)
 
 交互的办法有很多种,由于主要界面功能都是有QML来实现,Python只是作为辅助提供部分功能。
@@ -18,6 +20,7 @@
 1. 通过 `engine.rootContext().setContextProperty('_Window', w)` 注册提供一个Python对象
 2. Python对象中被访问的方法前面使用装饰器 `@pyqtSlot`,比如: `@pyqtSlot(int)` 或者 `@pyqtSlot(str, result=str)  # 可以获取返回值` 。
 3. QML中的信号或者Python对象中的信号都可以互相绑定对方的槽函数
+
 ```js
 Component.onCompleted: {
     // 绑定信号槽到python中的函数
@@ -27,5 +30,4 @@ Component.onCompleted: {
 }
 ```
 
-
 
diff --git a/QtQuick/Signals.py b/QtQuick/Signals.py
index 3ea5b812..7735c944 100644
--- a/QtQuick/Signals.py
+++ b/QtQuick/Signals.py
@@ -4,23 +4,25 @@
 """
 Created on 2019年9月18日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtQuick.Signals
 @description: 信号槽
 """
 
-from time import time
 import sys
+from time import time
 
-from PyQt5.QtCore import QCoreApplication, Qt, pyqtSlot, pyqtSignal, QTimer
-from PyQt5.QtQml import QQmlApplicationEngine
-from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget, QVBoxLayout,\
-    QPushButton, QTextBrowser
-
-
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019'
+try:
+    from PyQt5.QtCore import QCoreApplication, Qt, pyqtSlot, pyqtSignal, QTimer
+    from PyQt5.QtQml import QQmlApplicationEngine
+    from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget, QVBoxLayout, \
+        QPushButton, QTextBrowser
+except ImportError:
+    from PySide2.QtCore import QCoreApplication, Qt, Slot as pyqtSlot, Signal as pyqtSignal, QTimer
+    from PySide2.QtQml import QQmlApplicationEngine
+    from PySide2.QtWidgets import QApplication, QMessageBox, QWidget, QVBoxLayout, \
+        QPushButton, QTextBrowser
 
 QML = """import QtQuick 2.0
 import QtQuick.Controls 1.6
@@ -84,7 +86,6 @@
 
 
 class Window(QWidget):
-
     # 定义一个时间信号
     timerSignal = pyqtSignal(str)
 
diff --git a/QtRemoteObjects/README.md b/QtRemoteObjects/README.md
index ce6e3c53..87e22996 100644
--- a/QtRemoteObjects/README.md
+++ b/QtRemoteObjects/README.md
@@ -6,11 +6,13 @@
   - [简单界面数据同步](#3简单界面数据同步)
 
 ## 1、modelview
+
 [运行 modelviewserver.py](modelview/modelviewserver.py) | [运行 modelviewclient.py](modelview/modelviewclient.py)
 
 官方关于QTreeView/QStandardItemModel的同步model例子
 
 ## 2、simpleswitch
+
 [运行 directconnectdynamicserver.py](simpleswitch/directconnectdynamicserver.py) | [运行 directconnectdynamicclient.py](simpleswitch/directconnectdynamicclient.py)
 
 [运行 registryconnecteddynamicserver.py](simpleswitch/registryconnecteddynamicserver.py) | [运行 registryconnecteddynamicclient.py](simpleswitch/registryconnecteddynamicclient.py)
@@ -18,8 +20,9 @@
 官方关于简单的信号槽、属性访问测试例子
 
 ## 3、简单界面数据同步
+
 [运行 WindowMaster.py](SyncUi/WindowMaster.py) | [运行 WindowSlave.py](SyncUi/WindowSlave.py)
 
 绑定信号槽同步双方数据,属性方法测试没通过,详细注释在代码中
 
-
\ No newline at end of file
+
diff --git a/QtRemoteObjects/SyncUi/ClipboardMaster.py b/QtRemoteObjects/SyncUi/ClipboardMaster.py
new file mode 100644
index 00000000..50aea697
--- /dev/null
+++ b/QtRemoteObjects/SyncUi/ClipboardMaster.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/7/31
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ClipboardMaster
+@description: 
+"""
+import sys
+
+from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, QLoggingCategory, QVariant, QMimeData
+from PyQt5.QtRemoteObjects import QRemoteObjectHost
+from PyQt5.QtWidgets import QTextBrowser
+
+
+class WindowMaster(QTextBrowser):
+    SignalUpdateMimeData = pyqtSignal(
+        bool, QVariant,  # color
+        bool, QVariant,  # html
+        bool, QVariant,  # image
+        bool, QVariant,  # text
+        bool, QVariant,  # urls
+    )
+
+    def __init__(self, *args, **kwargs):
+        super(WindowMaster, self).__init__(*args, **kwargs)
+        # 监听剪切板
+        clipboard = QApplication.clipboard()
+        clipboard.dataChanged.connect(self.on_data_changed)
+        # 开启节点
+        host = QRemoteObjectHost(QUrl('tcp://0.0.0.0:' + sys.argv[1]), parent=self)
+        host.enableRemoting(self, 'WindowMaster')
+        self.append('开启节点完成')
+
+    def on_data_changed(self):
+        # 服务端剪贴板变化后发送到客户端
+        clipboard = QApplication.clipboard()
+        clipboard.blockSignals(True)
+        mime_data = clipboard.mimeData()
+        self.SignalUpdateMimeData.emit(
+            mime_data.hasColor(), mime_data.colorData(),
+            mime_data.hasHtml(), mime_data.html(),
+            mime_data.hasImage(), mime_data.imageData(),
+            mime_data.hasText(), mime_data.text(),
+            mime_data.hasUrls(), mime_data.urls(),
+        )
+        clipboard.blockSignals(False)
+
+    @pyqtSlot(
+        bool, QVariant,  # color
+        bool, QVariant,  # html
+        bool, QVariant,  # image
+        bool, QVariant,  # text
+        bool, QVariant,  # urls
+        bool, QVariant  # files
+    )
+    def updateMimeData(self,
+                       hasColor, color,
+                       hasHtml, html,
+                       hasImage, image,
+                       hasText, text,
+                       hasUrls, urls,
+                       hasFiles, files,
+                       ):
+        # 客户端剪切板同步到服务端
+        self.append('收到客户端发送的剪贴板')
+        clipboard = QApplication.clipboard()
+        clipboard.blockSignals(True)
+        data = QMimeData()
+        if hasColor:
+            data.setColorData(color)
+        if hasHtml:
+            data.setHtml(html)
+        if hasImage:
+            data.setImageData(image)
+        if hasText:
+            data.setText(text)
+        # if hasUrls:
+        #     data.setUrls(urls)
+        if hasFiles:
+            data.setData('')
+        clipboard.setMimeData(data)
+        clipboard.blockSignals(False)
+
+
+if __name__ == '__main__':
+    import cgitb
+
+    cgitb.enable(format='text')
+    from PyQt5.QtWidgets import QApplication
+
+    QLoggingCategory.setFilterRules('qt.remoteobjects.debug=true\n'
+                                    'qt.remoteobjects.warning=true')
+
+    app = QApplication(sys.argv)
+    w = WindowMaster()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QtRemoteObjects/SyncUi/ClipboardSlave.py b/QtRemoteObjects/SyncUi/ClipboardSlave.py
new file mode 100644
index 00000000..2e271b1c
--- /dev/null
+++ b/QtRemoteObjects/SyncUi/ClipboardSlave.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/7/31
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ClipboardSlave
+@description: 
+"""
+from PyQt5.QtCore import QUrl, pyqtSignal, QVariant, QMimeData
+from PyQt5.QtRemoteObjects import QRemoteObjectNode, QRemoteObjectReplica
+from PyQt5.QtWidgets import QTextBrowser
+
+
+class WindowSlave(QTextBrowser):
+    SignalUpdateMimeData = pyqtSignal(
+        bool, QVariant,  # color
+        bool, QVariant,  # html
+        bool, QVariant,  # image
+        bool, QVariant,  # text
+        bool, QVariant,  # urls
+        bool, QVariant,  # files
+    )
+
+    def __init__(self, *args, **kwargs):
+        super(WindowSlave, self).__init__(*args, **kwargs)
+        # 监听剪切板
+        clipboard = QApplication.clipboard()
+        clipboard.dataChanged.connect(self.on_data_changed)
+        # 加入Master节点
+        node = QRemoteObjectNode(parent=self)
+        node.connectToNode(QUrl('tcp://{}:{}'.format(sys.argv[1], sys.argv[2])))
+        # 获取WindowMaster对象
+        self.windowMaster = node.acquireDynamic('WindowMaster')
+        # 初始化成功后才能去绑定信号等
+        self.windowMaster.initialized.connect(self.onInitialized)
+        # 状态改变 https://doc.qt.io/qt-5/qremoteobjectreplica.html#State-enum
+        self.windowMaster.stateChanged.connect(self.onStateChanged)
+
+    def onStateChanged(self, newState, oldState):
+        if newState == QRemoteObjectReplica.Suspect:
+            self.append('连接丢失')
+
+    def onInitialized(self):
+        self.SignalUpdateMimeData.connect(self.windowMaster.updateMimeData)
+        self.windowMaster.SignalUpdateMimeData.connect(self.updateMimeData)
+        self.append('绑定信号槽完成')
+
+    def on_data_changed(self):
+        # 客户端剪贴板变化后发送到远程
+        print('on_data_changed')
+        clipboard = QApplication.clipboard()
+        clipboard.blockSignals(True)
+        mime_data = clipboard.mimeData()
+        files = mime_data.data('text/uri-list')
+        self.SignalUpdateMimeData.emit(
+            mime_data.hasColor(), mime_data.colorData(),
+            mime_data.hasHtml(), mime_data.html(),
+            mime_data.hasImage(), mime_data.imageData(),
+            mime_data.hasText(), mime_data.text(),
+            mime_data.hasUrls(), mime_data.urls(),
+            True if files else False, files,
+        )
+        clipboard.blockSignals(False)
+
+    def updateMimeData(self,
+                       hasColor, color,
+                       hasHtml, html,
+                       hasImage, image,
+                       hasText, text,
+                       hasUrls, urls
+                       ):
+        # 远程的剪贴板同步到客户端
+        clipboard = QApplication.clipboard()
+        clipboard.blockSignals(True)
+        data = QMimeData()
+        if hasColor:
+            data.setColorData(color)
+        if hasHtml:
+            data.setHtml(html)
+        if hasImage:
+            data.setImageData(image)
+        if hasText:
+            data.setText(text)
+        if hasUrls:
+            data.setUrls(urls)
+        clipboard.setMimeData(data)
+        clipboard.blockSignals(False)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+
+    cgitb.enable(format='text')
+    from PyQt5.QtWidgets import QApplication
+
+    app = QApplication(sys.argv)
+    w = WindowSlave()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QtRemoteObjects/SyncUi/WindowMaster.py b/QtRemoteObjects/SyncUi/WindowMaster.py
index 22ba0bc8..d93e6561 100644
--- a/QtRemoteObjects/SyncUi/WindowMaster.py
+++ b/QtRemoteObjects/SyncUi/WindowMaster.py
@@ -4,24 +4,18 @@
 """
 Created on 2019年8月7日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtRemoteObjects.SyncUi.WindowMaster
 @description: 主窗口
 """
 from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtSlot
 from PyQt5.QtRemoteObjects import QRemoteObjectHost
-from PyQt5.QtWidgets import QWidget, QLineEdit, QVBoxLayout, QCheckBox,\
+from PyQt5.QtWidgets import QWidget, QLineEdit, QVBoxLayout, QCheckBox, \
     QProgressBar
 
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
-
-
 class WindowMaster(QWidget):
-
     # 输入框内容变化信号
     editValueChanged = pyqtSignal(str)
     # 勾选框变化信号
@@ -84,6 +78,7 @@ def updateCheck(self, checked):
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = WindowMaster()
     w.show()
diff --git a/QtRemoteObjects/SyncUi/WindowSlave.py b/QtRemoteObjects/SyncUi/WindowSlave.py
index 44cfc147..dc3ea26a 100644
--- a/QtRemoteObjects/SyncUi/WindowSlave.py
+++ b/QtRemoteObjects/SyncUi/WindowSlave.py
@@ -4,22 +4,17 @@
 """
 Created on 2019年8月7日
 @author: Irony
-@site: https://pyqt5.com https://github.com/892768447
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: QtRemoteObjects.SyncUi.WindowSlave
 @description: 备窗口
 """
 from PyQt5.QtCore import QUrl
 from PyQt5.QtRemoteObjects import QRemoteObjectNode, QRemoteObjectReplica
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QCheckBox,\
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QCheckBox, \
     QProgressBar, QMessageBox
 
 
-__Author__ = 'Irony'
-__Copyright__ = 'Copyright (c) 2019 Irony'
-__Version__ = 1.0
-
-
 class WindowSlave(QWidget):
 
     def __init__(self, *args, **kwargs):
@@ -65,13 +60,14 @@ def onInitialized(self):
         # Master进度条同步到Slave
         self.windowMaster.progressValueChanged.connect(
             self.progressBar.setValue)
-        
+
         print('绑定信号槽完成')
 
 
 if __name__ == '__main__':
     import sys
     from PyQt5.QtWidgets import QApplication
+
     app = QApplication(sys.argv)
     w = WindowSlave()
     w.show()
diff --git a/QtRemoteObjects/modelview/modelviewclient.py b/QtRemoteObjects/modelview/modelviewclient.py
index 3ae7e6a3..1dd65f9f 100644
--- a/QtRemoteObjects/modelview/modelviewclient.py
+++ b/QtRemoteObjects/modelview/modelviewclient.py
@@ -52,15 +52,22 @@
 
 import sys
 
-from PyQt5.QtCore import QLoggingCategory, QUrl
-from PyQt5.QtRemoteObjects import QRemoteObjectNode
-from PyQt5.QtWidgets import QApplication, QTreeView
+try:
+    from PyQt5.QtCore import QLoggingCategory, QUrl
+    from PyQt5.QtRemoteObjects import QRemoteObjectNode
+    from PyQt5.QtWidgets import QApplication, QTreeView
+except ImportError:
+    from PySide2.QtCore import QUrl
+    from PySide2.QtRemoteObjects import QRemoteObjectNode
+    from PySide2.QtWidgets import QApplication, QTreeView
 
-
-QLoggingCategory.setFilterRules('qt.remoteobjects.debug=false\n'
-                                'qt.remoteobjects.warning=false\n'
-                                'qt.remoteobjects.models.debug=false\n'
-                                'qt.remoteobjects.models.debug=false')
+try:
+    QLoggingCategory.setFilterRules('qt.remoteobjects.debug=false\n'
+                                    'qt.remoteobjects.warning=false\n'
+                                    'qt.remoteobjects.models.debug=false\n'
+                                    'qt.remoteobjects.models.debug=false')
+except NameError:
+    pass
 
 app = QApplication(sys.argv)
 
@@ -73,6 +80,6 @@
 
 model = node.acquireModel('RemoteModel')
 view.setModel(model)
-view.show();
+view.show()
 
 sys.exit(app.exec_())
diff --git a/QtRemoteObjects/modelview/modelviewserver.py b/QtRemoteObjects/modelview/modelviewserver.py
index 34cf04ce..e2af99c1 100644
--- a/QtRemoteObjects/modelview/modelviewserver.py
+++ b/QtRemoteObjects/modelview/modelviewserver.py
@@ -53,11 +53,16 @@
 
 import sys
 
-from PyQt5.QtCore import (pyqtSlot, QLoggingCategory, QModelIndex, QObject, Qt,
-        QTimer, QUrl)
-from PyQt5.QtGui import QColor, QStandardItem, QStandardItemModel
-from PyQt5.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectRegistryHost
-from PyQt5.QtWidgets import QApplication, QTreeView
+try:
+    from PyQt5.QtCore import pyqtSlot, QLoggingCategory, QModelIndex, QObject, Qt, QTimer, QUrl
+    from PyQt5.QtGui import QColor, QStandardItem, QStandardItemModel
+    from PyQt5.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectRegistryHost
+    from PyQt5.QtWidgets import QApplication, QTreeView
+except ImportError:
+    from PySide2.QtCore import Slot as pyqtSlot, QModelIndex, QObject, Qt, QTimer, QUrl
+    from PySide2.QtGui import QColor, QStandardItem, QStandardItemModel
+    from PySide2.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectRegistryHost
+    from PySide2.QtWidgets import QApplication, QTreeView
 
 
 class TimerHandler(QObject):
@@ -71,7 +76,7 @@ def __init__(self, model, parent=None):
     def changeData(self):
         for i in range(10, 50):
             self._model.setData(self._model.index(i, 1), QColor(Qt.blue),
-                    Qt.BackgroundRole)
+                                Qt.BackgroundRole)
 
     @pyqtSlot()
     def insertData(self):
@@ -79,9 +84,9 @@ def insertData(self):
 
         for i in range(2, 11):
             self._model.setData(self._model.index(i, 1), QColor(Qt.green),
-                    Qt.BackgroundRole)
+                                Qt.BackgroundRole)
             self._model.setData(self._model.index(i, 1), "InsertedRow",
-                    Qt.DisplayRole)
+                                Qt.DisplayRole)
 
     @pyqtSlot()
     def removeData(self):
@@ -108,7 +113,7 @@ def addChild(numChildren, nestingLevel):
 
     for i in range(numChildren):
         child = QStandardItem(
-                "Child num {}, nesting level {}".format(i + 1, nestingLevel))
+            "Child num {}, nesting level {}".format(i + 1, nestingLevel))
 
         if i == 0:
             child.appendRow(addChild(numChildren, nestingLevel - 1))
@@ -120,14 +125,17 @@ def addChild(numChildren, nestingLevel):
 
 if __name__ == '__main__':
 
-    QLoggingCategory.setFilterRules('qt.remoteobjects.debug=false\n'
-                                    'qt.remoteobjects.warning=false')
+    try:
+        QLoggingCategory.setFilterRules('qt.remoteobjects.debug=false\n'
+                                        'qt.remoteobjects.warning=false')
+    except NameError:
+        pass
 
     app = QApplication(sys.argv)
 
     sourceModel = QStandardItemModel()
     sourceModel.setHorizontalHeaderLabels(
-            ["First Column with spacing", "Second Column with spacing"])
+        ["First Column with spacing", "Second Column with spacing"])
 
     for i in range(10000):
         firstItem = QStandardItem("FancyTextNumber {}".format(i))
diff --git a/QtRemoteObjects/simpleswitch/directconnectdynamicclient.py b/QtRemoteObjects/simpleswitch/directconnectdynamicclient.py
index 27fcb9f1..abee8480 100644
--- a/QtRemoteObjects/simpleswitch/directconnectdynamicclient.py
+++ b/QtRemoteObjects/simpleswitch/directconnectdynamicclient.py
@@ -58,7 +58,6 @@
 
 
 class DynamicClient(QObject):
-
     # This signal is connected with server_slot() slot of the source object and
     # echoes back the switch state received from the source.
     echoSwitchState = pyqtSignal(bool)
@@ -92,7 +91,6 @@ def initConnection(self):
 
 
 if __name__ == '__main__':
-
     app = QCoreApplication(sys.argv)
 
     # Create the remote object node.
diff --git a/QtRemoteObjects/simpleswitch/directconnectdynamicserver.py b/QtRemoteObjects/simpleswitch/directconnectdynamicserver.py
index b7b0d05e..256d63bd 100644
--- a/QtRemoteObjects/simpleswitch/directconnectdynamicserver.py
+++ b/QtRemoteObjects/simpleswitch/directconnectdynamicserver.py
@@ -54,7 +54,7 @@
 import sys
 
 from PyQt5.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QCoreApplication,
-        QObject, QTimer, QUrl)
+                          QObject, QTimer, QUrl)
 from PyQt5.QtRemoteObjects import QRemoteObjectHost
 
 
@@ -98,7 +98,7 @@ def _set_currState(self, value):
 
     # The property exposed to a remote client.
     currState = pyqtProperty(bool, fget=_get_currState, fset=_set_currState,
-            notify=currStateChanged)
+                             notify=currStateChanged)
 
     # The slot exposed to a remote client.
     @pyqtSlot(bool)
@@ -115,7 +115,6 @@ def _timeout(self):
 
 
 if __name__ == '__main__':
-
     app = QCoreApplication(sys.argv)
 
     # Create the simple switch.
diff --git a/QtRemoteObjects/simpleswitch/registryconnecteddynamicclient.py b/QtRemoteObjects/simpleswitch/registryconnecteddynamicclient.py
index f8368787..56b1d381 100644
--- a/QtRemoteObjects/simpleswitch/registryconnecteddynamicclient.py
+++ b/QtRemoteObjects/simpleswitch/registryconnecteddynamicclient.py
@@ -58,7 +58,6 @@
 
 
 class DynamicClient(QObject):
-
     # This signal is connected with server_slot() slot of the source object and
     # echoes back the switch state received from the source.
     echoSwitchState = pyqtSignal(bool)
@@ -92,7 +91,6 @@ def initConnection(self):
 
 
 if __name__ == '__main__':
-
     app = QCoreApplication(sys.argv)
 
     # Create the remote object node.
diff --git a/QtRemoteObjects/simpleswitch/registryconnecteddynamicserver.py b/QtRemoteObjects/simpleswitch/registryconnecteddynamicserver.py
index 7745f947..c7760b4a 100644
--- a/QtRemoteObjects/simpleswitch/registryconnecteddynamicserver.py
+++ b/QtRemoteObjects/simpleswitch/registryconnecteddynamicserver.py
@@ -54,7 +54,7 @@
 import sys
 
 from PyQt5.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QCoreApplication,
-        QObject, QTimer, QUrl)
+                          QObject, QTimer, QUrl)
 from PyQt5.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectRegistryHost
 
 
@@ -98,7 +98,7 @@ def _set_currState(self, value):
 
     # The property exposed to a remote client.
     currState = pyqtProperty(bool, fget=_get_currState, fset=_set_currState,
-            notify=currStateChanged)
+                             notify=currStateChanged)
 
     # The slot exposed to a remote client.
     @pyqtSlot(bool)
@@ -115,7 +115,6 @@ def _timeout(self):
 
 
 if __name__ == '__main__':
-
     app = QCoreApplication(sys.argv)
 
     # Create the simple switch.
diff --git a/QtWinExtras/README.en.md b/QtWinExtras/README.en.md
new file mode 100644
index 00000000..e69de29b
diff --git a/QtWinExtras/README.md b/QtWinExtras/README.md
new file mode 100644
index 00000000..e1775b13
--- /dev/null
+++ b/QtWinExtras/README.md
@@ -0,0 +1,21 @@
+# QtWinExtras
+
+- 目录
+  - [任务栏进度条](#1任务栏进度条)
+  - [任务栏缩略图工具按钮](#2任务栏缩略图工具按钮)
+
+## 1、任务栏进度条
+
+[运行 TaskbarProgress.py](TaskbarProgress.py)
+
+`QWinTaskbarProgress`类似和`QProgressBar`一样的操作
+
+
+
+## 2、任务栏缩略图工具按钮
+
+[运行 ThumbnailToolBar.py](ThumbnailToolBar.py)
+
+`QWinThumbnailToolBar`和`QWinThumbnailToolButton`的组合实现音乐播放器的播放、上下一曲按钮
+
+
diff --git a/QtWinExtras/ScreenShot/TaskbarProgress.gif b/QtWinExtras/ScreenShot/TaskbarProgress.gif
new file mode 100644
index 00000000..5193d963
Binary files /dev/null and b/QtWinExtras/ScreenShot/TaskbarProgress.gif differ
diff --git a/QtWinExtras/ScreenShot/ThumbnailToolBar.gif b/QtWinExtras/ScreenShot/ThumbnailToolBar.gif
new file mode 100644
index 00000000..eae88c3e
Binary files /dev/null and b/QtWinExtras/ScreenShot/ThumbnailToolBar.gif differ
diff --git a/QtWinExtras/TaskbarProgress.py b/QtWinExtras/TaskbarProgress.py
new file mode 100644
index 00000000..96b3a0a3
--- /dev/null
+++ b/QtWinExtras/TaskbarProgress.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/7/1
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TaskbarProgress
+@description: 
+"""
+
+import cgitb
+import sys
+
+try:
+    from PyQt5.QtCore import QTimer
+    from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QSpinBox, QPushButton, QLabel
+    from PyQt5.QtWinExtras import QWinTaskbarButton
+except ImportError:
+    from PySide2.QtCore import QTimer
+    from PySide2.QtWidgets import QWidget, QApplication, QGridLayout, QSpinBox, QPushButton, QLabel
+    from PySide2.QtWinExtras import QWinTaskbarButton
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        # 获取任务栏按钮
+        self.taskButton = QWinTaskbarButton(self)
+        # 获取任务栏进度条
+        self.taskProgress = self.taskButton.progress()
+        # 定时器模拟进度
+        self.timerProgress = QTimer(self)
+        self.timerProgress.timeout.connect(self.update_progress)
+
+        self.setup_ui()
+
+    def showEvent(self, event):
+        super(Window, self).showEvent(event)
+        if not self.taskButton.window():
+            # 必须等窗口显示后设置才有效,或者通过软件流程在适当的时候设置也可以
+            self.taskButton.setWindow(self.windowHandle())
+            self.taskProgress.show()
+
+    def closeEvent(self, event):
+        self.timerProgress.stop()
+        super(Window, self).closeEvent(event)
+
+    def setup_ui(self):
+        layout = QGridLayout(self)
+
+        # 设置最新小值和最大值
+        self.spinBoxMin = QSpinBox(self)
+        self.spinBoxMax = QSpinBox(self)
+        self.spinBoxMax.setMaximum(100)
+        self.spinBoxMax.setValue(100)
+        layout.addWidget(self.spinBoxMin, 0, 0)
+        layout.addWidget(self.spinBoxMax, 0, 1)
+        layout.addWidget(QPushButton('设置范围值', self, clicked=self.set_range), 0, 2)
+
+        # 设置当前值
+        self.spinBoxCur = QSpinBox(self)
+        self.spinBoxCur.setMaximum(100)
+        self.spinBoxCur.setValue(50)
+        layout.addWidget(self.spinBoxCur, 0, 3)
+        layout.addWidget(QPushButton('设置当前值', self, clicked=self.set_current_value), 0, 4)
+
+        # 功能按钮
+        layout.addWidget(QPushButton('隐藏', self, clicked=self.set_show_hide), 1, 0)
+        layout.addWidget(QPushButton('暂停', self, clicked=self.set_pause_resume), 1, 1)
+        layout.addWidget(QPushButton('重置', self, clicked=self.set_reset), 1, 2)
+        layout.addWidget(QPushButton('停止', self, clicked=self.set_stop), 1, 3)
+        layout.addWidget(QPushButton('不可见', self, clicked=self.set_visible), 1, 4)
+
+        # 模拟进度
+        layout.addWidget(QPushButton('模拟进度动画', self, clicked=self.start_progress), 2, 0, 1, 5)
+
+        # 状态
+        layout.addWidget(QLabel('暂停信号 :', self), 3, 0)
+        self.labelPause = QLabel(self)
+        layout.addWidget(self.labelPause, 3, 1)
+        self.taskProgress.pausedChanged.connect(lambda v: self.labelPause.setText(str(v)))
+
+        layout.addWidget(QLabel('停止信号 :', self), 4, 0)
+        self.labelStop = QLabel(self)
+        layout.addWidget(self.labelStop, 4, 1)
+        self.taskProgress.stoppedChanged.connect(lambda v: self.labelStop.setText(str(v)))
+
+        layout.addWidget(QLabel('值改变信号:', self), 5, 0)
+        self.labelValue = QLabel(self)
+        layout.addWidget(self.labelValue, 5, 1)
+        self.taskProgress.valueChanged.connect(lambda v: self.labelValue.setText(str(v)))
+
+        layout.addWidget(QLabel('可见度信号:', self), 6, 0)
+        self.labelVisible = QLabel(self)
+        layout.addWidget(self.labelVisible, 6, 1)
+        self.taskProgress.visibilityChanged.connect(lambda v: self.labelVisible.setText(str(v)))
+
+    def set_range(self):
+        # 设置进度条范围值
+        vmin = min(self.spinBoxMin.value(), self.spinBoxMax.value())
+        vmax = max(self.spinBoxMin.value(), self.spinBoxMax.value())
+        self.taskProgress.setRange(vmin, vmax)
+
+    def set_current_value(self):
+        # 设置进度条当前值
+        self.taskProgress.setValue(self.spinBoxCur.value())
+
+    def set_show_hide(self):
+        # 显示/隐藏
+        visible = self.taskProgress.isVisible()
+        # 也可以使用self.taskProgress.setVisible
+        if visible:
+            self.taskProgress.hide()
+            self.sender().setText('显示')
+        else:
+            self.taskProgress.show()
+            self.sender().setText('隐藏')
+
+    def set_pause_resume(self):
+        # 暂停/恢复
+        paused = self.taskProgress.isPaused()
+        # 也可以使用self.taskProgress.setPaused
+        if paused:
+            self.taskProgress.resume()
+            self.timerProgress.start(100)
+            self.sender().setText('暂停')
+        else:
+            self.taskProgress.pause()
+            self.timerProgress.stop()
+            self.sender().setText('恢复')
+
+    def set_reset(self):
+        # 重置
+        self.taskProgress.reset()
+        paused = self.taskProgress.isPaused()
+        if not paused:
+            self.timerProgress.stop()
+            self.timerProgress.start(100)
+
+    def set_stop(self):
+        # 停止
+        self.timerProgress.stop()
+        self.taskProgress.stop()
+        self.setEnabled(False)
+
+    def set_visible(self):
+        # 可见/不可见
+        visible = self.taskProgress.isVisible()
+        self.taskProgress.setVisible(not visible)
+        self.sender().setText('可见' if visible else '不可见')
+
+    def start_progress(self):
+        # 模拟进度
+        self.timerProgress.start(100)
+        self.sender().setEnabled(False)
+
+    def update_progress(self):
+        value = self.taskProgress.value()
+        value += 1
+        if value > self.taskProgress.maximum():
+            value = 0
+        self.taskProgress.setValue(value)
+
+
+if __name__ == '__main__':
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/QtWinExtras/ThumbnailToolBar.py b/QtWinExtras/ThumbnailToolBar.py
new file mode 100644
index 00000000..8aab7b75
--- /dev/null
+++ b/QtWinExtras/ThumbnailToolBar.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2020/7/3
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: ThumbnailToolBar
+@description: 
+"""
+
+import cgitb
+import sys
+
+try:
+    from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QStyle, QVBoxLayout
+    from PyQt5.QtWinExtras import QWinThumbnailToolBar, QWinThumbnailToolButton
+except ImportError:
+    from PySide2.QtWidgets import QWidget, QApplication, QLabel, QStyle, QVBoxLayout
+    from PySide2.QtWinExtras import QWinThumbnailToolBar, QWinThumbnailToolButton
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.countPrev = 0
+        self.countNext = 0
+        self.init_ui()
+
+    def init_ui(self):
+        layout = QVBoxLayout(self)
+        self.labelPrev = QLabel(self)
+        self.labelControl = QLabel('暂停播放', self)
+        self.labelNext = QLabel(self)
+        layout.addWidget(self.labelPrev)
+        layout.addWidget(self.labelControl)
+        layout.addWidget(self.labelNext)
+
+        # 任务栏缩略图工具条
+        self.toolBar = QWinThumbnailToolBar(self)
+        # 上一首,播放/暂停,下一首按钮
+        self.toolBtnPrev = QWinThumbnailToolButton(self.toolBar)
+        self.toolBtnPrev.setToolTip('上一首')
+        self.toolBtnPrev.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward))
+        self.toolBtnPrev.clicked.connect(self.set_prev)
+        self.toolBar.addButton(self.toolBtnPrev)
+
+        self.toolBtnControl = QWinThumbnailToolButton(self.toolBar)
+        self.toolBtnControl.setToolTip('播放')
+        self.toolBtnControl.setProperty('status', 0)
+        self.toolBtnControl.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
+        self.toolBtnControl.clicked.connect(self.set_control)
+        self.toolBar.addButton(self.toolBtnControl)
+
+        self.toolBtnNext = QWinThumbnailToolButton(self.toolBar)
+        self.toolBtnNext.setToolTip('下一首')
+        self.toolBtnNext.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipForward))
+        self.toolBtnNext.clicked.connect(self.set_next)
+        self.toolBar.addButton(self.toolBtnNext)
+
+    def set_prev(self):
+        self.countPrev += 1
+        self.labelPrev.setText('点击上一首按钮: %d 次' % self.countPrev)
+
+    def set_next(self):
+        self.countNext += 1
+        self.labelNext.setText('点击下一首按钮: %d 次' % self.countNext)
+
+    def set_control(self):
+        if self.toolBtnControl.property('status') == 0:
+            self.labelControl.setText('正在播放')
+            self.toolBtnControl.setProperty('status', 1)
+            self.toolBtnControl.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
+        else:
+            self.labelControl.setText('暂停播放')
+            self.toolBtnControl.setProperty('status', 0)
+            self.toolBtnControl.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
+
+    def showEvent(self, event):
+        super(Window, self).showEvent(event)
+        if not self.toolBar.window():
+            # 必须等窗口显示后设置才有效,或者通过软件流程在适当的时候设置也可以
+            self.toolBar.setWindow(self.windowHandle())
+
+
+if __name__ == '__main__':
+    cgitb.enable(format='text')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/README.md b/README.md
index d038768b..3f3685da 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
 # 各种各样的PyQt测试和例子
 
-[](https://pyqt5.com)
+[](https://pyqt.site)
 [](https://codebeat.co/projects/github-com-pyqt5-pyqt-master)
+[](https://996.icu/#/zh_CN)
+[](https://github.com/996icu/996.ICU/blob/master/LICENSE)
 
-https://pyqt.site 论坛是专门针对PyQt5学习和提升开设的网站,分享大家平时学习中记录的笔记和例子,以及对遇到的问题进行收集整理。
+[https://pyqt.site](https://pyqt.site) 论坛是专门针对PyQt5学习和提升开设的网站,分享大家平时学习中记录的笔记和例子,以及对遇到的问题进行收集整理。
 
 [](https://github.com/PyQt5/PyQt)
 [](https://github.com/PyQt5/PyQt)
@@ -11,24 +13,36 @@ https://pyqt.site 论坛是专门针对PyQt5学习和提升开设的网站,分
 
 如果您觉得这里的东西对您有帮助,别忘了帮忙点一颗:star:小星星:star:
 
-## 微信博客小程序
+[客户端下载](https://github.com/PyQt5/PyQtClient/releases) | [自定义控件](https://github.com/PyQt5/CustomWidgets)
 
-
-  
-    
-      Debug 
-      Win32 
-     
-    
-      Release 
-      Win32 
-     
-    
-      Debug 
-      x64 
-     
-    
-      Release 
-      x64 
-     
-   
-  
-    {023C9F4B-FECC-430A-81D2-7044FAFF15D0} 
-    pydext 
-    3.5 
-    10.0.16299.0 
-   
-  
-    DynamicLibrary 
-    true 
-    v141 
-    Unicode 
-   
-  
-    DynamicLibrary 
-    false 
-    v141 
-    true 
-    Unicode 
-   
-  
-    DynamicLibrary 
-    true 
-    v141 
-    Unicode 
-   
-  
-    DynamicLibrary 
-    false 
-    v141 
-    true 
-    Unicode 
-   
-  
-    RegistryView.Registry32 
-    RegistryView.Registry64 
-    $(PythonVersion)-32 
-    $(PythonVersion) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\$(PythonTag)\InstallPath', null, null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\$(PythonTag)\InstallPath', null, null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\$(PythonTag)\InstallPath', 'ExecutablePath', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\$(PythonTag)\InstallPath', 'ExecutablePath', null, $(RegistryView))) 
-    $(PythonHome)python.exe 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'dev', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'dev', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'core_pdb', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'core_pdb', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'core_d', null, $(RegistryView))) 
-    $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\$(PythonTag)\InstalledFeatures', 'core_d', null, $(RegistryView))) 
-    _d 
-    $([System.IO.Path]::GetDirectoryName($(PythonExe)))\python$(PythonDebugSuffix).exe 
-    $(PythonExe) 
-   
-  
-    WindowsLocalDebugger 
-    PythonDebugLaunchProvider 
-   
-  
-   
-  
-   
-  
-     
-  
-     
-  
-     
-  
-     
-  
-    pydext$(PythonDebugSuffix) 
-    .pyd 
-    $(PythonDExe) 
-    -i -c "print('>>> import pydext'); import pydext" 
-    PYTHONPATH=$(OutDir) 
-    $(DefaultDebuggerFlavor) 
-   
-  
-    pydext 
-    .pyd 
-    $(PythonExe) 
-    -i -c "print('>>> import pydext'); import pydext" 
-    PYTHONPATH=$(OutDir) 
-    $(DefaultDebuggerFlavor) 
-   
-  
-    pydext$(PythonDebugSuffix) 
-    .pyd 
-    $(PythonDExe) 
-    -i -c "print('>>> import pydext'); import pydext" 
-    PYTHONPATH=$(OutDir) 
-    $(DefaultDebuggerFlavor) 
-   
-  
-    pydext 
-    .pyd 
-    $(PythonExe) 
-    -i -c "print('>>> import pydext'); import pydext" 
-    PYTHONPATH=$(OutDir) 
-    $(DefaultDebuggerFlavor) 
-   
-  
-    
-      Level3 
-      Disabled 
-      MultithreadedDLL 
-      D:\soft\Python35\include;$(PythonHome)Include;%(AdditionalIncludeDirectories) 
-      true 
-     
-    true 
-      $(PythonHome)libs;%(AdditionalLibraryDirectories) 
-    
-   
-  
-    
-      Level3 
-      Disabled 
-      MultithreadedDLL 
-      $(PythonHome)Include;%(AdditionalIncludeDirectories) 
-      true 
-     
-    true 
-      $(PythonHome)libs;%(AdditionalLibraryDirectories) 
-    
-   
-  
-    
-      Level3 
-      MaxSpeed 
-      true 
-      true 
-      Multithreaded 
-      D:\soft\Python35\include;$(PythonHome)Include;%(AdditionalIncludeDirectories) 
-      true 
-     
-    true 
-      true 
-      true 
-      libucrt.lib;%(IgnoreSpecificDefaultLibraries) 
-      ucrt.lib;%(AdditionalDependencies) 
-      $(PythonHome)libs;%(AdditionalLibraryDirectories) 
-    
-   
-  
-    
-      Level3 
-      MaxSpeed 
-      true 
-      true 
-      Multithreaded 
-      $(PythonHome)Include;%(AdditionalIncludeDirectories) 
-      true 
-     
-    true 
-      true 
-      true 
-      libucrt.lib;%(IgnoreSpecificDefaultLibraries) 
-      ucrt.lib;%(AdditionalDependencies) 
-      $(PythonHome)libs;%(AdditionalLibraryDirectories) 
-    
-   
-  
-     
-  
-   
-  
-     
- 
\ No newline at end of file
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.filters" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.filters"
deleted file mode 100644
index 2b201335..00000000
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.filters"
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-  
-    
-      {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 
-      cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 
-     
-    
-      {93995380-89BD-4b04-88EB-625FBE52EBFB} 
-      h;hh;hpp;hxx;hm;inl;inc;xsd 
-     
-    
-      {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 
-      rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 
-     
-   
-  
-    
-      Source Files 
-     
-   
- 
\ No newline at end of file
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.user" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.user"
deleted file mode 100644
index be250787..00000000
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/pydext.vcxproj.user"
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-   
\ No newline at end of file
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/setup.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/setup.py"
index 03ce03ee..233046e9 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/setup.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/setup.py"
@@ -1,8 +1,8 @@
 from distutils.core import setup, Extension
- 
-module = Extension('pydext', sources = ['pydext.c'])
- 
-setup (name = 'pydext',
-       version = '1.0.0',
-       description = 'This is pydext',
-       ext_modules = [module])
\ No newline at end of file
+
+module = Extension('pydext', sources=['pydext.c'])
+
+setup(name='pydext',
+      version='1.0.0',
+      description='This is pydext',
+      ext_modules=[module])
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/test.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/test.py"
index 3a3d8703..13062296 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/test.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/pydext/test.py"
@@ -1,8 +1,8 @@
 import sys
 
 sys.path.insert(0,
-    './build/lib.{0}-{1}.{2}'.format(sys.platform, sys.version_info.major, sys.version_info.minor))
-
+                './build/lib.{0}-{1}.{2}'.format(sys.platform, sys.version_info.major,
+                                                 sys.version_info.minor))
 
 import pydext
 
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/test.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/test.py"
index 38c6394e..ecf23016 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/test.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pydext/test.py"
@@ -1,4 +1,5 @@
 import sys
+
 sys.path.insert(0, './Release')
 
 import pydext
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/CalSpecSpea.pyx" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/CalSpecSpea.pyx"
index 669ea114..bd6e390f 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/CalSpecSpea.pyx"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/CalSpecSpea.pyx"
@@ -20,8 +20,8 @@ def calspecaccel(np.ndarray[double, ndim=1, mode="c"] acc, int length, double dt
     cdef np.ndarray[double, ndim=1] Period = np.arange(0.0, maxPeriod + periodStep, periodStep) # 10.0 + 0.02, 0.02
     Period[0] = 0.001
     # 调用CalSpecSpeaLib.cpp定义的函数对数组进行处理
-    cal_spec_accel( np.PyArray_DATA(acc), length, dt, maxPeriod, periodStep, dampRatio, 
-         np.PyArray_DATA(Period),  np.PyArray_DATA(Fre), 
-         np.PyArray_DATA(MAcc),  np.PyArray_DATA(MVel), 
+    cal_spec_accel( np.PyArray_DATA(acc), length, dt, maxPeriod, periodStep, dampRatio,
+         np.PyArray_DATA(Period),  np.PyArray_DATA(Fre),
+         np.PyArray_DATA(MAcc),  np.PyArray_DATA(MVel),
          np.PyArray_DATA(MDis), numt)
     return Period, Fre, MAcc, MVel, MDis
\ No newline at end of file
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/setup.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/setup.py"
index eb42160a..0b689bf2 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/setup.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/setup.py"
@@ -1,11 +1,10 @@
 from distutils.core import setup, Extension
 
-from Cython.Distutils import build_ext
 import numpy
-
+from Cython.Distutils import build_ext
 
 setup(
     cmdclass={'build_ext': build_ext},
     ext_modules=[Extension("CalSpecSpea", sources=[
-                           "CalSpecSpea.pyx", "CalSpecSpeaLib.cpp"], language="c++", include_dirs=[numpy.get_include()])]
+        "CalSpecSpea.pyx", "CalSpecSpeaLib.cpp"], language="c++", include_dirs=[numpy.get_include()])]
 )
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/test.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/test.py"
index c0781379..9ea580c5 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/test.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/pyx\345\222\214c++/test.py"
@@ -4,7 +4,6 @@
 sys.path.append(
     './build/lib.{0}-{1}.{2}'.format(sys.platform, sys.version_info.major, sys.version_info.minor))
 
-
 from CalSpecSpea import calspecaccel
 import matplotlib.pyplot as plt
 import numpy as np
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/pydmod.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/pydmod.py"
index 810a5bfb..50ed74bb 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/pydmod.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/pydmod.py"
@@ -1,18 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Created on 2018年5月6日
-# author: Irony
-# site: https://pyqt5.com , https://github.com/892768447
-# email: 892768447@qq.com
-# file:
-# description:
-
-__Author__ = """By: Irony
-QQ: 892768447
-Email: 892768447@qq.com"""
-__Copyright__ = 'Copyright (c) 2018 Irony'
-__Version__ = 1.0
+"""
+Created on 2018年5月6日
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file:
+@description:
+"""
 
 
 def sum(x, y):
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/setup.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/setup.py"
index d3cc302f..b18bc64b 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/setup.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/setup.py"
@@ -2,7 +2,6 @@
 
 from Cython.Distutils import build_ext
 
-
 setup(
     cmdclass={'build_ext': build_ext},
     ext_modules=[Extension("pydmod", sources=["pydmod.py"])]
diff --git "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/test.py" "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/test.py"
index 3c451454..387314a5 100644
--- "a/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/test.py"
+++ "b/Test/C\345\222\214C++\346\211\251\345\261\225/py\350\275\254pyd/test.py"
@@ -1,7 +1,10 @@
 import sys
+
 sys.path.insert(0,
-    './build/lib.{0}-{1}.{2}'.format(sys.platform, sys.version_info.major, sys.version_info.minor))
+                './build/lib.{0}-{1}.{2}'.format(sys.platform, sys.version_info.major,
+                                                 sys.version_info.minor))
 
 import pydmod
+
 print(pydmod)
 print(pydmod.sum(1, 5))
diff --git a/Test/Network/README.md b/Test/Network/README.md
index b6bbec5c..2aeccb1c 100644
--- a/Test/Network/README.md
+++ b/Test/Network/README.md
@@ -1,43 +1,46 @@
 # 网络
 
 ## [1、控制小车](控制小车/)
+
 通过TCP连接树莓派控制小车的简单例子
 
 需求:
 
- - 通过TCP连接到树莓派控制小车前后左右
- - 前进:0-100, 发送命令为F:2
- - 后退:0-100, 发送命令为B:2
- - 向左:32-42, 发送命令为L:2
- - 向右:42-52, 发送命令为R:2
+- 通过TCP连接到树莓派控制小车前后左右
+- 前进:0-100, 发送命令为F:2
+- 后退:0-100, 发送命令为B:2
+- 向左:32-42, 发送命令为L:2
+- 向右:42-52, 发送命令为R:2
 
 注意:
 
- - 这里只用了UI文件做界面,并没有转换为python代码
- - server.py只是做个本地echo服务器用来测试命令是否正常,依赖`tornado`库,可以通过`pip install tornado`来安装
- - 另外需要做粘包处理,以(\n)作为粘包符
- - 由于wifi能力不行,发送图片要尽量小
+- 这里只用了UI文件做界面,并没有转换为python代码
+- server.py只是做个本地echo服务器用来测试命令是否正常,依赖`tornado`库,可以通过`pip install tornado`来安装
+- 另外需要做粘包处理,以(\n)作为粘包符
+- 由于wifi能力不行,发送图片要尽量小
 
 说明:
 
- - `QTcpSocket.connected`    服务连接成功后触发该信号
- - `QTcpSocket.disconnected` 服务器丢失连接触发该信号
- - `QTcpSocket.readyRead`    服务器返回数据触发该信号
- - `QTcpSocket.error`        连接报错触发该信号(连接超时、服务器断开等等)
+- `QTcpSocket.connected`    服务连接成功后触发该信号
+- `QTcpSocket.disconnected` 服务器丢失连接触发该信号
+- `QTcpSocket.readyRead`    服务器返回数据触发该信号
+- `QTcpSocket.error`        连接报错触发该信号(连接超时、服务器断开等等)
 
 目前暂未修复接收图片异,原因在于`readyRead`中没有判断数据长度进行多次接收(类似粘包处理) 
 
 
 
 ## [2、窗口配合异步Http](窗口配合异步Http/)
+
 `asyncio`结合PyQt例子
 
 1. 依赖库:
-    1. `quamash`(对QT事件循环的封装替换):https://github.com/harvimt/quamash
-    2. `asyncio`:https://docs.python.org/3/library/asyncio.html
-    3. `aiohttp`:https://aiohttp.readthedocs.io/en/stable/
+    1. `quamash`(对QT事件循环的封装替换):
+    2. `asyncio`:
+    3. `aiohttp`:
+#include 
+#include 
+
+WigglyWidget::WigglyWidget(QWidget *parent) : QWidget(parent), step(0) {
+  setBackgroundRole(QPalette::Midlight);
+  setAutoFillBackground(true);
+
+  QFont newFont = font();
+  newFont.setPointSize(newFont.pointSize() + 20);
+  setFont(newFont);
+
+  timer.start(60, this);
+}
+
+void WigglyWidget::setText(const QString &newText) { text = newText; }
+
+void WigglyWidget::paintEvent(QPaintEvent * /* event */)
+
+{
+  static constexpr int sineTable[16] = {0, 38,  71,  92,  100,  92,  71,  38,
+                                        0, -38, -71, -92, -100, -92, -71, -38};
+
+  QFontMetrics metrics(font());
+  int x = (width() - metrics.horizontalAdvance(text)) / 2;
+  int y = (height() + metrics.ascent() - metrics.descent()) / 2;
+  QColor color;
+
+  QPainter painter(this);
+
+  for (int i = 0; i < text.size(); ++i) {
+    int index = (step + i) % 16;
+    color.setHsv((15 - index) * 16, 255, 191);
+    painter.setPen(color);
+    painter.drawText(x, y - ((sineTable[index] * metrics.height()) / 400),
+                     QString(text[i]));
+    x += metrics.horizontalAdvance(text[i]);
+  }
+}
+
+void WigglyWidget::timerEvent(QTimerEvent *event) {
+  if (event->timerId() == timer.timerId()) {
+    ++step;
+    update();
+  } else {
+    QWidget::timerEvent(event);
+  }
+}
diff --git a/Test/WigglyWidget/LibWigglyWidget/wigglywidget.h b/Test/WigglyWidget/LibWigglyWidget/wigglywidget.h
new file mode 100644
index 00000000..3df215e0
--- /dev/null
+++ b/Test/WigglyWidget/LibWigglyWidget/wigglywidget.h
@@ -0,0 +1,36 @@
+#ifndef WIGGLYWIDGET_H
+#define WIGGLYWIDGET_H
+
+#include 
+#include 
+
+#ifdef Q_OS_WIN
+#include 
+
+#if defined(WIGGLYWIDGET_LIBRARY)
+#define WIGGLYWIDGET_EXPORT Q_DECL_EXPORT
+#else
+#define WIGGLYWIDGET_EXPORT
+#endif
+#endif
+
+class WIGGLYWIDGET_EXPORT WigglyWidget : public QWidget {
+  Q_OBJECT
+
+public:
+  WigglyWidget(QWidget *parent = nullptr);
+
+public slots:
+  void setText(const QString &newText);
+
+protected:
+  virtual void paintEvent(QPaintEvent *event) override;
+  virtual void timerEvent(QTimerEvent *event) override;
+
+private:
+  QBasicTimer timer;
+  QString text;
+  int step;
+};
+
+#endif // WIGGLYWIDGET_H
diff --git a/Test/WigglyWidget/PyQtWrapper/CMakeLists.txt b/Test/WigglyWidget/PyQtWrapper/CMakeLists.txt
new file mode 100644
index 00000000..8218c322
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(PyQtWrapper)
diff --git a/Test/WigglyWidget/PyQtWrapper/TestWigglyWidget.py b/Test/WigglyWidget/PyQtWrapper/TestWigglyWidget.py
new file mode 100644
index 00000000..ac1177ae
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/TestWigglyWidget.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2024/04/26
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TestWigglyWidget.py
+@description:
+"""
+
+import os
+import sys
+
+sys.path.append(
+    os.path.join(os.path.dirname(os.path.abspath(__file__)), "build/WigglyWidget")
+)
+
+from PyQt5.QtWidgets import QApplication, QLineEdit, QVBoxLayout, QWidget
+from WigglyWidget import WigglyWidget
+
+
+class TestWigglyWidget(QWidget):
+    def __init__(self, *args, **kwargs):
+        super(TestWigglyWidget, self).__init__(*args, **kwargs)
+        self._layout = QVBoxLayout(self)
+        self._lineEdit = QLineEdit(self)
+        self._wigglyWidget = WigglyWidget(self)
+        self._layout.addWidget(self._lineEdit)
+        self._layout.addWidget(self._wigglyWidget)
+
+        self._lineEdit.textChanged.connect(self._wigglyWidget.setText)
+        self._lineEdit.setText("pyqt.site")
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWigglyWidget()
+    w.show()
+    w.resize(800, 600)
+    if not hasattr(app, "exec_"):
+        app.exec_ = app.exec
+    sys.exit(app.exec_())
diff --git a/Test/WigglyWidget/PyQtWrapper/pyproject.toml b/Test/WigglyWidget/PyQtWrapper/pyproject.toml
new file mode 100644
index 00000000..fb837510
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/pyproject.toml
@@ -0,0 +1,47 @@
+# Specify sip v6 as the build system for the package.
+[build-system]
+requires = ["sip >=5.3, <7", "PyQt-builder >=1.9, <2"]
+build-backend = "sipbuild.api"
+
+# Specify the PEP 621 metadata for the project.
+[project]
+name = "WigglyWidget"
+version = "0.1.0"
+description = "Python bindings for the WigglyWidget library"
+urls.homepage = "https://github.com/PyQt5/PyQt"
+dependencies = ["PyQt5 (>=5.15.0, <6.0.0)"]
+
+[[project.authors]]
+name = "Irony"
+email = "892768447@qq.com"
+
+# Specify a PyQt-based project.
+[tool.sip]
+project-factory = "pyqtbuild:PyQtProject"
+
+# Specify the PEP 566 metadata for the project.
+[tool.sip.metadata]
+name = "WigglyWidget"
+summary = "Python bindings for the WigglyWidget library"
+home-page = "https://github.com/PyQt5/PyQt"
+author = "Irony"
+author-email = "892768447@qq.com"
+requires-dist = "PyQt5 (>=5.15.0, <6.0.0)"
+
+# Configure the project.
+[tool.sip.project]
+tag-prefix = "WigglyWidget"
+sip-include-dirs = [
+    "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/PyQt5/bindings",
+    "/usr/local/lib64/python3.6/site-packages/PyQt5/bindings",
+    "C:/soft/Python311/Lib/site-packages/PyQt5/bindings",
+    "C:/soft/Python/lib/site-packages/PyQt6/bindings"
+]
+
+# Configure the building of the fib bindings.
+[tool.sip.bindings.WigglyWidget]
+qmake-QT = ["core", "gui", "widgets"]
+headers = ["wigglywidget.h"]
+include-dirs = ["../dist/include"]
+libraries = ["LibWigglyWidget"]
+library-dirs = ["../dist/lib"]
diff --git a/Test/WigglyWidget/PyQtWrapper/requirements.txt b/Test/WigglyWidget/PyQtWrapper/requirements.txt
new file mode 100644
index 00000000..4d4e9f19
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/requirements.txt
@@ -0,0 +1,6 @@
+PyQt5
+PyQt6
+sip
+PyQt5-sip
+PyQt6-sip
+PyQt-builder
diff --git a/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidget.sip b/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidget.sip
new file mode 100644
index 00000000..6fe00065
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidget.sip
@@ -0,0 +1,16 @@
+class WigglyWidget : QWidget
+{
+%TypeHeaderCode
+#include "wigglywidget.h"
+%End
+
+public:
+    WigglyWidget(QWidget *parent /TransferThis/ = 0);
+
+public slots:
+    void setText(const QString &newText);
+
+protected:
+    virtual void paintEvent(QPaintEvent *);
+    virtual void timerEvent(QTimerEvent *);
+};
diff --git a/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidgetmod.sip b/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidgetmod.sip
new file mode 100644
index 00000000..927d003f
--- /dev/null
+++ b/Test/WigglyWidget/PyQtWrapper/sip/WigglyWidget/WigglyWidgetmod.sip
@@ -0,0 +1,9 @@
+%Module(name=WigglyWidget, keyword_arguments="Optional", use_limited_api=True)
+
+
+%Import QtCore/QtCoremod.sip
+%Import QtWidgets/QtWidgetsmod.sip
+
+%DefaultSupertype sip.simplewrapper
+
+%Include WigglyWidget.sip
diff --git a/Test/WigglyWidget/PySideWrapper/CMakeLists.txt b/Test/WigglyWidget/PySideWrapper/CMakeLists.txt
new file mode 100644
index 00000000..99122055
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/CMakeLists.txt
@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.14)
+cmake_policy(VERSION 3.14)
+
+# Enable policy to not use RPATH settings for install_name on macOS.
+if(POLICY CMP0068)
+  cmake_policy(SET CMP0068 NEW)
+endif()
+
+# Enable policy to run automoc on generated files.
+if(POLICY CMP0071)
+  cmake_policy(SET CMP0071 NEW)
+endif()
+
+project(PySideWrapper)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(BINDINGS_HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/bindings.h")
+set(BINDINGS_TYPESYSTEM_FILE "${CMAKE_CURRENT_SOURCE_DIR}/bindings.xml")
+set(BINDINGS_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dist")
+set(BINDINGS_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../dist/include/")
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bindings.txt.in"
+               "${CMAKE_CURRENT_SOURCE_DIR}/bindings.txt")
diff --git a/Test/WigglyWidget/PySideWrapper/TestWigglyWidget.py b/Test/WigglyWidget/PySideWrapper/TestWigglyWidget.py
new file mode 100644
index 00000000..d2a851c9
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/TestWigglyWidget.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2024/04/26
+@author: Irony
+@site: https://pyqt.site | https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: TestWigglyWidget.py
+@description:
+"""
+
+import sys
+
+from PySide2.QtWidgets import QApplication, QLineEdit, QVBoxLayout, QWidget
+from WigglyWidget import WigglyWidget
+
+
+class TestWigglyWidget(QWidget):
+    def __init__(self, *args, **kwargs):
+        super(TestWigglyWidget, self).__init__(*args, **kwargs)
+        self._layout = QVBoxLayout(self)
+        self._lineEdit = QLineEdit(self)
+        self._wigglyWidget = WigglyWidget(self)
+        self._layout.addWidget(self._lineEdit)
+        self._layout.addWidget(self._wigglyWidget)
+
+        self._lineEdit.textChanged.connect(self._wigglyWidget.setText)
+        self._lineEdit.setText("pyqt.site")
+
+
+if __name__ == "__main__":
+    import cgitb
+    import sys
+
+    cgitb.enable(format="text")
+    app = QApplication(sys.argv)
+    w = TestWigglyWidget()
+    w.show()
+    sys.exit(app.exec_())
diff --git a/Test/WigglyWidget/PySideWrapper/bindings.h b/Test/WigglyWidget/PySideWrapper/bindings.h
new file mode 100644
index 00000000..0634a4b1
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/bindings.h
@@ -0,0 +1,4 @@
+#ifndef BINDINGS_H
+#define BINDINGS_H
+#include "wigglywidget.h"
+#endif // BINDINGS_H
diff --git a/Test/WigglyWidget/PySideWrapper/bindings.txt.in b/Test/WigglyWidget/PySideWrapper/bindings.txt.in
new file mode 100644
index 00000000..d1bb8a4b
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/bindings.txt.in
@@ -0,0 +1,13 @@
+[generator-project]
+generator-set = shiboken
+header-file = ${BINDINGS_HEADER_FILE}
+typesystem-file = ${BINDINGS_TYPESYSTEM_FILE}
+output-directory = ${BINDINGS_OUTPUT_DIR}
+include-path = ${BINDINGS_INCLUDE_PATH}
+framework-include-paths =
+typesystem-paths = ${BINDINGS_TYPESYSTEM_PATH}
+
+enable-parent-ctor-heuristic
+enable-pyside-extensions
+enable-return-value-heuristic
+use-isnull-as-nb_nonzero
diff --git a/Test/WigglyWidget/PySideWrapper/bindings.xml b/Test/WigglyWidget/PySideWrapper/bindings.xml
new file mode 100644
index 00000000..c3a31296
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/bindings.xml
@@ -0,0 +1,5 @@
+
+
+     
diff --git a/Test/WigglyWidget/PySideWrapper/generated.bat b/Test/WigglyWidget/PySideWrapper/generated.bat
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/WigglyWidget/PySideWrapper/generated.sh b/Test/WigglyWidget/PySideWrapper/generated.sh
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/WigglyWidget/PySideWrapper/requirements.txt b/Test/WigglyWidget/PySideWrapper/requirements.txt
new file mode 100644
index 00000000..fa259e98
--- /dev/null
+++ b/Test/WigglyWidget/PySideWrapper/requirements.txt
@@ -0,0 +1,9 @@
+PySide2==5.15.2.1
+
+https://download.qt.io/official_releases/QtForPython/shiboken2-generator/shiboken2_generator-5.15.2.1-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl; platform_machine == "x86_64" and platform_system == "Windows"
+
+https://download.qt.io/official_releases/QtForPython/shiboken2-generator/shiboken2_generator-5.15.2.1-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310-none-win32.whl; platform_machine == "i686" and platform_system == "Windows"
+
+https://download.qt.io/official_releases/QtForPython/shiboken2-generator/shiboken2_generator-5.15.2.1-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl; platform_machine == "x86_64" and platform_system == "Linux"
+
+https://download.qt.io/official_releases/QtForPython/shiboken2-generator/shiboken2_generator-5.15.2.1-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_13_intel.whl; platform_machine == "x86_64" and platform_system == "Darwin"
diff --git a/Test/WigglyWidget/README.md b/Test/WigglyWidget/README.md
new file mode 100644
index 00000000..bfd71091
--- /dev/null
+++ b/Test/WigglyWidget/README.md
@@ -0,0 +1,11 @@
+# WigglyWidget
+
+## Build
+
+Windows
+
+1. 使用 `QtCreator` 打开项目 `CMakeLists.txt`,勾选对应的Qt版本。
+2. 在 `QtCreator` 中通过 `项目`->`构建`->`构建步骤`->`详情` 里勾选 `all` 和 `install`
+3. 进入 `PyQtWrapper` 目录,打开`vs cmd`,运行 `python -m pip install -r requirements.txt`
+4. `sip-build --verbose --tracing --qmake=你的Qt目录下的qmake.exe路径`,等待编译完成
+5. `python TestWigglyWidget.py` 进行测试
diff --git a/Test/WigglyWidget/TestWigglyWidget/CMakeLists.txt b/Test/WigglyWidget/TestWigglyWidget/CMakeLists.txt
new file mode 100644
index 00000000..9ab36f94
--- /dev/null
+++ b/Test/WigglyWidget/TestWigglyWidget/CMakeLists.txt
@@ -0,0 +1,61 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(
+  TestWigglyWidget
+  VERSION 0.1
+  LANGUAGES CXX)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# 配置安装目录
+set(CMAKE_INSTALL_DIR "${CMAKE_SOURCE_DIR}/dist")
+
+find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
+find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
+
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../LibWigglyWidget")
+
+set(PROJECT_SOURCES main.cpp)
+
+if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
+  qt_add_executable(${PROJECT_NAME} MANUAL_FINALIZATION ${PROJECT_SOURCES})
+  # Define target properties for Android with Qt 6 as: set_property(TARGET
+  # TestWigglyWidget APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
+  # ${CMAKE_CURRENT_SOURCE_DIR}/android) For more information, see
+  # https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
+else()
+  if(ANDROID)
+    add_library(${PROJECT_NAME} SHARED ${PROJECT_SOURCES})
+    # Define properties for Android with Qt 5 after find_package() calls as:
+    # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
+  else()
+    add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
+  endif()
+endif()
+
+target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
+                                              LibWigglyWidget)
+
+# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. If
+# you are developing for iOS or macOS you should consider setting an explicit,
+# fixed bundle identifier manually though.
+if(${QT_VERSION} VERSION_LESS 6.1.0)
+  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER
+                       com.example.TestWigglyWidget)
+endif()
+set_target_properties(
+  ${PROJECT_NAME}
+  PROPERTIES ${BUNDLE_ID_OPTION} MACOSX_BUNDLE_BUNDLE_VERSION
+             ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING
+             ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE
+             TRUE WIN32_EXECUTABLE
+             TRUE)
+
+if(QT_VERSION_MAJOR EQUAL 6)
+  qt_finalize_executable(${PROJECT_NAME})
+endif()
diff --git a/Test/WigglyWidget/TestWigglyWidget/main.cpp b/Test/WigglyWidget/TestWigglyWidget/main.cpp
new file mode 100644
index 00000000..a74498e1
--- /dev/null
+++ b/Test/WigglyWidget/TestWigglyWidget/main.cpp
@@ -0,0 +1,11 @@
+#include 
+
+#include "wigglywidget.h"
+
+int main(int argc, char *argv[]) {
+  QApplication a(argc, argv);
+  WigglyWidget w;
+  w.setText("pyqt.site");
+  w.show();
+  return a.exec();
+}
diff --git "a/Test/\345\205\250\345\261\200\347\203\255\351\224\256/HotKey.py" "b/Test/\345\205\250\345\261\200\347\203\255\351\224\256/HotKey.py"
index 0d2d2409..b9b2f286 100644
--- "a/Test/\345\205\250\345\261\200\347\203\255\351\224\256/HotKey.py"
+++ "b/Test/\345\205\250\345\261\200\347\203\255\351\224\256/HotKey.py"
@@ -1,29 +1,27 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年12月11日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: HotKey
 @description: 
-'''
+"""
 import sys
 
-from PyQt5.QtCore import pyqtSignal, Qt
-from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QTextBrowser, QPushButton,\
-    QMessageBox
 import keyboard
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+try:
+    from PyQt5.QtCore import pyqtSignal, Qt
+    from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QTextBrowser, QPushButton, QMessageBox
+except ImportError:
+    from PySide2.QtCore import Signal as pyqtSignal, Qt
+    from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout, QTextBrowser, QPushButton, QMessageBox
 
 
 class Window(QWidget):
-
     dialogShow = pyqtSignal()
 
     def __init__(self, *args, **kwargs):
@@ -51,7 +49,8 @@ def __init__(self, *args, **kwargs):
             'ctrl+alt+del', lambda: self.logView.append('😏😏我知道你按了任务管理器😏😏'))
 
         # 这个函数类似while True,由于这里有界面GUI的loop事件,可以达到类似的效果
-#         keyboard.wait()#Block forever, like `while True`.==
+
+    #         keyboard.wait()#Block forever, like `while True`.==
 
     def onShow(self):
         """显示"""
diff --git "a/Test/\345\205\250\345\261\200\347\203\255\351\224\256/README.md" "b/Test/\345\205\250\345\261\200\347\203\255\351\224\256/README.md"
index f6db5d27..af9c9420 100644
--- "a/Test/\345\205\250\345\261\200\347\203\255\351\224\256/README.md"
+++ "b/Test/\345\205\250\345\261\200\347\203\255\351\224\256/README.md"
@@ -2,12 +2,13 @@
 
 pip install keyboard
 
-https://github.com/892768447/keyboard
+
 
 * keyboard
-    * 该模块使用全局低级钩子的方式hook键盘来处理,对系统有一定的影响
-    * 有反映说弹出对话框假死,这里粗略解决下使用信号槽的方式来弹出对话框
-    * 该模块里使用了每次产生一个子线程来回调函数
+  * 该模块使用全局低级钩子的方式hook键盘来处理,对系统有一定的影响
+  * 有反映说弹出对话框假死,这里粗略解决下使用信号槽的方式来弹出对话框
+  * 该模块里使用了每次产生一个子线程来回调函数
+
 ```
 def call_later(fn, args=(), delay=0.001):
     """
@@ -20,4 +21,5 @@ def call_later(fn, args=(), delay=0.001):
 ```
 
 # 截图
-
\ No newline at end of file
+
+
diff --git "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/README.md" "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/README.md"
index 1adc33fb..300bc484 100644
--- "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/README.md"
+++ "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/README.md"
@@ -1,6 +1,6 @@
 # 自动更新
 
- - dist/mylibs1.zip 为版本一的文件
- - dist/mylibs2.zip 为版本二的文件
+- dist/mylibs1.zip 为版本一的文件
+- dist/mylibs2.zip 为版本二的文件
 
-运行演示后,再次演示。需要把mylibs1.zip中的文件解压出来替换
\ No newline at end of file
+运行演示后,再次演示。需要把mylibs1.zip中的文件解压出来替换
diff --git "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/mylibs/testlibs.py" "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/mylibs/testlibs.py"
index 431226ea..6cd81036 100644
--- "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/mylibs/testlibs.py"
+++ "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/mylibs/testlibs.py"
@@ -1,18 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年5月7日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 自动更新.mylibs.testlibs
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 
 def version():
diff --git "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/setup.py" "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/setup.py"
index 6e994728..7bca2a0d 100644
--- "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/setup.py"
+++ "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/setup.py"
@@ -1,13 +1,11 @@
-from distutils.core import setup
 import glob
 import os
 import py_compile
 import site
 import sys
+from distutils.core import setup
 from zipfile import ZipFile, ZIP_DEFLATED
 
-import py2exe  # @UnusedImport
-
 sys.argv.append("py2exe")
 
 sitepackages = site.getsitepackages()[1]
diff --git "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/test.py" "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/test.py"
index b70643a7..dd7a40a9 100644
--- "a/Test/\350\207\252\345\212\250\346\233\264\346\226\260/test.py"
+++ "b/Test/\350\207\252\345\212\250\346\233\264\346\226\260/test.py"
@@ -1,26 +1,22 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2017年5月7日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: 自动更新.test
 @description: 
-'''
+"""
 import sys
+
 sys.path.append("mylibs")
 import os
 from time import sleep
 from zipfile import ZipFile
 
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
-
 def update():
     # 更新
     from mylibs import testlibs  # @UnresolvedImport
@@ -50,6 +46,7 @@ def main():
         os.startfile(sys.executable)  # 重启
         sys.exit()  # 退出本身
 
+
 if __name__ == "__main__":
     main()
     input("press any key exit")
diff --git "a/Test/\350\207\252\345\256\232\344\271\211import/IronyImporter.py" "b/Test/\350\207\252\345\256\232\344\271\211import/IronyImporter.py"
index 1a67eef0..d2b203f1 100644
--- "a/Test/\350\207\252\345\256\232\344\271\211import/IronyImporter.py"
+++ "b/Test/\350\207\252\345\256\232\344\271\211import/IronyImporter.py"
@@ -1,14 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: IronyImporter
 @description: 
-'''
+"""
 import base64
 import os
 import sys
@@ -16,11 +16,6 @@
 
 import xxtea  # @UnresolvedImport
 
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
-
 KEY = base64.b85decode("HF5^hbNbOVOKM=(SB`7h")
 
 
diff --git "a/Test/\350\207\252\345\256\232\344\271\211import/README.md" "b/Test/\350\207\252\345\256\232\344\271\211import/README.md"
index 4ee4369a..a339481d 100644
--- "a/Test/\350\207\252\345\256\232\344\271\211import/README.md"
+++ "b/Test/\350\207\252\345\256\232\344\271\211import/README.md"
@@ -1,14 +1,17 @@
 # 自定义import
+
 需要Python3.5.2(或者自行编译xxtea)
 
 简单的了解了下import的原理
 
 # 测试过程
- - 1.在src中编写一个test.py
- - 2.通过build.py 利用xxtea加密src/test.py 到当前目录的test.irony文件
- - 3.运行main.py 进行测试
+
+- 1.在src中编写一个test.py
+- 2.通过build.py 利用xxtea加密src/test.py 到当前目录的test.irony文件
+- 3.运行main.py 进行测试
 
 # 截图
+
 test.py
 
 
@@ -19,4 +22,4 @@ test.irony
 
 main.py
 
-
\ No newline at end of file
+
diff --git "a/Test/\350\207\252\345\256\232\344\271\211import/build.py" "b/Test/\350\207\252\345\256\232\344\271\211import/build.py"
index 86aeba8b..21eeb3a1 100644
--- "a/Test/\350\207\252\345\256\232\344\271\211import/build.py"
+++ "b/Test/\350\207\252\345\256\232\344\271\211import/build.py"
@@ -1,23 +1,19 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: build
 @description: 
-'''
+"""
 
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
 import base64
 
 import xxtea  # @UnresolvedImport
 
-
 KEY = base64.b85decode("HF5^hbNbOVOKM=(SB`7h")
 
 with open("src/test.py", "rb") as fi:
diff --git "a/Test/\350\207\252\345\256\232\344\271\211import/main.py" "b/Test/\350\207\252\345\256\232\344\271\211import/main.py"
index cccc6cbb..47ebb2e2 100644
--- "a/Test/\350\207\252\345\256\232\344\271\211import/main.py"
+++ "b/Test/\350\207\252\345\256\232\344\271\211import/main.py"
@@ -1,25 +1,20 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: main
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 # 首先要引入importer
-import IronyImporter  # @UnresolvedImport @UnusedImport
-
 
 # 测试开始
 import test
+
 print(test)
 print(dir(test))
 print(test.test(1, 5))  # @UndefinedVariable
diff --git "a/Test/\350\207\252\345\256\232\344\271\211import/src/test.py" "b/Test/\350\207\252\345\256\232\344\271\211import/src/test.py"
index 07d5d45e..4049409f 100644
--- "a/Test/\350\207\252\345\256\232\344\271\211import/src/test.py"
+++ "b/Test/\350\207\252\345\256\232\344\271\211import/src/test.py"
@@ -1,18 +1,14 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Created on 2018年1月28日
-@author: Irony."[讽刺]
-@site: https://pyqt5.com , https://github.com/892768447
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
 @email: 892768447@qq.com
 @file: test
 @description: 
-'''
-
-__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
-__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
-__Version__ = "Version 1.0"
+"""
 
 
 def test(a, b):
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..e873f8cf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,12 @@
+[project]
+name = "PyQt"
+
+[tool.ruff.lint]
+ignore = ["E402", "E501"]
+
+[tool.pyright]
+reportArgumentType = false
+reportOperatorIssue = false
+reportAttributeAccessIssue = false
+reportCallIssue = false
+reportIncompatibleMethodOverride = false