theme_text size不生效?90%人都忽略的6个关键参数细节

第一章:theme_text size不生效的常见误区

在使用 R 语言的 ggplot2 绘图系统时,许多用户会遇到 `theme_text` 中设置 `size` 参数后字体大小未生效的问题。这通常并非参数书写错误,而是由于主题元素继承机制或调用方式不当所导致。

误用基础 theme_text 函数

`theme_text` 是一个底层函数,不能直接用于修改图表主题。正确做法是通过 `element_text()` 来定义文本样式,并赋值给具体的主题元素,例如轴标签、标题等。

# 错误写法:直接使用 theme_text
theme(axis.title = theme_text(size = 14))

# 正确写法:使用 element_text
theme(axis.title = element_text(size = 14))

未正确指定主题元素路径

ggplot2 的主题系统具有层级结构,若目标元素路径不准确,样式将无法应用。常见的文本元素包括:
  • axis.title:坐标轴标题
  • axis.text:坐标轴刻度文字
  • plot.title:图表主标题
  • legend.text:图例文字

主题与自定义样式的优先级冲突

当同时使用预设主题(如 theme_minimal())和自定义 theme() 时,加载顺序会影响最终效果。建议将自定义样式置于主题之后,以确保其覆盖默认设置。
问题现象可能原因解决方案
字体大小无变化使用了已弃用的函数改用 element_text()
部分文本未生效元素路径错误检查 ggplot2 官方主题结构文档
样式被重置主题调用顺序不当将 theme() 放在最后

第二章:理解theme_text的核心参数机制

2.1 size参数的作用原理与继承规则

参数作用机制
size参数用于定义系统资源分配的基准单位,其值直接影响内存块或缓冲区的初始容量。该参数在父级配置中设定后,子组件默认继承其数值,确保层级间资源配置的一致性。
继承与覆盖规则
  • 子节点未显式声明size时,自动继承父节点值
  • 显式设置size将中断继承链,采用局部定义值
  • 特殊值inherit可强制恢复继承行为
type Config struct {
    Size   int  `json:"size,omitempty"`
    Inherit bool `json:"inherit"`
}
// 初始化逻辑:若size为0且inherit=true,则从上级上下文获取
上述结构体展示了size字段的典型定义方式,结合标签控制序列化行为,并通过业务逻辑实现继承判断。

2.2 element_text函数中size与其他属性的优先级关系

在ggplot2的主题系统中,`element_text`用于定义文本外观,其属性间存在明确的优先级顺序。当多个属性同时设置时,`size`作为基础样式,通常被其他显式设定覆盖。
属性优先级规则
  • family:字体族,独立于尺寸生效
  • face:字重与倾斜,不影响size计算
  • colour:颜色属性无层级冲突
  • size:基础属性,可被后续主题继承覆盖
代码示例
element_text(size = 12, face = "bold", colour = "blue")
该代码中,`size`作为基础值参与渲染,但若父主题已定义`text`全局大小,则局部`size`将被继承机制覆盖,除非显式设置`inherit.blank = FALSE`。

2.3 主题系统中text通用设置对子元素的影响

在主题系统中,`text` 通用设置作为全局文本样式的基础配置,会直接影响所有继承该主题的子元素渲染表现。
样式继承机制
当父级主题定义了 `text.color` 或 `text.fontSize` 等属性时,子组件在未显式覆盖的情况下将自动继承这些值。这种机制提升了设计一致性,但也可能导致预期外的视觉偏差。
{
  "text": {
    "color": "#333",
    "fontSize": 14,
    "fontFamily": "Roboto"
  }
}
上述主题配置会作用于所有依赖默认样式的文本子元素,如按钮、标签和输入框内的文字。
优先级与覆盖策略
子元素可通过局部样式声明来覆盖继承值。例如:
  • 显式设置 `color` 属性可忽略主题中的文本颜色;
  • 使用 `reset: true` 可切断继承链,强制独立渲染。

2.4 如何通过主题层级结构定位实际生效的size值

在复杂的UI主题系统中,`size`值常因层级覆盖而难以追踪。通过解析主题的继承结构,可逐层定位最终生效的尺寸配置。
主题层级查找流程
根路径 → 组件默认主题 → 全局覆盖 → 局部覆盖 → 运行时动态设置
示例代码:获取最终size值

// 从主题对象中递归查找size
function resolveSize(theme, component, variant) {
  return (
    theme.components?.[component]?.variants?.[variant]?.size || // 局部
    theme.components?.[component]?.size ||                      // 组件默认
    theme.size ||                                               // 全局默认
    'medium'
  );
}

该函数按优先级顺序查找size值,确保高优先级配置覆盖低优先级。

常见size配置优先级(由高到低)
  1. 运行时props直接传入
  2. 组件局部theme覆盖
  3. 全局theme.components定义
  4. 框架默认size

2.5 实战:使用theme_get()诊断当前绘图的主题参数

在ggplot2中,主题控制着图形的非数据元素,如字体、背景、网格线等。`theme_get()`函数能提取当前活动的主题对象,便于调试与定制。
查看默认主题结构
current_theme <- theme_get()
print(current_theme)
该代码返回一个包含所有主题元素的`list`结构,例如`text`、`axis.title`、`panel.background`等。每个元素均为`element_*`类对象(如`element_text`或`element_rect`),携带具体的绘制参数。
提取关键主题参数
可通过名称访问特定组件:
current_theme$axis.title.x
输出显示x轴标题的字体大小、颜色和粗细设置,适用于验证主题是否生效或构建一致性报告模板。
  • 返回结果为`element`类型,不可直接读取原始值
  • 需结合`calc_element()`解析实际渲染值
  • 适用于主题调试与自动化样式审计

第三章:影响size渲染的关键上下文因素

3.1 输出设备与分辨率对字体大小的视觉影响

不同输出设备的物理特性直接影响字体的可读性与视觉表现。屏幕分辨率、像素密度(PPI)以及设备尺寸共同决定了相同字号在不同设备上的实际显示效果。
常见设备的显示差异
  • 手机屏幕:高 PPI,小尺寸,字体更紧凑;
  • 桌面显示器:中等 PPI,较大可视区域,需增大字号提升可读性;
  • 打印输出:极高 DPI(通常 300+),字体边缘更锐利。
CSS 中的响应式字体设置
body {
  font-size: clamp(14px, 2.5vw, 18px);
}
该代码使用 clamp() 函数实现响应式字体:最小 14px,最大 18px,中间值随视口宽度变化(2.5vw)。适用于多设备适配,确保在高分辨率小屏设备上不显得过小,在大屏上不过度拉伸。
推荐实践
设备类型建议基础字号参考 PPI
智能手机16px~300
桌面显示器18px~96
打印文档12pt300+

3.2 图层顺序与geom_text叠加时的显示冲突排查

