(原文写于2011-9-3 ,发布于振动论坛,算法及编程语言分区)
(重编辑于2015-11-15)
今天说说用于快速开发简单图形界面的guidata库。和大多数的Python库一样,guidata有自己的技术支持页面和实例完善的帮助文档,然而文档毕竟是死的,只有使用起来更能体会其中的奇技淫巧。本文将通过一个数据处理的例子展示如何使用guidata。然后通过另一个例子说明guidata的一些特性是如何通过Python强大的面对对象机制达到的。
对于科学计算软件的开发,从最基本的模块直至最复杂的应用程序,开发者都需要灵活地方便地操纵数据(例如,数据处理时输入的参数)。这些数据由多种类型组成:实数(如一些物理量),整数(如数组的索引),字符串(如文件名),布尔值(如某个选项的开关)等等。如果这类数据的输入能够以图形界面的方式组织起来,将会是非常方便的。大多数时候,这类图形界面需满足如下需求:
1)允许用户在图形界面上输入每一个参数的值,即通过一些控件来对应特定数据类型的输入。例如,一个单选框/一组复选框对应于单选或复选操作。
2)输入的数据必须在程序中由于之对应的方式储存。即无需再进行类型转换,输入的是整数就在程序中返回整型值。用过MFC的同志们恐怕都有将一切数据都输入为字符串,再一一转型成所需类型的记忆吧?
3)该储存方式同时应当易于使用。例如,在后续的数据处理中易于访问。例如,将一个多选的结果直接储存到列表中是不是更方便呢?
4)通过图形界面向用户展示参数的设置结果,以便再次确认。
guidata就是这样的一个库,它的目的不是涵盖一切图形界面的设计(不同于Qt等通用图形界面库)。它的目的是在科学计算环境下,提供方便的GUI输入模块,使得科学计算的程序开发者不再需要专门学习通用界面库就可以有足够的图形界面工具可以利用。
一个例子:选择指定的实验数据进行对比
假设我们手里有几组实验数据,想根据需要选择几组进行对比。那么对于GUI程序的需求是:
1)用户在图形界面上选择要进行对比的数据组(多选)
2)根据用户的选择绘出指定的实验数据曲线(这一部分工作有Matplotlab完成)
3)当用户完成选择后回显以确认
完整的代码为:
# -*- coding: cp936 -*-
import matplotlib.pyplot as plt
import guidata
import guidata.dataset.datatypes as dt
import guidata.dataset.dataitems as di
def initPlot():
plt.figure(1)
plt.subplot(2,1,1)
plt.grid(True)
plt.yscale('log')
plt.xlabel('Frequency / Hz')
plt.ylabel(u'Amptitude / m / (N·s^2)')
plt.subplot(2,1,2)
plt.grid(True)
plt.xlabel('Frequency / Hz')
plt.ylabel(u'indicator / ')
def PlotSingleFile(FileName,col,mar):
with open(FileName) as f:
freqList = []
ampList = []
angList =[]
while(True):
line = f.readline()
if not line:
break
infolist = line.split()
freqList.append(float(infolist[0]))
ampList.append(float(infolist[1]))
angList.append(float(infolist[2]))
plt.figure(1)
plt.subplot(2,1,1)
line1,=plt.plot(freqList,ampList,color = col,marker = mar)
plt.xlim(0,max(freqList))
plt.subplot(2,1,2)
line2,=plt.plot(freqList,angList,color = col,marker = mar)
plt.xlim(0,max(freqList))
return line1,line2
def editFromGUI(fnameList):
#_app = guidata.qapplication()
class ParameterUGI(dt.DataSet):
choice = di.MultipleChoiceItem("Select files to plot", fnameList)
param = ParameterUGI()
while(True):
param.edit()
if len(param.choice)!=0:
break
param.view()
return param.choice
def main():
_app = guidata.qapplication()
fnameList = ['OnAxis_R.txt','OnAxis_Z.txt','OnBox_Box.txt',
'OnGear_1.txt','OnGear_2.txt']
colorList = ['blue','green','black','red','purple']
markerList = [None,'x','o','+','_']
lineList = [[],[]]
labelList = []
l = editFromGUI(fnameList)
initPlot()
for i in l:
line1,line2=PlotSingleFile(fnameList[i],colorList[i],markerList[i])
lineList[0].append(line1)
lineList[1].append(line2)
labelList.append(fnameList[i])
plt.figure(1)
plt.figlegend(lineList[0],labelList,loc=1)
plt.show()
_app.exec_()
if __name__ == '__main__':
main()
这里需要说明的是上述需求1)和3)的实现全在函数editFromGUI(fnameList)中。阅读这个函数并不难理解,那么guidata是如何体现“储存的数据易于使用”的呢?请函数main(),其中,l = editFromGUI(fnameList) 说明 l 引用的正是在函数 editFromGUI中定义param.choice的,显然,这是一个类型为di.MultipleChoiceItem的对象。而在main()函数中,完成对editFromGUI调用后,出现了 for i in l: ,这意味着l同时具有了可遍历的功能(即,l具有list对象对具有的基本访问功能),在这里完全可以当作一个list使用,这正是概述中“3)该储存方式同时应当易于使用”的完美体现!
这个例子运行起来时,会先展示这样一个窗口:

可以选择几项:

点击确定,出现这个窗口:

点击确定后,根据所选读取文件数据并绘制图形:

奇技淫巧:如何让你的对象表现得像个具有特性功能的list
先看代码:
class rainyboy:
__index = -1
def __init__(self,a,b):
self.list = range(a,b,2)
def __getitem__(self,key):
return self.list[key]
def __str__(self):
return repr(self.list)
def __len__(self):
return len(self.list)
def __call__(self):
return 'rainyboy'
def __iter__(self):
return iter(self.list)
return self
def next(self):
self.__index = self.__index + 1
if self.__index == len(self):
raise StopIteration
return self.list[self.__index]
if __name__ == '__main__':
c = rainyboy(1,20)
print c,c[0],c[1],len(c)
print c()
for i in c:
print i
对于上面这个类,执行这样的代码会输出:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] 1 3 10
rainyboy
1
3
5
7
9
11
13
15
17
19
怎么样?C是一个Rainyboy类的对象,C可以像list那样被整个print出来,也可以单独按索引值访问,还可以返回数据的长度,还可以像函数那样被调用,甚至可以迭代访问!这些功能得益于重写或定义了如下函数:
可以像list那样被print出来: __str__
可以按索引值访问:__getitem__
可以求得数据区的长度:__len__
可以像函数那样被调用:__call__
可以迭代访问:__iter__ , next
使用感受
正如guidata在review中所言,这确实是一款适合为科学计算定制数据输入界面的库类——使用该库类的人只需要关注很少量的图形界面知识,甚至没有定义任何的消息-响应映射。布局(layout)是图形界面设计中很重要的一环,在guidata中甚至也不用刻意设置——前提是你接受它默认的样子。当然,布局是可以设置的,更多的例子可以运行它的示例:
from guidata import tests tests.run()