Qt Quick QML (包括PySide,QtCreator)的各种坑

实在是无力吐槽。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都还在。。。

  1. Qt多线程QThread

Python Qt多线程QThread把一个实例的方法传入到另一个Qt线程的实例中,self指的实例不会改变

  1. Qt/QML qmlRegisterType和setContextProperty在注册外部类class时的区别

简单说就是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调用时 表现的像属性行为 而费这么大麻烦,直接调用方法就完事了。

  1. 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来说功能更完整,希望能有所好转。

  1. QML的UI界面显示不更新

不是指线程被阻塞,界面没反应。是指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可以立即更新界面!

对于外面来的名叫signalNamesignal,可以在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
  1. QML WebEngineView点击链接没有反应

QML这个情况出现在链接要开新页面/窗口的情况,我的解决方法是

    onNewViewRequested: function (request) {
        if (request.userInitiated) {
            //先判断是否用户点击,打开防止弹窗链接
            myWebEngineView.url = request.requestedUrl
        }
    }

看到有人用request.openIn(),这种方法缺点是navigationHistory会丢失。

onNewViewRequested: function (request) {
    request.openIn(myWebEngineView)
}

如果是Widget,最好重载acceptNavigationRequest。也可以重载createWindow,urlChanged之类的。

  1. 键盘事件event.key

键盘事件首先传递到焦点组件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 5QML事件处理–按键处理 - 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等多个键导航

待续…



  1. Call Python method from Qt Quick WorkerScript - StackOverflow.com ↩︎

  2. QML使用moveToThread线程 - 博客园 ↩︎

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值