前言

  上期教程我们介绍了 Qt Designer 开发的便利之处。
  利用 Qt Designer 可以快速开发出跨平台的界面,不仅仅适用于maya,也同样适用于适用 Qt 平台的各个软件。
  这期教程我们来聊聊 Qt 组件封装,实现自己个性化的组件。

什么是组件

  组件又叫 控件 , 英文名为 Component。
  我们可以将 组件 理解为各个零件,通过将组件组装到一起,就可以做出一个统一的 UI 界面。
  当然 Qt 提供给你的默认零件可能有些功能不足,那么你可以将多个零件组装在一起,形成一个新的零件,下次开发的时候就可以用新的零件来快速组装 UI 界面了。
  组件封装就是用现有的组件组合出我们想要的组件效果。
  
  组件式开发可以参考 前端 React 框架。
  模块化的思想对于后期的代码维护非常方便,出问题也可以快速锁定到出问题的组件进行Debug

QLabel 组件封装

  在 Qt 当中,要实现组件分装必须使用类来实现。
  这就是为什么第一期教程推荐使用类来实现效果的原因,用类继承Qt的组件进行扩展就是组件封装。
  下面我们来实现一个比第一期教程还要简单的类封装。
  另外再补上第一期教程关于 Qt.py 兼容的问题


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:UTF-8 -*-
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

class HelloWorldLabel(QLabel):
def __init__(self):
super(HelloWorldLabel,self).__init__()
self.setText("hello,world")


win = QWidget()
layout = QVBoxLayout(win)
label = HelloWorldLabel()
layout.addWidget(label)

win.show()

  上面代码就实现了最为简单的封装效果,我们可以创建出一个名为 hello,world 的内容标题
  self.setText 函数来自 QLabel.setText 可以在 API文档 查到
  QLabel.setText 可以修改 QLabel 显示的内容。

代码执行效果

  不过上述的代码只能在 Maya 2016 及以上的版本才能执行,如果用 Maya2015 及更旧的版本会出错。
  报错提示是找不到 PySide2 的包
  这是因为 Maya2015 的时候还没有升级 PySide 包,因此要在老版本上使用 PySide 库需要将导入的代码修改为如下面所示
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

1
2
from PySide.QtCore import *
from PySide.QtGui import *

  当然每一次写代码都需要注意maya的版本号是很繁琐的,而且对于那些以前写的代码,维护起来就更加麻烦。
  于是 Qt.py 就针对这种问题而诞生了。
  可以将 Qt.py 放到 我的文档/maya/scripts 目录下,就可以直接导入 Qt 了。

扩展说明

  • 想要了解更多 Qt.py 的说明 参考这里
  • 为什么代码放到 我的文档/maya/scripts 文件夹下可以被 maya 调用 参考这里

给 QLabel 组件添加右键菜单

  完成了最简单的 hello,world 封装之后,我们可以实现稍微复杂一点的效果了。
  下面我们来给 Label 添加右键菜单来清空 Label 当中的内容。
  另外这个功能也是完全可以用 MEL 来实现的。

  利用 MEL 实现右键菜单删除文字功能。

1
2
3
4
5
6
7
//MEL text 添加右键菜单
window -t "利用 MEL 实现右键菜单删除文字功能";
columnLayout;
string $label = `text -l "hello,world"`;
popupMenu;
menuItem -l "清空内容" -c ("text -e -l \"\" " + $label);
showWindow;

  利用 python cmds 实现右键菜单删除文字功能。

1
2
3
4
5
6
7
8
import maya.cmds as cmds

cmds.window(t="利用 python cmds 实现右键菜单删除文字功能")
cmds.columnLayout()
label = cmds.text(l='hello,world')
cmds.popupMenu()
cmds.menuItem(l=u"清空内容",c="cmds.text('%s',e=1,l='')"%label)
cmds.showWindow()

  利用 PySide2 实现右键菜单删除文字功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding:UTF-8 -*-
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

class HelloWorldLabel(QLabel):
def __init__(self):
super(HelloWorldLabel,self).__init__()
self.setText("hello,world")

# 生成 Menu 菜单
self.menu = QMenu(self)
# 给菜单添加一个 QAction
action = QAction(self)
# QAction 的内容
action.setText(u"清空内容")
# 直接触发 QLabel 的清空函数 达到清空文字的目的
# triggered 为 QAction 的信号槽,点击触发 connect 中的函数
action.triggered.connect(self.clear) # QAction使用 triggered 信号触发,和按钮触发的 clicked 不同
self.menu.addAction(action)

