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

PySimpleGUI按钮事件无响应及跨线程创建窗口报错求助

问题分析与解决

核心错误原因

你遇到的RuntimeError: Calling Tcl from different apartment是因为PySimpleGUI(底层基于tkinter)不允许在非主线程中创建或操作GUI元素。你的recv_msg是子线程,却在里面直接调用window2()创建新窗口,这违反了tkinter的单线程GUI规则,导致窗口按钮无响应且触发报错。

第一个窗口的交互问题,也源于主线程与子线程的资源冲突。

解决思路

所有GUI操作必须放在主线程执行,子线程只负责接收网络消息,然后通过PySimpleGUI的window.write_event_value()方法向主线程发送事件通知,由主线程处理窗口创建、弹窗等GUI逻辑。

具体修改步骤

  1. 子线程只发事件,不碰GUI:修改recv_msg函数,遇到需要创建窗口的消息时,给主线程窗口发送自定义事件,而非直接调用window2()。
  2. 主线程监听自定义事件:在主窗口的事件循环中,监听子线程发来的事件,触发时创建第二个窗口。
  3. 修复全局变量问题:read_keys函数中prikey是局部变量,需要用global声明,否则全局prikey始终为None,解密会失败。

修改后的完整代码

import os
import threading
import PySimpleGUI as gui
from rsa_controller import decryptwithPrivatekey, loadPublicKey, loadPrivateKey

id = 0
target_id = 0
prikey = None


def popout(title):
    gui.popup(title)


def read_keys():
    global prikey  # 声明使用全局变量
    print("Opening key files: " + os.getcwd() + "\\keys\\")
    pubkey = loadPublicKey(os.getcwd() + "\\keys\\public.pem")
    prikey = loadPrivateKey(os.getcwd() + "\\keys\\private.pem")


def recv_msg(main_window):
    global target_id
    from main import s
    while True:
        try:
            data = s.recv(1024)
            if not data:
                break
            decoded_data = data.decode('utf-8')
            if decoded_data == 'target_connected_Success':
                print("Received message:", decoded_data)
                main_window.write_event_value('-SUCCESS-', decoded_data)
            elif decoded_data.startswith("!+@"):
                target_id = decoded_data[3:]
                # 发送自定义事件给主线程,触发创建窗口2
                main_window.write_event_value('-SHOW_CONNECT_WINDOW-', target_id)
            elif decoded_data == 'target_connect_denied':
                main_window.write_event_value('-POPUP_DENIED-', '')
            else:
                msg_to_recv = decryptwithPrivatekey(decoded_data, prikey)
                print("Received message:", msg_to_recv)
                main_window.write_event_value('-RECV_MSG-', msg_to_recv)
        except Exception as e:
            print(f"接收消息出错: {e}")
            break


def window2(target_id):
    from main import s
    layout2 = [
        [gui.Text('Connecting with'), gui.Text(str(target_id), key='target_id'), gui.Text("Establishing contact")],
        [gui.Button('Accept and share my public key', key='accept'),
         gui.Button('Deny connection invitation', key='denied')]
    ]
    window = gui.Window("Connection Request", layout2)
    while True:
        event2, values2 = window.read()
        if event2 == gui.WINDOW_CLOSED:
            break
        if event2 == 'denied':
            print("Connection denied")
            s.send('!x!{}'.format(target_id).encode('utf-8'))
            window.close()
        if event2 == 'accept':
            print("Accepting and sharing public key")
            # 在这里添加接受连接后分享公钥的逻辑
            window.close()
    window.close()


def start_GUI_progress(user_id):
    global id
    id = user_id
    from main import s
    read_keys()
    layout = [
        [gui.Text('Your identification code'), gui.Text(id)],
        [gui.Text('Hint: Please enter the identification code of the person you want to connect to in the input box below and click the Connect button')],
        [gui.Input(key='target_id'), gui.Button('Connect', key='connect')]
    ]
    window = gui.Window("RSA Encrypted Chat Software", layout, finalize=True)
    host = "localhost"
    port = 23333
    s.connect((host, port))
    print(s.recv(1024))
    # 把主窗口传给子线程,用于发送事件
    t_recv = threading.Thread(target=recv_msg, args=(window,), daemon=True)
    t_recv.start()
    s.send(b"__!" + str(id).encode('utf-8'))
    
    # 主线程事件循环,处理所有GUI操作
    while True:
        event, values = window.read()
        if event is None:
            break
        if event == 'connect':
            print("Client is attempting to connect to: {}".format(values['target_id']))
            message = "_!?{}".format(values['target_id'])
            s.send(message.encode('utf-8'))
        # 处理子线程发来的创建连接窗口事件
        elif event == '-SHOW_CONNECT_WINDOW-':
            window2(values[event])
        # 处理连接成功事件
        elif event == '-SUCCESS-':
            gui.popup(values[event])
        # 处理连接被拒绝弹窗
        elif event == '-POPUP_DENIED-':
            gui.popup('Connection request denied')
        # 处理接收消息事件(后续扩展聊天窗口用)
        elif event == '-RECV_MSG-':
            print(f"收到解密消息: {values[event]}")
    window.close()

关键修改说明

  • recv_msg函数接收主窗口对象,通过main_window.write_event_value()发送自定义事件,所有GUI操作交给主线程。
  • 主窗口的事件循环新增对自定义事件的监听,触发时调用window2()创建窗口,确保窗口在主线程中初始化。
  • 修复了read_keys中prikey的全局变量声明问题,避免解密时出现None错误。
  • 子线程设置为daemon=True,确保主程序退出时子线程自动终止。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:02:01