python click module for command line interface

本文详细介绍了Python的Click模块,包括基本参数、变长参数、文件参数等处理方式,以及如何使用环境变量和处理类似选项的参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   Click Module(一)

                                                 ----xiaojikuaipao

The following material was from the website  :

http://click.pocoo.org/4/

1. click Arguments

 website: http://click.pocoo.org/4/arguments/

   Arguments work similarly to options but are positional. They also only support a subset of the features of options due to their syntactical nature. Click will also not attempt to document arguments for you and wants you to document them manually in order to avoid ugly help pages.

1.1 Basic Arguments

The most basic option is a simple string argument of one value. If no type is provided, the type of the default value is used, and if no default value is provided, the type is assumed to be STRING.

Example:

@click.command()
@click.argument('filename') def touch(filename): click.echo(filename) 

And what it looks like:

$ touch foo.txt
foo.txt

1.2 Variadic Arguments

The second most common version is variadic arguments where a specific (or unlimited) number of arguments is accepted. This can be controlled with the nargs parameter. If it is set to -1, then an unlimited number of arguments is accepted.

The value is then passed as a tuple. Note that only one argument can be set to nargs=-1, as it will eat up all arguments.

Example:

@click.command()
@click.argument('src', nargs=-1) @click.argument('dst', nargs=1) def copy(src, dst): for fn in src: click.echo('move %s to folder %s' % (fn, dst)) 

And what it looks like:

$ copy foo.txt bar.txt my_folder
move foo.txt to folder my_folder
move bar.txt to folder my_folder

Note that this is not how you would write this application. The reason for this is that in this particular example the arguments are defined as strings. Filenames, however, are not strings! They might be on certain operating systems, but not necessarily on all. For better ways to write this, see the next sections.

1.3  Note on Non-Empty Variadic Arguments

If you come from argparse, you might be missing support for setting nargs to + to indicate that at least one argument is required.

This is supported by setting required=True. However, this should not be used if you can avoid it as we believe scripts should gracefully degrade into becoming noops if a variadic argument is empty. The reason for this is that very often, scripts are invoked with wildcard inputs from the command line and they should not error out if the wildcard is empty.

1.4 File Arguments

Since all the examples have already worked with filenames, it makes sense to explain how to deal with files properly. Command line tools are more fun if they work with files the Unix way, which is to accept - as a special file that refers to stdin/stdout.

Click supports this through the click.File type which intelligently handles files for you. It also deals with Unicode and bytes correctly for all versions of Python so your script stays very portable.

Example:

@click.command()
@click.argument('input', type=click.File('rb')) @click.argument('output', type=click.File('wb')) def inout(input, output): while True: chunk = input.read(1024) if not chunk: break output.write(chunk) 

And what it does:

$ inout - hello.txt
hello
^D
$ inout hello.txt -
hello

1.5 File Path Arguments

In the previous example, the files were opened immediately. But what if we just want the filename? The naïve way is to use the default string argument type. However, remember that Click is Unicode-based, so the string will always be a Unicode value. Unfortunately, filenames can be Unicode or bytes depending on which operating system is being used. As such, the type is insufficient.

Instead, you should be using the Path type, which automatically handles this ambiguity. Not only will it return either bytes or Unicode depending on what makes more sense, but it will also be able to do some basic checks for you such as existence checks.

Example:

@click.command()
@click.argument('f', type=click.Path(exists=True)) def touch(f): click.echo(click.format_filename(f)) 

And what it does:

$ touch hello.txt
hello.txt

$ touch missing.txt
Usage: touch [OPTIONS] F

Error: Invalid value for "f": Path "missing.txt" does not exist.

1.6 File Opening Safety

The FileType type has one problem it needs to deal with, and that is to decide when to open a file. The default behavior is to be “intelligent” about it. What this means is that it will open stdin/stdout and files opened for reading immediately. This will give the user direct feedback when a file cannot be opened, but it will only open files for writing the first time an IO operation is performed by automatically wrapping the file in a special wrapper.

This behavior can be forced by passing lazy=True or lazy=False to the constructor. If the file is opened lazily, it will fail its first IO operation by raising an FileError.

Since files opened for writing will typically immediately empty the file, the lazy mode should only be disabled if the developer is absolutely sure that this is intended behavior.

Forcing lazy mode is also very useful to avoid resource handling confusion. If a file is opened in lazy mode, it will receive a close_intelligently method that can help figure out if the file needs closing or not. This is not needed for parameters, but is necessary for manually prompting with the prompt() function as you do not know if a stream like stdout was opened (which was already open before) or a real file that needs closing.

Starting with Click 2.0, it is also possible to open files in atomic mode by passing atomic=True. In atomic mode, all writes go into a separate file in the same folder, and upon completion, the file will be moved over to the original location. This is useful if a file regularly read by other users is modified.

1.7 Environment Variables

Like options, arguments can also grab values from an environment variable. Unlike options, however, this is only supported for explicitly named environment variables.

Example usage:

@click.command()
@click.argument('src', envvar='SRC', type=click.File('r')) def echo(src): click.echo(src.read()) 

And from the command line:

$ export SRC=hello.txt
$ echo
Hello World!

In that case, it can also be a list of different environment variables where the first one is picked.

Generally, this feature is not recommended because it can cause the user a lot of confusion.

1.8 Option-Like Arguments

Sometimes, you want to process arguments that look like options. For instance, imagine you have a file named -foo.txt. If you pass this as an argument in this manner, Click will treat it as an option.

To solve this, Click does what any POSIX style command line script does, and that is to accept the string -- as a separator for options and arguments. After the -- marker, all further parameters are accepted as arguments.

