diff --git a/ChanceCustom/GUI.py b/ChanceCustom/GUI.py index 97ff7f1..6234058 100644 --- a/ChanceCustom/GUI.py +++ b/ChanceCustom/GUI.py @@ -48,6 +48,65 @@ } +_owned_root = None + + +def resolve_host_root(): + """取宿主(OlivOS 插件进程)提供的 tk 根窗口,取不到时返回 None""" + proc = getattr(ChanceCustom.load, 'globalProc', None) + getter = getattr(proc, 'get_main_root', None) + if getter is None: + return None + try: + return getter() + except Exception: + return None + + +def _is_alive(widget): + if widget is None: + return False + try: + return bool(widget.winfo_exists()) + except tkinter.TclError: + return False + + +def get_shared_root(): + """返回本模块共用的 tk 根窗口 + + 优先用宿主 main_tk;它和 tkinter._default_root 不是同一个解释器时以 _default_root 为准, + 因为未指定 master 的 StringVar/BooleanVar 会绑到 _default_root,解释器不一致会让控件 + 初始化失败或取值不生效。宿主没有根窗口时(system.proc_mode = webUI)自建一个隐藏根窗口 + 并 withdraw(),这样所有窗口共用同一个解释器,且面板之间互为兄弟窗口。 + """ + global _owned_root + default_root = getattr(tkinter, '_default_root', None) + host_root = resolve_host_root() + if _is_alive(host_root) and (not _is_alive(default_root) or host_root.tk is default_root.tk): + return host_root + if _is_alive(default_root): + return default_root + if _is_alive(host_root): + return host_root + if not _is_alive(_owned_root): + _owned_root = tkinter.Tk() + _owned_root.withdraw() + return _owned_root + + +def create_window(master=None): + """创建窗口对象,供本模块所有顶层窗口使用 + + 所有窗口都挂到同一个根窗口下:面板之间互为兄弟,关掉一个不会带走另一个。 + 不要直接写 tkinter.Toplevel():_default_root 为空时 tkinter 会隐式创建一个标题为 + tk 的空白根窗口,本窗口成为它的子窗口——关本窗口它留着,关它本窗口一起消失。 + """ + if not _is_alive(master): + master = get_shared_root() + return tkinter.Toplevel(master) + + class BrowserChoiceDialog(object): def __init__(self, parent, title, message, url): self.result = 'cancel' @@ -133,7 +192,7 @@ def __init__(self, Model_name, logger_proc=None): self.UIConfig.update(dictColorContext) def start(self): - self.UIObject['root'] = tkinter.Toplevel() + self.UIObject['root'] = create_window() self.UIObject['root'].title('程心自定义') self.UIObject['root'].geometry('518x430') self.UIObject['root'].resizable(width=False, height=False) @@ -835,7 +894,7 @@ def __init__(self, action, key=None, bot_hash='unity', edit_commit_callback=None self.UIData = {} def start(self): - self.UIObject['edit_root'] = tkinter.Toplevel() + self.UIObject['edit_root'] = create_window() self.UIObject['edit_root'].title('编辑') if 'create' == self.action: self.UIObject['edit_root'].title('添加') @@ -1045,7 +1104,7 @@ def __init__(self, action, key=None, bot_hash='unity', edit_commit_callback=None self.UIData['click_record'] = {} def start(self): - self.UIObject['edit_root'] = tkinter.Toplevel() + self.UIObject['edit_root'] = create_window() self.UIObject['edit_root'].title('打包...') self.UIObject['edit_root'].geometry('600x400') self.UIObject['edit_root'].resizable(width=False, height=False) diff --git a/ChanceCustom/app.json b/ChanceCustom/app.json index b2c851a..93796a9 100644 --- a/ChanceCustom/app.json +++ b/ChanceCustom/app.json @@ -4,8 +4,8 @@ "namespace" : "ChanceCustom", "info" : "以自定义回复为核心,附带群管、监控、词库、语音、图片、调用接口、对接网站、黑白名单、定时任务、网页爬虫、简单编程、入群验证、发言限制、简易QQ、Web服务器、制作接口等功能。交流群661366095", "priority" : 40000, - "version" : "0.2.24", - "svn" : 39, + "version" : "0.2.25", + "svn" : 40, "compatible_svn" : 190, "message_mode" : "old_string", "support" : [ diff --git a/ChanceCustom/main.py b/ChanceCustom/main.py index 4c82cce..20e64aa 100644 --- a/ChanceCustom/main.py +++ b/ChanceCustom/main.py @@ -18,8 +18,8 @@ import platform -version = '0.2.24' -svn = 39 +version = '0.2.25' +svn = 40 version_full = '%s(%d)' % (version, svn)