目录
实在是无力吐槽。Qt6,PySide6还不成熟也就算了,QtQuick,QtQuick.Controls 2重写了1然后改名去掉了2也就算了,QtCreator这么多年了还有各种不支持的 语句和Item,然后可视化图形界面设计QtQuickDesigner就直接白板了,现在直接用代码写界面反而更快。但是QtCreator的IDE也真烂,QML写起来就和用记事本差不多,各种假警报,错误提示也没什么参考价值。
QtWebEngine在Qt6.1中没有,要等到6.2才有也就算了,Qt5的QtWebEngine也不被QtCreator支持?MinGW不支持QtWebEngine的编译因为Google不喜欢?这都什么鬼,还不在官方文档里说。官方文档都什么鬼,写的也太模糊了。我的PySide2 5.15.2的WebEngineView各种bug,比如接收到后台的信号设置WebEngineView属性导致程序崩溃;接收不到键盘事件/或者是接收到不响应;等等。QML SplitView在QtQuick.Controls的1,和2的逻辑不一样?一样的代码和Controls并不怎么相关,在1里就正常,在2里就不正常,官方也从不解释改了啥,内部怎么个运行逻辑,但是文档是一样的,感觉是信号发出的时机变了。这不是逼人看源码呢,不知道文档是用来干啥的。结果自己重写了一个,反而需要的代码更少呢。各种功能还不全,比如QML TableView的Header type到现在也没有,TableViewColumn,rowDelegate什么的都不能用,虽说可以用Controls 1,但是1要被官方抛弃了,真是青黄不接。官方命名也是奇葩,Controls1,2也就算了,后来Controls 2又被重命名为Controls,2被删了,虽然可以理解,但也太混乱了,导致网上讨论问题和解答,一定要注意版本。QML ListView默认不处理鼠标事件,但是处理键盘事件。。。
各种奇葩问题,官方从来也不说,按说Qt用的人不少啊,但是讨论的人不算多,大概大多是大公司在用,遇到问题直接自己改源码,重新编译一个自己版本的Qt。尤其是Python版本的问题,用的人更少,大多是用原生C++。
不吐槽了,1天1夜都说不完。说正题,挑有价值还比较稳定的问题说说,随时间改变的就不说了。
更新一句:Qt/PySide 6.2也就增加了一些之前缺失的组件,bug都还在。。。
Python Qt多线程QThread把一个实例的方法传入到另一个Qt线程的实例中,self指的实例不会改变
简单说就是qmlRegisterType可以注册类class,在QML中调用实例化;而setContextProperty注册类或实例,QML不负责实例化,需要注册前就要实例化。参考Qt/QML qmlRegisterType vs. setContextProperty (difference) - stackoverflow.com。然后可以直接在QML中调用,或者在Connection{}
的target
中调用。
值得一提的是,实例instance属性property是无法在QML中直接读写的,需要通过建立读写属性的方法,通过方法return属性值。然后用Q_PROPERTY()
设置为可被QML调用的属性,并设置了刚建立的相应的读写方法。可以在写属性的方法里emit ‘propertyChanged’ 信号signal。在QML中把这个属性值设置为QML某属性值时,直接用这个instance.property,在QML需要读写时自动调用相应的方法。例如
// MyItem.qml
import QtQuick 2.0
Text {
width: 100; height: 100
text: msg.author // invokes Message::author() to get this value
Component.onCompleted: {
msg.author = "Jonah" // invokes Message::setAuthor()
}
}
详情见:Exposing Attributes of C++ Types to QML | Qt QML 5.15.5。Python版大概类似,没有试,因为没必要为了让这个实例属性被QML调用时 表现的像属性行为 而费这么大麻烦,直接调用方法就完事了。
QML本身支持的多线程只能运行js文件。传统的多线程在调用时和QML的通信要用signal/slot信号/槽,不能在子线程直接操作主线程(UI线程。UI只能通过主线程访问)。
PySide2不支持QtConcurrent.run,还是考虑moveToThread方法。(用QRunnable,QThreadPool,QMetaObject.invokeMethod()也可以,但是略显麻烦 1。)看到有人说 2 moveToThread方法,把worker和QML之间用中间类来建立QThread用于它们之间通信,但其实并没有必要建立中间类,因为可以注册worker所在的类实例到QML。而QThread在哪个类实例中建立都没有关系,因为只有目标函数/方法会在新的thread,其他都还在原线程工作。
唯一要注意的是QML存在的一些bug,比如说PySide2中的WebEngineView,在使用多线程时,如果在QML中把worker的signal用Connections调用WebEngineView,会导致程序崩溃,QtCreator的debug也只是摆设,只好用其他Component接收signal,然后再用onXXXChange来调用WebEngineView,相当于多个中间component。
这一切都要等PySide6.2来改变。。。看起来6.2相比2来说功能更完整,希望能有所好转。
不是指线程被阻塞,界面没反应。是指Component内容更新了,但是没有反映到界面上,也就是界面没有update/repaint/redraw。这是一个长久的bug了,但看起来没怎么修好。网上有人提出的解决办法是调用update/repaint/redraw方法,我试着在QML下没效果;还有很多人说用timer定时还是用update/repaint/redraw刷新或者改变一些属性。我试着能通过改变属性使界面刷新的只有改变窗口/Component大小之类的,其他属性比如颜色什么的没有用。这些都不好。
我的解决方法 是用onXXXChanged
。这个XXX
可以是自己定义的属性,比如:
property string name: 'a'
onNameChanged: {
// Do something...
}
通过onXXXChanged
来设置/调用QML可以立即更新界面!
对于外面来的名叫signalName
的signal
,可以在Connections{}
里给XXX
属性赋值,比如
Rectange {
id: rect
property string name: ''
Connections {
target: backend_instance
function onSignalName(signal_string) {
rect.name = signal_string
} //function
} //Connections
onNameChanged: {
// Do something...
} //onNameChanged
} //Rectangle
QML这个情况出现在链接要开新页面/窗口的情况,我的解决方法是
onNewViewRequested: function (request) {
if (request.userInitiated) {
//先判断是否用户点击,打开防止弹窗链接
myWebEngineView.url = request.requestedUrl
}
}
看到有人用request.openIn(),这种方法缺点是navigationHistory会丢失。
onNewViewRequested: function (request) {
request.openIn(myWebEngineView)
}
如果是Widget,最好重载acceptNavigationRequest
。也可以重载createWindow,urlChanged之类的。
键盘事件首先传递到焦点组件component上。如果在程序启动时,需要某组件默认获取焦点,可以在组件里设置focus: true
。如果设置了多个focus,那么最后一个执行的会获取焦点,但执行顺序一般是undefined,所以一般只设置一个。
在QML的Component作为单独的qml可导入文件/可复用item时,键盘事件用focus: true
是不起作用的。这时事件会向上传递,一直没有被接收的话,会一直传递到rootObject,一般是主窗口?传递到rooObject的时候,键盘事件会被忽略,直接当成普通按键处理regular Qt Key handling,什么鬼逻辑。如果需要设置的是全局键盘事件,我推荐直接设置快捷键。
Window{
id: window
Shortcut {
sequence: "Esc"
onActivated: window.close()
}
}
如果一定要设置键盘事件,可以用一个Item
把window里所有component都包起来,在这个Item里设置focus和键盘事件。
如果要给某个component设置键盘事件,可以考虑用一个Item
把这个component包起来,然后把focus和键盘事件设置到这个Item上;或者官方做法是在这个component外面加一层FocusScope
使内部component可以获取焦点,里面的component需要用property alias var name: value
设置成属性才能暴露给外层。如果最开始没有这样设计,后期再改就要修改涉及到的id引用。可以在实例化component的地方加一层FocusScope
,不需要修改component的qml文件了。
可以参考官网例子Keyboard Focus in Qt Quick | Qt 5 和 QML事件处理–按键处理 - CSDN.net。
我的PySide2 5.15.2的WebEngineView接收不到键盘事件/或者是接收到不响应。其他组件工作正常。
但是!!说了这么多,都是开头说的 “程序启动时,默认获取焦点的组件”!也就是说费了半天事,只是为了启动时那一下!!!其实根本不需要这么麻烦!把上面说的什么FocusScope
都忘了吧(这种基础component还是不要直接用了)!下面说更好的办法!
只要是获取了焦点的组件就会收到键盘事件,不在乎是内层的还是外层的,是否导入的/重用的!而获取焦点很容易,基本就是点击谁 谁就会获取焦点,然后直接接收键盘事件。组件内设置focus=true
只是为了启动那一下默认获取焦点。如果 导入/重用组件 要实现启动默认获取焦点,完全可以附着Component.onCompleted到任意组件:
Component.onCompleted: {
myItem.focus = true
}
或者用任何signal/slot,或者方法等等,都可以设置focus
在何处。
纯用键盘更改焦点用:
KeyNavigation.tab: myItem
//KeyNavigation.down: myItem //还支持上下左右,Backspace等多个键导航
待续…