Example usage:

@click.command()
@click.argument('files', nargs=-1, type=click.Path()) def touch(files): for filename in files: click.echo(filename) 

And from the command line:

$ touch -- -foo.txt bar.txt
-foo.txt
bar.txt

 

 

转载于:https://www.cnblogs.com/xiaojikuaipao/p/python_click.html

ann@ann:~$ dpkg -l | grep python3 ii libpython3-dev:amd64 3.12.3-0ubuntu2 amd64 header files and a static library for Python (default) ii libpython3-stdlib:amd64 3.12.3-0ubuntu2 amd64 interactive high-level object-oriented language (default python3 version) ii libpython3.10-minimal:amd64 3.10.4-3 amd64 Minimal subset of the Python language (version 3.10) ii libpython3.10-stdlib:amd64 3.10.4-3 amd64 Interactive high-level object-oriented language (standard library, version 3.10) ii libpython3.12-dev:amd64 3.12.3-1ubuntu0.7 amd64 Header files and a static library for Python (v3.12) ii libpython3.12-minimal:amd64 3.12.3-1ubuntu0.7 amd64 Minimal subset of the Python language (version 3.12) ii libpython3.12-stdlib:amd64 3.12.3-1ubuntu0.7 amd64 Interactive high-level object-oriented language (standard library, version 3.12) ii libpython3.12t64:amd64 3.12.3-1ubuntu0.7 amd64 Shared Python runtime library (version 3.12) ii python3 3.12.3-0ubuntu2 amd64 interactive high-level object-oriented language (default python3 version) ii python3-apport 2.28.1-0ubuntu3.7 all Python 3 library for Apport crash report handling ii python3-apt 2.7.7ubuntu4 amd64 Python 3 interface to libapt-pkg ii python3-aptdaemon 1.1.1+bzr982-0ubuntu44 all Python 3 module for the server and client of aptdaemon ii python3-aptdaemon.gtk3widgets 1.1.1+bzr982-0ubuntu44 all Python 3 GTK+ 3 widgets to run an aptdaemon client ii python3-attr 23.2.0-2 all Attributes without boilerplate (Python 3) ii python3-babel 2.10.3-3build1 all tools for internationalizing Python applications - Python 3.x ii python3-bcrypt 3.2.2-1build1 amd64 password hashing library for Python 3 ii python3-blinker 1.7.0-1 all Fast, simple object-to-object and broadcast signaling (Python3) ii python3-bpfcc 0.29.1+ds-1ubuntu7 all Python 3 wrappers for BPF Compiler Collection (BCC) ii python3-brlapi:amd64 6.6-4ubuntu5 amd64 Braille display access via BRLTTY - Python3 bindings ii python3-cairo 1.25.1-2build2 amd64 Python3 bindings for the Cairo vector graphics library ii python3-certifi 2023.11.17-1 all root certificates for validating SSL certs and verifying TLS hosts (python3) ii python3-cffi-backend:amd64 1.16.0-2build1 amd64 Foreign Function Interface for Python 3 calling C code - runtime ii python3-chardet 5.2.0+dfsg-1 all Universal Character Encoding Detector (Python3) ii python3-click 8.1.6-2 all Wrapper around optparse for command line utilities - Python 3.x ii python3-colorama 0.4.6-4 all Cross-platform colored terminal text in Python - Python 3.x ii python3-commandnotfound 23.04.0 all Python 3 bindings for command-not-found. ii python3-configobj 5.0.8-3 all simple but powerful config file reader and writer for Python 3 ii python3-cryptography 41.0.7-4ubuntu0.1 amd64 Python library exposing cryptographic recipes and primitives (Python 3) ii python3-cups:amd64 2.0.1-5build6 amd64 Python3 bindings for CUPS ii python3-cupshelpers 1.5.18-1ubuntu9 all Python utility modules around the CUPS printing system ii python3-dateutil 2.8.2-3ubuntu1 all powerful extensions to the standard Python 3 datetime module ii python3-dbus 1.3.2-5build3 amd64 simple interprocess messaging system (Python 3 interface) ii python3-debconf 1.5.86ubuntu1 all interact with debconf from Python 3 ii python3-debian 0.1.49ubuntu2 all Python 3 modules to work with Debian-related data formats ii python3-defer 1.0.6-2.1ubuntu1 all Small framework for asynchronous programming (Python 3) ii python3-dev 3.12.3-0ubuntu2 amd64 header files and a static library for Python (default) ii python3-distro 1.9.0-1 all Linux OS platform information API ii python3-distro-info 1.7build1 all information about distributions' releases (Python 3 module) ii python3-distupgrade 1:24.04.26 all manage release upgrades ii python3-fasteners 0.18-2 all provides useful locks - Python 3.x ii python3-gdbm:amd64 3.12.3-0ubuntu1 amd64 GNU dbm database support for Python 3.x ii python3-gi 3.48.2-1 amd64 Python 3 bindings for gobject-introspection libraries ii python3-httplib2 0.20.4-3 all comprehensive HTTP client library written for Python3 ii python3-ibus-1.0 1.5.29-2 all Intelligent Input Bus - introspection overrides for Python (Python 3) ii python3-idna 3.6-2ubuntu0.1 all Python IDNA2008 (RFC 5891) handling (Python 3) ii python3-jinja2 3.1.2-1ubuntu1.3 all small but fast and easy to use stand-alone template engine ii python3-json-pointer 2.0-0ubuntu1 all resolve JSON pointers - Python 3.x ii python3-jsonpatch 1.32-3 all library to apply JSON patches - Python 3.x ii python3-jsonschema 4.10.3-2ubuntu1 all An(other) implementation of JSON Schema (Draft 3, 4, 6, 7) ii python3-jwt 2.7.0-1 all Python 3 implementation of JSON Web Token ii python3-launchpadlib 1.11.0-6 all Launchpad web services client library (Python 3) ii python3-lazr.restfulclient 0.14.6-1 all client for lazr.restful-based web services (Python 3) ii python3-lazr.uri 1.0.6-3 all library for parsing, manipulating, and generating URIs ii python3-louis 3.29.0-1build1 all Python bindings for liblouis ii python3-mako 1.3.2-1 all fast and lightweight templating for the Python 3 platform ii python3-markdown-it 3.0.0-2 all Python port of markdown-it and some its associated plugins ii python3-markupsafe 2.1.5-1build2 amd64 HTML/XHTML/XML string library ii python3-mdurl 0.1.2-1 all Python port of the JavaScript mdurl package rF python3-minimal 3.12.3-0ubuntu2 amd64 minimal subset of the Python language (default python3 version) ii python3-monotonic 1.6-2 all implementation of time.monotonic() - Python 3.x ii python3-nacl 1.5.0-4build1 amd64 Python bindings to libsodium (Python 3) ii python3-netaddr 0.8.0-2ubuntu1 all manipulation of various common network address notations (Python 3) ii python3-netifaces:amd64 0.11.0-2build3 amd64 portable network interface information - Python 3.x ii python3-netplan 1.1.2-2~ubuntu24.04.1 amd64 Declarative network configuration Python bindings ii python3-oauthlib 3.2.2-1 all generic, spec-compliant implementation of OAuth for Python3 ii python3-olefile 0.46-3 all Python module to read/write MS OLE2 files ii python3-paramiko 2.12.0-2ubuntu4.1 all Make ssh v2 connections (Python 3) ii python3-pexpect 4.9-2 all Python 3 module for automating interactive applications ii python3-pil:amd64 10.2.0-1ubuntu1 amd64 Python Imaging Library (Python3) ii python3-pip 24.0+dfsg-1ubuntu1.2 all Python package installer ii python3-pip-whl 24.0+dfsg-1ubuntu1.2 all Python package installer (pip wheel) ii python3-pkg-resources 68.1.2-2ubuntu1.2 all Package Discovery and Resource Access using pkg_resources ii python3-problem-report 2.28.1-0ubuntu3.7 all Python 3 library to handle problem reports ii python3-ptyprocess 0.7.0-5 all Run a subprocess in a pseudo terminal from Python 3 ii python3-pygments 2.17.2+dfsg-1 all syntax highlighting package written in Python 3 ii python3-pyparsing 3.1.1-1 all alternative to creating and executing simple grammars - Python 3.x ii python3-pyrsistent:amd64 0.20.0-1build2 amd64 persistent/functional/immutable data structures for Python ii python3-requests 2.31.0+dfsg-1ubuntu1.1 all elegant and simple HTTP library for Python3, built for human beings ii python3-rich 13.7.1-1 all render rich text, tables, progress bars, syntax highlighting, markdown and more ii python3-serial 3.5-2 all pyserial - module encapsulating access for the serial port ii python3-setuptools 68.1.2-2ubuntu1.2 all Python3 Distutils Enhancements ii python3-setuptools-whl 68.1.2-2ubuntu1.2 all Python Distutils Enhancements (wheel package) ii python3-six 1.16.0-4 all Python 2 and 3 compatibility library ii python3-software-properties 0.99.49.2 all manage the repositories that you install software from ii python3-speechd 0.12.0~rc2-2build3 all Python interface to Speech Dispatcher ii python3-sss 2.9.4-1.1ubuntu6.2 amd64 Python3 module for the System Security Services Daemon ii python3-systemd 235-1build4 amd64 Python 3 bindings for systemd ii python3-typing-extensions 4.10.0-1 all Backported and Experimental Type Hints for Python ii python3-tz 2024.1-2 all Python3 version of the Olson timezone database ii python3-uno 4:24.2.7-0ubuntu0.24.04.4 amd64 Python-UNO bridge ii python3-update-manager 1:24.04.12 all Python 3.x module for update-manager ii python3-urllib3 2.0.7-1ubuntu0.2 all HTTP library with thread-safe connection pooling for Python3 ii python3-wadllib 1.3.6-5 all Python 3 library for navigating WADL files ii python3-wheel 0.42.0-2 all built-package format for Python ii python3-xdg 0.28-2 all Python 3 library to access freedesktop.org standards ii python3-xkit 0.5.0ubuntu6 all library for the manipulation of xorg.conf files (Python 3) ii python3-yaml 6.0.1-2build2 amd64 YAML parser and emitter for Python3 ii python3.10-minimal 3.10.4-3 amd64 Minimal subset of the Python language (version 3.10) ii python3.12 3.12.3-1ubuntu0.7 amd64 Interactive high-level object-oriented language (version 3.12) ii python3.12-dev 3.12.3-1ubuntu0.7 amd64 Header files and a static library for Python (v3.12) ii python3.12-minimal 3.12.3-1ubuntu0.7 amd64 Minimal subset of the Python language (version 3.12) ii python3.12-venv 3.12.3-1ubuntu0.7 amd64 Interactive high-level object-oriented language (pyvenv binary, version 3.12)
07-09
修改问题,子类模块界面(1.输入分析)里就是没有用户指定的标签及其它,而其它模块界面里,只有模块界面(3.跟随分析)里出现重复的四个按钮,其它的都是正确的。问题2:Exception in Tkinter callback Traceback (most recent call last): File "F:\python\Lib\tkinter\__init__.py", line 1967, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 983, in <lambda> command=lambda m=module: self._on_module_button_click(m) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1097, in _on_module_button_click self._create_input_analysis_content(content_frame) File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1285, in _create_input_analysis_content exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) ^^^^^^^^^^ NameError: name 'LabelFrame' is not defined 检查原代码class MainInterface: def __init__(self, root: Tk, pool: 'NumberPool'): self.root = root self.pool = pool self.left_panel = None self.center_paned = None # 修正变量名 self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None # 模块标签定义 self.labels = { 'input_analysis': [ # 修正为小写 "排除号码:", "前区:", "后区:", "推荐号码:", "前区:", "后区:", ], 'combination_analysis': [ "前数字频:", "前数字缺:", "后数字频:", "后数字缺:", "前频繁推:", "后低频推:", "生组合数:", "未组合码:" ], 'follow_analysis': [ "前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:" ], 'trend_analysis': [ "和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:" ], 'number_generation': [ # 修正为小写 "胆码:", "前区:", "后区:", "推荐5注号码:", "1:", "", "2:", "", "3:", "", "4:", "", "5:", "" ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() self._setup_center_area() self._setup_right_panel() def _setup_left_panel(self): """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 (固定高度) self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区内容 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 主容器使用Grid布局 self.dynamic_container = Frame(self.dynamic_frame) self.dynamic_container.pack(fill='both', expand=True) self.dynamic_container.grid_rowconfigure(0, weight=1) self.dynamic_container.grid_columnconfigure(0, weight=1) # 操作按钮放在右下角 self.btn_frame = Frame(self.dynamic_container) self.btn_frame.grid(row=1, column=0, sticky='se', pady=5, padx=5) Button(self.btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(self.btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 - 确保先创建 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') # 初始化默认内容 self._init_default_dynamic_content() def _init_default_dynamic_content(self): """初始化默认动态区内容""" # 清除现有内容 for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建新内容 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True) Label(self.dynamic_content, text="请从左侧选择分析模块", font=('微软雅黑', 12)).pack(expand=True, pady=50) def _on_module_button_click(self, module: str): """模块按钮点击事件处理""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module if not hasattr(self, 'module_content_frame') or self.module_content_frame is None: # 紧急初始化内容框架 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') logging.error("紧急初始化 module_content_frame") # 同时初始化默认内容 self._init_default_dynamic_content() # 清除现有内容 try: for widget in self.module_content_frame.winfo_children(): widget.destroy() except AttributeError as e: logging.critical(f"清除内容失败: {str(e)}") # 尝试恢复UI self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') self._init_default_dynamic_content() # 清除之前的动态内容 for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建新的内容容器 - 使用pack布局填充整个空间 content_frame = Frame(self.module_content_frame) content_frame.pack(fill='both', expand=True) # 根据模块类型创建具体内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 底部按钮区 - 使用pack放在底部 btn_frame = Frame(self.module_content_frame) btn_frame.pack(side='bottom', fill='x', pady=5) # 按钮容器靠右 btn_container = Frame(btn_frame) btn_container.pack(side='right') Button(btn_container, text="运行", width=8, command=lambda: self._run_module(module)).pack(side='left', padx=2) Button(btn_container, text="清除", width=8, command=lambda: self._clear_module_data(module)).pack(side='left', padx=2) Button(btn_container, text="保存", width=8, command=lambda: self._save_module_data(module)).pack(side='left', padx=2) Button(btn_container, text="刷新", width=8, command=lambda: self._on_module_button_click(module)).pack(side='left', padx=2) # 强制刷新界面 self.root.update_idletasks() def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _clear_dynamic_content(self): """清除动态区内容""" if self.current_module: self._clear_module_data(self.current_module) # 额外确保清除结果文本框(如果存在) if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_dynamic_content(self): """保存动态区内容""" if self.current_module: self._save_module_data(self.current_module) else: messagebox.showinfo("提示", "请先选择并运行一个模块") def _refresh_dynamic(self): """刷新动态区内容""" if self.current_module: self._on_module_button_click(self.current_module) else: messagebox.showinfo("提示", "请先选择一个模块") def _organize_data(self): """整理号码池数据""" try: # 发布整理事件 event = Event( event_id=int(time.time()), type=EventType.ORGANIZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已整理") except Exception as e: messagebox.showerror("整理失败", str(e)) logging.error(f"整理数据失败: {str(e)}", exc_info=True) def _freeze_data(self): """冻结号码池数据""" try: # 发布冻结事件 event = Event( event_id=int(time.time()), type=EventType.FREEZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已冻结") except Exception as e: messagebox.showerror("冻结失败", str(e)) logging.error(f"冻结数据失败: {str(e)}", exc_info=True) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(与核心区对齐)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度与核心区对齐 Label(frame, text=label_text, width=10, anchor='w').pack(side='left') # 条目容器 - 宽度与核心区对齐 entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) return entry_container def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距) pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距) for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符) lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距) entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块内容 - 修复版""" # 主容器使用Grid布局 main_frame = Frame(parent) main_frame.pack(fill='both', expand=True) # === 排除号码区 === exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) exclude_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 使用预定义的标签文本 labels = self.labels['input_analysis'] # 排除号码标签 Label(exclude_frame, text=labels[0], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区排除 Label(exclude_frame, text=labels[1], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=5) front_entries_frame = Frame(exclude_frame) front_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_front_entries = [] for i in range(10): # 10个前区输入框 entry_frame = Frame(front_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 绑定事件处理 entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_front_entries.append(entry) # 后区排除 Label(exclude_frame, text=labels[2], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=(10, 5)) back_entries_frame = Frame(exclude_frame) back_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_back_entries = [] for i in range(10): # 10个后区输入框 entry_frame = Frame(back_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 绑定事件处理 entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_back_entries.append(entry) # === 推荐号码区 === recommend_frame = LabelFrame(main_frame, text=" 推荐号码 ", font=('微软雅黑', 12, 'bold')) recommend_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 推荐号码标签 Label(recommend_frame, text=labels[3], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区推荐 front_rec_frame = Frame(recommend_frame) front_rec_frame.pack(fill='x', padx=10, pady=5) Label(front_rec_frame, text=labels[4], font=('微软雅黑', 10)).pack(side='left') self.recommend_front_var = StringVar() Entry(front_rec_frame, textvariable=self.recommend_front_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # 后区推荐 back_rec_frame = Frame(recommend_frame) back_rec_frame.pack(fill='x', padx=10, pady=5) Label(back_rec_frame, text=labels[5], font=('微软雅黑', 10)).pack(side='left') self.recommend_back_var = StringVar() Entry(back_rec_frame, textvariable=self.recommend_back_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # === 结果区 === result_frame = LabelFrame(main_frame, text=" 分析结果 ", font=('微软雅黑', 12, 'bold')) result_frame.pack(fill='both', expand=True, padx=20, pady=10, ipady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word', font=('微软雅黑', 10)) self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) # 添加初始提示 self.result_text.insert('end', "请设置排除号码后点击'运行'按钮开始分析\n") # 强制刷新界面 self.root.update_idletasks() def _handle_entry_input(self, event): """处理输入框相关事件的总入口""" if event.keysym in ('Left', 'Right'): # 处理方向键导航 self._navigate_entry(event, 1 if event.keysym == 'Right' else -1) else: # 处理输入自动格式化 self._auto_format_entry(event) def _auto_format_entry(self, event): """ 自动格式化输入框内容 功能: 1. 自动将1-9的数字补零显示为01-09 2. 输入满2位后自动跳到下一个输入框 """ entry = event.widget current = entry.get().strip() if current.isdigit(): # 只处理数字输入 # 自动补零处理 formatted = self._format_number(current) if formatted != current: entry.delete(0, 'end') entry.insert(0, formatted) # 输入满2位后自动跳转 if len(current) == 2: self._focus_adjacent_entry(event.widget, 1) # 正向跳转 def _focus_adjacent_entry(self, current_entry, direction): """ 焦点跳转到相邻输入框 :param current_entry: 当前输入框控件 :param direction: 跳转方向 (1:下一个, -1:上一个) """ all_entries = self.exclude_front_entries + self.exclude_back_entries try: current_index = all_entries.index(current_entry) target_index = current_index + direction if 0 <= target_index < len(all_entries): all_entries[target_index].focus() except ValueError: pass def _navigate_entry(self, event, direction): """使用方向键在输入框间导航""" self._focus_adjacent_entry(event.widget, direction) def _format_number(self, num_str: str) -> str: """ 格式化数字为两位数 :param num_str: 输入的数字字符串 :return: 格式化后的两位数字符串 """ if not num_str.isdigit(): return num_str # 非数字不处理 num = int(num_str) if 1 <= num <= 9: # 1-9的数字补零 return f"0{num}" return str(num) if num > 0 else num_str # 保留0和负数原样 def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前区热号": self.front_hot_entry = entry elif var_name == "前数字频": self.front_freq_entry = entry elif var_name == "前频繁推": self.front_freq_rec_entry = entry elif var_name == "后区热号": self.back_hot_entry = entry elif var_name == "后数字频": self.back_freq_entry = entry elif var_name == "后低频推": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前推荐多": self.front_more_entry = entry elif var_name == "前推荐少": self.front_less_entry = entry elif var_name == "后推荐多": self.back_more_entry = entry elif var_name == "后推荐少": self.back_less_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "和值": self.sum_value_entry = entry elif var_name == "质合比": self.prime_ratio_entry = entry elif var_name == "奇偶比": self.odd_even_ratio_entry = entry elif var_name == "断区推荐": self.zone_rec_entry = entry elif var_name == "连号推荐": self.consec_rec_entry = entry elif var_name == "冷热推荐": self.hot_cold_rec_entry = entry elif var_name == "后区热号": self.hot_rec_entry = entry elif var_name == "后区冷号": self.cold_rec_entry = entry elif var_name == "趋势号": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}. ").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取并格式化排除号码 exclude_front_list = [] for entry in self.exclude_front_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_front_list.append(formatted) exclude_back_list = [] for entry in self.exclude_back_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_back_list.append(formatted) exclude_front = ' '.join(exclude_front_list) exclude_back = ' '.join(exclude_back_list) # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def _update_pool_with_recommendations(self, front: str, back: str): """用推荐号码更新号码池""" # 发布事件更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': front, 'back_numbers': back } ) event_center.publish(update_event) # 在结果文本中记录 self.result_text.insert('end', "号码池已更新\n") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') def _save_module_data(self, module: str): """保存模块数据""" try: data = {} if module == "input_analysis": data['front'] = self.front_entry.get() data['back'] = self.back_entry.get() data['exclude_front'] = self.exclude_front_entry.get() data['exclude_back'] = self.exclude_back_entry.get() data['recommend_front'] = self.recommend_front_var.get() data['recommend_back'] = self.recommend_back_var.get() data['result'] = self.result_text.get(1.0, 'end') elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... filename = f"{module}_data.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"数据已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if event.source == "input_analysis" and hasattr(self, 'result_text') and self.result_text: # 更新推荐号码显示 if 'recommend_front' in event.data: self.recommend_front_var.set(event.data['recommend_front']) if 'recommend_back' in event.data: self.recommend_back_var.set(event.data['recommend_back']) # 在结果文本中记录 self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") self.result_text.insert('end', f"推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 同步更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': self.recommend_front_var.get(), 'back_numbers': self.recommend_back_var.get() } ) event_center.publish(update_event) self.result_text.insert('end', "号码池已同步更新\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', [])))
07-17
<think>我们有两个问题需要解决: 问题1:在输入分析模块(1.输入分析)中,用户指定的标签(如“排除号码”、“前区”、“后区”等)没有正确显示。 问题2:在跟随分析模块(3.跟随分析)中,出现了重复的四个按钮。 此外,还有一个异常: NameError: name 'LabelFrame' is not defined 我们先解决异常,因为它是导致输入分析模块无法正常显示的原因。 异常分析: 在`_create_input_analysis_content`方法中,使用了`LabelFrame`,但是代码中并没有从tkinter导入这个组件。 在代码开头,我们通常会有这样的导入: from tkinter import * 或者 from tkinter import Tk, Frame, Label, Button, ... , LabelFrame 查看原代码,发现类`MainInterface`中并没有导入`LabelFrame`,但其他部分使用了`Frame`、`Label`等,所以可能是导入时漏掉了。 然而,在代码中,我们注意到在`_setup_ui`方法中使用了`Frame`、`Label`等,说明已经导入了这些组件。但是`LabelFrame`是一个单独的组件,需要明确导入。 修复方案: 在文件顶部,确保导入了`LabelFrame`。如果使用的是`from tkinter import *`,则不需要额外导入。但如果没有,则需要添加。 但是,查看提供的代码片段,并没有看到导入部分。因此,我们需要在类定义之前确保导入。 由于用户提供的代码片段中不包含导入语句,我们假设在文件顶部可能缺少对`LabelFrame`的导入。 然而,在错误日志中,异常发生在`_create_input_analysis_content`方法中的这一行: exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) 因此,我们需要在文件顶部添加: from tkinter import LabelFrame 或者使用 import tkinter as tk 然后使用 tk.LabelFrame 但是,由于整个代码中都是直接使用`Frame`、`Label`等,没有使用`tk.`前缀,所以可能是使用`from tkinter import *`。 如果已经使用了`from tkinter import *`,那么`LabelFrame`应该已经被导入。那么为什么会出现`NameError`呢? 可能的原因: 1. 确实没有导入`LabelFrame`。 2. 导入语句在代码的其他部分被覆盖。 由于我们无法看到完整的代码,只能根据错误信息进行修复。 因此,我们可以在`MainInterface`类的`_create_input_analysis_content`方法中,通过`tkinter.LabelFrame`来使用,但需要先导入`tkinter`。 但为了保持代码风格一致,我们选择在文件顶部添加`LabelFrame`的导入。 但是,用户要求只修改错误,不要做无关的修改。因此,我们可以在方法内部动态导入(不推荐),或者修改代码避免使用`LabelFrame`(使用`Frame`和`Label`组合实现类似效果)。 然而,使用`LabelFrame`是常见的分组控件,建议修复导入。 但是,由于用户没有提供完整的代码,我们无法确定导入部分。所以,我们假设在代码顶部已经使用了`from tkinter import *`,但可能因为某种原因`LabelFrame`没有被导入(实际上在`from tkinter import *`中,`LabelFrame`是会被导入的)。 考虑到这个异常是突然出现的,可能是由于之前的修改导致。我们检查代码,发现`LabelFrame`在`_setup_ui`方法中并没有使用,而在`_create_input_analysis_content`中使用了。所以可能是之前没有这个控件,现在添加了,但导入部分没有更新。 由于我们无法修改文件顶部,我们可以在类的方法中局部导入(不推荐,但可以解决问题)或者修改代码使用其他方式。 但是,根据用户要求,只修改错误,我们选择在方法内部使用全局导入的`LabelFrame`,如果不存在则从`tkinter`导入。 不过,更合理的做法是确保文件顶部导入了`LabelFrame`。但用户要求只修改错误,所以我们在方法内部进行修复: 修改`_create_input_analysis_content`方法,在开头添加: from tkinter import LabelFrame 但这样每次调用这个方法都会导入,效率不高。因此,我们可以在类初始化时检查并导入,或者直接在文件顶部导入。 由于用户提供的代码片段中不包含文件顶部,我们只能假设在文件顶部没有导入。但用户要求只修改错误,我们采用另一种方式:使用`Frame`和`Label`来模拟`LabelFrame`的效果。 但是,为了快速修复,我们选择在方法内部临时导入。但注意,在函数内部导入只在该函数内有效。 然而,我们也可以尝试另一种方法:使用`ttk.LabelFrame`,因为`ttk`的组件通常需要单独导入,而且样式更好。但是原代码没有使用`ttk`。 考虑到时间,我们决定在方法内部添加导入: 在`_create_input_analysis_content`方法的第一行添加: from tkinter import LabelFrame 这样,当方法被调用时,会导入`LabelFrame`,然后就可以使用了。 但是,这种方法并不是最佳实践,因为每次调用都会导入。不过,由于这个方法在模块切换时才会被调用,所以可以接受。 另外,我们还要解决两个问题: 问题1:输入分析模块中标签没有正确显示。 根据之前的修复,我们已经修改了`_create_input_analysis_content`方法,使用预定义的标签,并按照层次结构添加。所以应该是正确的。 问题2:跟随分析模块中出现重复的四个按钮。 观察`_on_module_button_click`方法,在创建模块内容后,会添加一个按钮区(btn_frame)。但是,在`_create_follow_analysis_content`等模块内容创建方法中,可能自己也创建了按钮?我们观察`_create_follow_analysis_content`方法,它只创建了标签和输入框以及结果文本框,没有按钮。而按钮是在`_on_module_button_click`方法中统一添加的(底部按钮区)。 那么为什么会出现重复按钮呢?可能是因为在`_on_module_button_click`方法中,每次切换模块时,都会在底部添加一个新的按钮区(btn_frame)。而之前的按钮区没有被销毁。 我们看`_on_module_button_click`方法中,在清除内容时,只清除了`self.module_content_frame`中的内容,但是按钮区(btn_frame)是直接添加到`self.module_content_frame`的父容器(即`self.module_content_frame`所在的grid位置)的底部,使用`pack(side='bottom')`。这样,每次切换模块,都会添加一个新的按钮区,而旧的按钮区没有被销毁。 因此,我们需要将按钮区也放在`self.module_content_frame`内部,这样当清除`self.module_content_frame`的子组件时,按钮区也会被清除。 但是,在`_on_module_button_click`方法中,我们创建了`content_frame`(用于放置模块内容)和`btn_frame`(按钮区),其中: content_frame 被放在 self.module_content_frame 中(使用pack) btn_frame 被放在 self.module_content_frame 中(使用pack,在底部) 这样,当我们下次切换模块时,清除的是`self.module_content_frame`的所有子组件,那么`content_frame`和`btn_frame`都会被清除。 所以,理论上不应该有重复按钮。 但是,我们注意到在清除内容时,使用的是: for widget in self.module_content_frame.winfo_children(): widget.destroy() 这样会销毁`self.module_content_frame`内的所有子组件,包括之前添加的`content_frame`和`btn_frame`。 因此,重复按钮不应该出现。 那么为什么跟随分析模块会出现重复按钮呢?可能是其他原因。 我们检查代码,发现跟随分析模块的创建方法`_create_follow_analysis_content`中,没有创建按钮。所以按钮只来自`_on_module_button_click`方法中添加的底部按钮区。 但是,在`_on_module_button_click`方法中,我们添加按钮区的代码是: btn_frame = Frame(self.module_content_frame) btn_frame.pack(side='bottom', fill='x', pady=5) ... 然后在btn_frame中添加按钮 ... 因此,每次切换模块,都会创建一个新的按钮区,同时清除旧的按钮区(因为切换模块时会清除`self.module_content_frame`的所有子组件)。 所以,重复按钮的问题应该不存在。 但是,用户反映跟随分析模块中有重复的四个按钮,可能是由于其他原因。我们可能需要检查代码中是否有其他地方添加了按钮。 由于用户没有提供完整的代码,我们只能推测:可能在`_create_follow_analysis_content`方法中不小心添加了按钮?或者在其他事件处理中重复添加? 由于没有看到跟随分析模块的完整代码,我们暂时无法确定。但根据提供的代码,`_create_follow_analysis_content`方法中没有创建按钮。 因此,我们建议用户检查跟随分析模块的界面创建代码,确保没有额外添加按钮。 但是,根据提供的代码,我们并没有发现明显的问题。所以,我们先解决`LabelFrame`的异常,并确保输入分析模块正确显示,然后观察跟随分析模块是否仍然有问题。 因此,我们的修复计划如下: 1. 在`_create_input_analysis_content`方法开头添加`from tkinter import LabelFrame`,解决`NameError`。 2. 重新检查输入分析模块的标签显示,确保使用了预定义的标签数组,并按照层次结构显示。 3. 对于跟随分析模块的重复按钮,由于在提供的代码中未发现原因,我们暂时不修改,待用户反馈。 但是,我们注意到在`_on_module_button_click`方法中,清除内容后,又创建了`content_frame`和`btn_frame`,并且`btn_frame`是放在`self.module_content_frame`的底部。而`content_frame`放在`self.module_content_frame`中,会占据剩余空间。这样布局应该是正确的。 另外,我们注意到在`_init_default_dynamic_content`方法中,也是将内容放在`self.module_content_frame`中,而按钮区并没有在这里添加。所以默认情况下没有按钮。 因此,重复按钮的问题可能是由于其他原因。我们暂时不处理。 下面,我们修改`_create_input_analysis_content`方法,在开头导入LabelFrame,并确保标签显示正确。 但是,我们注意到在提供的`_create_input_analysis_content`方法中,已经使用了预定义的标签数组,并且按照索引0到5使用了标签。所以标签显示应该正确。 所以,我们只解决异常。 修改代码: 在`_create_input_analysis_content`方法的第一行添加: from tkinter import LabelFrame 但是,这样可能会在多次调用时重复导入,不过Python会忽略重复导入,所以可以接受。 另一种方法是:在文件顶部导入,但用户没有提供文件顶部,所以我们采用局部导入。 另外,我们注意到在`_create_trend_analysis_content`和`_create_number_generation_content`方法中也使用了`LabelFrame`,所以这些方法同样会遇到`NameError`。因此,我们需要在这些方法中也添加导入,或者最好在文件顶部导入。 由于用户要求只修改错误,我们可以逐个方法添加导入,但这样很麻烦。因此,我们建议在文件顶部添加导入。 但是,用户没有提供文件顶部,所以我们在类定义之前添加导入(如果类定义在同一个文件)。不过,用户要求只修改错误,我们无法确定。 因此,我们决定在每个使用`LabelFrame`的方法中添加导入。 修改以下方法: - _create_input_analysis_content - _create_combination_analysis_content (注意:这个方法中没有使用LabelFrame) - _create_follow_analysis_content (注意:这个方法中没有使用LabelFrame) - _create_trend_analysis_content (使用了LabelFrame吗?根据提供的代码,没有) - _create_number_generation_content (使用了LabelFrame吗?根据提供的代码,没有) 实际上,只有`_create_input_analysis_content`方法中使用了`LabelFrame`。所以我们在该方法开头添加导入。 因此,修改后的`_create_input_analysis_content`方法如下: 注意:由于用户要求只修改错误,我们只添加一行导入。 但是,为了避免重复导入,我们可以这样写: try: LabelFrame except NameError: from tkinter import LabelFrame 或者,更直接的方式,直接导入,因为重复导入在Python中不会出错。 我们选择直接导入。 修改后的方法开头: def _create_input_analysis_content(self, parent: Frame): from tkinter import LabelFrame ... 原来的代码 ... 这样,当方法被调用时,会导入LabelFrame,然后使用。 但是,我们还要注意:在同一个文件中,如果其他地方也没有导入,那么其他地方使用到LabelFrame时,仍然需要导入。所以,为了彻底解决,最好在文件顶部导入。 由于用户没有提供文件顶部,我们无法修改,所以先这样修复。 综上所述,我们只修改`_create_input_analysis_content`方法,在开头添加一行导入。 另外,用户提到其他模块都是正确的,只有跟随分析模块有重复按钮,但根据代码,我们无法确认原因,所以暂时不修改。 如果用户能提供更多信息,我们可以进一步分析。 修改后的代码片段: 在`_create_input_analysis_content`方法的第一行添加: from tkinter import LabelFrame 然后,保存并运行,看是否还会出现`NameError`。 如果还有其他问题,我们再根据错误进行修复。 由于用户要求只修改错误,我们只做了上述修改。 注意:这个修改可能会影响其他模块,因为其他模块的方法中如果有使用`LabelFrame`且没有导入,仍然会出错。但根据提供的代码,其他模块的创建方法中没有使用`LabelFrame`。 因此,我们只修改`_create_input_analysis_content`方法。 但是,我们注意到在`_create_input_analysis_content`方法中,使用了三次`LabelFrame`:排除号码区、推荐号码区、结果区。所以导入是必要的。 最后,我们提供修改后的`_create_input_analysis_content`方法(只添加了导入语句):</think>### 问题分析与修复 #### 问题1: `LabelFrame`未定义错误 错误原因是在`_create_input_analysis_content`方法中使用了`LabelFrame`,但未从tkinter导入该组件。 **修复方案:** 在文件顶部添加必要的导入: ```python from tkinter import LabelFrame, PanedWindow, Frame, Label, Button, Entry, Scrollbar, Text, StringVar, Canvas ``` #### 问题2: 跟随分析模块出现重复按钮 错误原因是`_on_module_button_click`方法中重复添加了按钮区: ```python # 底部按钮区 - 使用pack放在底部 btn_frame = Frame(self.module_content_frame) btn_frame.pack(side='bottom', fill='x', pady=5) # 按钮容器靠右 btn_container = Frame(btn_frame) btn_container.pack(side='right') Button(btn_container, text="运行", width=8, command=lambda: self._run_module(module)).pack(side='left', padx=2) # ...其他按钮... ``` 但在`_setup_center_area`中已经创建了全局按钮区: ```python # 操作按钮放在右下角 self.btn_frame = Frame(self.dynamic_container) self.btn_frame.grid(row=1, column=0, sticky='se', pady=5, padx=5) Button(self.btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) # ...其他按钮... ``` **修复方案:** 移除`_on_module_button_click`方法中的按钮创建代码,保留全局按钮区。 ### 完整修复代码 ```python def _on_module_button_click(self, module: str): """模块按钮点击事件处理 - 修复按钮重复问题""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module if not hasattr(self, 'module_content_frame') or self.module_content_frame is None: # 紧急初始化内容框架 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') logging.error("紧急初始化 module_content_frame") self._init_default_dynamic_content() # 清除现有内容 try: for widget in self.module_content_frame.winfo_children(): widget.destroy() except AttributeError as e: logging.critical(f"清除内容失败: {str(e)}") self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') self._init_default_dynamic_content() # 创建新的内容容器 content_frame = Frame(self.module_content_frame) content_frame.pack(fill='both', expand=True) # 根据模块类型创建具体内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 强制刷新界面 self.root.update_idletasks() ``` ### 修复说明 1. **`LabelFrame`导入问题** - 确保在文件顶部导入所有必要的Tkinter组件: ```python from tkinter import LabelFrame, PanedWindow, Frame, Label, Button, Entry, Scrollbar, Text, StringVar, Canvas ``` 2. **按钮重复问题** - 移除了`_on_module_button_click`方法中创建按钮区的代码 - 保留全局按钮区`self.btn_frame`在`_setup_center_area`中的创建 - 使用全局按钮区的`_run_current_module`方法替代模块特定的运行函数 3. **模块内容框架初始化** - 添加了更健壮的错误处理 - 确保在`module_content_frame`不存在时正确初始化 ### 修复效果 1. 输入分析模块将正确显示所有标签和输入框 2. 跟随分析模块不再显示重复按钮 3. 所有模块使用统一的全局按钮区 4. 增强错误处理,避免UI初始化失败
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值