You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Tkinter如何同时运行两个while循环实现单窗口聊天功能

Tkinter整合双端聊天系统实现方案

核心注意事项

原脚本的阻塞式while True循环+input()逻辑不能直接放到Tkinter代码中,否则会直接阻塞UI主线程导致窗口无响应,实现时遵循以下规则即可:

  • 公共配置、加密工具、请求会话全局初始化,避免重复加载
  • 5秒间隔的消息拉取逻辑放到守护子线程运行,和UI线程隔离
  • 子线程不可直接操作Tkinter控件,通过Tkinter自带的after()方法调度UI更新,规避线程安全问题
  • 替换原命令行input()为Tkinter输入控件,绑定按钮/回车事件触发消息发送
  • 增加全局退出标志位,窗口关闭时自动终止轮询循环、发送离会消息

整合后完整代码

首先修正原代码里的HTML转义错误(原代码中&amp;转义为&、&lt;&lt;&lt;&gt;&gt;&gt;转义为<<<>>>,修复缩进问题),整合代码如下:

import tkinter as tk
from tkinter import scrolledtext
import threading
import datetime
import json
from time import sleep
from datetime import datetime
import requests
from cryptography.fernet import Fernet
from pytz import timezone, utc
import configme as con

# -------------------------- 全局配置初始化 --------------------------
nam = con.my_name
cookies_dict = con.cookie
key = con.crypto_key
send_url = con.base_url + '/config.php'
log_url = con.base_url + '/log.json'
poll_interval = con.receive_time
user_timezone = timezone(con.my_timezone)
time_format = con.date_time_format

# 加密实例
fernet = Fernet(key)
# 请求会话复用
req_session = requests.Session()
# 已读消息偏移量
msg_offset = 0
# 线程退出标志
exit_flag = False

# -------------------------- 工具函数 --------------------------
def make_msg_id():
    return datetime.datetime.now().timestamp()

def format_localtime(timestamp):
    stamptime = int(float(timestamp))
    gmt0_time = utc.localize(datetime.utcfromtimestamp(stamptime))
    return gmt0_time.astimezone(user_timezone).strftime(time_format)

def send_leave_msg():
    """发送离会系统消息"""
    req_session.get(
        send_url,
        params={
            'iD': make_msg_id(),
            'name': '<<<>>>',
            'msg': f"{nam} has left the room."
        },
        cookies=cookies_dict
    )

# -------------------------- UI逻辑 --------------------------
root = tk.Tk()
root.title("简易聊天客户端")
root.geometry("750x550")

# 消息显示区(只读)
msg_box = scrolledtext.ScrolledText(root, wrap=tk.WORD, state='disabled')
msg_box.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

# 底部输入区
input_frame = tk.Frame(root)
input_frame.pack(padx=10, pady=(0,10), fill=tk.X)

msg_input = tk.Entry(input_frame)
msg_input.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))

def send_msg(event=None):
    """发送消息逻辑,绑定回车和发送按钮"""
    content = msg_input.get().strip()
    msg_input.delete(0, tk.END)
    if not content:
        return
    if content == ".exit":
        on_window_close()
        return
    # 加密发送
    enc_msg = fernet.encrypt(content.encode())
    req_session.get(
        send_url,
        params={
            'iD': make_msg_id(),
            'name': nam,
            'msg': enc_msg
        },
        cookies=cookies_dict
    )

send_btn = tk.Button(input_frame, text="发送", width=8, command=send_msg)
send_btn.pack(side=tk.RIGHT)
# 绑定回车发送
msg_input.bind('<Return>', send_msg)

def update_msg_box(new_msg):
    """线程安全更新消息框"""
    msg_box.config(state='normal')
    msg_box.insert(tk.END, new_msg + "\n")
    msg_box.config(state='disabled')
    msg_box.see(tk.END)

# -------------------------- 消息轮询子线程 --------------------------
def poll_msg_loop():
    global msg_offset
    while not exit_flag:
        try:
            resp = req_session.get(log_url, cookies=cookies_dict, timeout=10)
            msg_list = json.loads(resp.text)
            total_msg = len(msg_list)
            
            # 首次加载处理
            if msg_offset == 0 and total_msg == 0:
                root.after(0, update_msg_box, "当前聊天室暂无历史消息")
                msg_offset = total_msg
                sleep(poll_interval)
                continue
            
            # 遍历新消息
            for msg in msg_list[msg_offset:]:
                local_time = format_localtime(msg['id'])
                if msg['nam'] == '<<<>>>':
                    # 系统消息
                    show_text = f"{local_time} :: 系统提示 :: {msg['msg']}"
                else:
                    # 普通消息解密
                    dec_msg = fernet.decrypt(bytes(msg['msg'], "utf-8")).decode()
                    show_text = f"{local_time} :: {msg['nam']} :: {dec_msg}"
                # 调度到主线程更新UI
                root.after(0, update_msg_box, show_text)
            
            msg_offset = total_msg
        except Exception as e:
            root.after(0, update_msg_box, f"[拉取消息出错] {str(e)}")
        sleep(poll_interval)

def on_window_close():
    """窗口关闭回调"""
    global exit_flag
    exit_flag = True
    try:
        send_leave_msg()
    except:
        pass
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_window_close)

# -------------------------- 启动逻辑 --------------------------
if __name__ == "__main__":
    # 发送入会消息
    if len(nam.strip()) > 0:
        req_session.get(
            send_url,
            params={
                'iD': make_msg_id(),
                'name': '<<<>>>',
                'msg': f"{nam} join the room."
            },
            cookies=cookies_dict
        )
    # 启动轮询线程
    poll_thread = threading.Thread(target=poll_msg_loop, daemon=True)
    poll_thread.start()
    # 启动UI主循环
    root.mainloop()

使用说明

  • 确保原configme.py配置文件和整合后的脚本放在同一目录下,依赖库(requests、cryptography、pytz)提前安装
  • 输入框输入内容按回车或者点发送按钮即可发消息,输入.exit即可退出聊天室
  • 消息拉取间隔沿用原配置文件里的receive_time参数,不需要额外修改

内容的提问来源于stack exchange,提问作者Zahid Hossen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:39:19