Python 可视化图形界面简单实践

最近工作上常用的一个操作需要进行二进制数bit翻转。由于不知道windows系统计算器就有这个功能,自己用python写了一个。简单记录下实践过程。

Python进行GUI编程,可用的库并不多。功能也不算强大,这篇文章简单列举了几个库的特点。
我呢,没有被它们任何一个吸引。所以直截了当地选择了Python标准库自带的Tkinter
( Python Tkinter 官方文档:https://docs.python.org/2/library/tkinter.html

完成品大致是这样的:

基本框架是:

from Tkinter import *
top = Tk()
top.title("Listen's Option Checker")
# Your code here
top.mainloop()

在这个基础的窗口里,用Frame控件分割空间(类似于html和CSS那种方式)实现布局。
然后在各个分区的frame里,根据与用户的交互方式,选择对应的控件:Button、Entry、Label……

控件由类似这样的句子生成:

Confirm_Button = Button(frm2Sub1,text="确认",command=hex2bin)
Confirm_Button.pack(side=LEFT)

第一句创建了一个Button实例,这个按钮的父容器是frm2Sub1,按钮上文字为「确认」,当用户按下这个按钮时,就会立即触发一个名为hex2bin的函数,来对用户的操作进行响应。
现在命名空间内的Confirm_Button指向了这个Button实例。用.pack()方法来完成对它的部署。参数side=LEFT是要让它在所属Frame容器内靠左放置,若不指定位置,每个新的控件都会被部署在前一个控件下方。

当然,包括Frame在内的所有控件都需要通过.pack()来呈现。如果漏了这一句,程序界面内就看不到对应控件。

去查一下Tkinter支持控件列表,再配合以上基本信息,已经足以制作一些功能极其简单的可视化程序界面。其实网络上大多数介绍文章也没有讲的更多。
但还有一些值得注意的地方。

 

Command传参

Button控件的command参数,是不允许传参的
我理解此处的command参数就类似于一个函数指针,指向目标函数。
网上比较常见的传参方法是用lambda函数

Button(frm, textvariable=strvar31, command = lambda : FlipBit(31,strvar31))

它其实是定义了一个匿名函数,在Button被按下时执行的是lambda,再由lambda将参数交给你的执行函数。类似这样的用法,在只有一两个同类控件的时候是可行的。也很方便。

但对我这个程序来说,需要32个按钮充当32位二进制bit,不可能手动复制32个button的创建过程。
这里的Lambda函数,如果用在for循环创建的button内,就会导致它传进去的参数固定为for循环的控制变量i(的最大值,也就是最后一次循环的值)。
原因也很简单,lambda函数在未被执行时并不会被解释(编译成固定的函数)。它只有被真正执行时才生效。而真正执行时是Button按下时,不是For循环当初loop经过它时。所以i始终为定值(终值)。

最终的办法是用一个Event Handler。定义如下两个function:

def handler(bit):
num[bit] = num[bit] ^ 1
strvarlist[bit].set(str(num[bit]))
binary = "".join(map(str,num))
decimal = int(binary,2)
var16.set(hex(decimal))

def handlerAdaptor(fun,v):
return lambda event, fun=fun, v=v: fun(v)

由handlerAdaptor作中介,将参数传进去。方法是将handler与鼠标点击event绑定。
绑定鼠标事件的Button写法如下:

ButtonList.append(Button(SubSubFrameList[i], textvariable=strvarlist[i]))
ButtonList[i].bind('', handlerAdaptor(handler, i))
ButtonList[i].pack()

这里跟前面创建控件的方式不同,有一个小变化:
为了使我们这32个bit的控件(包括button和其上的textvariable)可以被方便地遍历,我不再是给它们赋予一个固定的命名,而是将每个实例直接.append()到它们各自的list里
这样我在另外的函数里处理它们时,可以直接用下标来控制和选择。——包括这里创建完成后的.bind()和.pack(),也都是用list加下标来做选择。


一些参考资料:
tkinter官方文档学习笔记
Tkinter 控件简单计算器示例
Python Tkinter参考资料之(通用控件属性)
tkinter模块常用参数(python3)
Python Tkinter GUI(三)显示图片
Python GUI进阶(ttk)—让界面变得更美


打包exe程序


上表来自《Python程序打包成exe可执行文件》,文章很有用。我此前用过的py2exe不好用,这次选择pyinstaller,简单打包方法:

pyinstaller -F -w main.py

生成文件在dist目录。build仅为中间文件。

-F 是打包成一个单独的exe文件,不加则会生成一整个目录的文件。(加了该参数会导致程序调用外部image失败,在spec配置内添加DATA也没用)

-w 不需要查看命令行时,用这个参数隐藏cmd画面。

亲测-F之后程序启动速度慢了几十倍不止。所以我不建议-F

更复杂的配置,需要使用脚本相同目录下,由pyinstaller生成的一个.spec文件。我没有深入研究,需要时可以在网上查。(参考:pyinstaller打包工具的使用说明将自己的python程序打包成.exe/.app)


完整的代码

# -*- coding: utf-8 -*-
from Tkinter import *

totalBitNum = 32
SubFrameList = []
LabelList = []
SubSubFrameList = []
ButtonList = []
strvarlist = []
lalalist = ["*"," "," "," ","*","*","*","*","*","*","└","┘","└","┘","*","└",
"┘","└","┘","*","*","*","*","└","┘","└","─","─","┘","└","─","┘"]

def handler(bit):
num[bit] = num[bit] ^ 1
print num
strvarlist[bit].set(str(num[bit]))
binary = "".join(map(str,num))
decimal = int(binary,2)
var16.set(hex(decimal))

def handlerAdaptor(fun,v):
return lambda event, fun=fun, v=v: fun(v)

def hex2bin():
hexadecimal = var16.get()
print hexadecimal
decimal = int(hexadecimal,16)
binary = bin(decimal)[2:]
align_loc = totalBitNum - len(binary)
for bit in range(totalBitNum):
if bit >= align_loc:
strvarlist[bit].set(binary[bit - align_loc])
num[bit] = int(binary[bit - align_loc])
else:
strvarlist[bit].set('0')
num[bit] = 0

def clear2zero():
var16.set("0x0")
for bit in range(totalBitNum):
strvarlist[bit].set("0")
num[bit] = 0

top = Tk()
top.title("Listen's Option Checker")
top.geometry('1000x350') #是x 不是*

num = [0]*totalBitNum
# bits keyboard frm
frm = Frame(top)

for FrmCnt in range(totalBitNum/4):

SubFrameList.append(Frame(frm,bd=3)) #,relief=GROOVE

for i in range(4):

SubSubFrameList.append(Frame(SubFrameList[FrmCnt],bd=1))

LabelList.append(Label(SubSubFrameList[4*FrmCnt + i],text=str(31-(4*FrmCnt + i)),bg="Grey",font='Times 9 normal'))
LabelList[4*FrmCnt + i].pack(side=TOP)

#Buttons and their string vars
strvarlist.append(StringVar())
strvarlist[4*FrmCnt + i].set('0')
ButtonList.append(Button(SubSubFrameList[4*FrmCnt + i], textvariable=strvarlist[4*FrmCnt + i]))
ButtonList[4*FrmCnt + i].bind('', handlerAdaptor(handler,4*FrmCnt + i))
ButtonList[4*FrmCnt + i].pack()

Label(SubSubFrameList[4*FrmCnt + i],text=lalalist[4*FrmCnt + i],font='Times 13 normal').pack(side=BOTTOM)

SubSubFrameList[4*FrmCnt + i].pack(side=LEFT)

SubFrameList[FrmCnt].pack(side=LEFT)

frm.pack()

frm2 = Frame(top)

frm2Sub1 = Frame(frm2, bd = 5)

#16进制文字框
var16 = StringVar()
e = Entry(frm2Sub1, textvariable = var16)#, bd = 5,relief=SUNKEN)
var16.set("0x00000000")
e.pack(side=LEFT)

#Confirm Button
Confirm_Button = Button(frm2Sub1,text="确认",command=hex2bin)#,bd = 5,relief=RIDGE)
Confirm_Button.pack(side=LEFT)

#Clear Button
Clear_Button = Button(frm2Sub1,text="清零",command=clear2zero)#,bd = 5,relief=SOLID)
Clear_Button.pack(side=RIGHT)
frm2Sub1.pack(side=TOP)

#Option image
frm2Sub2 = Frame(frm2)
pic = PhotoImage(file = 'option.gif')
Label(frm2Sub2,image=pic,width=800).pack()

frm2Sub2.pack(side=BOTTOM)

frm2.pack()

# 进入消息循环
top.mainloop()

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.