使用Python和Tkinter库创建一个简单的窗口工具是非常直接的。首先,确保你的环境中已经安装了Python。Tkinter通常随Python一同安装,特别是对于Windows和macOS用户。如果你使用的是Linux,并且没有安装Tkinter,你可以通过包管理器来安装它。
下面是一个使用Python和Tkinter创建简单窗口应用程序的基本示例:
import tkinter as tk
def main():
# 创建主窗口
root = tk.Tk()
# 设置窗口标题
root.title("我的第一个Tkinter应用")
# 设置窗口大小
root.geometry('300x200') # 宽x高
# 添加一个标签到窗口
label = tk.Label(root, text="欢迎使用Tkinter!", font=("Arial", 14))
label.pack(pady=20)
# 添加一个按钮,并设置点击事件处理函数
button = tk.Button(root, text="点击我", command=lambda: print("按钮被点击!"))
button.pack(pady=20)
# 进入消息循环
root.mainloop()
if __name__ == "__main__":
main()
这段代码完成了一个基础的GUI(图形用户界面)应用程序的创建,包括:
导入tkinter模块。
创建主窗口(root)并设置其属性,如标题和尺寸。
向窗口添加组件,例如标签(Label)和按钮(Button),并通过pack()方法将它们放置在窗口中。
最后,启动Tkinter的消息循环(mainloop()),这是保持窗口打开所必需的,直到用户关闭它。
你可以根据自己的需求修改这个基本框架,比如添加更多的控件、调整布局或者实现更复杂的交互逻辑。Tkinter提供了丰富的组件和功能来帮助你构建复杂的桌面应用程序。
将上述程序生成一个可执行程序
要将你的Python脚本转换为独立的可执行文件,你可以使用一个名为PyInstaller的工具。PyInstaller能够将Python应用程序及其所有依赖项打包成一个或多个独立的可执行文件,适用于Windows、macOS和Linux。
以下是使用PyInstaller将你的Tkinter应用转换为可执行程序的步骤:
安装PyInstaller
首先,你需要安装PyInstaller。如果你还没有安装,请打开命令行工具(如Terminal或CMD),并运行以下命令:
pip install pyinstaller
使用PyInstaller创建可执行文件
假设你的Python脚本文件名为tkinter_app.py,你可以通过下面的步骤将其转换为可执行文件:
打开命令行工具。
导航到包含你的Python脚本的目录。
运行以下命令来生成可执行文件:
pyinstaller --onefile tkinter_app.py
--onefile选项表示将所有内容打包成一个单独的可执行文件。
如果你想让窗口应用在运行时没有控制台窗口弹出(对于GUI应用来说通常更合适),可以添加--noconsole标志:
pyinstaller --onefile --noconsole tkinter_app.py
查找生成的可执行文件
一旦上述命令完成,你可以在dist子目录下找到生成的可执行文件(例如,在Windows上是一个.exe文件)。这个文件可以直接分发给其他用户,而不需要他们安装Python或其他任何依赖库
要在命令行或终端中导航到包含你的Python脚本的目录,你需要使用cd(Change Directory)命令。下面是如何根据不同的操作系统执行这一操作的步骤:
Windows
假设你的Python脚本位于C:\Users\YourUsername\Documents\PythonScripts文件夹中,你可以按照以下步骤操作:
打开命令提示符(CMD)。你可以通过按 Win + R 键,然后输入 cmd 并按下回车键来打开命令提示符。
使用 cd 命令导航到包含Python脚本的目录。在命令提示符中输入以下命令并按回车键
cd C:\Users\YourUsername\Documents\PythonScripts
为了确认你是否已经正确导航到了目标目录,可以输入 dir 命令列出该目录下的所有文件和子目录。你应该能看到你的Python脚本文件。
创建一个简单的串口工具小助手程序,可以使用Python的tkinter库来构建图形用户界面(GUI),并结合pyserial库来进行串口通信。以下是一个基本的例子,展示了如何实现这一功能:
步骤 1: 安装所需的库
首先,确保你已经安装了pyserial库。如果没有安装,可以通过pip安装
pip install pyserial
步骤 2: 编写代码
下面是一个简单的串口工具小助手程序示例:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=0, column=0, padx=5, pady=5)
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
self.refresh_ports()
# 创建波特率选择下拉框
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=1, column=0, padx=5, pady=5)
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(4) # 默认选择115200
self.baud_combobox.grid(row=1, column=1, padx=5, pady=5)
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=2, column=0, padx=5, pady=5)
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=2, column=1, padx=5, pady=5)
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=3, column=0, padx=5, pady=5)
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=3, column=1, padx=5, pady=5)
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=4, column=0, columnspan=2, padx=5, pady=5)
# 创建接收数据显示区域
self.receive_text = tk.Text(root, height=10, width=40)
self.receive_text.grid(row=5, column=0, columnspan=2, padx=5, pady=5)
# 初始化串口对象
self.ser = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get() + '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = SerialToolApp(root)
root.mainloop()
代码说明
界面组件:
Combobox用于选择串口号和波特率。
Button用于打开和关闭串口以及发送数据。
Text用于显示接收到的数据。
功能函数:
refresh_ports():刷新可用的串口列表。
open_serial():打开选定的串口,并设置相应的按钮状态。
close_serial():关闭当前打开的串口,并更新按钮状态。
send_data():通过串口发送数据。
update_buttons_state():根据串口是否打开的状态更新按钮的启用或禁用状态。
使用方法
运行程序后,选择要使用的串口和波特率。
点击“打开”按钮以打开串口。
在“发送数据”文本框中输入要发送的数据,然后点击“发送”按钮。
关闭串口时,点击“关闭”按钮。
这个例子提供了一个基本的框架,你可以根据需要添加更多功能,如接收数据的实时显示、更多的串口配置选项等。希望这对你有帮助!如果有任何问题,请随时提问。
为了在你的串口工具小助手中增加一个十六进制发送选择框,我们需要对现有代码进行一些修改。具体来说,我们将添加一个复选框(Checkbutton),让用户可以选择是否以十六进制格式发送数据。如果选择了十六进制模式,则将输入的数据转换为相应的十六进制字节序列再通过串口发送。
以下是更新后的完整代码示例:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=0, column=0, padx=5, pady=5)
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
self.refresh_ports()
# 创建波特率选择下拉框
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=1, column=0, padx=5, pady=5)
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(4) # 默认选择115200
self.baud_combobox.grid(row=1, column=1, padx=5, pady=5)
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=2, column=0, padx=5, pady=5)
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=2, column=1, padx=5, pady=5)
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=3, column=0, padx=5, pady=5)
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=3, column=1, padx=5, pady=5)
# 创建十六进制发送选择框
self.hex_var = tk.BooleanVar()
self.hex_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_var)
self.hex_checkbutton.grid(row=4, column=0, padx=5, pady=5)
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=4, column=1, padx=5, pady=5)
# 创建接收数据显示区域
self.receive_text = tk.Text(root, height=10, width=40)
self.receive_text.grid(row=5, column=0, columnspan=2, padx=5, pady=5)
# 初始化串口对象
self.ser = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = SerialToolApp(root)
root.mainloop()
主要改动点
十六进制发送选择框:
添加了一个布尔类型的变量 self.hex_var 来存储用户是否选择了十六进制发送。
在界面中添加了一个 Checkbutton 组件,并将其与 self.hex_var 绑定。
发送数据处理:
在 send_data() 方法中,根据 self.hex_var.get() 的值判断用户是否选择了十六进制发送。
如果选择了十六进制发送,则将输入的十六进制字符串转换为字节并发送;否则,直接发送普通字符串。
使用方法
运行程序后,选择要使用的串口和波特率。
点击“打开”按钮以打开串口。
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
关闭串口时,点击“关闭”按钮。
这样,你就可以灵活地选择以普通字符串或十六进制格式发送数据了。如果有任何问题或需要进一步的功能扩展,请随时告诉我!
为了在接收数据显示区域增加滚动条,我们可以使用 tkinter 的 Scrollbar 组件。具体来说,我们将 Text 小部件与 Scrollbar 绑定,使得用户可以通过滚动条查看接收到的数据。
以下是更新后的完整代码示例,其中包含了接收数据显示区域的滚动条:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=0, column=0, padx=5, pady=5)
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
self.refresh_ports()
# 创建波特率选择下拉框
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=1, column=0, padx=5, pady=5)
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(4) # 默认选择115200
self.baud_combobox.grid(row=1, column=1, padx=5, pady=5)
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=2, column=0, padx=5, pady=5)
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=2, column=1, padx=5, pady=5)
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=3, column=0, padx=5, pady=5)
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=3, column=1, padx=5, pady=5)
# 创建十六进制发送选择框
self.hex_var = tk.BooleanVar()
self.hex_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_var)
self.hex_checkbutton.grid(row=4, column=0, padx=5, pady=5)
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=4, column=1, padx=5, pady=5)
# 创建接收数据显示区域(包含滚动条)
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=5, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 初始化串口对象
self.ser = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = SerialToolApp(root)
root.mainloop()
主要改动点
创建接收数据显示区域:
使用 Frame 包裹 Text 和 Scrollbar,以便更好地管理和布局。
在 Frame 中添加了一个 Scrollbar 组件,并将其与 Text 组件绑定。
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=5, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
调整布局:
将 receive_text_frame 放置在第5行,并设置其跨越两列 (columnspan=2) 以适应整个宽度。
设置 sticky='nsew' 以确保 Frame 可以根据窗口大小进行扩展。
使用方法
运行程序后,选择要使用的串口和波特率。
点击“打开”按钮以打开串口。
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
接收数据显示区域会显示接收到的数据,并且可以使用滚动条查看超出视窗范围的内容。
关闭串口时,点击“关闭”按钮。
为了确保接收数据显示区域的滚动条自动滚动到最新数据,并将默认波特率设置为9600,我们需要对现有代码进行一些调整。具体来说:
自动滚动:在每次插入新数据时,调用 Text 小部件的 see(tk.END) 方法来确保视图始终显示最新的数据。
默认波特率:直接在 Combobox 中设置默认值为 9600。
以下是更新后的完整代码示例:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=0, column=0, padx=5, pady=5)
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
self.refresh_ports()
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=1, column=0, padx=5, pady=5)
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=1, column=1, padx=5, pady=5)
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=2, column=0, padx=5, pady=5)
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=2, column=1, padx=5, pady=5)
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=3, column=0, padx=5, pady=5)
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=3, column=1, padx=5, pady=5)
# 创建十六进制发送选择框
self.hex_var = tk.BooleanVar()
self.hex_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_var)
self.hex_checkbutton.grid(row=4, column=0, padx=5, pady=5)
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=4, column=1, padx=5, pady=5)
# 创建接收数据显示区域(包含滚动条)
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=5, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 初始化串口对象
self.ser = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = SerialToolApp(root)
root.mainloop()
主要改动点
自动滚动:
在每次向 receive_text 插入新数据后,调用 self.receive_text.see(tk.END) 方法,以确保视图自动滚动到最新数据的位置。
这个方法分别在 open_serial()、close_serial() 和 send_data() 函数中被调用。
默认波特率:
将 self.baud_combobox.current(0) 设置为默认选择 9600 波特率。
使用方法
运行程序:选择要使用的串口和波特率(默认为 9600)。
打开串口:点击“打开”按钮以打开串口。
发送数据:在“发送数据”文本框中输入要发送的数据。
十六进制发送:如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
发送按钮:点击“发送”按钮发送数据。
接收数据显示:接收到的数据会显示在接收数据显示区域,并且滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
为了调整窗口布局,使得接收数据显示区域位于窗口的上部,而其他控件(如串口选择、波特率选择、打开/关闭按钮、发送数据输入框等)位于窗口的下部,我们需要对现有的布局进行一些调整。我们可以使用 grid 布局管理器来控制每个组件的位置和排列方式。
以下是更新后的完整代码示例
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_var = tk.BooleanVar()
self.hex_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_var)
self.hex_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 初始化串口对象
self.ser = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
主要改动点
布局调整:
将 receive_text_frame 放置在第0行 (row=0),占据两列 (columnspan=2),并设置 sticky='nsew' 以确保其能够扩展填充整个宽度。
其他控件(如串口选择、波特率选择、打开/关闭按钮、发送数据输入框等)从第1行开始 (row=1 及以后),并使用 sticky='e' 和 sticky='w' 来对齐控件的位置。
窗口大小:
使用 root.geometry("400x400") 设置初始窗口大小,可以根据需要调整这个值。
使用方法
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:选择要使用的串口和波特率(默认为 9600)。
打开串口:点击“打开”按钮以打开串口。
发送数据:在“发送数据”文本框中输入要发送的数据,并点击“发送”按钮。
十六进制发送:如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
接收数据显示:接收到的数据会显示在接收数据显示区域,并且滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
为了确保接收数据显示区域能够正确显示接收到的数据,我们需要在打开串口后启动一个后台线程来监听串口数据,并将接收到的数据更新到 Text 小部件中。由于 Tkinter 不支持直接在非主线程中更新 GUI 元素,我们需要使用 root.after() 方法或 queue.Queue 来安全地更新 GUI。
以下是更新后的完整代码示例,包含了一个后台线程用于监听串口数据并将其显示在接收数据显示区域:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_var = tk.BooleanVar()
self.hex_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_var)
self.hex_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
while not self.read_queue.empty():
data = self.read_queue.get()
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
主要改动点
引入 queue.Queue:
使用 queue.Queue 来存储从串口读取的数据,以便安全地在主线程中更新 GUI。
后台线程读取串口数据:
read_from_serial() 方法在一个后台线程中运行,不断地从串口读取数据并将其放入队列中。
线程设置为守护线程 (daemon=True),以确保程序退出时线程也会随之结束。
定期检查队列中的数据:
check_queue() 方法定期检查队列中的数据,并将其插入到 Text 小部件中。
使用 root.after(100, self.check_queue) 方法每隔 100 毫秒调用一次 check_queue(),以实现持续的数据更新。
使用方法
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:选择要使用的串口和波特率(默认为 9600)。
打开串口:点击“打开”按钮以打开串口。
发送数据:在“发送数据”文本框中输入要发送的数据,并点击“发送”按钮。
十六进制发送:如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
接收数据显示:接收到的数据会显示在接收数据显示区域,并且滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
为了增加一个“十六进制显示”复选框,以便用户可以选择是否以十六进制格式显示接收到的数据,我们需要在现有代码中添加一个新的 Checkbutton 控件,并根据其状态决定如何显示接收到的数据。
以下是更新后的完整代码示例:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar()
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
while not self.read_queue.empty():
data = self.read_queue.get()
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
主要改动点
新增“十六进制显示”复选框:
在第6行 (row=6) 添加了一个新的 Checkbutton 控件 hex_receive_checkbutton,用于控制是否以十六进制格式显示接收到的数据。
使用 self.hex_receive_var 来存储该复选框的状态。
处理接收到的数据:
在 check_queue() 方法中,根据 self.hex_receive_var.get() 的值来决定如何显示接收到的数据:
如果勾选了“十六进制显示”,则将数据转换为十六进制字符串并显示。
如果没有勾选,则直接将数据解码为字符串并显示。
使用方法
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:选择要使用的串口和波特率(默认为 9600)。
打开串口:点击“打开”按钮以打开串口。
发送数据:
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
接收数据显示:
接收到的数据会显示在接收数据显示区域。
如果勾选了“十六进制显示”,则数据将以十六进制格式显示;否则,数据将被解码为字符串并显示。
滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。