config.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. from utils.logger import GetLog
  2. import os
  3. import sys
  4. import psutil # 进程检查
  5. import json
  6. from enum import Enum
  7. import tkinter as tk
  8. import tkinter.messagebox
  9. from locale import getdefaultlocale
  10. Log = GetLog()
  11. # 项目属性
  12. class Umi:
  13. name = None # 带版本号的名称
  14. pname = None # 纯名称,固定
  15. ver = None # 版本号
  16. website = None # 主页
  17. about = None # 简介
  18. path = os.path.realpath(sys.argv[0]) # 当前入口文件的路径
  19. cwd = os.path.dirname(path) # 当前应设的工作目录
  20. # 重设工作目录,防止开机自启丢失工作目录。此行代码必须比asset.py等模块优先执行
  21. os.chdir(Umi.cwd)
  22. # 枚举
  23. class RunModeFlag(Enum):
  24. '''进程管理模式标志'''
  25. short = 0 # 按需关闭(减少空闲时内存占用)
  26. long = 1 # 后台常驻(大幅加快任务启动速度)
  27. class ScsModeFlag(Enum):
  28. '''截屏模式标志'''
  29. multi = 0 # 多屏幕模式,目前仅能适配缩放比相同的多个屏幕
  30. system = 1 # 系统截屏模式
  31. class ClickTrayModeFlag(Enum):
  32. '''点击托盘时模式标志'''
  33. show = 0 # 显示主面板
  34. screenshot = 1 # 截屏
  35. clipboard = 2 # 粘贴图片
  36. class WindowTopModeFlag():
  37. '''窗口置顶模式标志'''
  38. # 不继承枚举
  39. never = 0 # 永不,静默模式
  40. finish = 1 # 任务完成时置顶
  41. # 配置文件路径
  42. ConfigJsonFile = 'Umi-OCR_config.json'
  43. # 配置项
  44. _ConfigDict = {
  45. # 软件设置
  46. 'isDebug': { # T时Debug模式
  47. 'default': False,
  48. 'isSave': True,
  49. 'isTK': True,
  50. },
  51. 'isAdvanced': { # T时高级模式,显示额外的设置项
  52. 'default': False,
  53. 'isSave': True,
  54. 'isTK': True,
  55. },
  56. 'isTray': { # T时展示托盘图标
  57. 'default': True,
  58. 'isSave': True,
  59. 'isTK': True,
  60. },
  61. 'isBackground': { # T时点关闭进入后台运行
  62. 'default': True,
  63. 'isSave': True,
  64. 'isTK': True,
  65. },
  66. 'clickTrayModeName': { # 当前选择的点击托盘图标模式名称
  67. 'default': '',
  68. 'isSave': True,
  69. 'isTK': True,
  70. },
  71. 'clickTrayMode': { # 点击托盘图标模式
  72. 'default': {
  73. '显示面板': ClickTrayModeFlag.show,
  74. '屏幕截图': ClickTrayModeFlag.screenshot,
  75. '粘贴图片': ClickTrayModeFlag.clipboard,
  76. },
  77. 'isSave': False,
  78. 'isTK': False,
  79. },
  80. 'textpanelFontFamily': { # 主输出面板字体
  81. 'default': 'Microsoft YaHei UI',
  82. 'isSave': True,
  83. 'isTK': True,
  84. },
  85. 'textpanelFontSize': { # 主输出面板字体大小
  86. 'default': 14,
  87. 'isSave': True,
  88. 'isTK': True,
  89. },
  90. 'isTextpanelFontBold': { # T时主输出面板字体加粗
  91. 'default': False,
  92. 'isSave': True,
  93. 'isTK': True,
  94. },
  95. 'isWindowTop': { # T时窗口置顶
  96. 'default': False,
  97. 'isSave': True,
  98. 'isTK': True,
  99. },
  100. 'WindowTopMode': { # 窗口置顶模式
  101. 'default': WindowTopModeFlag.finish,
  102. 'isSave': True,
  103. 'isTK': True,
  104. },
  105. 'isNotify': { # T时启用消息弹窗
  106. 'default': True,
  107. 'isSave': True,
  108. 'isTK': True,
  109. },
  110. 'isAutoStartup': { # T时已添加开机自启
  111. 'default': False,
  112. 'isSave': True,
  113. 'isTK': True,
  114. },
  115. 'isStartMenu': { # T时已添加开始菜单
  116. 'default': False,
  117. 'isSave': True,
  118. 'isTK': True,
  119. },
  120. 'isDesktop': { # T时已添加桌面快捷方式
  121. 'default': False,
  122. 'isSave': True,
  123. 'isTK': True,
  124. },
  125. # 快捷识图设置
  126. 'isHotkeyClipboard': { # T时启用读剪贴板快捷键
  127. 'default': True,
  128. 'isSave': True,
  129. 'isTK': True,
  130. },
  131. 'hotkeyClipboard': { # 读剪贴板快捷键,字符串
  132. 'default': 'win+alt+v',
  133. 'isSave': True,
  134. 'isTK': True,
  135. },
  136. 'isHotkeyScreenshot': { # T时启用截屏快捷键
  137. 'default': True,
  138. 'isSave': True,
  139. 'isTK': True,
  140. },
  141. 'isScreenshotHideWindow': { # T时截屏前隐藏窗口
  142. 'default': False,
  143. 'isSave': True,
  144. 'isTK': True,
  145. },
  146. 'screenshotHideWindowWaitTime': { # 截屏隐藏窗口前等待时间
  147. 'default': 200,
  148. 'isSave': True,
  149. 'isTK': False,
  150. },
  151. 'hotkeyScreenshot': { # 截屏快捷键,字符串
  152. 'default': 'win+alt+c',
  153. 'isSave': True,
  154. 'isTK': True,
  155. },
  156. 'hotkeyMaxTtl': { # 组合键最长TTL(生存时间)
  157. 'default': 2.0,
  158. 'isSave': True,
  159. 'isTK': True,
  160. },
  161. 'isHotkeyStrict': { # T时组合键严格判定
  162. 'default': False,
  163. 'isSave': True,
  164. 'isTK': True,
  165. },
  166. 'scsModeName': { # 当前选择的截屏模式名称
  167. 'default': '',
  168. 'isSave': True,
  169. 'isTK': True,
  170. },
  171. 'scsMode': { # 截屏模式
  172. 'default': {
  173. 'Umi-OCR 软件截图': ScsModeFlag.multi,
  174. 'Windows 系统截图': ScsModeFlag.system,
  175. },
  176. 'isSave': False,
  177. 'isTK': False,
  178. },
  179. 'scsColorLine': { # 截屏瞄准线颜色
  180. 'default': '#3366ff',
  181. 'isSave': True,
  182. 'isTK': True,
  183. },
  184. 'scsColorBoxUp': { # 截屏瞄准盒上层颜色
  185. 'default': '#000000',
  186. 'isSave': True,
  187. 'isTK': True,
  188. },
  189. 'scsColorBoxDown': { # 截屏瞄准盒下层颜色
  190. 'default': '#ffffff',
  191. 'isSave': True,
  192. 'isTK': True,
  193. },
  194. 'isNeedCopy': { # T时识别完成后自动复制文字
  195. 'default': False,
  196. 'isSave': True,
  197. 'isTK': True,
  198. },
  199. 'isNeedClear': { # T时输出前清空面板
  200. 'default': False,
  201. 'isSave': True,
  202. 'isTK': True,
  203. },
  204. 'isShowImage': { # T时截图后展示窗口,F时直接识别
  205. 'default': False,
  206. 'isSave': True,
  207. 'isTK': True,
  208. },
  209. # 计划任务设置
  210. 'isOpenExplorer': { # T时任务完成后打开资源管理器到输出目录
  211. 'default': False,
  212. 'isSave': True,
  213. 'isTK': True,
  214. },
  215. 'isOpenOutputFile': { # T时任务完成后打开输出文件
  216. 'default': False,
  217. 'isSave': True,
  218. 'isTK': True,
  219. },
  220. 'isOkMission': { # T时本次任务完成后执行指定计划任务。
  221. 'default': False,
  222. 'isSave': False,
  223. 'isTK': True,
  224. },
  225. 'okMissionName': { # 当前选择的计划任务的name。
  226. 'default': '',
  227. 'isSave': True,
  228. 'isTK': True,
  229. },
  230. 'okMission': { # 计划任务事件,code为cmd代码
  231. 'default': {
  232. '关机': # 取消:shutdown /a
  233. {'code': r'msg %username% /time:25 "Umi-OCR任务完成,将在30s后关机" & echo 关闭本窗口可取消关机 & choice /t 30 /d y /n >nul & shutdown /f /s /t 0'},
  234. '休眠': # 用choice实现延时
  235. {'code': r'msg %username% /time:25 "Umi-OCR任务完成,将在30s后休眠" & echo 关闭本窗口可取消休眠 & choice /t 30 /d y /n >nul & shutdown /f /h'},
  236. },
  237. 'isSave': True,
  238. 'isTK': False,
  239. },
  240. # 输入文件设置
  241. 'isRecursiveSearch': { # T时导入文件夹将递归查找子文件夹中所有图片
  242. 'default': False,
  243. 'isSave': True,
  244. 'isTK': True,
  245. },
  246. # 输出文件设置
  247. 'isOutputTxt': { # T时输出内容写入txt文件
  248. 'default': True,
  249. 'isSave': True,
  250. 'isTK': True,
  251. },
  252. 'isOutputSeparateTxt': { # T时输出内容写入每个图片同名的单独txt文件
  253. 'default': False,
  254. 'isSave': True,
  255. 'isTK': True,
  256. },
  257. 'isOutputMD': { # T时输出内容写入md文件
  258. 'default': False,
  259. 'isSave': True,
  260. 'isTK': True,
  261. },
  262. 'isOutputJsonl': { # T时输出内容写入jsonl文件
  263. 'default': False,
  264. 'isSave': True,
  265. 'isTK': True,
  266. },
  267. 'outputFilePath': { # 输出文件目录
  268. 'default': '',
  269. 'isSave': False,
  270. 'isTK': True,
  271. },
  272. 'outputFileName': { # 输出文件名称
  273. 'default': '',
  274. 'isSave': False,
  275. 'isTK': True,
  276. },
  277. # 输出格式设置
  278. 'isIgnoreNoText': { # T时忽略(不输出)没有文字的图片信息
  279. 'default': True,
  280. 'isSave': True,
  281. 'isTK': True,
  282. },
  283. # 文块后处理
  284. 'tbpuName': { # 当前选择的文块后处理
  285. 'default': '',
  286. 'isSave': True,
  287. 'isTK': True,
  288. },
  289. 'tbpu': { # 文块后处理。这个参数通过 ocr\tbpu\__init__.py 导入,避免循环引用
  290. 'default': {
  291. '通用': None,
  292. },
  293. 'isSave': False,
  294. 'isTK': False,
  295. },
  296. 'isAreaWinAutoTbpu': { # T时忽略区域编辑器预览文本块后处理
  297. 'default': False,
  298. 'isSave': True,
  299. 'isTK': True,
  300. },
  301. # 引擎设置
  302. 'ocrToolPath': { # 引擎路径
  303. 'default': 'PaddleOCR-json/PaddleOCR_json.exe',
  304. 'isSave': True,
  305. 'isTK': False,
  306. },
  307. 'ocrRunModeName': { # 当前选择的进程管理策略
  308. 'default': '',
  309. 'isSave': True,
  310. 'isTK': True,
  311. },
  312. 'ocrRunMode': { # 进程管理策略
  313. 'default': {
  314. '后台常驻(大幅加快任务启动速度)': RunModeFlag.long,
  315. '按需关闭(减少空闲时内存占用)': RunModeFlag.short,
  316. },
  317. 'isSave': False,
  318. 'isTK': False,
  319. },
  320. 'ocrProcessStatus': { # 进程运行状态字符串,由引擎单例传到tk窗口
  321. 'default': '未启动',
  322. 'isSave': False,
  323. 'isTK': True,
  324. },
  325. 'ocrConfigName': { # 当前选择的配置文件的name
  326. 'default': '',
  327. 'isSave': True,
  328. 'isTK': True,
  329. },
  330. 'ocrConfig': { # 配置文件信息
  331. 'default': { # 配置文件信息
  332. '简体中文': {
  333. 'path': 'PaddleOCR_json_config_ch.txt'
  334. }
  335. },
  336. 'isSave': True,
  337. 'isTK': False,
  338. },
  339. 'argsStr': { # 启动参数字符串
  340. 'default': '',
  341. 'isSave': True,
  342. 'isTK': True,
  343. },
  344. 'isOcrAngle': { # T时启用cls
  345. 'default': False,
  346. 'isSave': True,
  347. 'isTK': True,
  348. },
  349. 'ocrCpuThreads': { # CPU线程数
  350. 'default': 10,
  351. 'isSave': True,
  352. 'isTK': True,
  353. },
  354. 'isOcrMkldnn': { # 启用mkldnn加速
  355. 'default': True,
  356. 'isSave': True,
  357. 'isTK': True,
  358. },
  359. 'ocrLimitModeName': { # 当前选择的压缩限制模式的name
  360. 'default': '',
  361. 'isSave': True,
  362. 'isTK': True,
  363. },
  364. 'ocrLimitMode': { # 压缩限制模式
  365. 'default': {
  366. '长边压缩模式': 'max',
  367. '短边扩大模式': 'min',
  368. },
  369. 'isSave': False,
  370. 'isTK': False,
  371. },
  372. 'ocrLimitSize': { # 压缩阈值
  373. 'default': 960,
  374. 'isSave': True,
  375. 'isTK': True,
  376. },
  377. 'ocrRamMaxFootprint': { # 内存占用容量上限
  378. 'default': 0,
  379. 'isSave': True,
  380. 'isTK': True,
  381. },
  382. 'ocrRamMaxTime': { # 内存占用时间上限
  383. 'default': 0,
  384. 'isSave': True,
  385. 'isTK': True,
  386. },
  387. 'imageSuffix': { # 图片后缀
  388. 'default': '.jpg .jpe .jpeg .jfif .png .webp .bmp .tif .tiff',
  389. 'isSave': True,
  390. 'isTK': True,
  391. },
  392. # 更改语言窗口
  393. 'isLanguageWinAutoExit': { # T时语言窗口自动关闭
  394. 'default': False,
  395. 'isSave': True,
  396. 'isTK': True,
  397. },
  398. 'isLanguageWinAutoOcr': { # T时语言窗口修改后重复任务
  399. 'default': False,
  400. 'isSave': True,
  401. 'isTK': True,
  402. },
  403. # 防止多开相关
  404. 'processID': { # 正在运行的进程的PID
  405. 'default': -1,
  406. 'isSave': True,
  407. 'isTK': False,
  408. },
  409. 'processKey': { # 可标识一个进程的信息,如进程名或路径
  410. 'default': '',
  411. 'isSave': True,
  412. 'isTK': False,
  413. },
  414. # 记录不再提示
  415. 'promptScreenshotScale': { # 截图时比例不对
  416. 'default': True,
  417. 'isSave': True,
  418. 'isTK': False,
  419. },
  420. 'promptMultiOpen': { # 多开提示
  421. 'default': True,
  422. 'isSave': True,
  423. 'isTK': False,
  424. },
  425. # 不同模块交流的接口
  426. 'ignoreArea': { # 忽略区域
  427. 'default': None,
  428. 'isSave': False,
  429. 'isTK': False,
  430. },
  431. 'tipsTop1': { # 主窗口顶部进度条上方的label,左侧
  432. 'default': '',
  433. 'isTK': True,
  434. },
  435. 'tipsTop2': { # 主窗口顶部进度条上方的label,右侧
  436. 'default': '拖入图片或快捷截图',
  437. 'isTK': True,
  438. },
  439. }
  440. class ConfigModule:
  441. # ↓ 在这些编码下能使用全部功能,其它编码不保证能使用如拖入含中文路径的图片等功能。
  442. # ↓ 但识图功能是可以正常使用的。
  443. __sysEncodingSafe = ['cp936', 'cp65001']
  444. __tkSaveTime = 200 # tk变量改变多长时间后写入本地。毫秒
  445. _initFlag = False # 标记程序初始化完成,可以正常接客
  446. # ==================== 初始化 ====================
  447. def __init__(self):
  448. self.main = None # win_main的self,可用来获取主it刷新界面或创建计时器
  449. self.sysEncoding = 'ascii' # 系统编码。初始化时获取
  450. self.__saveTimer = None # 计时器,用来更新tk变量一段时间后写入本地
  451. self.__optDict = {} # 配置项的数据
  452. self.__tkDict = {} # tk绑定变量
  453. self.__saveList = [] # 需要保存的项
  454. self.__traceDict = {} # 跟踪值改变
  455. # 将配置项加载到self
  456. for key in _ConfigDict:
  457. value = _ConfigDict[key]
  458. self.__optDict[key] = value['default']
  459. if value.get('isSave', False):
  460. self.__saveList.append(key)
  461. if value.get('isTK', False):
  462. self.__tkDict[key] = None
  463. def isInit(self):
  464. '''查询程序是否初始化完成'''
  465. return self._initFlag
  466. def initOK(self):
  467. self._initFlag = True
  468. def initTK(self, main):
  469. '''初始化tk变量'''
  470. self.main = main # 主窗口
  471. def toSaveConfig(): # 保存值的事件
  472. self.save()
  473. self.__saveTimer = None
  474. def onTkVarChange(key): # 值改变的事件
  475. self.update(key) # 更新配置项
  476. if key in self.__saveList: # 需要保存
  477. if self.__saveTimer: # 计时器已存在,则停止已存在的
  478. self.main.win.after_cancel(self.__saveTimer) # 取消计时
  479. self.__saveTimer = None
  480. self.__saveTimer = self.main.win.after( # 重新计时
  481. self.__tkSaveTime, toSaveConfig)
  482. for key in self.__tkDict:
  483. if isinstance(self.__optDict[key], bool): # 布尔最优先,以免被int覆盖
  484. self.__tkDict[key] = tk.BooleanVar()
  485. elif isinstance(self.__optDict[key], str):
  486. self.__tkDict[key] = tk.StringVar()
  487. elif isinstance(self.__optDict[key], float):
  488. self.__tkDict[key] = tk.DoubleVar()
  489. elif isinstance(self.__optDict[key], int):
  490. self.__tkDict[key] = tk.IntVar()
  491. else: # 给开发者提醒
  492. raise Exception(f'配置项{key}要生成tk变量,但类型不合法!')
  493. # 赋予初值
  494. self.__tkDict[key].set(self.__optDict[key])
  495. # 跟踪值改变事件
  496. self.__tkDict[key].trace(
  497. "w", lambda *e, key=key: onTkVarChange(key))
  498. # ==================== 读写本地文件 ====================
  499. def load(self):
  500. '''从本地json文件读取配置。必须在initTK后执行'''
  501. # 初始化编码,获取系统编码
  502. # https://docs.python.org/zh-cn/3.8/library/locale.html#locale.getdefaultlocale
  503. # https://docs.python.org/zh-cn/3.8/library/codecs.html#standard-encodings
  504. syse = getdefaultlocale()[1]
  505. if syse:
  506. self.sysEncoding = syse
  507. try:
  508. with open(ConfigJsonFile, 'r', encoding='utf8')as fp:
  509. jsonData = json.load(fp) # 读取json文件
  510. for key in jsonData:
  511. if key in self.__optDict:
  512. self.set(key, jsonData[key])
  513. except json.JSONDecodeError: # 反序列化json错误
  514. if tk.messagebox.askyesno(
  515. '遇到了一点小问题',
  516. f'配置文件 {ConfigJsonFile} 格式错误。\n\n【是】 重置该文件\n【否】 退出本次运行'):
  517. self.save()
  518. else:
  519. os._exit(0)
  520. except FileNotFoundError: # 无配置文件
  521. # 当成是首次启动软件,提示
  522. if self.sysEncoding not in self.__sysEncodingSafe: # 不安全的地区
  523. tk.messagebox.showwarning(
  524. '警告',
  525. f'您的系统地区语言编码为[{self.sysEncoding}],可能导致拖入图片的功能异常,建议使用浏览按钮导入图片。其它功能不受影响。')
  526. self.save()
  527. def checkMultiOpen(self):
  528. '''检查多开'''
  529. def isMultiOpen():
  530. '''主进程多开时返回T'''
  531. def getProcessKey(pid):
  532. # 区分不同时间和空间上同一个进程的识别信息
  533. try:
  534. return str(psutil.Process(pid).create_time())
  535. except psutil.NoSuchProcess as e: # 虽然psutil.pid_exists验证pid存在,但 Process 无法生成对象
  536. return ''
  537. # 检查上次记录的pid和key是否还在运行
  538. lastPID = self.get('processID')
  539. lastKey = self.get('processKey')
  540. if psutil.pid_exists(lastPID): # 上次记录的pid如今存在
  541. runningKey = getProcessKey(lastPID)
  542. if lastKey == runningKey: # 上次记录的key与它pid当前key对应,则证实多开
  543. Log.info(f'检查到进程已在运行。pid:{lastPID},key:{lastKey}')
  544. if tk.messagebox.askyesno(
  545. '提示',
  546. f'Umi-OCR 已在运行。\n\n【是】 退出本次运行\n【否】 多开软件'):
  547. os._exit(0)
  548. else: # 忽视警告继续多开,不记录当前信息
  549. return
  550. # 本次为唯一的进程,记录当前进程信息
  551. nowPid = os.getpid()
  552. nowKey = getProcessKey(nowPid)
  553. self.set('processID', nowPid)
  554. self.set('processKey', nowKey, isSave=True)
  555. if self.get('promptMultiOpen'):
  556. isMultiOpen()
  557. def save(self):
  558. '''保存配置到本地json文件'''
  559. saveDict = {} # 提取需要保存的项
  560. for key in self.__saveList:
  561. saveDict[key] = self.__optDict[key]
  562. try:
  563. with open(ConfigJsonFile, 'w', encoding='utf8')as fp:
  564. fp.write(json.dumps(saveDict, indent=4, ensure_ascii=False))
  565. except Exception as e:
  566. tk.messagebox.showerror(
  567. '警告',
  568. f'无法保存配置文件,请检查是否有足够的权限。\n{e}')
  569. # ==================== 操作变量 ====================
  570. def update(self, key):
  571. '''更新某个值,从tk变量读取到配置项'''
  572. try:
  573. self.__optDict[key] = self.__tkDict[key].get()
  574. except Exception as err:
  575. Log.error(f'设置项{key}刷新失败:\n{err}')
  576. if key in self.__traceDict:
  577. try:
  578. self.__traceDict[key]()
  579. except Exception as err:
  580. Log.error(f'设置项{key}跟踪事件调用失败:\n{err}')
  581. def get(self, key):
  582. '''获取一个配置项的值'''
  583. return self.__optDict[key]
  584. def set(self, key, value, isUpdateTK=False, isSave=False):
  585. '''设置一个配置项的值。isSave表示非tk配置项立刻保存本地(需要先在_ConfigDict里设)'''
  586. if key in self.__tkDict: # 若是tk,则通过tk的update事件去更新optDict值
  587. self.__tkDict[key].set(value)
  588. if isUpdateTK: # 需要刷新界面
  589. self.main.win.update()
  590. else: # 不是tk,直接更新optDict
  591. self.__optDict[key] = value
  592. if isSave and _ConfigDict[key].get('isSave', False):
  593. self.save() # 保存本地
  594. def getTK(self, key):
  595. '''获取一个TK变量'''
  596. return self.__tkDict[key]
  597. def addTrace(self, key, func):
  598. '''跟踪一个变量,值改变时调用函数。同一个值只能注册一个函数'''
  599. self.__traceDict[key] = func
  600. Config = ConfigModule() # 设置模块 单例