import PySimpleGUI as sg
import time
import os
import logging
import threading
# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
log = logging.getLogger(__name__)
# 图片路径定义
image_path3 = "D:/software/demo/pythonProject/images/image3.png"
image_path4 = "D:/software/demo/pythonProject/images/image4.png"
image_path5 = "D:/software/demo/pythonProject/images/image5.png"
image_path6 = "D:/software/demo/pythonProject/images/image6.png"
image_path7 = "D:/software/demo/pythonProject/images/image7.png"
def check_image(path):
return path if path and os.path.exists(path) else None
img3 = check_image(image_path3)
img4 = check_image(image_path4)
img5 = check_image(image_path5)
img6 = check_image(image_path6)
img7 = check_image(image_path7)
for path, exists in [(image_path3, img3), (image_path4, img4), (image_path5, img5),
(image_path6, img6), (image_path7, img7)]:
if not exists:
log.warning(f"图片不存在: {path}")
b = 'zbjszj'
# 全局状态变量
reminder_running = False
single_flow_running = False
window7_ref = None # 持有 window7 引用
input_popup_ref = None # 持有当前弹窗引用
def safe_update(window, key, value=None, append=False, **kwargs):
"""安全更新元素"""
if not window or not window.TKroot:
return False
try:
element = window[key]
if hasattr(element, 'widget') and element.widget is None:
return False
current = element.get() or ""
if append:
kwargs['value'] = current + str(value or "")
else:
kwargs['value'] = value
element.update(**kwargs)
return True
except Exception as e:
log.debug(f"更新失败: {e}")
return False
def safe_close(window):
"""安全关闭窗口"""
try:
if window and window.TKroot:
window.close()
except Exception as e:
log.debug(f"关闭异常: {e}")
def center_window(parent_win, popup_win, width=300, height=120):
"""居中弹窗"""
px, py = parent_win.current_location()
pw, ph = parent_win.size
x = int(px + (pw - width) // 2)
y = int(py + (ph - height) // 2)
popup_win.move(x, y)
def create_placeholder(size=(100, 50)):
"""图片缺失占位符"""
return sg.Frame('', [[sg.Text("🖼️", font=('Arial', 16))],
[sg.Text("图片\n缺失", size=size, justification='c')]],
size=size, relief='sunken', pad=5)
# =============================
# ✅ 周期提醒系统:主GUI在主线程,后台线程发事件
# =============================
def start_periodic_reminder(trigger_window):
"""启动周期提醒(由主线程调用)"""
global reminder_running, window7_ref
if reminder_running:
log.info("[!] 提醒已在运行,忽略重复请求")
return
reminder_running = True
layout7 = [
[sg.Image(filename=img7, key='IMG7') if img7 else create_placeholder((120, 60))],
[sg.Text("txdj")],
[sg.Text("📌 周期性提醒已激活:每次取消后10秒自动重试")],
[sg.Multiline("", size=(60, 4), key='-OUTPUT7-', disabled=True, autoscroll=True)],
[sg.Button('yzxpc', key='EXIT')]
]
window7 = sg.Window('jbdz:cc', layout7, size=(475, 480), finalize=True,
keep_on_top=True, enable_close_attempted_event=False)
window7_ref = window7
# 启动后台提醒线程(只负责倒计时和发送事件)
def background_timer():
next_popup_time = time.time() + 1 # 第一次提醒延迟1秒
while reminder_running:
now = time.time()
if now >= next_popup_time:
try:
window7.write_event_value('-SHOW_POPUP_REQUEST-', None)
except Exception as e:
log.warning(f"发送事件失败: {e}")
break
# 等待弹窗被处理完成后再继续
while reminder_running and input_popup_ref is not None:
time.sleep(0.1)
next_popup_time = time.time() + 10
time.sleep(0.5)
try:
window7.write_event_value('-REMINDER_STOPPED-', None)
except:
pass
thread = threading.Thread(target=background_timer, daemon=True)
thread.start()
def handle_popup_request(window7):
"""处理弹窗请求(在主线程执行)"""
global input_popup_ref
if not window7_active():
return
pop_layout = [
[sg.Text('mbwbyd')],
[sg.Input(key='-POPUP_INPUT-', size=(15, 1), focus=True)],
[sg.Button('fsydlc', bind_return_key=True), sg.Button('bsyyd')]
]
input_popup_ref = sg.Window('xkdz', pop_layout, finalize=True, keep_on_top=True, modal=False)
center_window(window7, input_popup_ref, 280, 120)
input_popup_ref.bring_to_front()
def process_input_popup_event(window7, event, values):
"""处理弹窗事件"""
global input_popup_ref, reminder_running
if not input_popup_ref:
return False
if event == 'fsydlc':
user_input = values['-POPUP_INPUT-'].strip()
try:
num = float(user_input)
formatted_num = int(num) if num.is_integer() else num
safe_update(window7, '-OUTPUT7-', f"✔️ 录入成功:{formatted_num} —— 提醒停止\n", append=True)
reminder_running = False
except ValueError:
safe_update(window7, '-OUTPUT7-', f"❌ 输入无效:'{user_input}',10秒后重试...\n", append=True)
finally:
safe_close(input_popup_ref)
input_popup_ref = None
return True
elif event in ('bsyyd', sg.WINDOW_CLOSED):
safe_update(window7, '-OUTPUT7-', "⛔ 用户取消,10秒后将再次提醒...\n", append=True)
safe_close(input_popup_ref)
input_popup_ref = None
return True
return False
def window7_active():
"""判断 window7 是否有效"""
return window7_ref is not None and window7_ref.TKroot is not None
# =============================
# ✅ 单流程主窗口 run_single_flow
# =============================
def run_single_flow():
global single_flow_running
if single_flow_running:
log.info("[!] 单流程已在运行")
return
single_flow_running = True
try:
sg.theme('LightBlue')
input_history = []
last_input_time = None
INPUT_TIMEOUT = 5.0
current_text = ''
layout3 = [
[sg.Image(filename=img3, key='IMG3') if img3 else create_placeholder((120, 60))],
[sg.Text("rmxglx")],
[sg.Text("请在5秒内输入当前数字,之后5秒无操作将自动记录:")],
[sg.Input(key='-INPUT3-', size=(20, 1), focus=True)],
[sg.Text("", size=(60, 1), key='-STATUS3-')],
[sg.Text("已记录的数字(横向排列,自动换行):")],
[sg.Frame('', [[sg.Text("", size=(590, 10), key='-HISTORY3-',
relief='sunken', background_color='white',
text_color='black', font=('Courier', 10))]],
size=(590, 50), pad=10)],
[sg.Button('mphcxxl'), sg.Button('jchdj')],
[sg.Button('txlxk'), sg.Button('bfhycg')]
]
window3 = sg.Window('jbdz:pddb', layout3, size=(630, 565), resizable=True, finalize=True)
window3.keep_on_top_set()
while True:
event3, values3 = window3.read(timeout=100)
if event3 in (None, sg.WINDOW_CLOSED, 'bfhycg'):
break
new_text = values3['-INPUT3-'].strip()
input_changed = new_text != current_text
current_text = new_text
valid_number = False
try:
if current_text:
float(current_text)
valid_number = True
except ValueError:
pass
if input_changed and valid_number:
last_input_time = time.time()
safe_update(window3, '-STATUS3-', f"✅ 输入中 '{current_text}' ... 5秒无操作将自动提交")
if last_input_time is not None:
elapsed = time.time() - last_input_time
if elapsed >= INPUT_TIMEOUT:
try:
num = float(current_text)
formatted_num = int(num) if num.is_integer() else num
input_history.append(formatted_num)
history_str = ' '.join(map(str, input_history))
safe_update(window3, '-HISTORY3-', history_str)
safe_update(window3, '-STATUS3-', "🎉 已自动记录!")
safe_update(window3, '-INPUT3-', '')
current_text = ''
last_input_time = None
except Exception as e:
safe_update(window3, '-STATUS3-', "❌ 提交失败")
last_input_time = None
elif last_input_time is None and current_text and not valid_number:
safe_update(window3, '-STATUS3-', "❌ 请输入有效的数字")
elif last_input_time and valid_number:
remaining = max(0, int(INPUT_TIMEOUT - (time.time() - last_input_time) + 0.9))
if remaining > 0:
safe_update(window3, '-STATUS3-', f"⏳ 还剩 {remaining} 秒自动提交...")
if event3 == 'mphcxxl':
window1.hide()
elif event3 == 'txlxk':
window3.close()
_run_sub_flow_4(input_history)
break
elif event3 == 'jchdj':
window3.close()
window1.un_hide()
start_periodic_reminder(window1) # ✅ 安全启动提醒
break
safe_close(window3)
finally:
single_flow_running = False
# =============================
# ✅ 子流程4 和 保留窗口
# =============================
def _run_sub_flow_4(input_history):
sg.theme('LightBlue')
last_input_time = None
INPUT_TIMEOUT = 5.0
current_text = ''
layout4 = [
[sg.Image(filename=img4, key='IMG4') if img4 else create_placeholder((100, 50))],
[sg.Text("请在5秒内输入当前数字,之后5秒无操作将自动记录:")],
[sg.Input(key='-INPUT4-', size=(20, 1), focus=True)],
[sg.Text("", size=(60, 1), key='-STATUS4-')],
[sg.Text("已记录的数字(横向排列,自动换行):")],
[sg.Frame('', [[sg.Text("", size=(410, 10), key='-HISTORY4-',
relief='sunken', background_color='white',
text_color='black', font=('Courier', 10))]],
size=(410, 50), pad=10)],
[sg.Button('jrrsby'), sg.Button('jrqsby')]
]
window4 = sg.Window('jbdz:pdbyqr', layout4, size=(450, 440), resizable=True, finalize=True)
window4.keep_on_top_set()
while True:
event4, values4 = window4.read(timeout=100)
if event4 in (None, sg.WINDOW_CLOSED):
break
new_text = values4['-INPUT4-'].strip()
input_changed = new_text != current_text
current_text = new_text
valid_number = False
try:
if current_text:
float(current_text)
valid_number = True
except ValueError:
pass
if input_changed and valid_number:
last_input_time = time.time()
safe_update(window4, '-STATUS4-', f"✅ 输入中 '{current_text}' ... 5秒无操作将自动提交")
if last_input_time is not None:
elapsed = time.time() - last_input_time
if elapsed >= INPUT_TIMEOUT:
try:
num = float(current_text)
formatted_num = int(num) if num.is_integer() else num
input_history.append(formatted_num)
history_str = ' '.join(map(str, input_history))
safe_update(window4, '-HISTORY4-', history_str)
safe_update(window4, '-STATUS4-', "🎉 已自动记录!")
safe_update(window4, '-INPUT4-', '')
current_text = ''
last_input_time = None
except Exception as e:
safe_update(window4, '-STATUS4-', "❌ 提交失败")
last_input_time = None
elif last_input_time is None and current_text and not valid_number:
safe_update(window4, '-STATUS4-', "❌ 请输入有效的数字")
elif last_input_time and valid_number:
remaining = max(0, int(INPUT_TIMEOUT - (time.time() - last_input_time) + 0.9))
if remaining > 0:
safe_update(window4, '-STATUS4-', f"⏳ 还剩 {remaining} 秒自动提交...")
if event4 == 'jrrsby':
window4.close()
_open_retain_window('jbdz:rsby', img5, 'cxzjqpqh', input_history)
break
elif event4 == 'jrqsby':
window4.close()
_open_retain_window('jbdz:qsby', img6, 'cxzcpb', input_history)
break
safe_close(window4)
def _open_retain_window(title, image_path, button_key, shared_history):
sg.theme('LightBlue')
recorded_numbers = []
layout = [
[sg.Image(filename=image_path) if image_path else create_placeholder((80, 40))],
[sg.Text("请输入当前数字:")],
[sg.Input(key='-RETAIN_INPUT-', size=(30, 1))],
[sg.Button('确认', key=button_key, bind_return_key=True), sg.Button('清空')],
[sg.HorizontalSeparator()],
[sg.Text("已保留的数字(横向排列,自动换行):")],
[sg.Text("", size=(50, 2), key='-RETAIN_HISTORY-',
relief='sunken', background_color='white', text_color='black')]
]
window = sg.Window(title, layout, finalize=True)
window.keep_on_top_set()
while True:
event, values = window.read()
if event in (None, sg.WINDOW_CLOSED):
break
if event == button_key:
user_input = values['-RETAIN_INPUT-'].strip()
if not user_input:
safe_update(window, '-RETAIN_HISTORY-', "⚠️ 输入为空")
else:
try:
num = float(user_input)
formatted_num = int(num) if num.is_integer() else num
recorded_numbers.append(formatted_num)
history_text = ' '.join(map(str, recorded_numbers))
safe_update(window, '-RETAIN_HISTORY-', history_text)
shared_history.append(formatted_num)
window['-RETAIN_INPUT-'].update('')
start_periodic_reminder(window) # ✅ 再次安全启动
except ValueError:
safe_update(window, '-RETAIN_HISTORY-', "❌ 不是有效数字!")
elif event == '清空':
window['-RETAIN_INPUT-'].update('')
safe_update(window, '-RETAIN_HISTORY-', "")
safe_close(window)
# ========== 主程序入口 ==========
if __name__ == "__main__":
sg.theme('DarkBlue3')
window_position = (435, 0)
layout1 = [
[sg.Text('jdsq---')],
[sg.Button('tips:zb'), sg.Button('wykd')]
]
window1 = sg.Window('jdsq', layout1, size=(1665, 68), no_titlebar=True,
location=window_position, finalize=True)
window1.keep_on_top_set()
# 主事件循环(统一处理所有窗口)
while True:
event, values = sg.read_all_windows(timeout=100)
# 统一关闭处理
if event in (None, sg.WINDOW_CLOSED):
win = values.get('window') if isinstance(values, dict) else None
if win == window7_ref:
safe_close(window7_ref)
window7_ref = None
reminder_running = False
elif win == window1:
break
continue
# 处理 window1 事件
if event == 'tips:zb':
win2 = sg.Window('ktrx', [[sg.Text(b, size=(170, 2), auto_size_text=True)]],
size=(940, 210), keep_on_top=True, finalize=True)
win2.read(timeout=5000, close=True)
elif event == 'wykd':
run_single_flow()
# 处理 window7 的自定义事件
elif window7_ref and event == '-SHOW_POPUP_REQUEST-':
handle_popup_request(window7_ref)
elif window7_ref and event == 'EXIT':
safe_close(window7_ref)
window7_ref = None
reminder_running = False
# 处理弹窗事件
elif input_popup_ref and event in ('fsydlc', 'bsyyd'):
process_input_popup_event(window7_ref, event, values)
# 清理退出
safe_close(window1)
log.info("程序正常退出")
运行提示Exception in thread Thread-2:
Traceback (most recent call last):
File "D:\software\python\lib\threading.py", line 932, in _bootstrap_inner
self.run()
File "D:\software\python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "D:\software\demo\pythonProject\study\mypy网络询问修改测试.py", line 104, in run_periodic_reminder
window7 = sg.Window('jbdz:cc', layout7, size=(475, 480), finalize=True,
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 9616, in __init__
self.Finalize()
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10302, in finalize
self.Read(timeout=1)
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10077, in read
results = self._read(timeout=timeout, timeout_key=timeout_key)
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10148, in _read
self._Show()
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 9888, in _Show
StartupTK(self)
File "D:\software\demo\pythonProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 16940, in StartupTK
window.TKroot.mainloop()
File "D:\software\python\lib\tkinter\__init__.py", line 1429, in mainloop
self.tk.mainloop(n)
RuntimeError: Calling Tcl from different apartment