小白的Python3.6学习笔记--tkinter一步步制作GUI读取并实时显示采集卡数据(一)

      最近需要用python做一个GUI,作为一个还没学过python的小白,在经历了两三周断断续续的学习和请教学长之后,总算是做出了一些成果,特于此记录一下学习过程。(第一次写博客,排版混乱还请见谅)

一、了解Python3基础语法

      因为做GUI不需要太复杂的语法或者功能,我简单的浏览了一个很不错的比较适合小白的Python3教程:

      廖雪峰的官方网站: 点击打开链接

      我觉得做GUI需要的基础语法,里面前七章和第十二章的内容就大概可以。

      紧接着,我选择了很好用的Python IDE,Pycharm作为编译器。

二、库的选择和学习

      Python令我很惊喜的就是库太强大了,几乎无所不包,大大降低了上手难度。就像当初玩单片机遇到Arduino一样。

      Python做GUI的库有好几个,我选择了上手比较容易的tkinter,它创建GUI的流程大体上就是,首先创建顶层窗口对象,然后创建其他组件放进该对象中,最后进入主事件循环:循环刷新窗口。所以在布局的过程中,布局到哪里,对应的功能就相应的跟在后面。

      由于实时显示采集数据和串口助手功能很相似,所以我决定先设计一个最普通的串口助手,为后面GUI的框架再用以改进。

      我最开始想的大概布局就是这样:

      

      接下来最重要的就是学会使用tkinter的各个组件了,csdn上有很多教程,我从里面学到了很多,这边我姑且以自己的理解总结一下我用到的一些组件和编程的思路,其中组件选项参数的图片均转载自博客:点击打开链接

      1.创建顶层窗口对象,我就叫它父容器

GUI = tk.Tk()            # 创建父容器GUI
GUI.title("Serial Tool") # 父容器标题
GUI.geometry("460x380")  # 设置父容器窗口初始大小,如果没有这个设置,窗口会随着组件大小的变化而变化

      此时的效果如下:

      

      2.接下来是每个子容器及其组件的创建

      首先是调试信息窗口,我设想的是一个带有滚动条的窗口,可以显示当前的操作状态和一些操作信息,当窗口显示满的时候可以自动往下滚动显示。

      在这里,我用到的组件主要有LabelFrame,ScrolledText,grid,place,button,Entry

      LabelFrame组件会自动绘制一个边框将子组件包围起来,并在它们上方显示一个文本标题,组件选项如下:

      

       

      ScrolledText组件是创建一个带有滚动条的文本窗口,全部参数选项有哪些我也没搞清楚,因为它是Text的一个子组件,所以Text部分选项参数它也可以调用,其中Text的全部选项我就不贴了,只选取一个贴一下:

      

      我在这里使用的是选项是wrap=tk.WORD,这个值表示在行的末尾如果有一个单词跨行,会将该单词放到下一行显示,比如输入hello,he在第一行的行尾,llo在第二行的行首, 这时如果wrap=tk.WORD,则表示会将 hello 这个单词挪到下一行行首显示

      grid,pack,place的用法和区别我参考了一篇博客,写的特别好:点击打开链接

      button组件用于实现各种各样的按钮,选项参数如下:

   

     

    

      Entry组件通常用于获取用户输入的文本,选项参数如下:

      

    

    

      以上很多选项默认即可,首先我的调试信息窗口写法如下:

Information = tk.LabelFrame(GUI, text="操作信息", padx=10, pady=10)  # 创建子容器,水平,垂直方向上的边距均为10
Information.place(x=20, y=20)
Information_Window = scrolledtext.ScrolledText(Information, width=20, height=5, padx=10, pady=10,wrap=tk.WORD)
Information_Window.grid()

      此时的效果如下:

      

      接下来如法炮制,创建数据接收子容器:

Receive = tk.LabelFrame(GUI, text="接收区", padx=10, pady=10 )  # 水平,垂直方向上的边距均为 10
Receive.place(x=240, y=150)
Receive_Window = scrolledtext.ScrolledText(Receive, width=20, height=12, padx=10, pady=10, wrap=tk.WORD)
Receive_Window.grid()

     此时效果如下:

     

     发送子容器需要文本窗口,和发送按钮,按下发送按钮时将文本窗口的内容发送至设备,写法如下:

Send = tk.LabelFrame(GUI, text="发送指令", padx=10, pady=5)  
Send.place(x=240, y=20)

