点击 开始扫描 弹出 选择路径窗口;
勾选路基;
3. 点击确定;
大概想测一下这个界面 :
步骤分别是 :
点击 开始扫描 弹出 选择路径窗口;
勾选路基;
3. 点击确定;
需要测试的函数 :
测试函数 :
可以发现断言失败 .
官方文档:测试模态窗体.
https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html
用的是官方的 monkeypatch
方式 .
大致意思就是替换 FileSelectPathDialog
类的 exec 函数.
1. 安装 Python
python-3.9.13-macosx10.9.pkg
2. 勾选自定义同时只勾选安装 pip
3. 设置 pip 源
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
4. 安装 PyQt5
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
5. 测试
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3
📢📢📢
也可以直接安装 Miniconda
然后:conda install -c conda-forge pyqt
]]>判断屏幕等宽字符串的长度?
【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到
【新手】重庆 - 搬砖 - NoWait 22:52:00
https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的
【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
fm = QFontMetrics(QFont())
fm.width(“qweqwe”)
修改 pyuic 代替 pyside2-uic.
最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
写个解决办法.
首先,
pip install qtpy
,
这个是兼容 pyqt5 和 pyside2 的,无缝转换 .
然后,
修改 pyqt5 的 uic ,
最后用 pyuic5 , 生成 Ui_XXX.py 文件 .
]]>在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。
必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。
Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等
Widget
的窗口,比如Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)
首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)
在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子
建议在更深入的学习 PyQt 之前多看看一些例子。
接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。
这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置
QWebEngineHistory * history() constQWebEnginePage * page() constQWebEngineSettings * settings() const
这三个函数返回了一个类实例,就意味着可以调用其中的方法。
点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *profile () const 这个函数比较可疑。
点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie
发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *cookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存
点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。
但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。
self.webview = QWebEngineView()# 得到pagepage = self.webview.page()# 得到profileprofile = page.profile()# 得到cookieStorecookieStore = profile.cookieStore()# 清空cookiecookieStore.deleteAllCookies()# 用简短代码来表达就是cookieStore = self.webview.page().profile().cookieStore()cookieStore.deleteAllCookies()
可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print
打印出变量值)
遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。
作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。
好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。
]]>GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .
python 的状态机模块挺多的,不过好像很多都不更新了.
推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .
1: pip install python-statemachine
官方例子 : https://github.com/fgmacedo/python-statemachine
2. pip install state_machine
官方例子 : https://github.com/jtushman/state_machine
1 的 最近一次更新在 6 个月以前,使用 类继承
和 mixin
方式,不过有些地方不如 2 个人性化;
2 的设计更人性化一些,包括状态改变 before
和 after
, 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.
两者实现实现方式不一样,有兴趣可以读读源码 .
https://blog.csdn.net/amnes1a/article/details/62418196
https://blog.csdn.net/dongfenghuojian/article/details/78187131
http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)
]]>pyHook3
安装命令 : pip install pyhook3
https://blog.csdn.net/q871063970/article/details/86648386
似乎将 pyhook 支持 py3 版本的了?没有太多研究.
缺点:只支持 win 平台.
2. keyboard
& mouse
安装命令: pip install keyboard mouse
from PyQt5 import QtGui, QtWidgets, QtCorefrom PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *import keyboardclass Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) layout = QVBoxLayout(self) self.testBtn = QPushButton(self) layout.addWidget(self.testBtn) keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey')) keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc")) def abc(self,a,b,c): print(a,b,c) if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec_())
更详细例子 : pyqt 中使用 keyboard 全局热键
优点:跨平台;
缺点:模块名字取得太差,不容易被发现.
]]>不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
没有目的地概览 , 不知不觉就追究到细节里面去了。
所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。
开始 :
eric6.py
的 main()
函数上加 snoop 装饰器;eric6start_.log
文件 (8 层深度 log 文件 34W 行,pycharm 对大文件支持很差);发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。
将 call
和 return
给加进去.
加 #000
是为了方便搜索 。
需要自己手动折叠 。
可以发现 每个 splash.showMessage()
都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。
问题所在:
如果模块都由自己开发, 正常操作
但是因为不能改,所以只能拦截:
代码:
pythonPath = self.pythonPath_cb.currentText()if suffix == "py": # 首次 self.pyCommand = [pythonPath, path] self.modifiedReloadPython(path)def modifiedReloadPython(self, path_): os.chdir(os.path.dirname(path_)) # 子进程调用 self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # self.stdoutWorker.p = self.p self.stdoutWorker = Worker(self.p) self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append("PYDEBUG:\n" + x)) self.stdoutWorker.start()class Worker(QThread): stdout_signal = pyqtSignal(str) def __init__(self, p, parent=None): super().__init__(parent) self.p = p def run(self): while True: QApplication.processEvents() if self.p is not None: line = self.p.stdout.readline() # line = line.strip() if line != b'': try: info = line.decode() self.stdout_signal.emit(info) except: self.stdout_signal.emit(repr(line))
]]>QWebView
的时候和 Javascript
交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView
,并通过其提供的 qwebchannel.js
来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebView
和 QWebEngineView
与 Js 交互都做了一个示例。QWebView
通过 QWebFrame
的 addToJavaScriptWindowObject
把对象传递到 Javascript
中QWebEngineView
通过 QWebChannel.registerObject('Bridge', QObject)
把对象传递到 Javascript
中@pyqtSlot
装饰器来申明该方法可以暴露给 Javascript
调用@pyqtSlot(str)def callFromJs(self, text): QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
QWebView
在 Javascript
中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)Bridge.windowTitleChanged.connect({fun: function(title) { showLog("标题被修改为:" + title);}}, "fun");// 绑定自定义的信号customSignalBridge.customSignal.connect({fun: function(text) { showLog("收到自定义信号内容:" + text);}}, "fun");
QWebEngineView
在 Javascript
中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用new QWebChannel(qt.webChannelTransport, function(channel) { window.Bridge = channel.objects.Bridge; // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的) Bridge.windowTitleChanged.connect(function(title) { showLog("标题被修改为:" + title); }); // 绑定自定义的信号customSignal Bridge.customSignal.connect(function(text) { showLog("收到自定义信号内容:" + text); }); });
QWebView
: https://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py
QWebEngineView
: https://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py
QWebView
的核心实现class WebView(QWebView): customSignal = pyqtSignal(str) def __init__(self, *args, **kwargs): super(WebView, self).__init__(*args, **kwargs) self.initSettings() # 暴露接口对象 self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface) def _exposeInterface(self): """向Js暴露调用本地方法接口 """ self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self) # 注意pyqtSlot用于把该函数暴露给js可以调用 @pyqtSlot(str) def callFromJs(self, text): QMessageBox.information(self, "提示", "来自js调用:{}".format(text)) def sendCustomSignal(self): # 发送自定义信号 self.customSignal.emit('当前时间: ' + str(time()))
QWebEngineView
的核心实现class WebEngineView(QWebEngineView): customSignal = pyqtSignal(str) def __init__(self, *args, **kwargs): super(WebEngineView, self).__init__(*args, **kwargs) self.channel = QWebChannel(self) # 把自身对象传递进去 self.channel.registerObject('Bridge', self) # 设置交互接口 self.page().setWebChannel(self.channel) # 注意pyqtSlot用于把该函数暴露给js可以调用 @pyqtSlot(str) def callFromJs(self, text): QMessageBox.information(self, "提示", "来自js调用:{}".format(text)) def sendCustomSignal(self): # 发送自定义信号 self.customSignal.emit('当前时间: ' + str(time()))
QStackedWidget
包含两个控件做切换,同时单独使用一个窗口做动画绘制。QLabel
来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击QStackedWidget
来存放上面的两个界面 QLabel
setWindowOpacity
控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
FlipWidget.py
主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画paintEvent
方法中使用 QTransform
对 QPainter
进行圆心变换以及 rotate
设置翻转角度def paintEvent(self, event): super(FlipWidget, self).paintEvent(event) if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible(): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) # 变换 transform = QTransform() # 把圆心设置为矩形中心 transform.translate(self.width() / 2, self.height() / 2) if self._angle >= -90 and self._angle <= 90: # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程 painter.save() # 设置翻转角度 transform.rotate(self._angle, Qt.YAxis) painter.setTransform(transform) # 缩放图片高度 width = self.image1.width() / 2 height = int(self.image1.height() * (1 - abs(self._angle / self.Scale) / 100)) image = self.image1.scaled( self.image1.width(), height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) painter.drawPixmap( QPointF(-width, -height / 2), image) painter.restore() else: # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程 painter.save() if self._angle > 0: angle = 180 + self._angle else: angle = self._angle - 180 # 设置翻转角度, 注意这里角度有差异 transform.rotate(angle, Qt.YAxis) painter.setTransform(transform) # 缩放图片高度 width = self.image2.width() / 2 height = int(self.image2.height() * (1 - ((360 - abs(angle)) / self.Scale / 100))) image = self.image2.scaled( self.image2.width(), height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) painter.drawPixmap( QPointF(-width, -height / 2), image) painter.restore()
https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py
QPropertyAnimation
继承自 QVariantAnimation
,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject
的对象中定义的属性做修改,QObject
且定义了属性变量,就可以用 QPropertyAnimation
来做属性动画。同时也可以通过 pyqtProperty
来增加自定义属性。首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None)
创建一个对象,其中
setTargetObject
设置setPropertyName
设置一些常见的设置函数
setPropertyName | 设置属性名 |
setTargetObject | 设置动画作用对象 |
setDuration | 设置动画持续时间(毫秒) |
setStartValue | 设置开始值 |
setEndValue | 设置结束值 |
setEasingCurve | 设置动画曲线 |
setKeyValueAt | 插入线性值 |
setLoopCount | 设置循环次数(-1 为永久) |
比如这个例子:
geometry
大小#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年5月8日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: @description: """from PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurvefrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\ QLabel, QProgressBar, QSpacerItem, QSizePolicy__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.resize(400, 400) self._value = 0 self.button = QPushButton('属性动画测试', self) self.button.clicked.connect(self.doStart) self.button.setGeometry(0, 0, 80, 40) self.buttonc = QPushButton('自定义属性 测试', self) self.buttonc.clicked.connect(self.doStartCustom) self.label = QLabel('', self) self.progressbar = QProgressBar(self) self.progressbar.setRange(0, 99) layout = QVBoxLayout(self) layout.addItem(QSpacerItem( 20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed)) layout.addWidget(self.buttonc) layout.addWidget(self.label) layout.addWidget(self.progressbar) # 进度条动画 self.progressStart() # 此处是自定义属性,并通过动画修改后,设置QLabel的值 @pyqtProperty(int) def value(self): return self._value @value.setter def value(self, v): self._value = v self.label.setText('当前值:{}'.format(v)) def doStart(self): # 第一个参数是要执行的对象 animation = QPropertyAnimation(self.button, b'geometry', self) animation.setDuration(2000) # 持续时间 # 缓和曲线风格,加了曲线动画会很大程度影响 animation.setEasingCurve(QEasingCurve.OutBounce) animation.setStartValue(QRect(0, 0, 40, 40)) animation.setEndValue(QRect(250, 250, 80, 80)) animation.start(animation.DeleteWhenStopped) def doStartCustom(self): # 自定义属性动画 # 由于定义的属性是在继承的QWidget, 所以第一个参数是self # 第二个参数就是 value animation = QPropertyAnimation(self, b'value', self) animation.setDuration(2000) # 持续时间 animation.setStartValue(0) animation.setEndValue(100) animation.start(animation.DeleteWhenStopped) def progressStart(self): # 进度条动画 # 这里 value是QProgressBar自带的属性,具体可以看文档 # https://doc.qt.io/qt-5/qprogressbar.html#properties animation = QPropertyAnimation(self.progressbar, b'value', self) animation.setDuration(2000) # 持续时间 animation.setLoopCount(-1) # 这里采用插入线性值,第一个参数的范围是(0-1) # 第二个参数的范围是进度(最小值-最大值) animation.setKeyValueAt(0, self.progressbar.minimum()) animation.setKeyValueAt(0.1, 10) animation.setKeyValueAt(0.2, 30) animation.setKeyValueAt(0.5, 60) animation.setKeyValueAt(0.7, 80) animation.setKeyValueAt(1, self.progressbar.maximum()) animation.start(animation.DeleteWhenStopped)if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec_())
]]>作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。
拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。
比如拿 输入框 QLineEdit
来说,怎么去查询它的用法和信号槽等资料?
https://doc.qt.io/qt-5/qlineedit.html
在文档左侧目录中有如下几个:
Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)
Public Slots - 这个是控件自己的槽函数(当作普通函数就行)
Signals - 这个是输入框的包含的信号
Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表
这里有两个注意点
QWidget
,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。
所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。
这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。
这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:
这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中
当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed
如图上所示,用翻译插件翻译,大部分就明白了,如下:
比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名
其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/
编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用
由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。
其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O
也可以在标红的代码上按下 Ctrl + F1 进行导入
打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本
这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__':
等等,比如我这里配置的创建文件的模版
格式化对齐 | Ctrl + Shift + F |
自动导包 | Ctrl + Shift + O |
快捷提示 | Alt + / |
这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectName
和 QtCore.QMetaObject.connectSlotsByName(Form)
提供的连接函数来自动完成注册,
比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:
self.pushButton = QtWidgets.QPushButton(Form)self.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))self.pushButton.setObjectName("pushButton")# 通过这里自动完成连接信号槽QtCore.QMetaObject.connectSlotsByName(Form)
此时只需要继承该 UI 文件类然后增加如下方法:
@pyqtSlot()def on_pushButton_clicked(self): print('button clicked')
这里解释一下, @pyqtSlot()
装饰器把函数 on_pushButton_clicked
包装为一个槽函数,
而 QtCore.QMetaObject.connectSlotsByName(Form)
这句代码的意思就是自动去寻找满足的槽函数
注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号
这样下划线连接起来的函数名才能被识别,
比如按钮的点击: on_pushButton_clicked
、勾选框的选中: on_checkbox_toggled(self, checked)
这种方式则直接通过代码里调用控件的信号的 connect
方法来进行绑定,比如:
# 按钮点击函数def doClicked(self): print(self.sender(), 'clicked')# 绑定点击信号self.pushButton.clicked.connect(self.doClicked)
注意: connect
的是函数名字, self.sender()
这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
用处在于有时候要循环创建一堆按钮
通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。
同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:
# 按钮点击函数def doClicked(self): print(self.sender(), 'clicked')pushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
这里可以通过参数(信号名字) = 函数来绑定信号
同时也可以设置其它参数,比如
button.setMinimumHeight(40)
也可以像参数里那样设置 minimumHeight=40
python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv
, 此外 在 virtualenv
基础上又开发了 virtualenvwrapper(virtualenvwrapper_win)
来管理
本文基于 virtualenvwrapper
创建的虚拟环境来讲解.
以下是收集的一些virtualenvwrapper配置教程:# linux平台https://www.cnblogs.com/netfoxman/p/5994697.html# window平台https://blog.csdn.net/shaququ/article/details/54292043 https://blog.csdn.net/iaau0908/article/details/54021518
虚拟环境创建多了我们就会发现,
有时候使用相同版本的环境,一些常用的库是需要重新安装的,
那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?
本文经过试验发现是可行的:
创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径]
. 例如 mkvirtualenv py34 -p c:\Python34\python.exe
切换到虚拟环境 workon py34
, 然后安装一下三方库,然后复制 py34
这个文件夹备份一下;
接着复制这个 py34
文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34
进入 new34文件夹
,用任意编辑器全路径搜索 py34
(替换虚拟环境的路径)
删除 new34/Scripts
下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe
(因为安装路径硬编码到这里面了,改不了,需要重新安装)
https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools
后再用 easy_install pip
安装 pip 后,完成;
如果有问题,就继续按照方式一的源码安装 pip;
在 new34
环境下 用 pip show 三方库
来看一些库的位置,确保正确.
QObject
的类才能有信号和自定义信号,而 QRunnable
并不是继承自 QObject
,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。pools 是 QThreadPool
实例
当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。
后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。
再来后发现了官方文档,查一查函数就行了 .
但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.
可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .
好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .
官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .
看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .
所以接下来,我们找找电脑上有没有这个东西
果然在 pyqt5-toos 文件夹下有这个东西,
我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),
好了,大功告成!
]]>windows
某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows
在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows
的外观和性能 -> 去掉勾选 拖动时显示窗口内容。由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。
wndproc
处理 WM_NCLBUTTONDOWN
消息事件。今天讲第二种方法:
SystemParametersInfo
API 函数SPI_GETDRAGFULLWINDOWS
:确定是否允许拖拉到最大窗口SPI_SETDRAGFULLWINDOWS
:设置是否允许拖至最大窗口效果就是这样的:
正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。
https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py
#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年4月23日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: ShowFrameWhenDrag@description: 调整窗口显示边框"""from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\ c_ulonglong, WINFUNCTYPE, c_uintfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0if sizeof(c_long) == sizeof(c_void_p): WPARAM = c_ulong LPARAM = c_longelif sizeof(c_longlong) == sizeof(c_void_p): WPARAM = c_ulonglong LPARAM = c_longlongWM_NCLBUTTONDOWN = 0x00a1GWL_WNDPROC = -4SPI_GETDRAGFULLWINDOWS = 38SPI_SETDRAGFULLWINDOWS = 37WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)try: CallWindowProc = windll.user32.CallWindowProcW SetWindowLong = windll.user32.SetWindowLongW SystemParametersInfo = windll.user32.SystemParametersInfoWexcept: CallWindowProc = windll.user32.CallWindowProcA SetWindowLong = windll.user32.SetWindowLongA SystemParametersInfo = windll.user32.SystemParametersInfoAdef GetDragFullwindows(): rv = c_int() SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0) return rv.valuedef SetDragFullwindows(value): SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) layout = QVBoxLayout(self) layout.addWidget(QLabel('拖动或者调整窗口试试看')) # 重点替换窗口处理过程 self._newwndproc = WNDPROC(self._wndproc) self._oldwndproc = SetWindowLong( int(self.winId()), GWL_WNDPROC, self._newwndproc) def _wndproc(self, hwnd, msg, wparam, lparam): if msg == WM_NCLBUTTONDOWN: # 获取系统本身是否已经开启 isDragFullWindow = GetDragFullwindows() if isDragFullWindow != 0: # 开启虚线框 SetDragFullwindows(0) # 系统本身处理 ret = CallWindowProc( self._oldwndproc, hwnd, msg, wparam, lparam) # 关闭虚线框 SetDragFullwindows(1) return ret return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec_())
替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。
]]>PyQt
中某些情况下需要取消原来的信号连接,此时需要使用 disconnect
方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect
方法而导致报错,当然可以通过 try except 来包裹代码。这里通过 isSignalConnected
来判断信号是否连接。在 QOjbect 文档中这样写到:
static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);if (isSignalConnected(valueChangedSignal)) { QByteArray data; data = get_the_value(); // expensive operation emit valueChanged(data);}
通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做
#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年2月24日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: IsSignalConnected@description: 判断信号是否连接"""from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) layout = QVBoxLayout(self) self.button1 = QPushButton('已连接', self, clicked=self.doTest) self.button2 = QPushButton('未连接', self) self.retView = QTextBrowser(self) layout.addWidget(self.button1) layout.addWidget(self.button2) layout.addWidget(self.retView) def doTest(self): self.retView.append(""" # button1 clicked 是否连接: %s # button2 clicked 是否连接: %s """ % ( self.isSignalConnected(self.button1, 'clicked()'), self.isSignalConnected(self.button2, 'clicked()') )) def isSignalConnected(self, obj, name): """判断信号是否连接 :param obj: 对象 :param name: 信号名,如 clicked() """ index = obj.metaObject().indexOfMethod(name) if index > -1: method = obj.metaObject().method(index) if method: return obj.isSignalConnected(method) return Falseif __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec_())