Merge branch 'master' of https://github.com/PyQt5/PyQt
This commit is contained in:
commit
5f33be5f92
9 changed files with 694 additions and 2 deletions
|
@ -23,6 +23,7 @@
|
|||
- [判断信号是否连接](#20判断信号是否连接)
|
||||
- [调用虚拟键盘](#21调用虚拟键盘)
|
||||
- [动态忙碌光标](#22动态忙碌光标)
|
||||
- [屏幕变动监听](#23屏幕变动监听)
|
||||
|
||||
## 1、重启窗口Widget
|
||||
[运行 RestartWindow.py](RestartWindow.py)
|
||||
|
@ -223,4 +224,11 @@ PyQt 结合 Opencv 进行人脸检测;
|
|||
|
||||
通过定时器不停的修改光标图片来实现动态效果
|
||||
|
||||
![GifCursor](ScreenShot/GifCursor.gif)
|
||||
![GifCursor](ScreenShot/GifCursor.gif)
|
||||
|
||||
## 23、屏幕变动监听
|
||||
[运行 ScreenNotify.py](ScreenNotify.py)
|
||||
|
||||
通过定时器减少不同的变化信号,尽量保证只调用一次槽函数来获取信息
|
||||
|
||||
![ScreenNotify](ScreenShot/ScreenNotify.png)
|
58
Demo/ScreenNotify.py
Normal file
58
Demo/ScreenNotify.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2021/4/13
|
||||
@author: Irony
|
||||
@site: https://github.com/PyQt5
|
||||
@email: 892768447@qq.com
|
||||
@file: ScreenNotify
|
||||
@description: 屏幕、分辨率、DPI变化通知
|
||||
"""
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QTimer, QRect
|
||||
from PyQt5.QtWidgets import QApplication, QPlainTextEdit, qApp
|
||||
|
||||
|
||||
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) # **重要** 保证多次信号尽量少的调用函数
|
||||
|
||||
# 主要是多屏幕->无屏幕->有屏幕
|
||||
qApp.primaryScreenChanged.connect(lambda _: self.m_timer.start(1000))
|
||||
# 其它信号最终基本上都会调用该信号
|
||||
qApp.primaryScreen().virtualGeometryChanged.connect(lambda _: self.m_timer.start(1000))
|
||||
# DPI变化
|
||||
qApp.primaryScreen().logicalDotsPerInchChanged.connect(lambda _: self.m_timer.start(1000))
|
||||
|
||||
def onSolutionChanged(self):
|
||||
# 获取主屏幕
|
||||
screen = qApp.primaryScreen()
|
||||
if self.m_rect == screen.availableVirtualGeometry():
|
||||
return
|
||||
self.m_rect = screen.availableVirtualGeometry()
|
||||
# 所有屏幕可用大小
|
||||
self.appendPlainText('\navailableVirtualGeometry: {0}'.format(str(screen.availableVirtualGeometry())))
|
||||
# 获取所有屏幕
|
||||
screens = qApp.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_())
|
BIN
Demo/ScreenShot/ScreenNotify.png
Normal file
BIN
Demo/ScreenShot/ScreenNotify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
236
QListView/ImageView.py
Normal file
236
QListView/ImageView.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2021/4/15
|
||||
@author: Irony
|
||||
@site: https://github.com/PyQt5
|
||||
@email: 892768447@qq.com
|
||||
@file: ImageView
|
||||
@description:
|
||||
"""
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QPointF, Qt, QRectF, QSizeF
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPainter, QColor, QImage, QPixmap
|
||||
from PyQt5.QtWidgets import 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')
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
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_())
|
127
QWebEngineView/GetRequestInfo.py
Normal file
127
QWebEngineView/GetRequestInfo.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2019年9月24日
|
||||
@author: Irony
|
||||
@site: https://pyqt5.com https://github.com/892768447
|
||||
@email: 892768447@qq.com
|
||||
@file: QWebEngineView.BlockAds
|
||||
@description: 拦截请求
|
||||
"""
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlScheme, \
|
||||
QWebEngineUrlRequestInterceptor
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
|
||||
|
||||
__Author__ = 'Irony'
|
||||
__Copyright__ = 'Copyright (c) 2019'
|
||||
__Version__ = 'Version 1.0'
|
||||
|
||||
|
||||
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(b'http')
|
||||
o_https = QWebEngineUrlScheme.schemeByName(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(b'myurl', self.urlSchemeHandler) # for http
|
||||
de.installUrlSchemeHandler(b'myurls', self.urlSchemeHandler) # for https
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import os
|
||||
import webbrowser
|
||||
import cgitb
|
||||
|
||||
cgitb.enable(format='text')
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
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_())
|
105
QtRemoteObjects/SyncUi/ClipboardMaster.py
Normal file
105
QtRemoteObjects/SyncUi/ClipboardMaster.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
#!/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:
|
||||
"""
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, QLoggingCategory, QVariant, QMimeData
|
||||
from PyQt5.QtRemoteObjects import QRemoteObjectHost
|
||||
from PyQt5.QtWidgets import QTextBrowser
|
||||
|
||||
__Author__ = 'Irony'
|
||||
__Copyright__ = 'Copyright (c) 2019 Irony'
|
||||
__Version__ = 1.0
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
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(1, None, 5, '')
|
||||
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_())
|
107
QtRemoteObjects/SyncUi/ClipboardSlave.py
Normal file
107
QtRemoteObjects/SyncUi/ClipboardSlave.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
#!/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
|
||||
|
||||
__Author__ = 'Irony'
|
||||
__Copyright__ = 'Copyright (c) 2019 Irony'
|
||||
__Version__ = 1.0
|
||||
|
||||
|
||||
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(1, None, 5, '')
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
w = WindowSlave()
|
||||
w.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,7 +1,9 @@
|
|||
# 各种各样的PyQt测试和例子
|
||||
|
||||
[![Blog](https://img.shields.io/badge/blog-pyqt5-green.svg)](https://pyqt5.com)
|
||||
[![Blog](https://img.shields.io/badge/blog-pyqt-green.svg)](https://pyqt.site)
|
||||
[![codebeat badge](https://codebeat.co/badges/d23d0dc8-aef3-43d2-96aa-e3215b2c9861)](https://codebeat.co/projects/github-com-pyqt5-pyqt-master)
|
||||
[![Badge](https://img.shields.io/badge/link-996.icu-%23FF4D5B.svg?style=flat-square)](https://996.icu/#/zh_CN)
|
||||
[![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg?style=flat-square)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
||||
|
||||
https://pyqt.site 论坛是专门针对PyQt5学习和提升开设的网站,分享大家平时学习中记录的笔记和例子,以及对遇到的问题进行收集整理。
|
||||
|
||||
|
@ -253,6 +255,7 @@ https://pyqt.site 论坛是专门针对PyQt5学习和提升开设的网站,分
|
|||
- [判断信号是否连接](Demo/IsSignalConnected.py)
|
||||
- [调用虚拟键盘](Demo/CallVirtualKeyboard.py)
|
||||
- [动态忙碌光标](Demo/GifCursor.py)
|
||||
- [屏幕变动监听](Demo/ScreenNotify.py)
|
||||
|
||||
# QQ群
|
||||
|
||||
|
|
48
Test/ColumnView.py
Normal file
48
Test/ColumnView.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2020/9/14
|
||||
@author: Irony
|
||||
@site: https://pyqt.site https://github.com/PyQt5
|
||||
@email: 892768447@qq.com
|
||||
@file: ColumnView
|
||||
@description:
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QComboBox, QFileSystemModel, QHBoxLayout, QSpacerItem, QSizePolicy
|
||||
|
||||
__Author__ = 'Irony'
|
||||
__Copyright__ = 'Copyright (c) 2020'
|
||||
__Version__ = 'Version 1.0'
|
||||
|
||||
|
||||
class PathComboBox(QComboBox):
|
||||
|
||||
def __init__(self, *args, is_item=False, **kwargs):
|
||||
super(PathComboBox, self).__init__(*args, **kwargs)
|
||||
self.is_item = is_item
|
||||
if not self.is_item:
|
||||
self.setEditable(True)
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 23)
|
||||
layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
||||
else:
|
||||
self.f_model = QFileSystemModel(self)
|
||||
self.f_model.setRootPath('')
|
||||
self.setModel(self.f_model)
|
||||
|
||||
def addWidget(self, widget):
|
||||
self.layout().insertWidget(self.layout().count()-1, widget)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
w = PathComboBox()
|
||||
w.show()
|
||||
w.addWidget(PathComboBox(w, is_item=True))
|
||||
sys.exit(app.exec_())
|
Loading…
Reference in a new issue