DataSend = tk.StringVar()  # 定义DataSend为保存文本框内容的字符串

EntrySend = tk.StringVar()
Send_Window = ttk.Entry(Send, textvariable=EntrySend, width=23)
Send_Window.grid()


def WriteData(): # 按钮按下时触发的动作函数
    global DataSend
    DataSend = EntrySend.get() # 读取当前文本框的内容保存到字符串变量DataSend
    Information_Window.insert("end", '发送指令为:' + str(DataSend) + '\n') # 在操作信息窗口显示发送的指令并换行,end为在窗口末尾处显示
    Information_Window.see("end") # 此处为显示操作信息窗口进度条末尾内容,以上两行可实现窗口内容满时,进度条自动下滚并在最下方显示新的内容  
    SerialPort.write(bytes(DataSend, encoding='utf8')) # 串口发送文本框内容


tk.Button(Send, text="发送", command=WriteData).grid(pady=5, sticky=tk.E)

     此时效果如下:

     

      接下来开始创建选项子容器,作为一个串口助手,选项里肯定得有端口号,波特率的下拉菜单栏,在这里使用的主要组件有Label,Combobox

      Label组件用于显示文本和图像,组件选项如下:

      

        

      

       Combobox用来创建下拉菜单栏,我用到的组件选项如下:

       values 设定下拉菜单可选内容
  state  设定状态。readonly时为只可选择,不可更改内容
  current 设定初始选择内容,参数为可选列表的0-index

       于是我选项子容器的写法如下:

option = tk.LabelFrame( GUI, text = "选项", padx = 10, pady = 10 )  
option.place(x = 20, y = 150, width = 203)  
# ************创建下拉列表**************
ttk.Label( option, text = "串口号:" ).grid( column = 0, row = 0 )  # 添加串口号标签
ttk.Label( option, text = "波特率:" ).grid( column = 0, row = 1 )  # 添加波特率标签

Port = tk.StringVar()  # 端口号字符串
Port_list = ttk.Combobox( option, width = 12, textvariable = Port, state = 'readonly' )
ListPorts = list(serial.tool.list_ports.comports) # 扫描当前可用串口保存到表ListPorts
Port_list['values'] = [i[0] for i in ListPorts] # 下拉列表的值为ListPorts的所有值
Port_list.current(0) # 初始显示表中第一个值
Port_list.grid(column=1, row=0)  # 设置其在界面中出现的位置  column代表列   row 代表行

BaudRate = tk.StringVar()  # 波特率字符串
BaudRate_list = ttk.Combobox( option, width = 12, textvariable = BaudRate, state = 'readonly' )
BaudRate_list['values'] = (1200, 2400, 4800, 9600, 14400, 19200, 38400, 43000, 57600, 76800, 115200)
BaudRate_list.current(3) # 初始显示9600
BaudRate_list.grid(column=1, row=1)  

        此时效果如下:

        

        最后是创建开/停按钮的子容器了,停止按钮则是直接关闭串口即可实现停止采集,而开始采集按钮按下后需要接收数据并把数据显示到接收区,接收数据的核心是要添加一个线程跟主线程并行,这样才能保证一直接收数据且不和主事件循环冲突。这一块我的写法如下:

switch = tk.LabelFrame( GUI, text = "", padx = 10, pady = 10 )
switch.place(x = 20, y = 315, width = 203)  


def ReceiveData():
    while SerialPort.isOpen():
        Receive_Window.insert("end", str(SerialPort.readline()) + '\n')
        Receive_Window.see("end")


def Close_Serial():
    SerialPort.close()


def Open_Serial():
    if not SerialPort.isOpen():
        SerialPort.port = Port_list.get() # 读取端口下拉菜单栏的选择,将其设为串口的端口
        SerialPort.baudrate = BaudRate_list.get() # 读取波特率下拉菜单栏的选择,将其设为串口的波特率
        SerialPort.timeout = 0.1
        SerialPort.open() # 打开串口
        if SerialPort.isOpen():
            t = threading.Thread(target=ReceiveData) # 新建线程用来不断接收数据并显示
            t.setDaemon(True) # 守护线程
            t.start() # 开始线程
    else:
        SerialPort.close()


