0前言
我实际上并没有用过python进行GUI编程,按照我的C#窗体编程习惯,应该先布置GUI,再对各控件的具体功能进行编程,所以本文就先介绍GUI的python实现。
OS:win10 x64 1909
开发工具:VS Code
Python GUI 库:tkinter
1参考
首先当然是参考现成的modbus调试工具,这里以modscan32为例:
需要参考的地方有两块,一块是连接配置(省去硬件流控制),一块是modbus协议配置。
2控件选择
打开VS Code,首先先创建程序主体:
import tkinter as tk #导入Tkinter模块,Python3环境下首字母t小写
menu = tk.Tk() #创建Tkinter主窗口
menu.title("MODBUS MASTER") #更改窗体标题
... #添加控件
menu.mainloop() #进入主循环
现在要添加控件,结合对modscan32的参考,这个小工具(至少)需要达成的配置内容有:
- 串口编号
- 波特率
- 字长度
- 奇偶校验
- 停止位
- 设备ID
- 从站地址
- 功能码
- 地址显示区显示长度
- 地址显示区
可以看到串口、波特率、字长度、奇偶校验、停止位、功能码都具备下拉框:
但实际上tkinter并没有combobox控件。在tkinter中有三个控件可以实现类似的效果,分别是checkbutton, Radiobutton 和 Listbox ,其中前者更适合二选一的情况,而后两者则适合于多选一的情况,就我个人觉得后两者在功能上并无太大不同,更多在美观性上考虑:
Radiobutton:
var = tk.IntVar()
MPT = [("01 COIL STATUS",1),("02 INPUT STATUS",2),("03 HOLIDING REGISTER",3),("04 INPUT REGISTER",4)]
for mptype, num in MPT:
pointtype_list = tk.Radiobutton(text=mptype, variable=var,value=num)
pointtype_list.grid()
Listbox:
var = tk.StringVar()
var.set(("01 COIL STATUS","02 INPUT STATUS","03 HOLIDING REGISTER","04 INPUT REGISTER"))
pointtype_list = tk.Listbox(listvariable=var,height=4).grid()
注意有多个listbox的时候,选中其中一个控件中的内容后再选择另一个控件,会使之前选中的内容消失,可以通过修改exportselection属性来保持选中:
parity_enter = tk.Listbox(connect_group,listvariable=varp,height=3,exportselection=False) #exportselection=False
————
数据输入框可以使用label控件和搭配entry控件实现;
数据输出框可以使用label控件和搭配text控件实现;
3布局
tkinter共有3种布局方法:pack()、grid()和place():
方法 | 含义 |
---|---|
pack | 按添加顺序排列控件 |
grid | 按行列形式排列控件 |
place | 允许程序员指定控件的大小和位置 |
这里选用grid()方法,最直接的原因是pack()不支持在同一行布置两个控件,这会破坏画面的简洁性:
pack()方法:
deviceid = tk.Label(text = "device id:").pack()
deviceid_enter = tk.Entry().pack()
grid()方法:
deviceid = tk.Label(text = "device id:").grid(row=0,column=0)
deviceid_enter = tk.Entry().grid(row=0,column=1)
grid()方法的基本使用,参考:Tkinter 布局管理器(二):grid
4美观性
4.1组件跨行/列
在使用了grid()方法后,画面布局被分为两列(在本项目中),如果希望控件(如用于表示标题的控件)在两列中位于居中位置,可以使用columnspan实现,其值为所跨的列数:
title = tk.Label(text = "Modbus调试工具",font = 15).grid(row=0,columnspan=2) #columnspan=2 组件跨两列
跨列前:
跨列后:
————
同理可使用rowspan实现跨行。
4.2控件对齐
参考上一张图,label控件默认居中对齐,但在使用习惯中控件一般需要右对齐,在grid()方法中提供了对齐选项:
sticky = “e”/“w”/“s”/"n"
deviceid = tk.Label(config_group,text = "device id:").grid(row=2,column=0,sticky="E") #sticky="E" 右对齐
4.3控件间距调整
在默认情况下控件和控件之间严密贴合,仅留有少量空隙,在视觉上会给人造成一种拥挤感,在grid()中可设置控件间间距:
属性 | 含义 |
---|---|
ipadx | 指定水平方向上的内边距 |
ipady | 指定垂直方向上的内边距 |
padx | 指定水平方向上的外边距 |
pady | 指定垂直方向上的外边距 |
调整前:
start = tk.Button(config_group,text = "开始",width=10).grid(columnspan=2)
调整后:
start = tk.Button(config_group,text = "开始",width=10).grid(columnspan=2,pady=5) #pady=5 按钮组件垂直方向上的外边距为5
4.4控件分区
工具可分为两部分:数据输入域和数据输出域。
可以使用LabelFrame将数据输入域包裹起来:
config_group = tk.LabelFrame(text="设置栏") #边框注释
config_group.grid(row=1,columnspan=2,padx=10, pady=10)
LabelFrame是一种容器,可以存放各类控件,如果希望将指定控件移入容器内,需要修改该控件的父容器:
deviceid = tk.Label(config_group,text = "device id:").grid(row=2,column=0,sticky="E")#label组件位于LabelFrame(config_group)容器中
5样品展示
后续加入功能后GUI肯定会修改的。
附录 获取控件输入值/令控件输出值
如何获取在entry()、listbox()和radiobutton()控件中输入的值,并通过text()输出?
这里设计了一个小程序进行演示:
import tkinter as tk
window=tk.Tk()
#创建entry控件
e=tk.Entry(window)
e.pack()
#创建listbox控件
varw = tk.StringVar()
varw.set(("7","8"))
wlength_enter = tk.Listbox(listvariable=varw,height=2,exportselection=False)
wlength_enter.pack()
#创建radiobutton控件
var = tk.IntVar()
MPT = [("01 COIL STATUS",1),("02 INPUT STATUS",2),("03 HOLIDING REGISTER",3),("04 INPUT REGISTER",4)]
for mptype, num in MPT:
pointtype_list = tk.Radiobutton(text=mptype, variable=var,value=num)
pointtype_list.pack()
################核心代码##################
#获取控件值通过text控件输出
def entry_enter():
var1=e.get() #获取控件输入值
t.insert('insert',var1)
def listbox_enter():
var1 = wlength_enter.get(wlength_enter.curselection()) #获取控件输入值
t.insert('insert',var1)
def radiobutton_enter():
var1 = var.get() #获取控件输入值
t.insert('insert',var1)
# 创建button控件
b1=tk.Button(window,text='entry',width=15,height=2,command=entry_enter)
b1.pack()
b2=tk.Button(window,text='listbox',width=15,height=2,command=listbox_enter)
b2.pack()
b3=tk.Button(window,text='radiobutton',width=15,height=2,command=radiobutton_enter)
b3.pack()
#创建text控件
t=tk.Text(window,height=2)
t.pack()
window.mainloop()
画面中布置了3个按钮,按动不同的按钮,会将不同控件中的输入值输出到text中,这样设置的原因是程序似乎不支持实时输出 输入值,所以需要通过这种方法手动执行。