PySimpleGUI按钮事件无响应及跨线程创建窗口报错求助
问题分析与解决
核心错误原因
你遇到的RuntimeError: Calling Tcl from different apartment是因为PySimpleGUI(底层基于tkinter)不允许在非主线程中创建或操作GUI元素。你的recv_msg是子线程,却在里面直接调用window2()创建新窗口,这违反了tkinter的单线程GUI规则,导致窗口按钮无响应且触发报错。
第一个窗口的交互问题,也源于主线程与子线程的资源冲突。
解决思路
所有GUI操作必须放在主线程执行,子线程只负责接收网络消息,然后通过PySimpleGUI的window.write_event_value()方法向主线程发送事件通知,由主线程处理窗口创建、弹窗等GUI逻辑。
具体修改步骤
- 子线程只发事件,不碰GUI:修改
recv_msg函数,遇到需要创建窗口的消息时,给主线程窗口发送自定义事件,而非直接调用window2()。 - 主线程监听自定义事件:在主窗口的事件循环中,监听子线程发来的事件,触发时创建第二个窗口。
- 修复全局变量问题:
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
相关产品推荐
相关产品推荐

