diff --git a/Test/Screenshots/chat_result_20260125_085757.jpg b/Test/Screenshots/chat_result_20260125_085757.jpg new file mode 100644 index 0000000..9ccc93d Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_085757.jpg differ diff --git a/Test/Screenshots/chat_result_20260125_085757_analyzed.jpg b/Test/Screenshots/chat_result_20260125_085757_analyzed.jpg new file mode 100644 index 0000000..ab242e9 Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_085757_analyzed.jpg differ diff --git a/Test/Screenshots/chat_result_20260125_085849.jpg b/Test/Screenshots/chat_result_20260125_085849.jpg new file mode 100644 index 0000000..5d97738 Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_085849.jpg differ diff --git a/Test/Screenshots/chat_result_20260125_085849_analyzed.jpg b/Test/Screenshots/chat_result_20260125_085849_analyzed.jpg new file mode 100644 index 0000000..85bff18 Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_085849_analyzed.jpg differ diff --git a/Test/Screenshots/chat_result_20260125_090600.jpg b/Test/Screenshots/chat_result_20260125_090600.jpg new file mode 100644 index 0000000..b8eeebe Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_090600.jpg differ diff --git a/Test/Screenshots/chat_result_20260125_090600_analyzed.jpg b/Test/Screenshots/chat_result_20260125_090600_analyzed.jpg new file mode 100644 index 0000000..4d459f6 Binary files /dev/null and b/Test/Screenshots/chat_result_20260125_090600_analyzed.jpg differ diff --git a/Test/Screenshots/debug_20260125_085800_coord_01_before_click_input.jpg b/Test/Screenshots/debug_20260125_085800_coord_01_before_click_input.jpg new file mode 100644 index 0000000..5533f8b Binary files /dev/null and b/Test/Screenshots/debug_20260125_085800_coord_01_before_click_input.jpg differ diff --git a/Test/Screenshots/debug_20260125_085802_coord_02_after_click_input_keyboard.jpg b/Test/Screenshots/debug_20260125_085802_coord_02_after_click_input_keyboard.jpg new file mode 100644 index 0000000..e81d6c6 Binary files /dev/null and b/Test/Screenshots/debug_20260125_085802_coord_02_after_click_input_keyboard.jpg differ diff --git a/Test/Screenshots/debug_20260125_085803_coord_03_after_input_text.jpg b/Test/Screenshots/debug_20260125_085803_coord_03_after_input_text.jpg new file mode 100644 index 0000000..4e8b6d6 Binary files /dev/null and b/Test/Screenshots/debug_20260125_085803_coord_03_after_input_text.jpg differ diff --git a/Test/Screenshots/debug_20260125_085804_coord_04_after_click_send_image.jpg b/Test/Screenshots/debug_20260125_085804_coord_04_after_click_send_image.jpg new file mode 100644 index 0000000..3a76a37 Binary files /dev/null and b/Test/Screenshots/debug_20260125_085804_coord_04_after_click_send_image.jpg differ diff --git a/Test/Screenshots/debug_20260125_090605_coord_01_before_click_input.jpg b/Test/Screenshots/debug_20260125_090605_coord_01_before_click_input.jpg new file mode 100644 index 0000000..b8eeebe Binary files /dev/null and b/Test/Screenshots/debug_20260125_090605_coord_01_before_click_input.jpg differ diff --git a/Test/Screenshots/debug_20260125_090608_coord_02_after_click_input_keyboard.jpg b/Test/Screenshots/debug_20260125_090608_coord_02_after_click_input_keyboard.jpg new file mode 100644 index 0000000..b8eeebe Binary files /dev/null and b/Test/Screenshots/debug_20260125_090608_coord_02_after_click_input_keyboard.jpg differ diff --git a/Test/Screenshots/debug_20260125_090609_coord_03_after_input_text.jpg b/Test/Screenshots/debug_20260125_090609_coord_03_after_input_text.jpg new file mode 100644 index 0000000..b8eeebe Binary files /dev/null and b/Test/Screenshots/debug_20260125_090609_coord_03_after_input_text.jpg differ diff --git a/Test/Screenshots/debug_20260125_090610_coord_04_after_click_send_image.jpg b/Test/Screenshots/debug_20260125_090610_coord_04_after_click_send_image.jpg new file mode 100644 index 0000000..1517c46 Binary files /dev/null and b/Test/Screenshots/debug_20260125_090610_coord_04_after_click_send_image.jpg differ diff --git a/Test/Screenshots/debug_send_check_20260125_085804.jpg b/Test/Screenshots/debug_send_check_20260125_085804.jpg new file mode 100644 index 0000000..e0a2eb4 Binary files /dev/null and b/Test/Screenshots/debug_send_check_20260125_085804.jpg differ diff --git a/Test/Screenshots/debug_send_check_20260125_090610.jpg b/Test/Screenshots/debug_send_check_20260125_090610.jpg new file mode 100644 index 0000000..1517c46 Binary files /dev/null and b/Test/Screenshots/debug_send_check_20260125_090610.jpg differ diff --git a/Test/T2_testWeXinChat.py b/Test/T2_testWeXinChat.py deleted file mode 100644 index 58f6f42..0000000 --- a/Test/T2_testWeXinChat.py +++ /dev/null @@ -1,271 +0,0 @@ -# coding=utf-8 -import uiautomator2 as u2 -import time -import logging -import sys -import os -import cv2 -import numpy as np - -# 配置日志 -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger("TestWeChat") - -def analyze_chat_image(image_path, output_path): - """ - 识别微信聊天截图中的头像并画框 - """ - logger.info(f"正在分析图片: {image_path}") - - # 读取图片 - # 注意:cv2.imread 不支持中文路径,需要用 np.fromfile 读取 - try: - img_data = np.fromfile(image_path, dtype=np.uint8) - img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) - except Exception as e: - logger.error(f"读取图片失败: {e}") - return - - if img is None: - logger.error("图片读取为空") - return - - height, width = img.shape[:2] - logger.info(f"图片尺寸: {width}x{height}") - - # 1. 预处理 - # 转为灰度图 - gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - - # 使用自适应阈值二值化 - thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, - cv2.THRESH_BINARY_INV, 11, 2) - - # 形态学操作:闭运算,填充内部空洞 - kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) - closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) - - # 2. 轮廓查找 - contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - logger.info(f"检测到轮廓数量: {len(contours)}") - - # 收集所有符合条件的头像 - avatars = [] - - # 3. 筛选轮廓 - for contour in contours: - # 获取外接矩形 - x, y, w, h = cv2.boundingRect(contour) - - # 筛选条件优化: - # 1. 形状接近正方形 (放宽宽高比限制: 0.8 ~ 1.2) - aspect_ratio = float(w) / h - - # 2. 尺寸适中 - # 假设头像宽度在屏幕宽度的 6% 到 15% 之间 - min_w = width * 0.06 - max_w = width * 0.15 - - # 3. 位置筛选 - # 排除底部输入框区域 (假设底部 10% 为输入区域) - if y > height * 0.9: - continue - - # 左侧头像:靠左边 (x < width * 0.18) - # 右侧头像:靠右边 (x > width * 0.82) - is_left = x < width * 0.18 - is_right = x > width * 0.82 - - if 0.8 <= aspect_ratio <= 1.2 and min_w < w < max_w: - if is_left or is_right: - side = "Left" if is_left else "Right" - avatars.append({ - 'x': x, 'y': y, 'w': w, 'h': h, - 'side': side - }) - - # 按 y 坐标排序 - avatars.sort(key=lambda a: a['y']) - - logger.info(f"找到有效头像数量: {len(avatars)}") - - # 4. 绘制对话内容框 (Green/Red Boxes) - # 策略:按顺序遍历头像,如果发现同侧连续,则视为一组。 - # 从当前组的第一个头像上方开始,直到下一个不同侧的头像上方(或底部)。 - - if avatars: - i = 0 - while i < len(avatars): - current_group_start = i - current_side = avatars[i]['side'] - - # 找到当前组的结束位置 (即下一个不同侧头像的索引) - j = i + 1 - while j < len(avatars) and avatars[j]['side'] == current_side: - j += 1 - - # 当前组范围: avatars[i] ... avatars[j-1] - # 确定绘制区域的 Y 轴范围 - - # Start Y: 当前组第一个头像的上方 (例如 -10px) - start_y = max(0, avatars[i]['y'] - 10) - - # End Y: 下一组第一个头像的上方 (减去较大间距,例如 -30px),或者当前组最后一个头像的底部加上边距 - # 为了让框之间有明显间隔,我们采取策略: - # 如果有下一组,End Y = 下一组第一个头像的 y - 30 (留出间隙) - # 如果没有下一组,End Y = 屏幕底部区域上方 - - if j < len(avatars): - end_y = max(start_y + 10, avatars[j]['y'] - 30) - else: - end_y = int(height * 0.9) # 到底部输入框上方 - - # 绘制大框 - # 左侧 (Left) -> 对方 -> 绿色 (0, 255, 0) - # 右侧 (Right) -> 我 -> 红色 (0, 0, 255) - # 注意 OpenCV 颜色是 BGR - box_color = (0, 255, 0) if current_side == "Left" else (0, 0, 255) - - # 绘制矩形 (空心,线宽 5) - # X 轴范围:0 到 width - cv2.rectangle(img, (0, start_y), (width, end_y), box_color, 5) - - logger.info(f"绘制内容框: 侧别={current_side}, 范围 Y={start_y} to {end_y}") - - # 移动到下一组 - i = j - - # 5. 绘制头像框 (Blue/Yellow Boxes) - 画在内容框之上 - for av in avatars: - x, y, w, h = av['x'], av['y'], av['w'], av['h'] - # 左侧:蓝色 (BGR: 255, 0, 0) - # 右侧:黄色 (BGR: 0, 255, 255) - color = (255, 0, 0) if av['side'] == "Left" else (0, 255, 255) - - cv2.rectangle(img, (x, y), (x + w, y + h), color, 10) - logger.info(f"绘制头像: 位置=({x},{y}), 侧别={av['side']}") - - logger.info(f"共标记了 {len(avatars)} 个头像") - - # 6. 保存结果 - try: - # cv2.imwrite 不支持中文路径,使用 imencode + tofile - ext = os.path.splitext(output_path)[1] - cv2.imencode(ext, img)[1].tofile(output_path) - logger.info(f"✅ 分析结果已保存至: {output_path}") - except Exception as e: - logger.error(f"保存分析图片失败: {e}") - -def main(): - logger.info("开始执行微信搜索测试...") - - # 连接设备 - try: - d = u2.connect() - logger.info(f"设备连接成功: {d.info.get('serial')}") - except Exception as e: - logger.error(f"设备连接失败: {e}") - return - - # 1. 启动微信 - logger.info("步骤 1: 启动微信...") - d.app_start("com.tencent.mm", stop=True) - # 等待微信启动完成 - time.sleep(5) - - # 获取屏幕尺寸 - w, h = d.window_size() - logger.info(f"屏幕尺寸: {w}x{h}") - - # 2. 点击搜索按钮 (参考 Opener.py 的坐标比例: w * 0.84, h * 0.08) - search_x = int(w * 0.84) - search_y = int(h * 0.08) - logger.info(f"步骤 2: 点击搜索按钮 (坐标: {search_x}, {search_y})...") - d.click(search_x, search_y) - time.sleep(2) - - # 3. 输入 "糖豆爸爸" - target_name = "糖豆爸爸" - logger.info(f"步骤 3: 输入搜索内容 '{target_name}'...") - - try: - # 启用 FastInputIME 以支持中文输入 - d.set_input_ime(True) - - # 点击搜索框获取焦点 (参考 Opener.py: w * 0.4, h * 0.08) - d.click(int(w * 0.4), int(h * 0.08)) - time.sleep(1) - - # 输入文字 - d.send_keys(target_name) - time.sleep(2) - - # 恢复输入法 - d.set_input_ime(False) - except Exception as e: - logger.error(f"输入文字失败: {e}") - try: - d(focused=True).set_text(target_name) - except: - pass - - # 4. 点击搜索结果 - logger.info("步骤 4: 查找并点击搜索结果...") - time.sleep(2) - - found = False - - # 策略 A: 精确匹配文本 - if d(text=target_name).exists: - logger.info(f"找到文本为 '{target_name}' 的元素,点击...") - d(text=target_name).click() - found = True - else: - logger.warning(f"未找到文本为 '{target_name}' 的元素,尝试模糊匹配或坐标点击...") - - # 策略 B: 坐标点击 (参考 Opener.py 点击第一个结果: w * 0.5, h * 0.18) - result_x = int(w * 0.5) - result_y = int(h * 0.18) - logger.info(f"尝试点击第一个搜索结果位置 ({result_x}, {result_y})...") - d.click(result_x, result_y) - found = True - - if found: - logger.info("✅ 已执行点击操作,应该已进入对话框。") - - # 5. 截图保存结果 - logger.info("步骤 5: 截图并分析...") - # 等待界面加载稳定 - time.sleep(3) - - # 创建截图目录 - screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") - if not os.path.exists(screenshot_dir): - os.makedirs(screenshot_dir) - - # 生成文件名 - timestamp = time.strftime("%Y%m%d_%H%M%S") - filename = f"chat_result_{timestamp}.jpg" - save_path = os.path.join(screenshot_dir, filename) - - try: - d.screenshot(save_path) - logger.info(f"✅ 原始截图已保存: {save_path}") - - # 6. 分析截图并标记头像 - logger.info("步骤 6: 自动标记头像...") - analyzed_filename = f"chat_result_{timestamp}_analyzed.jpg" - analyzed_path = os.path.join(screenshot_dir, analyzed_filename) - - analyze_chat_image(save_path, analyzed_path) - - except Exception as e: - logger.error(f"❌ 截图或分析失败: {e}") - - else: - logger.error("❌ 未能定位到搜索结果,跳过截图。") - - logger.info("测试结束。") - -if __name__ == "__main__": - main() diff --git a/WeiXin/T1_OpenChat.py b/WeiXin/T1_OpenChat.py new file mode 100644 index 0000000..fff150c --- /dev/null +++ b/WeiXin/T1_OpenChat.py @@ -0,0 +1,94 @@ +# coding=utf-8 +import uiautomator2 as u2 +import time +import logging +import sys +import os + +# 添加项目根目录到 sys.path +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if project_root not in sys.path: + sys.path.append(project_root) + +# 配置日志 +log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") +if not os.path.exists(log_dir): + os.makedirs(log_dir) + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(os.path.join(log_dir, "T1_OpenChat.log"), encoding='utf-8'), + logging.StreamHandler() + ] +) +logger = logging.getLogger("T1_OpenChat") + +def open_chat(target_name="糖豆爸爸"): + logger.info(f"开始执行 T1: 打开微信并进入 '{target_name}' 的对话框...") + + # 连接设备 + try: + d = u2.connect() + logger.info(f"设备连接成功: {d.info.get('serial')}") + except Exception as e: + logger.error(f"设备连接失败: {e}") + return + + # 1. 启动微信 + logger.info("步骤 1: 启动微信...") + d.app_start("com.tencent.mm", stop=True) + time.sleep(5) + + # 获取屏幕尺寸 + w, h = d.window_size() + logger.info(f"屏幕尺寸: {w}x{h}") + + # 2. 点击搜索按钮 + search_x = int(w * 0.84) + search_y = int(h * 0.08) + logger.info(f"步骤 2: 点击搜索按钮 (坐标: {search_x}, {search_y})...") + d.click(search_x, search_y) + time.sleep(2) + + # 3. 输入搜索内容 + logger.info(f"步骤 3: 输入搜索内容 '{target_name}'...") + try: + d.set_input_ime(True) + d.click(int(w * 0.4), int(h * 0.08)) + time.sleep(1) + d.send_keys(target_name) + time.sleep(2) + d.set_input_ime(False) + except Exception as e: + logger.error(f"输入文字失败: {e}") + try: + d(focused=True).set_text(target_name) + except: + pass + + # 4. 点击搜索结果 + logger.info("步骤 4: 查找并点击搜索结果...") + time.sleep(2) + + found = False + if d(text=target_name).exists: + logger.info(f"找到文本为 '{target_name}' 的元素,点击...") + d(text=target_name).click() + found = True + else: + logger.warning(f"未找到文本为 '{target_name}' 的元素,尝试模糊匹配或坐标点击...") + result_x = int(w * 0.5) + result_y = int(h * 0.18) + logger.info(f"尝试点击第一个搜索结果位置 ({result_x}, {result_y})...") + d.click(result_x, result_y) + found = True + + if found: + logger.info("✅ T1 执行完成: 已进入对话框。") + else: + logger.error("❌ T1 执行失败: 未能进入对话框。") + +if __name__ == "__main__": + open_chat() diff --git a/WeiXin/T2_GetHistory.py b/WeiXin/T2_GetHistory.py new file mode 100644 index 0000000..369fa6f --- /dev/null +++ b/WeiXin/T2_GetHistory.py @@ -0,0 +1,69 @@ +# coding=utf-8 +import uiautomator2 as u2 +import time +import logging +import sys +import os + +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if project_root not in sys.path: + sys.path.append(project_root) + +from WeiXin.WxUtil import analyze_chat_image + +# 配置日志 +log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") +if not os.path.exists(log_dir): + os.makedirs(log_dir) + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(os.path.join(log_dir, "T2_GetHistory.log"), encoding='utf-8'), + logging.StreamHandler() + ] +) +logger = logging.getLogger("T2_GetHistory") + +def get_history(target_name="对方"): + logger.info("开始执行 T2: 获取当前屏幕对话历史...") + + try: + d = u2.connect() + logger.info(f"设备连接成功: {d.info.get('serial')}") + except Exception as e: + logger.error(f"设备连接失败: {e}") + return + + # 截图 + screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") + if not os.path.exists(screenshot_dir): + os.makedirs(screenshot_dir) + + timestamp = time.strftime("%Y%m%d_%H%M%S") + filename = f"T2_history_{timestamp}.jpg" + save_path = os.path.join(screenshot_dir, filename) + + try: + d.screenshot(save_path) + logger.info(f"截图已保存: {save_path}") + + analyzed_filename = f"T2_history_{timestamp}_analyzed.jpg" + analyzed_path = os.path.join(screenshot_dir, analyzed_filename) + + # 调用 WxUtil 中的分析函数 + dialogue_log = analyze_chat_image(save_path, analyzed_path, target_name=target_name) + + logger.info("✅ T2 执行完成。历史对话如下:") + if dialogue_log: + for log in dialogue_log: + print(log) # 打印到控制台 + else: + logger.info("未提取到对话内容或当前屏幕无对话气泡。") + + except Exception as e: + logger.error(f"❌ T2 执行失败: {e}") + +if __name__ == "__main__": + get_history() diff --git a/WeiXin/T3_MarkInputBox.py b/WeiXin/T3_MarkInputBox.py new file mode 100644 index 0000000..25fd0a8 --- /dev/null +++ b/WeiXin/T3_MarkInputBox.py @@ -0,0 +1,88 @@ +# coding=utf-8 +import uiautomator2 as u2 +import time +import logging +import sys +import os +import cv2 +import numpy as np + +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if project_root not in sys.path: + sys.path.append(project_root) + +from WeiXin.WxUtil import find_input_box_center + +# 配置日志 +log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") +if not os.path.exists(log_dir): + os.makedirs(log_dir) + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(os.path.join(log_dir, "T3_MarkInputBox.log"), encoding='utf-8'), + logging.StreamHandler() + ] +) +logger = logging.getLogger("T3_MarkInputBox") + +def mark_input_box(): + logger.info("开始执行 T3: 标识输入框位置...") + + try: + d = u2.connect() + logger.info(f"设备连接成功: {d.info.get('serial')}") + except Exception as e: + logger.error(f"设备连接失败: {e}") + return + + screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") + if not os.path.exists(screenshot_dir): + os.makedirs(screenshot_dir) + + timestamp = time.strftime("%Y%m%d_%H%M%S") + filename = f"T3_input_{timestamp}.jpg" + save_path = os.path.join(screenshot_dir, filename) + + try: + d.screenshot(save_path) + logger.info(f"截图已保存: {save_path}") + + # 调用 WxUtil 识别输入框 + center_point, rect_box = find_input_box_center(save_path) + + if center_point and rect_box: + # 读取图片进行绘制 + img_data = np.fromfile(save_path, dtype=np.uint8) + img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) + + x, y, w, h = rect_box + cx, cy = center_point + + # 要求:绿色框,模块点击点处标识小红点 + # 绿色框 (Green): BGR (0, 255, 0) + cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 5) + + # 小红点 (Red): BGR (0, 0, 255) + # 半径 10, 实心 + cv2.circle(img, (cx, cy), 10, (0, 0, 255), -1) + + output_filename = f"T3_input_{timestamp}_marked.jpg" + output_path = os.path.join(screenshot_dir, output_filename) + + # 保存 + ext = os.path.splitext(output_path)[1] + cv2.imencode(ext, img)[1].tofile(output_path) + logger.info(f"✅ T3 执行完成: 标记图片已保存至 {output_path}") + logger.info(f"输入框区域: {rect_box}, 点击中心点: {center_point}") + + else: + logger.warning("❌ T3 执行结果: 未能识别到输入框。") + + except Exception as e: + logger.error(f"❌ T3 执行失败: {e}") + +if __name__ == "__main__": + mark_input_box() diff --git a/Test/T1_StartWeiXin.py b/WeiXin/WxUtil.py similarity index 77% rename from Test/T1_StartWeiXin.py rename to WeiXin/WxUtil.py index 9a4449f..206dc99 100644 --- a/Test/T1_StartWeiXin.py +++ b/WeiXin/WxUtil.py @@ -16,8 +16,9 @@ if project_root not in sys.path: from Util.EasyOcrKit import get_easyocr_reader # 配置日志 -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger("TestWeChat") +# 注意:作为库模块,不应直接调用 basicConfig,以免干扰调用者的日志配置 +# 调用者应自行配置日志(包括 FileHandler 等) +logger = logging.getLogger("WxUtil") def find_input_box_center(image_path): """ @@ -48,8 +49,7 @@ def find_input_box_center(image_path): contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - best_cnt = None - max_area = 0 + candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) @@ -61,15 +61,36 @@ def find_input_box_center(image_path): global_y = crop_y_start + y + h // 2 # 排除过于靠下的区域 (导航栏/手势条),通常在最后 2% - if global_y > height * 0.98: + if global_y > height * 0.99: continue - - if w * h > max_area: - max_area = w * h - best_cnt = (x, y, w, h) - - if best_cnt: - x, y, w, h = best_cnt + + candidates.append({ + 'x': x, 'y': y, 'w': w, 'h': h, + 'global_y': global_y, + 'area': w * h + }) + + best_candidate = None + if candidates: + # 优先选择最靠下的候选框 (Y 坐标最大) + # 同时考虑宽度不能太离谱 + candidates.sort(key=lambda c: c['global_y'], reverse=True) + + # 检查最靠下的候选框是否合理 + # 正常情况下输入框应该在屏幕底部 92% - 98% 之间 + # 如果最靠下的候选框 Y < 92%,说明可能识别错了 (可能是倒数第二条消息) + for cand in candidates: + if cand['global_y'] > height * 0.92: + best_candidate = cand + break + + # 如果没有找到足够靠下的,但有候选框,尝试放宽条件或者使用最靠下的那个 + if not best_candidate and candidates: + logger.warning("未找到 >92% 高度的输入框,尝试使用最靠下的候选框") + best_candidate = candidates[0] + + if best_candidate: + x, y, w, h = best_candidate['x'], best_candidate['y'], best_candidate['w'], best_candidate['h'] center_x = x + w // 2 center_y = crop_y_start + y + h // 2 logger.info(f"找到输入框(CV-Canny): ({center_x}, {center_y}), 尺寸: {w}x{h}") @@ -80,27 +101,33 @@ def find_input_box_center(image_path): cv2.THRESH_BINARY_INV, 11, 2) contours_thresh, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + candidates_thresh = [] for cnt in contours_thresh: x, y, w, h = cv2.boundingRect(cnt) if width * 0.4 < w < width * 0.95 and 20 < h < bottom_h * 0.8: global_y = crop_y_start + y + h // 2 - if global_y > height * 0.98: + if global_y > height * 0.99: continue - if w * h > max_area: - max_area = w * h - best_cnt = (x, y, w, h) - - if best_cnt: - x, y, w, h = best_cnt - center_x = x + w // 2 - center_y = crop_y_start + y + h // 2 - logger.info(f"找到输入框(CV-Adaptive): ({center_x}, {center_y}), 尺寸: {w}x{h}") - return (center_x, center_y), (x, crop_y_start + y, w, h) + candidates_thresh.append({ + 'x': x, 'y': y, 'w': w, 'h': h, + 'global_y': global_y + }) + + if candidates_thresh: + candidates_thresh.sort(key=lambda c: c['global_y'], reverse=True) + best = candidates_thresh[0] + # 同样应用 > 92% 规则 + if best['global_y'] > height * 0.92: + x, y, w, h = best['x'], best['y'], best['w'], best['h'] + center_x = x + w // 2 + center_y = crop_y_start + y + h // 2 + logger.info(f"找到输入框(CV-Adaptive): ({center_x}, {center_y}), 尺寸: {w}x{h}") + return (center_x, center_y), (x, crop_y_start + y, w, h) - # 兜底策略:使用更靠下的默认坐标 (94%) - # 之前的 90.5% 用户反馈偏上 - logger.warning("未找到明显输入框轮廓,使用更靠下的默认坐标 (94%)") - default_y = int(height * 0.94) + # 兜底策略:使用更靠下的默认坐标 (96% - 微信输入框通常很靠下) + # 之前的 94% 可能还是偏高 + logger.warning("未找到明显输入框轮廓,使用更靠下的默认坐标 (96.5%)") + default_y = int(height * 0.965) center_x = width // 2 # 构造假想框 @@ -357,6 +384,13 @@ def perform_input_action(coords, text): d.send_keys(text) except Exception as sk_e: logger.error(f"send_keys 失败: {sk_e}") + + # 尝试广播 input + try: + escaped_text = text.replace("'", "\\'").replace(" ", "%s") + d.shell(f"input text '{escaped_text}'") + except: + pass # 3. 截图:输入文本后 take_debug_screenshot(d, "coord_03_after_input_text") @@ -596,130 +630,4 @@ def analyze_chat_image(image_path, output_path, target_name="对方"): logger.info(line) logger.info("="*30 + "\n") -def main(): - logger.info("开始执行微信搜索测试...") - - # 连接设备 - try: - d = u2.connect() - logger.info(f"设备连接成功: {d.info.get('serial')}") - except Exception as e: - logger.error(f"设备连接失败: {e}") - return - - # 1. 启动微信 - logger.info("步骤 1: 启动微信...") - d.app_start("com.tencent.mm", stop=True) - # 等待微信启动完成 - time.sleep(5) - - # 获取屏幕尺寸 - w, h = d.window_size() - logger.info(f"屏幕尺寸: {w}x{h}") - - # 2. 点击搜索按钮 (参考 Opener.py 的坐标比例: w * 0.84, h * 0.08) - search_x = int(w * 0.84) - search_y = int(h * 0.08) - logger.info(f"步骤 2: 点击搜索按钮 (坐标: {search_x}, {search_y})...") - d.click(search_x, search_y) - time.sleep(2) - - # 3. 输入 "糖豆爸爸" - target_name = "糖豆爸爸" - logger.info(f"步骤 3: 输入搜索内容 '{target_name}'...") - - try: - # 启用 FastInputIME 以支持中文输入 - d.set_input_ime(True) - - # 点击搜索框获取焦点 (参考 Opener.py: w * 0.4, h * 0.08) - d.click(int(w * 0.4), int(h * 0.08)) - time.sleep(1) - - # 输入文字 - d.send_keys(target_name) - time.sleep(2) - - # 恢复输入法 - d.set_input_ime(False) - except Exception as e: - logger.error(f"输入文字失败: {e}") - try: - d(focused=True).set_text(target_name) - except: - pass - - # 4. 点击搜索结果 - logger.info("步骤 4: 查找并点击搜索结果...") - time.sleep(2) - - found = False - - # 策略 A: 精确匹配文本 - if d(text=target_name).exists: - logger.info(f"找到文本为 '{target_name}' 的元素,点击...") - d(text=target_name).click() - found = True - else: - logger.warning(f"未找到文本为 '{target_name}' 的元素,尝试模糊匹配或坐标点击...") - - # 策略 B: 坐标点击 (参考 Opener.py 点击第一个结果: w * 0.5, h * 0.18) - result_x = int(w * 0.5) - result_y = int(h * 0.18) - logger.info(f"尝试点击第一个搜索结果位置 ({result_x}, {result_y})...") - d.click(result_x, result_y) - found = True - - if found: - logger.info("✅ 已执行点击操作,应该已进入对话框。") - - # 5. 截图保存结果 - logger.info("步骤 5: 截图并分析...") - # 等待界面加载稳定 - time.sleep(3) - - # 创建截图目录 - screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") - if not os.path.exists(screenshot_dir): - os.makedirs(screenshot_dir) - - # 生成文件名 - timestamp = time.strftime("%Y%m%d_%H%M%S") - filename = f"chat_result_{timestamp}.jpg" - save_path = os.path.join(screenshot_dir, filename) - - try: - d.screenshot(save_path) - logger.info(f"✅ 原始截图已保存: {save_path}") - - # 6. 分析截图并标记头像 - logger.info("步骤 6: 自动标记头像和识别内容...") - analyzed_filename = f"chat_result_{timestamp}_analyzed.jpg" - analyzed_path = os.path.join(screenshot_dir, analyzed_filename) - - analyze_chat_image(save_path, analyzed_path, target_name=target_name) - - # 7. 查找输入框并执行自动化操作 - logger.info("步骤 7: 自动回复...") - # 注意:analyze_chat_image 内部已经调用了 find_input_box_center 并且画在图上了 - # 但我们需要返回值来执行操作。analyze_chat_image 并没有返回坐标。 - # 为了简单,再次调用一次 (或者修改 analyze_chat_image 返回坐标,但改动较大) - # 这里直接再次调用 find_input_box_center 获取坐标 - coords, _ = find_input_box_center(save_path) - - if coords: - logger.info(f"输入框坐标: {coords}") - perform_input_action(coords, "AI助手我现在可以开始和你聊天了!") - else: - logger.warning("未找到输入框坐标,跳过回复") - - except Exception as e: - logger.error(f"❌ 截图或分析失败: {e}") - - else: - logger.error("❌ 未能定位到搜索结果,跳过截图。") - - logger.info("测试结束。") - -if __name__ == "__main__": - main() + return dialogue_log diff --git a/WordAddIn/.vs/WordAddIn/FileContentIndex/01824cf5-110f-4df4-bef9-da7d42a8723b.vsidx b/WordAddIn/.vs/WordAddIn/FileContentIndex/01824cf5-110f-4df4-bef9-da7d42a8723b.vsidx new file mode 100644 index 0000000..89942fb Binary files /dev/null and b/WordAddIn/.vs/WordAddIn/FileContentIndex/01824cf5-110f-4df4-bef9-da7d42a8723b.vsidx differ diff --git a/WordAddIn/.vs/WordAddIn/FileContentIndex/267372ec-0c92-491b-b3e2-fc07590dfc90.vsidx b/WordAddIn/.vs/WordAddIn/FileContentIndex/267372ec-0c92-491b-b3e2-fc07590dfc90.vsidx new file mode 100644 index 0000000..3d8c1dd Binary files /dev/null and b/WordAddIn/.vs/WordAddIn/FileContentIndex/267372ec-0c92-491b-b3e2-fc07590dfc90.vsidx differ diff --git a/WordAddIn/.vs/WordAddIn/FileContentIndex/2c66d38d-0c2c-4ae7-b23e-a980a4a7e1fb.vsidx b/WordAddIn/.vs/WordAddIn/FileContentIndex/2c66d38d-0c2c-4ae7-b23e-a980a4a7e1fb.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/WordAddIn/.vs/WordAddIn/FileContentIndex/2c66d38d-0c2c-4ae7-b23e-a980a4a7e1fb.vsidx differ diff --git a/WordAddIn/.vs/WordAddIn/FileContentIndex/abbe6c8c-a6a1-4efb-831f-84215a9cba5b.vsidx b/WordAddIn/.vs/WordAddIn/FileContentIndex/abbe6c8c-a6a1-4efb-831f-84215a9cba5b.vsidx new file mode 100644 index 0000000..3b0a55c Binary files /dev/null and b/WordAddIn/.vs/WordAddIn/FileContentIndex/abbe6c8c-a6a1-4efb-831f-84215a9cba5b.vsidx differ diff --git a/WordAddIn/.vs/WordAddIn/FileContentIndex/daa1de98-5b55-4225-858f-4d331877adf6.vsidx b/WordAddIn/.vs/WordAddIn/FileContentIndex/daa1de98-5b55-4225-858f-4d331877adf6.vsidx new file mode 100644 index 0000000..1cfa1c3 Binary files /dev/null and b/WordAddIn/.vs/WordAddIn/FileContentIndex/daa1de98-5b55-4225-858f-4d331877adf6.vsidx differ diff --git a/WordAddIn/.vs/WordAddIn/v17/.suo b/WordAddIn/.vs/WordAddIn/v17/.suo index 3b19fbd..5a9feda 100644 Binary files a/WordAddIn/.vs/WordAddIn/v17/.suo and b/WordAddIn/.vs/WordAddIn/v17/.suo differ diff --git a/WordAddIn/WordAddIn/CreateOutlineForm.cs b/WordAddIn/WordAddIn/CreateOutlineForm.cs new file mode 100644 index 0000000..ffdb706 --- /dev/null +++ b/WordAddIn/WordAddIn/CreateOutlineForm.cs @@ -0,0 +1,116 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace WordAddIn +{ + /// + /// 创建大纲输入窗体。 + /// 功能描述: + /// 1. 提供一个文本框,让用户输入大纲的主题、关键词或详细描述。 + /// 2. 作为生成大纲的前置步骤,获取用户的初始意图。 + /// + public class CreateOutlineForm : Form + { + private TextBox txtTopic; + private Button btnGenerate; + private Button btnCancel; + private Label lblTopic; + + /// + /// 获取用户输入的主题内容。 + /// + public string Topic { get; private set; } + + public CreateOutlineForm() + { + InitializeComponent(); + } + + /// + /// 初始化窗体组件。 + /// + private void InitializeComponent() + { + this.lblTopic = new System.Windows.Forms.Label(); + this.txtTopic = new System.Windows.Forms.TextBox(); + this.btnGenerate = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // lblTopic + // + this.lblTopic.AutoSize = true; + this.lblTopic.Location = new System.Drawing.Point(20, 20); + this.lblTopic.Name = "lblTopic"; + this.lblTopic.Size = new System.Drawing.Size(191, 12); + this.lblTopic.TabIndex = 0; + this.lblTopic.Text = "请输入文档主题或描述 (支持多行):"; + // + // txtTopic + // + this.txtTopic.Location = new System.Drawing.Point(20, 50); + this.txtTopic.Multiline = true; + this.txtTopic.Name = "txtTopic"; + this.txtTopic.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtTopic.Size = new System.Drawing.Size(440, 240); + this.txtTopic.TabIndex = 1; + // + // btnGenerate + // + this.btnGenerate.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnGenerate.Location = new System.Drawing.Point(240, 310); + this.btnGenerate.Name = "btnGenerate"; + this.btnGenerate.Size = new System.Drawing.Size(100, 35); + this.btnGenerate.TabIndex = 2; + this.btnGenerate.Text = "生成大纲"; + this.btnGenerate.UseVisualStyleBackColor = true; + this.btnGenerate.Click += new System.EventHandler(this.BtnGenerate_Click); + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(360, 310); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(100, 35); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "取消"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // CreateOutlineForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(500, 370); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnGenerate); + this.Controls.Add(this.txtTopic); + this.Controls.Add(this.lblTopic); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CreateOutlineForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "创建大纲 - AI助手"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + /// + /// 生成按钮点击事件。 + /// 验证输入是否为空,若通过则设置 Topic 属性并关闭窗体。 + /// + private void BtnGenerate_Click(object sender, EventArgs e) + { + Topic = txtTopic.Text.Trim(); + if (string.IsNullOrWhiteSpace(Topic)) + { + MessageBox.Show("请输入主题或描述内容。"); + this.DialogResult = DialogResult.None; + return; + } + } + } +} diff --git a/WordAddIn/WordAddIn/OutlineSettingsForm.cs b/WordAddIn/WordAddIn/OutlineSettingsForm.cs new file mode 100644 index 0000000..9614294 --- /dev/null +++ b/WordAddIn/WordAddIn/OutlineSettingsForm.cs @@ -0,0 +1,177 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using WordAddIn.Models; +using WordAddIn.Properties; +using Newtonsoft.Json; + +namespace WordAddIn +{ + /// + /// 大纲样式配置窗体。 + /// 功能描述: + /// 1. 允许用户为大纲的不同层级(主标题、一级目录、二级目录、三级目录)配置样式。 + /// 2. 使用 TabControl 组织不同层级的样式编辑器(StyleEditorControl)。 + /// 3. 支持保存配置到本地设置(Settings.Default.OutlineStyles)和加载已有配置。 + /// + public class OutlineSettingsForm : Form + { + private TabControl tabControl; + private Button btnSave; + private Button btnCancel; + + private StyleEditorControl mainTitleEditor; + private StyleEditorControl h1Editor; + private StyleEditorControl h2Editor; + private StyleEditorControl h3Editor; + + public OutlineSettingsForm() + { + InitializeComponent(); + InitializeCustomControls(); + LoadSettings(); + } + + /// + /// 初始化基本窗体组件。 + /// + private void InitializeComponent() + { + this.tabControl = new System.Windows.Forms.TabControl(); + this.btnSave = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // tabControl + // + this.tabControl.Location = new System.Drawing.Point(12, 12); + this.tabControl.Name = "tabControl"; + this.tabControl.SelectedIndex = 0; + this.tabControl.Size = new System.Drawing.Size(410, 280); + this.tabControl.TabIndex = 0; + // + // btnSave + // + this.btnSave.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnSave.Location = new System.Drawing.Point(210, 310); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(100, 35); + this.btnSave.TabIndex = 1; + this.btnSave.Text = "保存配置"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.BtnSave_Click); + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(320, 310); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(100, 35); + this.btnCancel.TabIndex = 2; + this.btnCancel.Text = "取消"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // OutlineSettingsForm + // + this.AcceptButton = this.btnSave; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(450, 360); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.tabControl); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "OutlineSettingsForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "大纲样式配置"; + this.ResumeLayout(false); + + } + + /// + /// 初始化自定义样式编辑器控件,并添加到 Tab 页中。 + /// + private void InitializeCustomControls() + { + mainTitleEditor = new StyleEditorControl("主标题", true); + h1Editor = new StyleEditorControl("一级目录", false); + h2Editor = new StyleEditorControl("二级目录", false); + h3Editor = new StyleEditorControl("三级目录", false); + + AddTab("主标题", mainTitleEditor); + AddTab("一级目录", h1Editor); + AddTab("二级目录", h2Editor); + AddTab("三级目录", h3Editor); + } + + /// + /// 辅助方法:添加一个新的 Tab 页。 + /// + private void AddTab(string title, Control content) + { + TabPage page = new TabPage(title); + content.Dock = DockStyle.Fill; + page.Controls.Add(content); + tabControl.TabPages.Add(page); + } + + /// + /// 从 Settings 中加载保存的样式配置。 + /// + private void LoadSettings() + { + try + { + string json = Settings.Default.OutlineStyles; + OutlineStyleConfig config = null; + + if (!string.IsNullOrEmpty(json)) + { + config = JsonConvert.DeserializeObject(json); + } + + if (config == null) + { + config = new OutlineStyleConfig(); // Use defaults + } + + mainTitleEditor.SetStyle(config.MainTitle); + h1Editor.SetStyle(config.Heading1); + h2Editor.SetStyle(config.Heading2); + h3Editor.SetStyle(config.Heading3); + } + catch (Exception ex) + { + MessageBox.Show("加载配置失败: " + ex.Message); + } + } + + /// + /// 保存按钮点击事件。 + /// 将当前配置序列化为 JSON 并保存到 Settings。 + /// + private void BtnSave_Click(object sender, EventArgs e) + { + try + { + OutlineStyleConfig config = new OutlineStyleConfig(); + config.MainTitle = mainTitleEditor.GetStyle(); + config.Heading1 = h1Editor.GetStyle(); + config.Heading2 = h2Editor.GetStyle(); + config.Heading3 = h3Editor.GetStyle(); + + string json = JsonConvert.SerializeObject(config); + Settings.Default.OutlineStyles = json; + Settings.Default.Save(); + } + catch (Exception ex) + { + MessageBox.Show("保存配置失败: " + ex.Message); + this.DialogResult = DialogResult.None; + } + } + } +} diff --git a/WordAddIn/WordAddIn/StyleEditorControl.cs b/WordAddIn/WordAddIn/StyleEditorControl.cs new file mode 100644 index 0000000..220573f --- /dev/null +++ b/WordAddIn/WordAddIn/StyleEditorControl.cs @@ -0,0 +1,126 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using WordAddIn.Models; + +namespace WordAddIn +{ + /// + /// 样式编辑器控件。 + /// 功能描述: + /// 1. 提供可复用的用户界面,用于配置文本样式(字体、字号、加粗、对齐方式)。 + /// 2. 封装了 WinForms 控件(ComboBox, NumericUpDown, CheckBox),简化样式设置。 + /// 3. 支持数据绑定:SetStyle() 设置界面状态,GetStyle() 获取用户配置。 + /// + public class StyleEditorControl : UserControl + { + private ComboBox cmbFont; + private NumericUpDown numSize; + private CheckBox chkBold; + private ComboBox cmbAlign; + private bool _showAlign; + + /// + /// 默认构造函数 (设计器支持)。 + /// + public StyleEditorControl() + { + // Default constructor for designer support + _showAlign = false; + Initialize("样式"); + } + + /// + /// 带参数的构造函数。 + /// + /// 分组标题文本 (如 "一级目录") + /// 是否显示对齐方式选项 + public StyleEditorControl(string labelText, bool showAlign) + { + _showAlign = showAlign; + Initialize(labelText); + } + + /// + /// 初始化控件界面。 + /// + /// 分组标题 + private void Initialize(string labelText) + { + this.Padding = new Padding(20); + this.Size = new Size(380, 200); // Set a default size + + // 字体选择 + Label lblFont = new Label { Text = "字体:", Location = new Point(20, 30), AutoSize = true }; + cmbFont = new ComboBox { Location = new Point(80, 27), Width = 150, DropDownStyle = ComboBoxStyle.DropDownList }; + // Add common Chinese fonts + cmbFont.Items.AddRange(new object[] { "宋体", "黑体", "微软雅黑", "楷体", "仿宋", "Arial", "Times New Roman" }); + cmbFont.SelectedIndex = 0; + + // 字号选择 + Label lblSize = new Label { Text = "字号:", Location = new Point(20, 70), AutoSize = true }; + numSize = new NumericUpDown { Location = new Point(80, 67), Width = 80, Minimum = 5, Maximum = 72, DecimalPlaces = 1 }; + + // 加粗选项 + chkBold = new CheckBox { Text = "加粗", Location = new Point(80, 110), AutoSize = true }; + + this.Controls.Add(lblFont); + this.Controls.Add(cmbFont); + this.Controls.Add(lblSize); + this.Controls.Add(numSize); + this.Controls.Add(chkBold); + + // 对齐方式 (可选) + if (_showAlign) + { + Label lblAlign = new Label { Text = "对齐:", Location = new Point(20, 150), AutoSize = true }; + cmbAlign = new ComboBox { Location = new Point(80, 147), Width = 100, DropDownStyle = ComboBoxStyle.DropDownList }; + cmbAlign.Items.AddRange(new object[] { "左对齐", "居中", "右对齐" }); + cmbAlign.SelectedIndex = 0; + + this.Controls.Add(lblAlign); + this.Controls.Add(cmbAlign); + } + } + + /// + /// 将 TextStyle 对象应用到界面控件。 + /// + /// 要显示的样式对象 + public void SetStyle(TextStyle style) + { + if (style == null) return; + + if (cmbFont.Items.Contains(style.FontName)) + cmbFont.SelectedItem = style.FontName; + else + cmbFont.Text = style.FontName; // Fallback + + numSize.Value = (decimal)style.FontSize; + chkBold.Checked = style.IsBold; + + if (_showAlign && cmbAlign != null) + { + if (style.Alignment >= 0 && style.Alignment < cmbAlign.Items.Count) + cmbAlign.SelectedIndex = style.Alignment; + } + } + + /// + /// 从界面控件获取当前的 TextStyle 配置。 + /// + /// 包含用户配置的样式对象 + public TextStyle GetStyle() + { + TextStyle style = new TextStyle(); + style.FontName = cmbFont.Text; + style.FontSize = (float)numSize.Value; + style.IsBold = chkBold.Checked; + if (_showAlign && cmbAlign != null) + { + style.Alignment = cmbAlign.SelectedIndex; + } + return style; + } + } +} diff --git a/WordAddIn/WordAddIn/WordAddIn.csproj.user b/WordAddIn/WordAddIn/WordAddIn.csproj.user new file mode 100644 index 0000000..65b9c48 --- /dev/null +++ b/WordAddIn/WordAddIn/WordAddIn.csproj.user @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/WordAddIn/WordAddIn/obj/Debug/DesignTimeResolveAssemblyReferences.cache b/WordAddIn/WordAddIn/obj/Debug/DesignTimeResolveAssemblyReferences.cache new file mode 100644 index 0000000..fb25e66 Binary files /dev/null and b/WordAddIn/WordAddIn/obj/Debug/DesignTimeResolveAssemblyReferences.cache differ diff --git a/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Microsoft.Office.Tools.Common.v4.0.Utilities.dll.deploy b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Microsoft.Office.Tools.Common.v4.0.Utilities.dll.deploy new file mode 100644 index 0000000..03a6ef8 Binary files /dev/null and b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Microsoft.Office.Tools.Common.v4.0.Utilities.dll.deploy differ diff --git a/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Newtonsoft.Json.dll.deploy b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Newtonsoft.Json.dll.deploy new file mode 100644 index 0000000..3f6541a Binary files /dev/null and b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/Newtonsoft.Json.dll.deploy differ diff --git a/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.deploy b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.deploy new file mode 100644 index 0000000..2ade180 Binary files /dev/null and b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.deploy differ diff --git a/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.manifest b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.manifest new file mode 100644 index 0000000..549870c --- /dev/null +++ b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.dll.manifest @@ -0,0 +1,203 @@ + + + + WordAddIn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jLCTF8Mm6bD4PDN+rnzN6q0+ReXaNgPh68kMWgatFwI= + + + + + + + + + + + + 8MB68OhNTdTaS9eCO6RTW8BIGzv2I+1Atlm2gUemu3U= + + + + + + + + + + + + y4xivocoIp8Py1pQadRda9HJK1lJPDi2e/MIWHzz8V4= + + + + + + + + + + + + + + + + + + + WordAddIn + WordAddIn + + + + + + +nzFmuqGMTufidijFdHzd5EGSf5WOKf7c2AjATAY/QXQ=kec11gHTkSnlIv0W9zVym1S7bKdmUlth8lply4yAThpZy4vYVECTxPs+WnqUb2jfF2GZ0S5HnDirRBDuabJWZ6hzWxH0qZhiwVS4gqQz64IHo6xn+WZ7Jewbq6HRgUNl+JYFoe4MfmT9BsyOaSjjqcSttZHJocW2TMZvSC2CDuY=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABCN=DESKTOP-Q6H7B6L\Administratorlkht93KCRD6xvp4ITwaBZBfKJqkHiRlwzLkUqlOxs2o=B+FiLgSCXupEf1Nhejue8+299l1qYdgURdD7C2LLB2VbzFLLk9sR8wmGoQhBa3fIFUBAtCJxIMlYOlebZChMrXiiSiol05gBWxrMYk3wprnNUMUOQrJQ+SvHyOWYXiuFgWJMLW3z/VzeNZmelF2HMuyUPOqz3Sd3rnphDvmiu9k=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABMIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q== \ No newline at end of file diff --git a/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.vsto b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.vsto new file mode 100644 index 0000000..ad65739 --- /dev/null +++ b/WordAddIn/WordAddIn/publish/Application Files/WordAddIn_1_0_0_0/WordAddIn.vsto @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + l0YIXWniJAOVvXIuq2gcsxgh6HiLzzq3Qbzl/vvdGcU= + + + +XIAhjSP9NMvto3hNCon8XWja0spk/WAz/YRgsaC9NAo=ZEub2vJdHkXv+rnwN2Rfq6vBnN+mUYuOr4/X76AcTre2N9HNv36HIeuufOaCwSIm9I37R09oAa0YKgWsr0K/CCgSF7wqaVqBe6fJcvepI/Gtaolrmta1FwKCh1C53YDt7PNakBpydLsreZW0P0aBY68U2MJAfTNWUs2NmeqXVG4=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABCN=DESKTOP-Q6H7B6L\AdministratorcV57IfAuq85wNkKjrzi3jhRZNc7Cf6ZvKM70ci4T5wQ=AzHMNpKdtaXkn+WGDS5auR1B95LwvsxcHO+9+9oxPZqVD9RSQ+E/LmN9xteIZF2ugOZn2LwMDUW7Kc2fOkvwoKycCxJE9avHhK284WaQR+94tOjrlBpm7Tiw8GsWz2Ts/sqhI4n5v07hPY9nGJVcJgGLJEBKrMMdGTFJwbOE7Ps=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABMIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q== \ No newline at end of file diff --git a/WordAddIn/WordAddIn/publish/WordAddIn.vsto b/WordAddIn/WordAddIn/publish/WordAddIn.vsto new file mode 100644 index 0000000..ad65739 --- /dev/null +++ b/WordAddIn/WordAddIn/publish/WordAddIn.vsto @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + l0YIXWniJAOVvXIuq2gcsxgh6HiLzzq3Qbzl/vvdGcU= + + + +XIAhjSP9NMvto3hNCon8XWja0spk/WAz/YRgsaC9NAo=ZEub2vJdHkXv+rnwN2Rfq6vBnN+mUYuOr4/X76AcTre2N9HNv36HIeuufOaCwSIm9I37R09oAa0YKgWsr0K/CCgSF7wqaVqBe6fJcvepI/Gtaolrmta1FwKCh1C53YDt7PNakBpydLsreZW0P0aBY68U2MJAfTNWUs2NmeqXVG4=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABCN=DESKTOP-Q6H7B6L\AdministratorcV57IfAuq85wNkKjrzi3jhRZNc7Cf6ZvKM70ci4T5wQ=AzHMNpKdtaXkn+WGDS5auR1B95LwvsxcHO+9+9oxPZqVD9RSQ+E/LmN9xteIZF2ugOZn2LwMDUW7Kc2fOkvwoKycCxJE9avHhK284WaQR+94tOjrlBpm7Tiw8GsWz2Ts/sqhI4n5v07hPY9nGJVcJgGLJEBKrMMdGTFJwbOE7Ps=23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=AQABMIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q== \ No newline at end of file diff --git a/WordAddIn/WordAddIn/publish/setup.exe b/WordAddIn/WordAddIn/publish/setup.exe new file mode 100644 index 0000000..46f3761 Binary files /dev/null and b/WordAddIn/WordAddIn/publish/setup.exe differ