关于参数的界面化
开始
最开始,可能有不少人写的python源码都是跟我一样,将所有的变量都写死在源码里面(比如我的上一篇博客:用 matplotlib 绘制会动的雪花 就是这样的。),每次需要修改都是直接改源码的。但这样总感觉哪里不对的说。
接着,我知道了启动带参数的用法(python a.py 参数),发现了argparse,这个会自动处理命令行参数,并格式化,而且带帮助提示,终于可以在源码里面大胆用参数了。
终于有一天,我脚本里写的处理argparse部分超过了100行!虽然我这个是因为pep8的原因,将代码进行处理之后的结果。如果不用pep8处理,也有20行了。
我当时很惊讶,然后我关上了那个源码的编辑界面。嗯,刚刚啥都没发生,嗯。
终于,在这几天,我发现了一个利器,终于可以再次正视那个问题。
源码
#!/usr/bin/python
# vim: set fileencoding=utf8 :
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
app = QtGui.QApplication([])
import pyqtgraph.parametertree.parameterTypes as pTypes
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
# 这一组变量会同时变化, a和b的值互为倒数
class ComplexParameter(pTypes.GroupParameter):
def __init__(self, **opts):
opts['type'] = 'bool'
opts['value'] = True
pTypes.GroupParameter.__init__(self, **opts)
self.addChild({
'name': 'A = 1/B',
'type': 'float',
'value': 7,
'suffix': 'Hz',
'siPrefix': True
})
self.addChild({
'name': 'B = 1/A',
'type': 'float',
'value': 1 / 7.,
'suffix': 's',
'siPrefix': True
})
self.a = self.param('A = 1/B')
self.b = self.param('B = 1/A')
self.a.sigValueChanged.connect(self.aChanged)
self.b.sigValueChanged.connect(self.bChanged)
def aChanged(self):
self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged)
def bChanged(self):
self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged)
# 这个用来实时添加项目
class ScalableGroup(pTypes.GroupParameter):
def __init__(self, **opts):
opts['type'] = 'group'
opts['addText'] = "Add"
opts['addList'] = ['str', 'float', 'int']
pTypes.GroupParameter.__init__(self, **opts)
def addNew(self, typ):
val = {'str': '', 'float': 0.0, 'int': 0}[typ]
self.addChild(
dict(
name="ScalableParam %d" % (len(self.childs) + 1),
type=typ,
value=val,
removable=True,
renamable=True))
params = [
{
'name':
'Basic parameter data types',
'type':
'group',
'children': [
{
'name': 'Integer',
'type': 'int',
'value': 10
},
{
'name': 'Float',
'type': 'float',
'value': 10.5,
'step': 0.1
},
{
'name': 'String',
'type': 'str',
'value': "hi"
},
{
'name': 'List',
'type': 'list',
'values': [1, 2, 3],
'value': 2
},
{
'name': 'Named List',
'type': 'list',
'values': {
"one": 1,
"two": "twosies",
"three": [3, 3, 3]
},
'value': 2
},
{
'name': 'Boolean',
'type': 'bool',
'value': True,
'tip': "This is a checkbox"
},
{
'name': 'Color',
'type': 'color',
'value': "FF0",
'tip': "This is a color button"
},
{
'name': 'Gradient',
'type': 'colormap'
},
{
'name':
'Subgroup',
'type':
'group',
'children': [
{
'name': 'Sub-param 1',
'type': 'int',
'value': 10
},
{
'name': 'Sub-param 2',
'type': 'float',
'value': 1.2e6
},
]
},
{
'name': 'Text Parameter',
'type': 'text',
'value': 'Some text...'
},
{
'name': 'Action Parameter',
'type': 'action'
},
]
},
{
'name':
'Numerical Parameter Options',
'type':
'group',
'children': [
{
'name': 'Units + SI prefix',
'type': 'float',
'value': 1.2e-6,
'step': 1e-6,
'siPrefix': True,
'suffix': 'V'
},
{
'name': 'Limits (min=7;max=15)',
'type': 'int',
'value': 11,
'limits': (7, 15),
'default': -6
},
{
'name': 'DEC stepping',
'type': 'float',
'value': 1.2e6,
'dec': True,
'step': 1,
'siPrefix': True,
'suffix': 'Hz'
},
]
},
{
'name':
'Save/Restore functionality',
'type':
'group',
'children': [
{
'name': 'Save State',
'type': 'action'
},
{
'name':
'Restore State',
'type':
'action',
'children': [
{
'name': 'Add missing items',
'type': 'bool',
'value': True
},
{
'name': 'Remove extra items',
'type': 'bool',
'value': True
},
]
},
]
},
{
'name':
'Extra Parameter Options',
'type':
'group',
'children': [
{
'name': 'Read-only',
'type': 'float',
'value': 1.2e6,
'siPrefix': True,
'suffix': 'Hz',
'readonly': True
},
{
'name': 'Renamable',
'type': 'float',
'value': 1.2e6,
'siPrefix': True,
'suffix': 'Hz',
'renamable': True
},
{
'name': 'Removable',
'type': 'float',
'value': 1.2e6,
'siPrefix': True,
'suffix': 'Hz',
'removable': True
},
]
},
ComplexParameter(name='Custom parameter group (reciprocal values)'),
ScalableGroup(
name="Expandable Parameter Group",
children=[
{
'name': 'ScalableParam 1',
'type': 'str',
'value': "default param 1"
},
{
'name': 'ScalableParam 2',
'type': 'str',
'value': "default param 2"
},
]),
]
# 创建Parameter对象
p = Parameter.create(name='params', type='group', children=params)
def change(param, changes):
# 这个用来监视输出哪个项改变了
print("tree 改变:")
for param, change, data in changes:
path = p.childPath(param)
if path is not None:
childName = '.'.join(path)
else:
childName = param.name()
print(' 变量: %s' % childName)
print(' 改变: %s' % change)
print(' 数据: %s' % str(data))
print(' ----------')
p.sigTreeStateChanged.connect(change)
def valueChanging(param, value):
# 这个监视正在改变,但是没有改变完成的
print(" %s值正在变化(没有完成): %s" % (param, value))
# Too lazy for recursion:
# 将所有对象的信号连接到相应的函数
for child in p.children():
child.sigValueChanging.connect(valueChanging)
for ch2 in child.children():
ch2.sigValueChanging.connect(valueChanging)
def save():
# 这里可以将数据state存储到文件里,可以重新读入
state = p.saveState()
fn = pg.QtGui.QFileDialog.getSaveFileName(None, "保存到文件..", "conf/gui.cfg",
"Config Files (*.cfg)")
fn, _ = fn
if not fn:
return
pg.configfile.writeConfigFile(state, fn)
def restore():
global state
add = p['Save/Restore functionality', 'Restore State', 'Add missing items']
rem = p['Save/Restore functionality', 'Restore State',
'Remove extra items']
p.restoreState(state, addChildren=add, removeChildren=rem)
p.param('Save/Restore functionality', 'Save State').sigActivated.connect(save)
p.param('Save/Restore functionality',
'Restore State').sigActivated.connect(restore)
# 创建两个相同的对象,他们应该显示同样的值
t = ParameterTree()
t.setParameters(p, showTop=False)
t.setWindowTitle('Parameter Tree例子')
t2 = ParameterTree()
t2.setParameters(p, showTop=False)
# 用来存储初始值,方便重置
state = p.saveState()
win = QtGui.QWidget()
layout = QtGui.QGridLayout()
win.setLayout(layout)
layout.addWidget(QtGui.QLabel("两边显示同样的数据. 它们一直显示同样的值."), 0, 0, 1, 2)
layout.addWidget(t, 1, 0, 1, 1)
layout.addWidget(t2, 1, 1, 1, 1)
win.show()
win.resize(800, 800)
## test save/restore
s = p.saveState()
p.restoreState(s)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
界面
源码说明
以上代码是pyqtgraph里面关于parametertree的示例代码,我只把一点英文改成了中文而已。这个示例大家运行起来就知道那些功能都有什么了。初始的功能有:
- 限制变量的类型
- 可以方便的设定颜色,只要一行代码
- 指定设置的值的范围,在定义的时候加上’limits’: (最小值, 最大值)
- 如果参数只有有限的几个值,只要设置类型为list, 然后在values里面指定就行
- 可以非常方便的自定义自己特定的一组项目
- 可以任意嵌套,包括自定义的
我的一个自定义
因为经常用到划图表,画线条,所以自定义了一个1:
class Line(pTypes.GroupParameter):
def __init__(self, **opts):
opts['type'] = 'group'
opts['value'] = True
super().__init__(**opts)
print(opts)
aa = {"name": "_check", "type": "bool", "value": True}
title = {}
title['title'] = opts["title"] if opts.get("title") else opts["name"]
self.addChild({**opts, **aa, **title})
self.addChild({
"name":
"_moresit",
"title":
"设置",
"type":
"group",
"visible":
True,
"children": [{
"name": "width",
"title": "线宽",
"type": "float",
"value": 1.5,
"step": 0.5
},
{
"name": "color",
"type": "color",
"title": "颜色",
"value": "993399"
}]
})
self.a = self.param("_check")
self.b = self.param("_moresit")
self.a.sigValueChanged.connect(self.ischange)
def ischange(self):
# 如果a被选择则添加后面的设置,否则删除.
if self.a.value():
self.addChild(self.b)
else:
self.b.remove()
这个可以实现根据需要自动增删后面的设置。比如,一个线条要显示的时候,才需要定义后面的线条的宽度,颜色,如果不显示,则不需要。
如何获取自己想要的变量的值
将示例里面的params改成下面这样:
params = [{
"name":
"a",
"type":
"group",
"children": [{
"name": "b",
"type": "int",
"value": 10
}, Line(name="c")]
},
{
"name":
"保存/重置",
"type":
"group",
"children": [{
"name": "保存",
'type': 'action'
},
{
"name":
"重置",
"type":
"action",
'children': [
{
'name': '添加删除项目',
'type': 'bool',
'value': True
},
{
'name': '移除添加项目',
'type': 'bool',
'value': True
},
]
}]
}]
那么, 读取b的值只要 p[“a”, “b”]就行了。
这段代码里面用到了一个有用的技巧,那就是字典合并, 即代码中的{**opts, **aa, **title}。里面的opts、 aa、 title都是字典,依次用后面的字典内容更新前面的字典。
这个有用的地方很多,比如,最前面的是默认设置,后面的是用户配置,用户没有配置的项目就是默认值。 ↩︎