在使用ggplot2绘制复杂图表时,图层绘制顺序直接影响元素的可见性。当geom_text()与其他几何对象(如geom_col())叠加时,若文本标签被图形覆盖,通常是因图层添加顺序不当所致。
图层绘制逻辑
ggplot2按代码中图层的添加顺序逐层渲染,后添加的图层位于上方。因此,应将geom_text()置于其他填充图层之后,确保标签不被遮挡。

ggplot(data, aes(x = category, y = value)) +
  geom_col(fill = "steelblue") +
  geom_text(aes(label = value), vjust = -0.5)
上述代码中,柱状图先绘制,文本随后叠加,vjust = -0.5使标签略高于柱顶,避免贴边显示。
常见问题对照表
问题现象可能原因解决方案
文本不可见被背景图层覆盖调整图层顺序
标签重叠数据点密集使用nudge_y微调位置

3.3 坐标系缩放(如coord_cartesian)对文本呈现的间接作用

在数据可视化中,`coord_cartesian` 函数用于缩放笛卡尔坐标系,虽不改变原始数据,但会直接影响图形元素的显示范围,包括文本标签。
缩放对文本可见性的影响
当使用 `coord_cartesian(xlim = c(0, 5))` 缩放坐标轴时,超出范围的文本将被裁剪。这与 `scale_x_continuous` 不同,后者会直接过滤数据。

ggplot(mtcars, aes(wt, mpg)) +
  geom_text(aes(label = rownames(mtcars))) +
  coord_cartesian(xlim = c(2, 4))
上述代码中,仅重量(wt)在 2 到 4 范围内的车辆标签会被显示,其余被视觉裁剪。这种机制保持了数据完整性,但改变了呈现逻辑。
布局优化策略
  • 缩放可避免标签重叠,提升可读性
  • 结合 `vjust` 和 `hjust` 微调文本位置
  • 预判裁剪区域,确保关键标签可见

第四章:解决size不生效的典型场景与对策

4.1 场景一:title或axis.text设定了size但无变化——检查主题覆盖逻辑

在使用ggplot2进行可视化时,即使显式设置了`title`或`axis.text`的`size`参数,字体大小仍可能未生效。这通常源于主题(theme)设置中存在后续代码覆盖了先前定义。
常见问题根源
当用户在绘图过程中多次调用`theme()`函数时,后出现的主题属性会覆盖前面的设定。例如:

p <- ggplot(mtcars, aes(x = mpg)) +
  geom_histogram() +
  theme(axis.text = element_text(size = 12)) +
  theme(axis.text = element_text(color = "black"))
上述代码中,第二次调用`theme()`虽仅修改颜色,但未保留`size = 12`,导致字体恢复默认值。
解决方案建议
  • 合并所有样式设定至单个theme()调用中
  • 使用 %+replace% 操作符精确控制主题继承
  • 通过theme_get()查看当前有效主题配置

4.2 场景二:局部文本自定义失败——正确使用element_text(size = )而非纯数值

在ggplot2中对图例或坐标轴标签进行局部文本样式调整时,直接传入纯数值设置字体大小会导致样式失效。
常见错误写法

theme(axis.text.x = 12)  # 错误:不能直接使用数值
该写法会引发警告且不生效,因为theme元素需通过专用函数配置。
正确语法结构

theme(axis.text.x = element_text(size = 12, color = "blue"))
element_text() 是控制文本外观的标准函数,支持 sizecolorface 等参数,确保精确控制文本样式。
  • 必须使用 element_text() 包裹文本属性
  • 直接赋值仅适用于全局主题设置(如 base_size)

4.3 场景三:保存图片后字体变小——调整输出尺寸与DPI匹配策略

在生成图像时,尤其是使用Matplotlib等绘图库保存图表,常出现“保存后字体变小”的问题。这通常源于屏幕显示分辨率与输出图像DPI(每英寸点数)不匹配。
DPI与输出尺寸的关系
图像保存时的物理尺寸由 figsizedpi 共同决定。若未显式设置,高DPI值会导致相同figsize下像素密度增加,视觉上字体缩小。
解决方案:显式控制DPI与尺寸
使用以下代码统一配置:

import matplotlib.pyplot as plt

plt.figure(figsize=(8, 6), dpi=300)
plt.plot([1, 2, 3], [1, 4, 2])
plt.title("示例标题", fontsize=14)
plt.savefig("output.png", dpi=300, bbox_inches='tight')
该代码中,figsize=(8,6) 定义图形为8×6英寸,结合 dpi=300,输出图像分辨率为2400×1800像素。保存时保持DPI一致,避免字体压缩。
推荐参数对照表
用途推荐DPI说明
屏幕展示100适配常规显示器
论文印刷300满足出版清晰度要求
高清展示600用于大幅面打印

4.4 场景四:R Markdown中渲染异常——块选项与主题兼容性处理

在使用 R Markdown 生成报告时,常因代码块选项与输出主题(如 `bookdown`、`xaringan`)不兼容导致渲染失败。例如,某些主题不支持 `fig.cap` 或 `out.width` 等图形参数。
常见冲突的块选项示例
```{r, fig.cap="分布图", out.width="80%"}
plot(pressure)
```
上述代码在 `xaringan` 中会忽略 `out.width`,因其基于 remark.js,不解析 knitr 的输出控制。应改用 CSS 控制图像尺寸。
推荐处理策略
  • 查阅目标主题文档,确认支持的块选项子集
  • 使用条件块选项或自定义钩子动态适配输出格式
  • 通过外部 CSS 替代内联图形控制(如 .left[50%] 在 xaringan 中)

第五章:构建可维护的ggplot2主题配置体系

统一视觉风格的最佳实践
在团队协作或长期项目中,保持图表风格一致至关重要。通过自定义 `theme()` 函数并封装为可复用对象,可实现主题的集中管理。

# 定义企业级主题
theme_corporate <- function(base_size = 12) {
  theme_minimal(base_size) +
    theme(
      text = element_text(family = "Arial"),
      plot.title = element_text(size = rel(1.2), face = "bold"),
      axis.text = element_text(color = "gray30"),
      panel.grid.minor = element_blank(),
      legend.position = "bottom"
    )
}
模块化主题配置策略
将颜色、字体、间距等设计变量抽离为独立参数,提升配置可读性与维护效率。
  • 使用 R 列表存储设计系统常量(如主色、辅助色)
  • 通过 %+replace% 实现主题增量更新
  • 利用 with_theme() 在局部调整中保留全局设定