tk.Button( switch, text = "开始采集", command = Open_Serial ).pack( side = "left", padx = 13 )
tk.Button( switch, text = "停止采集", command = Close_Serial ).pack( side = "right", padx = 13 )

   此时效果如下:

   

   至此,最基础的串口助手设计完成了,可以实现最基础的串口助手功能,也为我后边GUI的设计搭建了基础框架,最后运行效果如下图所示:

   

   最后附上我的最终全部代码:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import threading
import tkinter as tk
import serial.tools.list_ports
from tkinter import ttk
from tkinter import scrolledtext

SerialPort = serial.Serial()
GUI = tk.Tk()  # 父容器
GUI.title("Serial Tool")  # 父容器标题
GUI.geometry("440x320")  # 父容器大小

Information = tk.LabelFrame(GUI, text="操作信息", padx=10, pady=10)  # 水平,垂直方向上的边距均为10
Information.place(x=20, y=20)
Information_Window = scrolledtext.ScrolledText(Information, width=20, height=5, padx=10, pady = 10,wrap = tk.WORD)
Information_Window.grid()

Send = tk.LabelFrame(GUI, text="发送指令", padx=10, pady=5)  # 水平,垂直方向上的边距均为 10
Send.place(x=240, y=20)

DataSend = tk.StringVar()  # 定义DataSend为保存文本框内容的字符串

EntrySend = tk.StringVar()
Send_Window = ttk.Entry(Send, textvariable=EntrySend, width=23)
Send_Window.grid()


def WriteData():
    global DataSend
    DataSend = EntrySend.get()
    Information_Window.insert("end", '发送指令为:' + str(DataSend) + '\n')
    Information_Window.see("end")
    SerialPort.write(bytes(DataSend, encoding='utf8'))


tk.Button(Send, text="发送", command=WriteData).grid(pady=5, sticky=tk.E)

Receive = tk.LabelFrame( GUI, text = "接收区", padx = 10, pady = 10 )  # 水平,垂直方向上的边距均为 10
Receive.place(x = 240, y = 124)
Receive_Window = scrolledtext.ScrolledText(Receive, width = 18, height = 9, padx = 8, pady = 10,wrap = tk.WORD)
Receive_Window.grid()

option = tk.LabelFrame( GUI, text = "选项", padx = 10, pady = 10 )  # 水平,垂直方向上的边距均为10
option.place(x = 20, y = 150, width = 203)  # 定位坐标
# ************创建下拉列表**************
ttk.Label( option, text = "串口号:" ).grid( column = 0, row = 0 )  # 添加串口号标签
ttk.Label( option, text = "波特率:" ).grid( column = 0, row = 1 )  # 添加波特率标签

Port = tk.StringVar()  # 端口号字符串
Port_list = ttk.Combobox( option, width = 12, textvariable = Port, state = 'readonly' )
ListPorts = list(serial.tools.list_ports.comports())
Port_list['values'] = [i[0] for i in ListPorts]
Port_list.current(0)
Port_list.grid(column=1, row=0)  # 设置其在界面中出现的位置  column代表列   row 代表行

BaudRate = tk.StringVar()  # 波特率字符串
BaudRate_list = ttk.Combobox( option, width = 12, textvariable = BaudRate, state = 'readonly' )
BaudRate_list['values'] = (1200, 2400, 4800, 9600, 14400, 19200, 38400, 43000, 57600, 76800, 115200)
BaudRate_list.current(3)
BaudRate_list.grid(column=1, row=1)  # 设置其在界面中出现的位置  column代表列   row 代表行

switch = tk.LabelFrame( GUI, text = "", padx = 10, pady = 10 )  # 水平,垂直方向上的边距均为 10
switch.place(x = 20, y = 250, width = 203)  # 定位坐标


def ReceiveData():
    while SerialPort.isOpen():
        Receive_Window.insert("end", str(SerialPort.readline()) + '\n')
        Receive_Window.see("end")


def Close_Serial():
    SerialPort.close()


def Open_Serial():
    if not SerialPort.isOpen():
        SerialPort.port = Port_list.get()
        SerialPort.baudrate = BaudRate_list.get()
        SerialPort.timeout = 0.1
        SerialPort.open()
        if SerialPort.isOpen():
            t = threading.Thread(target=ReceiveData)
            t.setDaemon(True)
            t.start()
    else:
        SerialPort.close()


tk.Button( switch, text = "开始采集", command = Open_Serial ).pack( side = "left", padx = 13 )
tk.Button( switch, text = "停止采集", command = Close_Serial ).pack( side = "right", padx = 13 )

GUI.mainloop()

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值