def contextMenuEvent(self, event):
'''
按右键触发函数 函数名为QT的固定调用名称 - 可以在QWidget的API下找到该虚函数说明
event 为函数的固定传入参数 类型为 QContextMenuEvent
'''
# 在鼠标光标位置触发 右键菜单
self.menu.exec_(event.globalPos())

win = QWidget()
win.setWindowTitle(u"利用 PySide2 实现右键菜单删除文字功能")
layout = QVBoxLayout(win)
label = HelloWorldLabel()
layout.addWidget(label)

win.show()

代码执行效果

  下面的讲解会大量引用PySide文档。如果不懂得如何使用可以参照下面的扩展说明

信号槽

  信号槽是 Qt 框架的一大创举,网上也有很多剖析它的文章,都写得很详细。
  这里我说说我自己目前的理解: action.triggered.connect(self.clear)
  triggered是信号槽的触发条件,API文档中还提供了其他的信号槽作为触发条件。
  triggered的触发条件是点击,connect后面接的是触发的函数。
  也就是说当 triggered 信号槽被触发就会执行 connect 之后的函数。
  同一个信号槽也可以接多个 connect 函数,函数会被逐一执行。
  同样信号槽也有 disconnect 函数可以注销无用的函数触发。

虚函数

  在 Qt 中右键触发已经在 QWidget 中提供了相关的入口虚函数
  这些虚函数继承于 QWidget ,满足特定条件就会传入相关事件参数触发函数。
  如上面代码所示 contextMenuEvent 事件在点击右键的时候就会触发,通过传入的 event 可以获取到触发的世界坐标。
  从而实现将菜单生成到鼠标的位置上。

小结

  看到这里,似乎又会发现, Qt 框架又是写了一大堆的代码,然而才实现了 MEL 或者 cmds 简单几行 代码就可以实现的效果。
  而且 PySide 文档还如此复杂,没有人说明根本就不知道从何查起。
  这种情况就要学会使用搜索引擎来解决问题。

扩展说明

给 QLabel 组件添加单击事件

  下面就开始来实现一些 MEL 做不到的功能了。
  在MEL的 text API文档中并没有点击事件的触发函数,因此要实现这种效果只能通过 Qt 来实现。
  在类中要实现点击事件有两种方法:

  • 使用 Virtual functions 虚函数
  • 使用 Signal Slot 信号槽

  虚函数只可以在类中使用,而信号槽实例化后也可以使用。
  其实上面的右键触发菜单和 QAction 点击就刚好刚好用到了这两种不同的方法。
  下面我们来实现点击 QLabel 弹出输入框来修改内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding:UTF-8 -*-
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

class HelloWorldLabel(QLabel):
def __init__(self):
super(HelloWorldLabel,self).__init__()
self.setText("hello,world")

# 生成 Menu 菜单
self.menu = QMenu(self)
# 给菜单添加一个 QAction
action = QAction(self)
# QAction 的内容
action.setText(u"清空内容")
# 直接触发 QLabel 的清空函数 达到清空文字的目的
# triggered 为 QAction 的信号槽,点击触发 connect 中的函数
action.triggered.connect(self.clear) # QAction使用 triggered 信号触发,和按钮触发的 clicked 不同
self.menu.addAction(action)

def contextMenuEvent(self, event):
'''
按右键触发函数 函数名为QT的固定调用名称 - 可以在QWidget的API下找到该虚函数说明
event 为函数的固定传入参数 类型为 QContextMenuEvent
'''
# 在鼠标光标位置触发 右键菜单
self.menu.exec_(event.globalPos())

def mousePressEvent(self,event):
# 输入 Dialog
text, ok = QInputDialog.getText(self, u'修改', u'输入内容')

if ok:
self.setText(text)

win = QWidget()
win.setWindowTitle(u"利用 PySide2 实现点击弹出输入弹窗修改组件内容")
layout = QVBoxLayout(win)
label = HelloWorldLabel()
layout.addWidget(label)

win.show()

代码执行效果

  由于 QLabel 并没有 clicked 相关的触发信号,因此只能使用虚函数来实现点击效果。
  这里使用了Qt封装好的 QInputDialog ,可以实现弹窗输入的效果。(MEL中也有 promptDialog 函数)


总结

  这期教程我们介绍了 Qt 封装组件的过程,其实并没有那么神秘。
  代码封装的本质就是用面向对象的方法将代码合成到一起,下一次调用就可以初始化很多已经写好的东西,不需要再次书写了。
  本期内容不算很多,不过扩展的内容量很大,尽量做到让所有的新手都能够看得懂的程度。
  另外本期教程虽然实现了点击效果,但是实现的效果感觉很一般,Qt难道就没有更牛逼的用法吗?
  那么敬请期待第四期教程吧。