跨项目主题共享机制
借助 R 包或配置文件(如 YAML)导出主题模板,实现多项目间无缝复用。
方法适用场景维护成本
R Package企业级可视化标准
Source() 脚本小型团队协作
流程图:主题加载流程
配置文件读取 → 主题初始化 → 图层叠加 → 渲染输出
import PySimpleGUI as Py import time import os # ======== 全局设置 ======== Py.theme('Default') a = ' 漫息!' b = '【' # 图片路径定义 image_path2 = "D:/software/demo/pythonProject/images/image2.png" 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" image_path8 = "D:/software/demo/pythonProject/images/image8.png" image_path9 = "D:/software/demo/pythonProject/images/image9.png" # 检查图片是否存在 def check_image(path): if not os.path.exists(path): print(f"[!] 图片不存在: {path}") return None return path image_path2 = check_image(image_path2) image_path3 = check_image(image_path3) image_path4 = check_image(image_path4) image_path5 = check_image(image_path5) image_path6 = check_image(image_path6) image_path7 = check_image(image_path7) image_path8 = check_image(image_path8) image_path9 = check_image(image_path9) # ======== 全局窗口变量 ======== window10 = None # 用于保存 window10 的引用 # ======== 关闭 window10 的安全函数 ======== def close_window10(): global window10 if window10: try: window10.close() except: pass window10 = None # ======== 主窗口 layout1 & window1 ======== layout1 = [[Py.Button(b, button_color=('#006400', 'white'), font=("华文楷体", 12))]] window1_position = (514, 24) window1 = Py.Window('', layout1, size=(1532, 44), no_titlebar=True, location=window1_position, finalize=True) window1.keep_on_top_set() # ======== 辅助窗口 layout2 & window2 ======== layout2 = [[Py.Image(key='img2', filename=image_path2) if image_path2 else Py.Text("加载失败")], [Py.Text(a, size=(23, 200), auto_size_text=True)]] window2_position = (2340, 145) window2 = Py.Window('', layout2, size=(220, 750), no_titlebar=True, location=window2_position, finalize=True) window2.keep_on_top_set() # ======== 周期性提醒功能 ======== reminder_running = False def run_periodic_reminder(): global reminder_running if reminder_running: print("[!] 提醒功能已在运行,忽略重复调用") return reminder_running = True layout9 = [ [Py.Image(key='img9', filename=image_path9) if image_path9 else Py.Text("加载失败")], [Py.Text("天", font=("华文楷体", 10), text_color='blue')], [Py.Text("📌 周期性提醒已激活:每次取消后10秒自动重试")], [Py.Multiline("", size=(60, 4), key='-OUTPUT-', disabled=True, autoscroll=True)], [Py.Button('退出', key='EXIT', button_color=('#800000', 'Silver')), Py.Text("", font=("华文楷体", 10), text_color='red')] ] window9_position = (1795, 300) window9 = Py.Window('', layout9, size=(460, 490), finalize=True, location=window9_position) window9.keep_on_top_set() next_popup_time = time.time() + 1 reminder_active = True popup_active = False input_popup = None def show_input_popup(): layout_popup = [ [Py.Text(':')], [Py.Input(key='-INPUT-', size=(6, 1), font=("Arial", 20), focus=True), Py.Text('或', font=("华文楷体", 16), text_color='#000000'), Py.Text('动', font=("华文楷体", 22), text_color='red', background_color='#86A8FF')], [Py.Button('属于', button_color=('#800000', 'white'), bind_return_key=True), Py.Push(), Py.Button('不属于', button_color=('#000000', 'white'))] ] windowpopup_position = (1890, 680) return Py.Window('', layout_popup, keep_on_top=True, modal=True, location=windowpopup_position, finalize=True) try: while True: try: event9, values9 = window9.read(timeout=100) except (RuntimeError, Exception) as e: if "wrapped C/C++ object has been deleted" in str(e) or "TclError" in str(e): print("[INFO] 窗口已被系统关闭,退出提醒循环。") break else: print(f"[ERROR] Unexpected error: {e}") break if event9 in (None, 'EXIT', Py.WINDOW_CLOSED): close_window10() # 确保旧窗口关闭 layout10 = [ [Py.Image(key='img2', filename=image_path2) if image_path2 else Py.Text("加载失败")], [Py.Text(a, size=(23, 200), auto_size_text=True)] ] window10_pos = (2340, 145) global window10 window10 = Py.Window('', layout10, size=(220, 750), location=window10_pos, finalize=True) window10.keep_on_top_set() break now = time.time() if reminder_active and not popup_active and next_popup_time and now >= next_popup_time: popup_active = True try: input_popup = show_input_popup() except Exception as e: print(f"[Error] 无法创建弹窗: {e}") popup_active = False next_popup_time = now + 10 continue if input_popup: try: event_p, values_p = input_popup.read(timeout=100) except: input_popup = None popup_active = False continue if event_p == '属于': user_input = values_p['-INPUT-'].strip() try: num = float(user_input) formatted_num = int(num) if num.is_integer() else num window9['-OUTPUT-'].update(f"✔️ 录入成功:{formatted_num} —— 提醒停止\n", append=True) reminder_active = False next_popup_time = None except ValueError: window9['-OUTPUT-'].update(f"❌ 输入无效:'{user_input}',60秒后重试...\n", append=True) next_popup_time = time.time() + 60 finally: try: input_popup.close() except: pass input_popup = None popup_active = False elif event_p in (None, '不属于', Py.WINDOW_CLOSED): try: input_popup.close() except: pass input_popup = None popup_active = False window9['-OUTPUT-'].update("⛔ 用户取消,60秒后将再次提醒...\n", append=True) next_popup_time = time.time() + 60 finally: try: window9.close() except: pass if input_popup: try: input_popup.close() except: pass reminder_running = False # ======== 单次流程函数 run_single_flow ======== def run_single_flow(): Py.theme('LightBlue') input_history = [] last_input_time = None INPUT_TIMEOUT = 5.0 current_text = '' layout4 = [ [Py.Image(key='img4', filename=image_path4) if image_path4 else Py.Text("加载失败")], [Py.Text("请在5秒内输入当前动量,之后5秒无操作将自动记录:")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), focus=True), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Text(":")], [Py.Frame('', [[Py.Text("", size=(48, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(590, 75), pad=10)], [Py.Button('美边', button_color=('#006400', 'Silver'))], [Py.Button('同向', button_color=('#000000', 'white')), Py.Button('不符合', button_color=('#800000', 'Silver'))] ] window4_position = (1795, 300) window4 = Py.Window('', layout4, size=(460, 500), resizable=True, location=window4_position, finalize=True) window4.keep_on_top_set() while True: event4, values4 = window4.read(timeout=100) if event4 == Py.WINDOW_CLOSED: break new_text = values4['-INPUT-'].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() window4['-STATUS-'].update(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)) window4['-HISTORY-'].update(history_str) window4['-STATUS-'].update("🎉 已自动记录!") window4['-INPUT-'].update('') current_text = '' last_input_time = None except Exception as e: window4['-STATUS-'].update("❌ 提交失败") last_input_time = None elif last_input_time is None and current_text and not valid_number: window4['-STATUS-'].update("❌ 请输入有效的数字") elif last_input_time and valid_number: remaining = max(0, int(INPUT_TIMEOUT - (time.time() - last_input_time) + 0.9)) if remaining > 0: window4['-STATUS-'].update(f"⏳ 还剩 {remaining} 秒自动提交...") if event4 in (Py.WIN_CLOSED, '不符合'): window4.close() elif event4 == '同向': window4.close() layout5 = [ [Py.Image(key='img5', filename=image_path5) if image_path5 else Py.Text("加载失败")], [Py.Text("请在5秒内输入当前动量,之后5秒无操作将自动记录:")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20)), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Frame('', [[Py.Text("", size=(49, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(410, 50), pad=10)], [Py.Button('进入弱', button_color=('#000000', 'Silver')), Py.Push(), Py.Button('进入强', button_color=('#000000', 'Silver'))] ] window5_position = (1795, 300) window5 = Py.Window('', layout5, size=(460, 430), resizable=True, location=window5_position, finalize=True) window5.keep_on_top_set() while True: event5, values5 = window5.read(timeout=100) if event5 == Py.WINDOW_CLOSED: break new_text = values5['-INPUT-'].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() window5['-STATUS-'].update(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)) window5['-HISTORY-'].update(history_str) window5['-STATUS-'].update("🎉 已自动记录!") window5['-INPUT-'].update('') current_text = '' last_input_time = None except Exception as e: window5['-STATUS-'].update("❌ 提交失败") last_input_time = None elif last_input_time is None and current_text and not valid_number: window5['-STATUS-'].update("❌ 请输入有效的数字") elif last_input_time and valid_number: remaining = max(0, int(INPUT_TIMEOUT - (time.time() - last_input_time) + 0.9)) if remaining > 0: window5['-STATUS-'].update(f"⏳ 还剩 {remaining} 秒自动提交...") elif event5 == '进入弱': window5.close() recorded_numbers = [] layout6 = [ [Py.Image(key='img6', filename=image_path6) if image_path6 else Py.Text("加载失败")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), do_not_clear=False)], [Py.Button('【中进】', button_color=('#006400', 'Silver'), bind_return_key=True), Py.Button('不于进', button_color=('#800000', 'Silver'))], [Py.HorizontalSeparator()], [Py.Text("", size=(50, 2), key='-HISTORY-', relief='sunken', background_color='white', text_color='black')] ] window6_position = (1795, 300) window6 = Py.Window('', layout6, size=(460, 395), location=window6_position, finalize=True) window6.keep_on_top_set() while True: event6, values6 = window6.read() if event6 == Py.WINDOW_CLOSED: break input_value = values6['-INPUT-'].strip() if event6 == '【中进】': close_window10() # <<< 新增:关闭 window10 window2.close() if input_value == '': window6['-HISTORY-'].update("⚠️ 输入为空,无法保留") else: try: num = float(input_value) formatted_num = int(num) if num.is_integer() else num recorded_numbers.append(formatted_num) history_text = ' '.join(map(str, recorded_numbers)) window6['-HISTORY-'].update(history_text) window6['-INPUT-'].update('') except ValueError: pass window6.close() run_periodic_reminder() elif event6 == '不于进': window6['-INPUT-'].update('') elif event5 == '进入强': window5.close() recorded_numbers = [] layout7 = [ [Py.Image(key='img7', filename=image_path7) if image_path7 else Py.Text("加载失败")], [Py.Text("请:")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), do_not_clear=False)], [Py.Button('出现【再', button_color=('#006400', 'Silver'), bind_return_key=True), Py.Button('不于进', button_color=('#800000', 'Silver'))], [Py.HorizontalSeparator()], [Py.Text("", size=(53, 2), key='-HISTORY-', relief='sunken', background_color='white', text_color='black')] ] window7_position = (1795, 350) window7 = Py.Window('', layout7, size=(460, 450), location=window7_position, finalize=True) window7.keep_on_top_set() while True: event7, values7 = window7.read() if event7 == Py.WINDOW_CLOSED: break input_value = values7['-INPUT-'].strip() if event7 == '出现【再': close_window10() # <<< 新增:关闭 window10 window2.close() if input_value == '': window7['-HISTORY-'].update("⚠️ 输入为空,无法保留") else: try: num = float(input_value) formatted_num = int(num) if num.is_integer() else num recorded_numbers.append(formatted_num) history_text = ' '.join(map(str, recorded_numbers)) window7['-HISTORY-'].update(history_text) window7['-INPUT-'].update('') except ValueError: pass window7.close() run_periodic_reminder() elif event7 == '不于进': window7['-INPUT-'].update('') elif event4 == '美边': close_window10() # <<< 新增:关闭 window10 window2.close() window4.close() run_periodic_reminder() # ======== 主事件循环 ======== while True: event1, values1 = window1.read() if event1 == Py.WIN_CLOSED: break if event1 == b: layout3 = [ [Py.Image(key='img3', filename=image_path3) if image_path3 else Py.Text("加载失败")], [Py.Button('第1', button_color=('#006400', 'white')), Py.Push(), Py.Button('第3', button_color=('#006400', 'white'))] ] window3_position = (1795, 400) window3 = Py.Window('交易类型', layout3, size=(460, 190), location=window3_position, finalize=True, keep_on_top=True) window3.keep_on_top_set() while True: event3, values3 = window3.read() if event3 == Py.WINDOW_CLOSED: break if event3 == '第1': window3.close() run_single_flow() if event3 == '第3': window3.close() layout8 = [ [Py.Image(key='img8', filename=image_path8) if image_path8 else Py.Text("加载失败")], [Py.Text("决战", font=("华文楷体", 13), text_color='blue')], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), focus=True), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Frame('', [[Py.Text("", size=(48, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(590, 75), pad=10)], [Py.Button('败退', button_color=('#006400', 'Silver')), Py.Button('反击', button_color=('#006400', 'Silver')), Py.Button('不符合', button_color=('#800000', 'Silver'))] ] window8_position = (1795, 300) window8 = Py.Window('', layout8, size=(460, 470), resizable=True, location=window8_position, finalize=True) window8.keep_on_top_set() while True: event8, values8 = window8.read(timeout=100) if event8 == Py.WINDOW_CLOSED: break if event8 in ('不符合', Py.WINDOW_CLOSED): window8.close() if event8 == '败退' or event8 == '反击': close_window10() # <<< 新增:关闭 window10 window8.close() window2.close() run_periodic_reminder() # ======== 程序结束前清理 ======== try: window1.close() window2.close() close_window10() except: pass 使得所有输入框必须有输入后才能点击按钮,否则点击按钮无效
11-15
import PySimpleGUI as Py import time import os # ======== 全局设置 ======== Py.theme('Default') a = ' 漫息!' b = '【' # 图片路径定义(请确保这些路径在你电脑上真实存在) image_path2 = "D:/software/demo/pythonProject/images/image2.png" 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" image_path8 = "D:/software/demo/pythonProject/images/image8.png" image_path9 = "D:/software/demo/pythonProject/images/image9.png" # 检查图片是否存在 def check_image(path): if not os.path.exists(path): print(f"[!] 图片不存在: {path}") return None return path image_path2 = check_image(image_path2) image_path3 = check_image(image_path3) image_path4 = check_image(image_path4) image_path5 = check_image(image_path5) image_path6 = check_image(image_path6) image_path7 = check_image(image_path7) image_path8 = check_image(image_path8) image_path9 = check_image(image_path9) # ======== 全局窗口变量 ======== window10 = None # 用于保存 window10 的引用 # ======== 关闭 window10 的安全函数 ======== def close_window10(): global window10 if window10: try: window10.close() except Exception: pass window10 = None # ======== 周期性提醒功能 ======== reminder_running = False def run_periodic_reminder(): global reminder_running if reminder_running: print("[!] 提醒功能已在运行,忽略重复调用") return reminder_running = True layout9 = [ [Py.Image(key='img9', filename=image_path9) if image_path9 else Py.Text("加载失败")], [Py.Text("天", font=("华文楷体", 10), text_color='blue')], [Py.Text("📌 周期性提醒已激活:每次取消后10秒自动重试")], [Py.Multiline("", size=(60, 4), key='-OUTPUT-', disabled=True, autoscroll=True)], [Py.Button('退出', key='EXIT', button_color=('#800000', 'Silver')), Py.Text("", font=("华文楷体", 10), text_color='red')] ] window9_position = (1795, 300) window9 = Py.Window('', layout9, size=(460, 490), finalize=True, location=window9_position) window9.keep_on_top_set() next_popup_time = time.time() + 1 reminder_active = True popup_active = False input_popup = None def show_input_popup(): layout_popup = [ [Py.Text(':')], [Py.Input(key='-INPUT-', size=(6, 1), font=("Arial", 20), focus=True), Py.Text('或', font=("华文楷体", 16), text_color='#000000'), Py.Text('动', font=("华文楷体", 22), text_color='red', background_color='#86A8FF')], [Py.Button('属于', key='BELONG', button_color=('#800000', 'white'), bind_return_key=True), Py.Push(), Py.Button('不属于', key='NOT_BELONG', button_color=('#000000', 'white'))] ] windowpopup_position = (1890, 680) return Py.Window('', layout_popup, keep_on_top=True, modal=True, location=windowpopup_position, finalize=True) try: while True: try: event9, values9 = window9.read(timeout=100) except Exception as e: if "wrapped C/C++ object has been deleted" in str(e) or "TclError" in str(e): print("[INFO] 窗口已被系统关闭,退出提醒循环。") break else: print(f"[ERROR] Unexpected error: {e}") break if event9 in (None, 'EXIT', Py.WINDOW_CLOSED): close_window10() layout10 = [ [Py.Image(key='img2', filename=image_path2) if image_path2 else Py.Text("加载失败")], [Py.Text(a, size=(23, 200), auto_size_text=True)] ] window10_pos = (2340, 145) global window10 window10 = Py.Window('', layout10, size=(220, 750), location=window10_pos, finalize=True) window10.keep_on_top_set() break now = time.time() if reminder_active and not popup_active and next_popup_time and now >= next_popup_time: popup_active = True try: input_popup = show_input_popup() except Exception as e: print(f"[Error] 无法创建弹窗: {e}") popup_active = False next_popup_time = now + 10 continue if input_popup: try: event_p, values_p = input_popup.read(timeout=100) except Exception: input_popup = None popup_active = False continue user_input = values_p.get('-INPUT-', '').strip() if values_p else '' valid_number = False try: if user_input: float(user_input) valid_number = True except ValueError: pass # 动态启用按钮 btn_enabled = valid_number input_popup['BELONG'].update(disabled=not btn_enabled) if event_p == 'BELONG' and btn_enabled: try: num = float(user_input) formatted_num = int(num) if num.is_integer() else num window9['-OUTPUT-'].update(f"✔️ 录入成功:{formatted_num} —— 提醒停止\n", append=True) reminder_active = False next_popup_time = None except Exception: window9['-OUTPUT-'].update(f"❌ 输入处理失败:'{user_input}'\n", append=True) next_popup_time = time.time() + 60 finally: try: input_popup.close() except Exception: pass input_popup = None popup_active = False elif event_p in (None, 'NOT_BELONG', Py.WINDOW_CLOSED): try: input_popup.close() except Exception: pass input_popup = None popup_active = False window9['-OUTPUT-'].update("⛔ 用户取消,60秒后将再次提醒...\n", append=True) next_popup_time = time.time() + 60 finally: try: window9.close() except Exception: pass reminder_running = False # ======== 单次流程函数 run_single_flow ======== def run_single_flow(): input_history = [] current_text = '' layout4 = [ [Py.Image(key='img4', filename=image_path4) if image_path4 else Py.Text("加载失败")], [Py.Text("请在5秒内输入当前动量,之后5秒无操作将自动记录:")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), focus=True), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Text(":")], [Py.Frame('', [[Py.Text("", size=(48, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(590, 75), pad=10)], [Py.Button('美边', key='MEIBIAN', button_color=('#006400', 'Silver'), disabled=True)], [Py.Button('同向', key='TONGXIANG', button_color=('#000000', 'white'), disabled=True), Py.Button('不符合', key='CANCEL', button_color=('#800000', 'Silver'))] ] window4_position = (1795, 300) window4 = Py.Window('', layout4, size=(460, 500), resizable=True, location=window4_position, finalize=True) window4.keep_on_top_set() last_input_time = None while True: event4, values4 = window4.read(timeout=100) if event4 == Py.WINDOW_CLOSED or event4 == 'CANCEL': window4.close() return raw_input = values4['-INPUT-'].strip() changed = raw_input != current_text current_text = raw_input # 验证是否为有效数字 valid_number = False try: if current_text: float(current_text) valid_number = True except ValueError: pass # 更新按钮状态 window4['MEIBIAN'].update(disabled=not valid_number) window4['TONGXIANG'].update(disabled=not valid_number) if valid_number: window4['-STATUS-'].update(f"✅ 输入中 '{current_text}' ... 可提交") elif current_text: window4['-STATUS-'].update("❌ 请输入有效的数字") else: window4['-STATUS-'].update("") # 自动提交逻辑 if valid_number and changed: last_input_time = time.time() elif valid_number and last_input_time is not None: elapsed = time.time() - last_input_time remaining = max(0, int(5 - elapsed + 0.9)) if remaining > 0: window4['-STATUS-'].update(f"⏳ 还剩 {remaining} 秒自动提交...") else: 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)) window4['-HISTORY-'].update(history_str) window4['-STATUS-'].update("🎉 已自动记录!") window4['-INPUT-'].update('') current_text = '' last_input_time = None except Exception: window4['-STATUS-'].update("❌ 提交失败") finally: window4['MEIBIAN'].update(disabled=True) window4['TONGXIANG'].update(disabled=True) # 处理按钮事件 if event4 == 'MEIBIAN' and valid_number: close_window10() window2.close() window4.close() run_periodic_reminder() return elif event4 == 'TONGXIANG' and valid_number: window4.close() _run_tongxiang_flow(input_history) return def _run_tongxiang_flow(input_history): current_text = '' layout5 = [ [Py.Image(key='img5', filename=image_path5) if image_path5 else Py.Text("加载失败")], [Py.Text("请在5秒内输入当前动量,之后5秒无操作将自动记录:")], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20)), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Frame('', [[Py.Text("", size=(49, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(410, 50), pad=10)], [Py.Button('进入弱', key='RUO', button_color=('#000000', 'Silver'), disabled=True), Py.Button('进入强', key='QIANG', button_color=('#000000', 'Silver'), disabled=True)] ] window5_position = (1795, 300) window5 = Py.Window('', layout5, size=(460, 430), resizable=True, location=window5_position, finalize=True) window5.keep_on_top_set() last_input_time = None while True: event5, values5 = window5.read(timeout=100) if event5 == Py.WINDOW_CLOSED: window5.close() return raw_input = values5['-INPUT-'].strip() changed = raw_input != current_text current_text = raw_input valid_number = False try: if current_text: float(current_text) valid_number = True except ValueError: pass window5['RUO'].update(disabled=not valid_number) window5['QIANG'].update(disabled=not valid_number) if valid_number: window5['-STATUS-'].update(f"✅ 输入中 '{current_text}' ... 可提交") elif current_text: window5['-STATUS-'].update("❌ 请输入有效的数字") else: window5['-STATUS-'].update("") if valid_number and changed: last_input_time = time.time() if valid_number and last_input_time is not None: elapsed = time.time() - last_input_time if elapsed >= 5.0: 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)) window5['-HISTORY-'].update(history_str) window5['-STATUS-'].update("🎉 已自动记录!") window5['-INPUT-'].update('') current_text = '' last_input_time = None except Exception: window5['-STATUS-'].update("❌ 提交失败") finally: window5['RUO'].update(disabled=True) window5['QIANG'].update(disabled=True) if event5 == 'RUO' and valid_number: window5.close() _run_zhongjin_flow('RUO') return elif event5 == 'QIANG' and valid_number: window5.close() _run_zhongjin_flow('QIANG') return def _run_zhongjin_flow(mode): recorded_numbers = [] title = "【中进】" if mode == 'RUO' else "出现【再" btn_key = 'ZHONGJIN' if mode == 'RUO' else 'CHUXIAN_ZAI' layout = [ [Py.Image(key='img6' if mode == 'RUO' else 'img7', filename=image_path6 if mode == 'RUO' else image_path7)], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), do_not_clear=False)], [Py.Button(title, key=btn_key, button_color=('#006400', 'Silver'), bind_return_key=True, disabled=True), Py.Button('不于进', key='CLEAR_INPUT', button_color=('#800000', 'Silver'))], [Py.HorizontalSeparator()], [Py.Text("", size=(50, 2), key='-HISTORY-', relief='sunken', background_color='white', text_color='black')] ] pos_y = 300 if mode == 'RUO' else 350 window = Py.Window('', layout, size=(460, 395), location=(1795, pos_y), finalize=True) window.keep_on_top_set() while True: event, values = window.read() if event in (None, Py.WINDOW_CLOSED): break user_input = values['-INPUT-'].strip() valid_number = False try: if user_input: float(user_input) valid_number = True except ValueError: pass window[btn_key].update(disabled=not valid_number) if event == btn_key and valid_number: close_window10() window2.close() 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)) window['-HISTORY-'].update(history_text) window['-INPUT-'].update('') except Exception: pass window.close() run_periodic_reminder() break elif event == 'CLEAR_INPUT': window['-INPUT-'].update('') window.close() # ======== 新增:_run_juezhan_flow 必须放在使用前 ======== def _run_juezhan_flow(): current_text = '' layout8 = [ [Py.Image(key='img8', filename=image_path8) if image_path8 else Py.Text("加载失败")], [Py.Text("决战", font=("华文楷体", 13), text_color='blue')], [Py.Input(key='-INPUT-', size=(10, 1), font=("Arial", 20), focus=True), Py.Text("", size=(60, 1), key='-STATUS-')], [Py.Frame('', [[Py.Text("", size=(48, 15), key='-HISTORY-', relief='sunken', background_color='white', text_color='black', font=('Courier', 15))]], size=(590, 75), pad=10)], [Py.Button('败退', key='BUTUI', button_color=('#006400', 'Silver'), disabled=True), Py.Button('反击', key='FANJI', button_color=('#006400', 'Silver'), disabled=True), Py.Button('不符合', key='CANCEL', button_color=('#800000', 'Silver'))] ] window8_position = (1795, 300) window8 = Py.Window('', layout8, size=(460, 470), resizable=True, location=window8_position, finalize=True) window8.keep_on_top_set() input_history = [] last_input_time = None while True: event8, values8 = window8.read(timeout=100) if event8 == Py.WINDOW_CLOSED or event8 == 'CANCEL': window8.close() return raw_input = values8['-INPUT-'].strip() changed = raw_input != current_text current_text = raw_input valid_number = False try: if current_text: float(current_text) valid_number = True except ValueError: pass window8['BUTUI'].update(disabled=not valid_number) window8['FANJI'].update(disabled=not valid_number) if valid_number: window8['-STATUS-'].update(f"✅ 输入中 '{current_text}' ... 可提交") elif current_text: window8['-STATUS-'].update("❌ 请输入有效的数字") else: window8['-STATUS-'].update("") if valid_number and changed: last_input_time = time.time() if valid_number and last_input_time is not None: elapsed = time.time() - last_input_time if elapsed >= 5.0: 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)) window8['-HISTORY-'].update(history_str) window8['-STATUS-'].update("🎉 已自动记录!") window8['-INPUT-'].update('') current_text = '' last_input_time = None except Exception: window8['-STATUS-'].update("❌ 提交失败") finally: window8['BUTUI'].update(disabled=True) window8['FANJI'].update(disabled=True) if event8 in ('BUTUI', 'FANJI') and valid_number: close_window10() window2.close() window8.close() run_periodic_reminder() return # ======== 主窗口 layout1 & window1 ======== layout1 = [[Py.Button(b, button_color=('#006400', 'white'), font=("华文楷体", 12))]] window1_position = (514, 24) window1 = Py.Window('', layout1, size=(1532, 44), no_titlebar=True, location=window1_position, finalize=True) window1.keep_on_top_set() # ======== 辅助窗口 layout2 & window2 ======== layout2 = [ [Py.Image(key='img2', filename=image_path2) if image_path2 else Py.Text("加载失败")], [Py.Text(a, size=(23, 200), auto_size_text=True)] ] window2_position = (2340, 145) window2 = Py.Window('', layout2, size=(220, 750), no_titlebar=True, location=window2_position, finalize=True) window2.keep_on_top_set() # ======== 主事件循环 ======== while True: event1, values1 = window1.read() if event1 == Py.WIN_CLOSED: break if event1 == b: layout3 = [ [Py.Image(key='img3', filename=image_path3) if image_path3 else Py.Text("加载失败")], [Py.Button('第1', key='TYPE1', button_color=('#006400', 'white')), Py.Push(), Py.Button('第3', key='TYPE3', button_color=('#006400', 'white'))] ] window3_position = (1795, 400) window3 = Py.Window('交易类型', layout3, size=(460, 190), location=window3_position, finalize=True, keep_on_top=True) window3.keep_on_top_set() while True: event3, values3 = window3.read() if event3 == Py.WINDOW_CLOSED: break if event3 == 'TYPE1': window3.close() run_single_flow() break if event3 == 'TYPE3': window3.close() _run_juezhan_flow() # ✅ 此时函数已正确定义 break try: window3.close() except Exception: pass # ======== 清理资源 ======== try: window1.close() window2.close() close_window10() except Exception: pass 设置代码实现在窗口9的退出按钮在初始时点击无反应,在popup窗口内输入有效数字,点击属于按钮后,再次点击窗口9的退出才有效
11-15
# -*- coding: utf-8 -*- import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd import tempfile class EnhancedFileSearchApp: def __init__(self, master): self.master = master master.title("🎯 高级文件搜索工具") master.geometry("1200x800") master.minsize(1000, 700) # 设置现代主题和颜色方案 theme = "vista" if sys.platform == "win32" else "aqua" self.style = ttk.Style() self.style.theme_use(theme) # 自定义配色方案 self.colors = { "bg": "#f5f6fa", "header": "#3498db", "accent": "#2980b9", "warning": "#e74c3c", "success": "#2ecc71", "text": "#2c3e50", "highlight": "#f1c40f" } # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 - 使用grid布局更精确控制 main_frame = ttk.Frame(master) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 减少主框架外边距 # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(10, 8), # 减少内边距 style="Search.TLabelframe" ) search_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=(0, 5)) # 减少下边距 # 配置搜索面板样式 self.style.configure("Search.TLabelframe", background=self.colors["bg"], bordercolor=self.colors["accent"], lightcolor=self.colors["accent"]) self.style.configure("Search.TLabelframe.Label", font=("Arial", 10, "bold"), foreground=self.colors["header"]) # 搜索目录 - 优化布局减少垂直空间 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=3) # 减少垂直间距 ttk.Label(dir_frame, text="📁 搜索目录:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.dir_entry = ttk.Entry(dir_frame, width=35) self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.dir_entry.insert(0, os.getcwd()) browse_btn = ttk.Button( dir_frame, text="浏览...", command=self.browse_directory, style="Accent.TButton" ) browse_btn.pack(side=tk.RIGHT) # 关键词 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=5) ttk.Label(kw_frame, text="🔍 关键词:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.keyword_entry = ttk.Entry(kw_frame, width=40) self.keyword_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) # 文件过滤 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=5) ttk.Label(filter_frame, text="📄 文件过滤:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.filter_entry = ttk.Entry(filter_frame, width=40) self.filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.filter_entry.insert(0, "*.c;*.h;*.prm;*.xlsx;*.xls;*.doc;*.docx;*.pdf") # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=5) self.case_var = tk.BooleanVar(value=False) case_chk = ttk.Checkbutton( options_frame, text="忽略大小写", variable=self.case_var, style="Custom.TCheckbutton" ) case_chk.pack(side=tk.LEFT, padx=(0, 15)) self.regex_var = tk.BooleanVar(value=False) regex_chk = ttk.Checkbutton( options_frame, text="正则表达式", variable=self.regex_var, style="Custom.TCheckbutton" ) regex_chk.pack(side=tk.LEFT, padx=(0, 15)) self.binary_var = tk.BooleanVar(value=False) binary_chk = ttk.Checkbutton( options_frame, text="包含二进制", variable=self.binary_var, style="Custom.TCheckbutton" ) binary_chk.pack(side=tk.LEFT, padx=(0, 15)) self.highlight_var = tk.BooleanVar(value=True) highlight_chk = ttk.Checkbutton( options_frame, text="关键字高亮", variable=self.highlight_var, style="Custom.TCheckbutton" ) highlight_chk.pack(side=tk.LEFT) # 配置选项样式 self.style.configure("Custom.TCheckbutton", font=("Arial", 9), foreground=self.colors["text"]) # 按钮组 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(8, 5)) self.search_btn = ttk.Button( btn_frame, text="🔎 开始搜索", command=self.start_search, style="Accent.TButton" ) self.search_btn.pack(side=tk.LEFT, padx=(0, 10)) self.stop_btn = ttk.Button( btn_frame, text="⏹️ 停止", command=self.stop_search, style="Warning.TButton", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) self.export_btn = ttk.Button( btn_frame, text="💾 导出结果", command=self.export_results, style="Success.TButton" ) self.export_btn.pack(side=tk.LEFT) # 按钮样式 self.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white", background=self.colors["accent"]) self.style.configure("Warning.TButton", foreground="white", background=self.colors["warning"]) self.style.configure("Success.TButton", foreground="white", background=self.colors["success"]) # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label( status_frame, text="🟢 就绪", foreground=self.colors["success"], font=("Arial", 9, "bold") ) self.status_label.pack(side=tk.LEFT) self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", thickness=20, background=self.colors["accent"], troughcolor=self.colors["bg"]) self.stats_label = ttk.Label( status_frame, text="", foreground=self.colors["text"], font=("Arial", 9) ) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 - 使用grid放置在下层 results_frame = ttk.Frame(main_frame) results_frame.grid(row=1, column=0, sticky="nsew", pady=0) # 无垂直间距 # 配置网格权重 - 结果面板占据剩余空间 main_frame.rowconfigure(1, weight=1) main_frame.columnconfigure(0, weight=1) # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 文件列表(左侧) file_frame = ttk.LabelFrame( pane, text="📂 搜索结果", padding=(8, 6) # 减少内边距 ) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame( pane, text="🔍 预览内容", padding=(8, 6) # 减少内边距 ) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 定义树状视图列时添加fullpath列 columns = ("filename", "size", "modified", "fullpath") self.file_tree = ttk.Treeview( file_tree_frame, columns=columns, show="headings", selectmode="browse" ) # 配置列时隐藏fullpath列 self.file_tree.column("fullpath", width=0, stretch=False) # 隐藏列 # 配置Treeview样式 self.style.configure("Custom.Treeview", font=("Arial", 9), rowheight=25) self.style.configure("Custom.Treeview.Heading", font=("Arial", 9, "bold"), background=self.colors["accent"], foreground="white") # 配置列 self.file_tree.heading("filename", text="文件名", anchor="w") self.file_tree.heading("size", text="大小", anchor="center") self.file_tree.heading("modified", text="修改时间", anchor="w") self.file_tree.column("filename", width=250, anchor="w") self.file_tree.column("size", width=80, anchor="center") self.file_tree.column("modified", width=150, anchor="w") scroll_y = ttk.Scrollbar(file_tree_frame, orient="vertical", command=self.file_tree.yview) scroll_x = ttk.Scrollbar(file_tree_frame, orient="horizontal", command=self.file_tree.xview) self.file_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.file_tree.grid(row=0, column=0, sticky="nsew") scroll_y.grid(row=0, column=1, sticky="ns") scroll_x.grid(row=1, column=0, sticky="ew") file_tree_frame.rowconfigure(0, weight=1) file_tree_frame.columnconfigure(0, weight=1) self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=8, # 减少内边距 pady=8, # 减少内边距 bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 配置高亮标签 self.preview_text.tag_configure("highlight", background=self.colors["highlight"]) self.preview_text.tag_configure("match", background="#c8e6c9") self.preview_text.tag_configure("header", foreground=self.colors["success"], font=("Arial", 10, "bold")) self.preview_text.tag_configure("warning", foreground=self.colors["warning"], font=("Arial", 9, "italic")) self.preview_text.tag_configure("linenum", foreground="#7f8c8d") # 初始化变量 self.search_thread = None self.stop_requested = False self.results = {} self.all_files = [] # 绑定事件 self.file_tree.bind("<<TreeviewSelect>>", self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) # 配置网格布局 results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) def browse_directory(self): """浏览目录""" directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): """开始搜索""" # 重置状态 self.progress["value"] = 0 self.stop_requested = False self.results = {} self.all_files = [] # 清空UI for item in self.file_tree.get_children(): self.file_tree.delete(item) self.preview_text.delete("1.0", tk.END) # 获取参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") return self.status_label.config(text="🟡 正在搜索...", foreground=self.colors["warning"]) self.search_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 解析文件过滤器 filter_patterns = [pat.strip() for pat in file_filter.split(';')] if ';' in file_filter else [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: pattern = re.compile(re.escape(keyword), flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) return # 在后台线程搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config( text="🟠 搜索已取消", foreground=self.colors["warning"])) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) limit = 100 * 1024 * 1024 # 100MB if file_size > limit: continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 更新UI self.master.after(0, lambda: self.progress.configure(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"0/{total_files}")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in all_files: if self.stop_requested: break processed += 1 # 更新进度 self.master.after(0, lambda v=processed: self.progress.configure(value=v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"{p}/{t} ({p/t*100:.1f}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理不同文件类型 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 获取文件信息 file_name = os.path.basename(file_path) try: size = os.path.getsize(file_path) size_str = f"{size/1024:.1f}KB" if size < 1024*1024 else f"{size/(1024*1024):.1f}MB" mod_time = time.ctime(os.path.getmtime(file_path)) except: size_str = "N/A" mod_time = "N/A" # 在UI线程中添加文件到列表 self.master.after(0, lambda fp=file_path, fn=file_name, sz=size_str, mt=mod_time: self.file_tree.insert("", "end", values=(fn, sz, mt, fp))) # 更新完成状态 if self.stop_requested: status_text = f"🟠 搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"🟢 搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text, foreground=self.colors["success"])) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress.configure(value=total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="🟠 正在停止...", foreground=self.colors["warning"]) self.stop_btn.config(state=tk.DISABLED) def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, "values")[3] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.preview_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = EnhancedFileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 关键字高亮功能没有起作用,点击或者不点击在预览窗口都不会高亮
09-13
library(ggplot2) library(reshape2) library(ggsci) #导入文件"ImmuCellAI_abundance_result" inputFile <- ImmuCellAI_abundance_result outFile="vioplot.pdf" setwd("/Volumes/芦苇常用/DPN学习/2.免疫浸润/immue_analysis") #读取输入文件 rt=read.table("/Volumes/芦苇常用/DPN学习/2.免疫浸润/immuAI/ImmuCellAI_abundance_result.txt", header=T,sep="\t",check.names=F,row.names=1) rt <- rt[,-26] x=colnames(rt)[1] colnames(rt)[1]="Type" # 初始化向量存储原始P值和显著性标记 raw_p_values <- numeric(0) # 用于收集所有免疫细胞的原始P值 immune_cellSig <- c("") # 初始化显著性标记向量,第一列为空字符串(非免疫细胞列) # 循环处理每个免疫细胞列(从第2列开始) for(immune_cell in colnames(rt)[2:ncol(rt)]) { # 提取当前免疫细胞的数据和分组信息 rt1 <- rt[, c(immune_cell, "Type")] colnames(rt1) <- c("relative_abundance", "Type") p <- 1 # 默认P值为1 # 根据分组数量选择检验方法 if(length(levels(factor(rt1$Type))) > 2) { test <- kruskal.test(relative_abundance ~ Type, data = rt1) p <- test$p.value } else { test <- wilcox.test(relative_abundance ~ Type, data = rt1) p <- test$p.value } # 存储原始P值(不立即生成标记) raw_p_values <- c(raw_p_values, p) } # 应用FDR矫正(使用Benjamini-Hochberg方法) adj_p_values <- p.adjust(raw_p_values, method = "fdr") # 校正后P值(q值) # 基于校正后P值生成显著性标记 for(i in 1:length(adj_p_values)) { p_adj <- adj_p_values[i] Sig <- ifelse(p_adj < 0.001, "***", ifelse(p_adj < 0.01, "**", ifelse(p_adj < 0.05, "*", ""))) immune_cellSig <- c(immune_cellSig, Sig) # 添加到标记向量,确保与列顺序匹配 } # 更新列名:添加显著性标记 colnames(rt) <- paste0(colnames(rt), immune_cellSig) #把数据转换成ggplot2文件 data=melt(rt,id.vars=c("Type")) colnames(data)=c("Type","immune_cell","relative_abundance") mycolors <- c("#0055AA", "#C40003", "#4AA329") #绘制 p1=ggplot(data,aes(x=Type,y=relative_abundance,fill=Type))+ guides(fill=guide_legend(title=x))+ labs(x = x, y = "Immune cell abundance")+ geom_violin()+ geom_boxplot(width=0.7,position=position_dodge(1.0))+ facet_wrap(~immune_cell,nrow =2)+ theme_bw()+ theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 13), axis.title.y = element_text(size = 14), # 纵坐标标题字体大小 axis.text.y = element_text(size = 12), # 纵坐标刻度标签字体大小)+ strip.text = element_text(size = 12), scale_fill_manual(values = mycolors)) # 使用AAAS期刊的配色方案 #出图 # 保存优化后的图形 #ggsave("vioplot.svg", plot = p1, width = 27, height =12) pdf(file=outFile, width=18, height=12) print(p1) dev.off() #把数据转换成ggplot2文件 #差异分析 #绘制 #输出 getwd() 检查一下这个代码,为什么做出来的图颜色跟指定的不一样,而且变换了颜色也只有固定的三种颜色
10-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值