import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import threading
import xlwings as xw
import pythoncom
import subprocess
class WPSImageInserter:
def __init__(self, root):
self.root = root
self.root.title("WPS表格自由图片插入工具")
self.root.geometry("900x1000")
self.root.resizable(True, True)
# 变量初始化
self.excel_file = tk.StringVar()
self.image_folder = tk.StringVar()
self.sheet_name_var = tk.StringVar(value="")
self.start_cell_var = tk.StringVar(value="B19") # 起始单元格
self.end_cell_var = tk.StringVar(value="E22") # 结束单元格
self.status_var = tk.StringVar(value="准备就绪")
self.current_preview_path = None # 当前预览图片的完整路径
self.processing = False
self.all_image_files = [] # 所有图片文件
self.selected_image_files = [] # 选中的图片文件
self.sheet_names = [] # 存储工作表名称
self.preview_image = None # 预览图片对象
self.inserted_images = [] # 存储已插入的图片
# 设置默认选项(硬编码)
self.keep_aspect = True # 默认保持宽高比
self.backup = True # 默认创建备份
self.create_widgets()
def create_widgets(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="15")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置行列权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(9, weight=1) # 图片选择区域行
# 文件选择区域
ttk.Label(main_frame, text="WPS表格文件:", font=("Arial", 11)).grid(
row=1, column=0, sticky=tk.W, pady=5)
excel_frame = ttk.Frame(main_frame)
excel_frame.grid(row=1, column=1, columnspan=3, sticky=(tk.W, tk.E), pady=5)
excel_frame.columnconfigure(0, weight=1)
excel_entry = ttk.Entry(excel_frame, textvariable=self.excel_file)
excel_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(excel_frame, text="选择文件", command=self.browse_excel).grid(row=0, column=1)
ttk.Label(main_frame, text="图片文件夹:", font=("Arial", 11)).grid(
row=2, column=0, sticky=tk.W, pady=5)
image_frame = ttk.Frame(main_frame)
image_frame.grid(row=2, column=1, columnspan=3, sticky=(tk.W, tk.E), pady=5)
image_frame.columnconfigure(0, weight=1)
image_entry = ttk.Entry(image_frame, textvariable=self.image_folder)
image_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(image_frame, text="选择文件夹", command=self.browse_image_folder).grid(row=0, column=1)
# 工作表选择
ttk.Label(main_frame, text="选择工作表:", font=("Arial", 11)).grid(
row=3, column=0, sticky=tk.W, pady=5)
sheet_frame = ttk.Frame(main_frame)
sheet_frame.grid(row=3, column=1, columnspan=3, sticky=(tk.W, tk.E), pady=5)
sheet_frame.columnconfigure(0, weight=1)
self.sheet_combo = ttk.Combobox(sheet_frame, textvariable=self.sheet_name_var, state="readonly")
self.sheet_combo.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(sheet_frame, text="刷新工作表", command=self.refresh_sheets).grid(row=0, column=1)
# 插入区域设置
ttk.Label(main_frame, text="插入区域设置:", font=("Arial", 12, "bold")).grid(
row=4, column=0, sticky=tk.W, columnspan=4, pady=(10, 5))
# 起始单元格
ttk.Label(main_frame, text="起始单元格:").grid(row=5, column=0, sticky=tk.W, pady=5)
start_cell_entry = ttk.Entry(main_frame, textvariable=self.start_cell_var, width=10)
start_cell_entry.grid(row=5, column=1, sticky=tk.W, pady=5)
ttk.Label(main_frame).grid(row=1, column=1, sticky=tk.W, padx=(80, 0), pady=5)
# 结束单元格
ttk.Label(main_frame, text="结束单元格:").grid(row=5, column=2, sticky=tk.W, pady=5)
end_cell_entry = ttk.Entry(main_frame, textvariable=self.end_cell_var, width=10)
end_cell_entry.grid(row=5, column=3, sticky=tk.W, pady=5)
ttk.Label(main_frame).grid(row=2, column=3, sticky=tk.W, padx=(80, 0), pady=5)
# 区域信息显示
self.area_info_var = tk.StringVar(value="区域信息将在这里显示")
ttk.Label(main_frame, textvariable=self.area_info_var, font=("Arial", 10),
foreground="blue").grid(row=6, column=0, columnspan=4, sticky=tk.W, pady=5)
# 图片选择区域
ttk.Label(main_frame, text="图片选择 (双击添加或预览):", font=("Arial", 12, "bold")).grid(
row=7, column=0, sticky=tk.W, columnspan=4, pady=(10, 5))
# 创建左右分栏
selection_frame = ttk.Frame(main_frame)
selection_frame.grid(row=8, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
selection_frame.columnconfigure(0, weight=1)
selection_frame.columnconfigure(1, weight=1)
selection_frame.rowconfigure(0, weight=1)
# 左侧:所有图片列表 - 增加数量显示
self.all_images_label = ttk.LabelFrame(selection_frame, text="所有图片 (0)")
self.all_images_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
self.all_images_label.columnconfigure(0, weight=1)
self.all_images_label.rowconfigure(0, weight=1)
self.all_images_listbox = tk.Listbox(self.all_images_label, selectmode=tk.SINGLE,
font=("Consolas", 9), height=20) # 增加高度
scrollbar_left = ttk.Scrollbar(self.all_images_label, orient="vertical", command=self.all_images_listbox.yview)
self.all_images_listbox.configure(yscrollcommand=scrollbar_left.set)
self.all_images_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar_left.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 右侧:选中图片列表 - 增加数量显示
self.selected_images_label = ttk.LabelFrame(selection_frame, text="已选图片 (0)")
self.selected_images_label.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(10, 0))
self.selected_images_label.columnconfigure(0, weight=1)
self.selected_images_label.rowconfigure(0, weight=1)
self.selected_images_listbox = tk.Listbox(self.selected_images_label, font=("Consolas", 9), height=20) # 增加高度
scrollbar_right = ttk.Scrollbar(self.selected_images_label, orient="vertical",
command=self.selected_images_listbox.yview)
self.selected_images_listbox.configure(yscrollcommand=scrollbar_right.set)
self.selected_images_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar_right.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 选择按钮
button_frame = ttk.Frame(selection_frame)
button_frame.grid(row=1, column=0, columnspan=2, pady=10)
ttk.Button(button_frame, text="全选", command=self.select_all).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="添加", command=self.add_selected).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="清除选择", command=self.clear_selection).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="上移", command=self.move_up).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="下移", command=self.move_down).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=5)
# 图片预览区域
ttk.Label(main_frame, text="图片预览:", font=("Arial", 12, "bold")).grid(
row=9, column=0, sticky=tk.W, columnspan=4, pady=(10, 5))
preview_frame = ttk.Frame(main_frame)
preview_frame.grid(row=10, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=6)
# 预览画布
self.preview_canvas = tk.Canvas(preview_frame, width=300, height=150, bg="#f0f0f0",
highlightthickness=1, highlightbackground="#cccccc")
self.preview_canvas.pack(side=tk.LEFT, padx=(0, 20))
self.preview_canvas.create_text(150, 75, text="双击图片预览", fill="#666666")
# 添加双击事件绑定
self.preview_canvas.bind('<Double-Button-1>', self.open_original_image)
# 图片信息显示
self.preview_info = tk.Text(preview_frame, height=8, width=20, wrap=tk.WORD,
font=("Consolas", 9), bg="#f8f8f8")
scrollbar_preview = ttk.Scrollbar(preview_frame, orient="vertical", command=self.preview_info.yview)
self.preview_info.configure(yscrollcommand=scrollbar_preview.set)
self.preview_info.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar_preview.pack(side=tk.RIGHT, fill=tk.Y)
self.preview_info.insert("1.0",
"双击列表中的图片可在此预览\n图片信息将显示在这里\n\n提示: 双击预览图片可打开原图")
self.preview_info.configure(state="disabled")
# 绑定列表事件
self.all_images_listbox.bind('<Double-Button-1>', self.preview_and_select_image)
self.selected_images_listbox.bind('<Double-Button-1>', self.preview_selected_image)
# 进度和控制区域
control_frame = ttk.Frame(main_frame)
control_frame.grid(row=12, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
self.progress = ttk.Progressbar(control_frame, orient="horizontal", mode="determinate")
self.progress.pack(fill=tk.X, pady=5)
status_label = ttk.Label(control_frame, textvariable=self.status_var)
status_label.pack(pady=5)
action_frame = ttk.Frame(control_frame)
action_frame.pack(pady=15)
self.start_button = ttk.Button(action_frame, text="开始插入图片", command=self.start_insertion)
self.start_button.pack(side=tk.LEFT, padx=10)
ttk.Button(action_frame, text="刷新图片列表", command=self.refresh_image_list).pack(side=tk.LEFT, padx=10)
ttk.Button(action_frame, text="打开文件位置", command=self.open_file_location).pack(side=tk.LEFT, padx=10)
ttk.Button(action_frame, text="退出", command=self.root.quit).pack(side=tk.LEFT, padx=10)
# 配置权重
main_frame.rowconfigure(8, weight=1) # 图片选择区域行
def update_count_labels(self):
"""更新所有图片和已选图片的数量显示"""
self.all_images_label.config(text=f"所有图片 ({len(self.all_image_files)})")
self.selected_images_label.config(text=f"已选图片 ({len(self.selected_image_files)})")
def browse_excel(self):
filetypes = [
("所有文件", "*.*")
]
filename = filedialog.askopenfilename(title="选择WPS表格文件", filetypes=filetypes)
if filename:
self.excel_file.set(filename)
self.status_var.set("表格文件已选择")
self.refresh_sheets()
def refresh_sheets(self):
excel_file = self.excel_file.get()
if excel_file and os.path.exists(excel_file):
try:
# 使用xlwings获取工作表名称
app = xw.App(visible=False)
wb = app.books.open(excel_file)
self.sheet_names = [sheet.name for sheet in wb.sheets]
wb.close()
app.quit()
self.sheet_combo['values'] = self.sheet_names
if self.sheet_names:
self.sheet_name_var.set(self.sheet_names[0])
self.status_var.set(f"找到 {len(self.sheet_names)} 个工作表")
else:
self.status_var.set("未找到工作表")
except Exception as e:
messagebox.showerror("错误", f"读取工作表失败: {str(e)}")
def browse_image_folder(self):
folder = filedialog.askdirectory(title="选择图片文件夹")
if folder:
self.image_folder.set(folder)
self.status_var.set("图片文件夹已选择")
self.refresh_image_list()
def refresh_image_list(self):
folder = self.image_folder.get()
if folder and os.path.exists(folder):
supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')
try:
all_files = os.listdir(folder)
self.all_image_files = [f for f in all_files if f.lower().endswith(supported_formats)]
# 更新所有图片列表
self.all_images_listbox.delete(0, tk.END)
for filename in self.all_image_files:
self.all_images_listbox.insert(tk.END, filename)
# 更新颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
self.update_area_info()
# 自动选择并预览第一张图片
self.auto_preview_first_image()
except Exception as e:
messagebox.showerror("错误", f"读取文件夹错误: {e}")
def auto_preview_first_image(self):
"""自动预览第一张可用图片"""
if self.all_image_files:
# 选择第一张图片
self.all_images_listbox.selection_clear(0, tk.END)
self.all_images_listbox.selection_set(0)
self.all_images_listbox.see(0)
# 预览第一张图片
first_image_path = os.path.join(self.image_folder.get(), self.all_image_files[0])
self.update_preview(first_image_path)
self.show_preview_info(first_image_path)
else:
# 如果没有图片,清空预览
self.clear_preview()
def clear_preview(self):
"""清空预览区域"""
self.preview_canvas.delete("all")
self.preview_canvas.create_text(150, 75, text="双击图片预览", fill="#666666")
self.current_preview_path = None
self.preview_info.configure(state="normal")
self.preview_info.delete("1.0", "end")
self.preview_info.insert("1.0",
"双击列表中的图片可在此预览\n图片信息将显示在这里\n\n提示: 双击预览图片可打开原图")
self.preview_info.configure(state="disabled")
def update_all_listbox_colors(self):
"""更新所有图片列表的颜色,已选的置灰"""
for i, filename in enumerate(self.all_image_files):
if filename in self.selected_image_files:
self.all_images_listbox.itemconfig(i, {'fg': 'gray'})
else:
self.all_images_listbox.itemconfig(i, {'fg': 'black'})
def update_area_info(self):
"""更新区域信息"""
start_cell = self.start_cell_var.get().strip()
end_cell = self.end_cell_var.get().strip()
if start_cell and end_cell:
try:
start_row, start_col = self.parse_cell_reference(start_cell)
end_row, end_col = self.parse_cell_reference(end_cell)
if start_row and end_row:
rows = end_row - start_row + 1
cols = end_col - start_col + 1
total_cells = rows * cols
info_text = f"区域: {start_cell}:{end_cell} | 行数: {rows} | 列数: {cols} | 总单元格: {total_cells}"
self.area_info_var.set(info_text)
return
except:
pass
self.area_info_var.set("请输入有效的单元格区域")
def add_selected(self):
"""添加选中的图片到已选图片列表,并选中下一张"""
selection = self.all_images_listbox.curselection()
if selection:
index = selection[0]
filename = self.all_image_files[index]
# 添加到已选列表(如果尚未选中)
if filename not in self.selected_image_files:
self.selected_image_files.append(filename)
self.selected_images_listbox.insert(tk.END, filename)
# 更新列表颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 选中下一张图片(如果有)
next_index = index + 1
if next_index < len(self.all_image_files):
self.all_images_listbox.selection_clear(0, tk.END)
self.all_images_listbox.selection_set(next_index)
self.all_images_listbox.see(next_index)
# 显示预览
image_path = os.path.join(self.image_folder.get(), self.all_image_files[next_index])
self.update_preview(image_path)
self.show_preview_info(image_path)
else:
# 如果是最后一张,保持当前预览
image_path = os.path.join(self.image_folder.get(), filename)
self.update_preview(image_path)
self.show_preview_info(image_path)
def preview_and_select_image(self, event):
"""双击预览并选中图片"""
selection = self.all_images_listbox.curselection()
if selection:
index = selection[0]
filename = self.all_image_files[index]
# 添加到已选列表(如果尚未选中)
if filename not in self.selected_image_files:
self.selected_image_files.append(filename)
self.selected_images_listbox.insert(tk.END, filename)
# 更新列表颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 显示预览
image_path = os.path.join(self.image_folder.get(), filename)
self.update_preview(image_path)
self.show_preview_info(image_path)
def preview_selected_image(self, event):
"""双击已选图片列表预览"""
selection = self.selected_images_listbox.curselection()
if selection:
index = selection[0]
filename = self.selected_image_files[index]
# 显示预览
image_path = os.path.join(self.image_folder.get(), filename)
self.update_preview(image_path)
self.show_preview_info(image_path)
def update_preview(self, image_path):
"""更新图片预览"""
try:
img = Image.open(image_path)
# 保持宽高比进行缩放
img.thumbnail((280, 130), Image.LANCZOS) # 稍微增大预览尺寸
self.preview_image = ImageTk.PhotoImage(img)
self.preview_canvas.delete("all")
self.preview_canvas.create_image(150, 75, image=self.preview_image) # 更新中心点
# 保存当前预览图片路径
self.current_preview_path = image_path
except Exception as e:
self.preview_canvas.delete("all")
self.preview_canvas.create_text(150, 75, text="预览失败", fill="red")
print(f"预览错误: {e}")
self.current_preview_path = None
def show_preview_info(self, image_path):
"""显示图片详细信息"""
try:
img = Image.open(image_path)
filename = os.path.basename(image_path)
file_size = os.path.getsize(image_path) / 1024 # KB
file_size_str = f"{file_size:.1f} KB" if file_size < 1024 else f"{file_size / 1024:.1f} MB"
info_text = f"文件名: {filename}\n"
info_text += f"尺寸: {img.width} × {img.height} 像素\n"
info_text += f"格式: {img.format}\n"
info_text += f"大小: {file_size_str}\n"
info_text += f"模式: {img.mode}\n"
self.preview_info.configure(state="normal")
self.preview_info.delete("1.0", "end")
self.preview_info.insert("1.0", info_text)
self.preview_info.configure(state="disabled")
except Exception as e:
self.preview_info.configure(state="normal")
self.preview_info.delete("1.0", "end")
self.preview_info.insert("1.0", f"无法读取图片信息: {str(e)}")
self.preview_info.configure(state="disabled")
def open_original_image(self, event=None):
"""使用系统默认程序打开原图"""
if not self.current_preview_path or not os.path.exists(self.current_preview_path):
messagebox.showwarning("警告", "没有可用的图片预览或图片不存在")
return
try:
# 根据操作系统使用不同的方式打开图片
if os.name == 'nt': # Windows
os.startfile(self.current_preview_path)
elif os.name == 'posix': # macOS or Linux
if os.uname().sysname == 'Darwin': # macOS
subprocess.call(('open', self.current_preview_path))
else: # Linux
subprocess.call(('xdg-open', self.current_preview_path))
self.status_var.set(f"已打开原图: {os.path.basename(self.current_preview_path)}")
except Exception as e:
messagebox.showerror("错误", f"无法打开图片: {str(e)}")
self.status_var.set("打开图片失败")
def select_all(self):
"""全选所有图片"""
self.selected_image_files = self.all_image_files.copy()
self.selected_images_listbox.delete(0, tk.END)
for filename in self.selected_image_files:
self.selected_images_listbox.insert(tk.END, filename)
# 更新列表颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 如果所有图片列表中有图片,预览第一张
if self.all_image_files:
image_path = os.path.join(self.image_folder.get(), self.all_image_files[0])
self.update_preview(image_path)
self.show_preview_info(image_path)
def clear_selection(self):
"""清除所有选择"""
self.selected_image_files = []
self.selected_images_listbox.delete(0, tk.END)
# 更新列表颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 如果所有图片列表中有图片,预览第一张
if self.all_image_files:
image_path = os.path.join(self.image_folder.get(), self.all_image_files[0])
self.update_preview(image_path)
self.show_preview_info(image_path)
else:
self.clear_preview()
def move_up(self):
"""上移选中图片"""
selection = self.selected_images_listbox.curselection()
if selection:
index = selection[0]
if index > 0:
# 交换位置
self.selected_image_files[index], self.selected_image_files[index - 1] = \
self.selected_image_files[index - 1], self.selected_image_files[index]
# 更新列表显示
self.update_selected_listbox()
self.selected_images_listbox.select_set(index - 1)
def move_down(self):
"""下移选中图片"""
selection = self.selected_images_listbox.curselection()
if selection:
index = selection[0]
if index < len(self.selected_image_files) - 1:
# 交换位置
self.selected_image_files[index], self.selected_image_files[index + 1] = \
self.selected_image_files[index + 1], self.selected_image_files[index]
# 更新列表显示
self.update_selected_listbox()
self.selected_images_listbox.select_set(index + 1)
def remove_selected(self):
"""移除选中的图片"""
selection = self.selected_images_listbox.curselection()
if selection:
# 从后往前删除,避免索引变化
for index in sorted(selection, reverse=True):
if 0 <= index < len(self.selected_image_files):
del self.selected_image_files[index]
self.update_selected_listbox()
# 更新列表颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 自动预览第一张可用图片
self.auto_preview_first_image()
def update_selected_listbox(self):
"""更新已选图片列表显示"""
self.selected_images_listbox.delete(0, tk.END)
for filename in self.selected_image_files:
self.selected_images_listbox.insert(tk.END, filename)
def parse_cell_reference(self, cell_ref):
"""解析单元格引用,返回行和列号"""
import re
match = re.match(r'^([A-Za-z]+)(\d+)$', cell_ref)
if not match:
return None, None
col_letter = match.group(1).upper()
row_number = int(match.group(2))
# 将列字母转换为列号
col_number = 0
for char in col_letter:
col_number = col_number * 26 + (ord(char) - ord('A') + 1)
return row_number, col_number
def column_number_to_letter(self, n):
"""将列号转换为列字母"""
result = ""
while n > 0:
n, remainder = divmod(n - 1, 26)
result = chr(65 + remainder) + result
return result
def cm_to_points(self, cm):
"""将厘米转换为点"""
return cm * 28.3465
def remove_inserted_images(self):
"""移除已插入的图片"""
# 从所有图片列表中移除已插入的图片
for img in self.inserted_images:
if img in self.all_image_files:
self.all_image_files.remove(img)
# 从已选图片列表中移除已插入的图片
for img in self.inserted_images:
if img in self.selected_image_files:
self.selected_image_files.remove(img)
# 更新列表显示
self.all_images_listbox.delete(0, tk.END)
for filename in self.all_image_files:
self.all_images_listbox.insert(tk.END, filename)
self.update_selected_listbox()
# 更新颜色和数量显示
self.update_all_listbox_colors()
self.update_count_labels()
# 自动预览第一张可用图片
self.auto_preview_first_image()
# 清空已插入图片列表
self.inserted_images = []
def start_insertion(self):
if self.processing:
return
# 验证输入
excel_file = self.excel_file.get()
image_folder = self.image_folder.get()
sheet_name = self.sheet_name_var.get()
start_cell = self.start_cell_var.get().strip()
end_cell = self.end_cell_var.get().strip()
if not excel_file or not os.path.exists(excel_file):
messagebox.showerror("错误", "请选择有效的WPS表格文件!")
return
if not image_folder or not os.path.exists(image_folder):
messagebox.showerror("错误", "请选择有效的图片文件夹!")
return
if not sheet_name:
messagebox.showerror("错误", "请选择工作表!")
return
if not start_cell or not end_cell:
messagebox.showerror("错误", "请输入起始和结束单元格!")
return
if not self.selected_image_files:
messagebox.showerror("错误", "请选择要插入的图片!")
return
# 解析单元格区域
start_row, start_col = self.parse_cell_reference(start_cell)
end_row, end_col = self.parse_cell_reference(end_cell)
if not all([start_row, start_col, end_row, end_col]):
messagebox.showerror("错误", "请输入有效的单元格引用!")
return
if start_row > end_row or start_col > end_col:
messagebox.showerror("错误", "起始单元格必须在结束单元格的左上方!")
return
# 清空已插入图片列表
self.inserted_images = []
self.processing = True
self.start_button.config(state="disabled")
self.status_var.set("正在插入图片...")
self.progress["value"] = 0
# 在新线程中处理
thread = threading.Thread(
target=self.insert_images,
args=(excel_file, image_folder, sheet_name, start_row, start_col, end_row, end_col)
)
thread.daemon = True
thread.start()
def insert_images(self, excel_file, image_folder, sheet_name, start_row, start_col, end_row, end_col):
try:
# 创建备份文件
if self.backup:
backup_path = excel_file.replace('.xlsx', '_backup.xlsx').replace('.xls', '_backup.xls')
import shutil
shutil.copy2(excel_file, backup_path)
self.root.after(0, lambda: self.update_status(f"已创建备份文件: {os.path.basename(backup_path)}"))
# 初始化COM
pythoncom.CoInitialize()
# 打开Excel文件
self.root.after(0, lambda: self.update_status("正在打开WPS表格..."))
app = xw.App(visible=False)
wb = app.books.open(excel_file)
# 选择指定的工作表
try:
ws = wb.sheets[sheet_name]
except:
self.root.after(0, lambda: self.update_status(f"找不到工作表: {sheet_name}"))
ws = wb.sheets[0]
self.root.after(0, lambda: self.update_status(f"使用默认工作表: {ws.name}"))
# 计算区域大小
rows = end_row - start_row + 1
cols = end_col - start_col + 1
total_cells = rows * cols
# 转换厘米为点
width_points = self.cm_to_points(4.31)
height_points = self.cm_to_points(7.37)
# 插入图片
self.root.after(0, lambda: self.update_status("正在插入图片..."))
inserted_count = 0
for i, filename in enumerate(self.selected_image_files):
if i >= total_cells:
break # 超出区域范围的图片不插入
image_path = os.path.join(image_folder, filename)
# 计算目标单元格位置
cell_row = start_row + (i // cols)
cell_col = start_col + (i % cols)
target_cell = f"{self.column_number_to_letter(cell_col)}{cell_row}"
try:
# 获取目标单元格位置
cell_range = ws.range(target_cell)
left = cell_range.left
top = cell_range.top
# 插入图片
pic = ws.pictures.add(image_path,
left=left,
top=top,
width=width_points,
height=height_points)
# 保持宽高比
if self.keep_aspect:
pic.api.ShapeRange.LockAspectRatio = 1
inserted_count += 1
# 记录已插入的图片
self.inserted_images.append(filename)
self.root.after(0, lambda idx=i, cell=target_cell:
self.update_status(f"插入图片 {idx + 1} 到 {cell}"))
progress = (i + 1) / min(len(self.selected_image_files), total_cells) * 100
self.root.after(0, lambda p=progress: self.progress.config(value=p))
except Exception as e:
self.root.after(0, lambda: self.update_status(f"插入图片到 {target_cell} 失败: {str(e)}"))
# 保存文件
self.root.after(0, lambda: self.update_status("正在保存文件..."))
wb.save()
wb.close()
app.quit()
pythoncom.CoUninitialize()
self.root.after(0, lambda: self.update_status(f"完成!成功插入 {inserted_count} 张图片"))
self.root.after(0, self.process_complete, True, inserted_count)
except Exception as e:
self.root.after(0, lambda: self.update_status(f"处理过程中出错: {str(e)}"))
self.root.after(0, self.process_complete, False)
try:
pythoncom.CoUninitialize()
except:
pass
def update_status(self, message):
self.status_var.set(message)
def process_complete(self, success, inserted_count=0):
self.processing = False
self.start_button.config(state="normal")
if success:
# 询问用户是否要删除已插入的图片
if messagebox.askyesno("完成",
f"处理完成!\n成功插入 {inserted_count} 张图片\n"
f"图片尺寸: 4.31cm × 7.37cm\n"
f"插入区域: {self.start_cell_var.get()} 到 {self.end_cell_var.get()}\n\n"
"是否从列表中移除已插入的图片?"):
# 移除已插入的图片
self.remove_inserted_images()
else:
messagebox.showerror("错误", "处理失败,请查看状态信息")
def open_file_location(self):
excel_file = self.excel_file.get()
if excel_file and os.path.exists(excel_file):
try:
folder_path = os.path.dirname(excel_file)
if os.name == 'nt': # Windows
os.startfile(folder_path)
elif os.name == 'posix': # macOS or Linux
if os.uname().sysname == 'Darwin': # macOS
os.system(f'open "{folder_path}"')
else: # Linux
os.system(f'xdg-open "{folder_path}"')
except:
messagebox.showerror("错误", "无法打开文件夹位置")
else:
messagebox.showerror("错误", "文件不存在")
def main():
root = tk.Tk()
app = WPSImageInserter(root)
root.mainloop()
if __name__ == "__main__":
main()
首次插入图片后,手动输入起始单元格,自动生成结束单元格,如输入的是B1结束单元格自动更新为D1,如果输入的是B2结束单元格为D2以此类推,另外区域信息需要在输入后可以自动计算,目前仅会计算首次
最新发布