一、实战常用方法
1、控件数量
上一节我们知道了descendants的用法,那么如何使用该方法获取控件对象的数量呢?可以这样:
len(self.dialog.descendants(class_name="QLineEdit", control_type="Edit"))
获取到过后就可以动态知道当前元素的数量,接着在不同数量情况下去定位该元素,类似这样:
def set_trigger_interval(self, num): '''设置曝光时间''' element_num = len(self.dialog.descendants(class_name="QLineEdit", control_type="Edit")) if element_num == 2: self.dialog.child_window(class_name="QLineEdit", control_type="Edit", found_index=0).set_text(num) elif element_num == 4: self.dialog.child_window(class_name="QLineEdit", control_type="Edit", found_index=2).set_text(num) send_keys('{ENTER}')
2、一个奇怪现象
对于浏览系统文件的操作,如下图,会存在一个小问题,那就是输入盘符目录+文件夹会导致pywinauto连接的窗口丢失,必须三级目录以上才能正常工作。排查发现输入盘符目录+文件夹会出现两个一样名称的窗口,于是报错。
pywinauto.findwindows.ElementAmbiguousError: There are 2 elements that match the criteria {'title': 'SMoreVision', 'top_level_only': False, 'parent': <uia_element_info.UIAElementInfo - 'Smore Vision', MainForm, 2435420>, 'backend': 'uia'}
def choise_folder(self, folder_way): '''浏览:输入保存路径, 不能是盘符目录+文件目录,必须三级目录以上''' browse_win = self.study_win.child_window(title='选择保存路径') # 分割路径 directory, folder = os.path.split(folder_way) print("Directory:", directory) print("folder:", folder) way_element = browse_win.child_window(title_re=".*地址.*", found_index=0) rectangle = self.element_num_of_copies(way_element, 5 / 6) click(coords=rectangle) browse_win.child_window(title_re=".*地址.*", class_name="Edit", found_index=0).type_keys("{BACKSPACE}") browse_win.child_window(title_re=".*地址.*", class_name="Edit", found_index=0).set_text(directory) send_keys("{ENTER}") browse_win.child_window(title="文件夹:", class_name="Edit").set_text(folder) def cancel_choise_folder(self): '''浏览:取消保存路径''' self.study_win.child_window(title="取消", class_name="Button").click_input() def confirm_choise_folder(self): '''浏览:确认保存路径''' self.study_win.child_window(title="选择文件夹").click_input()
3、如何处理同名窗口
有这样一种场景,点击一个控件后,会出现一个窗口,再点击一个控件后,又出现一个弹窗。此时弹窗名和窗口名同名,就会出现“2”这种情况,那么该如何操作这两个窗口呢?
前面我们说过found_index这个参数,以及应用每次都能轮训到最新界面的元素,也就是说只要found_index为0,他至少能找到一个窗口,窗口中的元素也会实时更新,设计如下:
def get_study_win(self): '''获取 study_win窗口,需要在操作study_win前调用一次''' title_list = ['SMoreVision', 'Form'] for i in range(2): try: # 获取窗口 self.study_win = self.dialog.child_window(title=title_list[i], found_index=0) # Form # self.print_window_info(self.study_win) break except: print(f"获取窗口,第{i+1}次失败!") def add_trigger_samples(self): '''添加触发样本''' element_num = len(self.study_win.descendants(class_name="QPushButton")) # 窗口中的元素数量 print(element_num) self.study_win.child_window(class_name="QPushButton", found_index=element_num - 6).click_input() # 当弹窗未出现时,窗口的found_index为0,此时self.study_win为窗口 time.sleep(5) self.study_win.child_window(class_name="QPushButton", found_index=1).click_input() # 当弹窗未出现时,窗口的found_index为1,弹窗为0,,此时self.study_win为弹窗
4、发送组合键的方式
如果我想发送组合键Ctrl + A 可以"^a"。在 pywinauto 的 type_keys 方法中,可以使用特殊符号来发送不同的组合键。以下是 pywinauto 支持的主要组合键符号及其对应按键:
^:Ctrl键%:Alt键+:Shift键
5、窗口大小获取
可以通过get_show_state方法获取状态,示例如下:
def get_win_state(self): '''获取 窗口状态 1为最大化 0为缩小 ''' return self.dialog.get_show_state()
官方源码:
def get_show_state(self): """Get the show state and Maximized/minimzed/restored state Returns values as following window_visual_state_normal = 0 window_visual_state_maximized = 1 window_visual_state_minimized = 2 """ iface = self.iface_window ret = iface.CurrentWindowVisualState return ret
6、右键任务栏应用的窗口获取不到
如下图,很明显该窗口是二级窗口,需要连接应用后再连接窗口,问题在于应用都连接不了。这种情况可以采用不连接应用,直接连接窗口进行操作,方法如下:
time.sleep(5) # 需要增加适当延时等待窗口出现from pywinauto import Desktopapp = Desktop("uia")win = app.window(title="******* 的跳转列表")
7、AI识别:模板匹配
之前文章我们有讲到有些元素找不到,这种情况没办法定位元素,因此,我们可以采用AI识别的方式,加上pywinauto混合使用,如下识别效果:
代码设计如下:
封装ai识别到AiRecognize类中
def imread_unicode(self, path, flags=cv2.IMREAD_COLOR): '''# 中文路径图片读取函数''' # 使用 np.fromfile 读取文件并转换为 numpy 数组,再用 cv2.imdecode 解码 return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags) def match_image(self, image, template_image_path): '''模板匹配图标控件 image: 实际图片,可以为本地图片路径,也可以为Image对象,如通过pywinauto的 self.dialog.capture_as_image() 获取 template_image_path: 为模板图像路径,即期望找到的图案 :return match_coords : 返回模板所在实际图片的坐标系 示例用法: match_image(r"../data/images/示例实际图片.jpg", r"../data/images/工具列表-编辑图标.jpg") ''' # 处理实际图像 if isinstance(image, str): image_path = image # 实际图像 img_rgb = self.imread_unicode(image_path, cv2.IMREAD_UNCHANGED) # 读取 RGB 图像,转成numpy数组 else: img_rgb = np.array(image) img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) # 转为灰度图 # 处理模板图像 template = self.imread_unicode(template_image_path, cv2.IMREAD_GRAYSCALE) # 读取模板图像 w, h = template.shape[::-1] # 获取模板的宽和高 # 执行模板匹配 res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED) threshold = 0.8 loc = np.where(res >= threshold) # 获取匹配到的图标坐标系(左上角和右下角坐标) match_coords = [] # 用于存储匹配到的坐标 for pt in zip(*loc[::-1]): top_left = pt # 左上角坐标 bottom_right = (pt[0] + w, pt[1] + h) # 右下角坐标 match_coords.append((top_left, bottom_right)) # 将坐标添加到列表中 # 可视化:在匹配到的位置画矩形 cv2.rectangle(img_rgb, top_left, bottom_right, (0, 0, 255), 2) # 保存带有匹配结果的图像 output_path = r'../data/images/匹配结果.jpg' cv2.imencode('.jpg', img_rgb)[1].tofile(output_path) print('找到匹配图案:', match_coords) # 检查当前坐标是否与已有的匹配点接近 filtered_coords = [] filtered_threshold = 5 for coord in match_coords: is_similar = False for f_coord in filtered_coords: # 检查当前坐标是否与已有的匹配点接近 if abs(coord[0][0] - f_coord[0][0]) < filtered_threshold and abs(coord[0][1] - f_coord[0][1]) < filtered_threshold: is_similar = True break if not is_similar: filtered_coords.append(coord) print('过滤匹配图案:', filtered_coords) # 返回过滤后匹配到的坐标列表 return filtered_coords
使用ai识别pywinauto元素对象
def element_num_of_copies_from_ai(self, element, template_image_path, res_x=0, fraction=0.5): '''通用方法:通过AI识别图标的方式获取其坐标 element: pywinauto的元素对象 template_image_path: AiRecognize.match_image方法需要的对象 res_x: 选择匹配结果的第x个结果,默认第一个 fraction: 选择图标元素中间位置 ''' # 处理实际图片 rectangle = element.rectangle() # 元素的左上角和右下角坐标 L, T, R, B = rectangle.left, rectangle.top, rectangle.right, rectangle.bottom print('实际图片绝对坐标:', L, T, R, B) image = element.capture_as_image() # 获取实际图片 # 处理模板图在实际图片的位置 match_list = AiRecognize().match_image(image, template_image_path) # 图案的相对坐标 (相对于元素) tL, tT, tR, tB = match_list[res_x][0] + match_list[res_x][1] # 获取图标的左上角和右下角坐标 t_width = tR - tL t_height = tB - tT tx = int(tL + t_width * fraction) ty = int(tT + t_height * fraction) print('模板图案中心点的相对坐标:', tx, ty) print('模板图案中心点的绝对坐标:', L + tx, T + ty) # click(coords=(L + tx, T + ty)) return (L + tx, T + ty)
8、AI识别:OCR
同理,之前我们说到的ocr识别,现在将ocr识别封装到AiRecognize 中:
def ocr_reader(self, image): '''读取图片中的文字 可以为本地图片路径,也可以为Image对象,如通过pywinauto的 self.dialog.capture_as_image() 获取 ''' # 处理实际图像 if isinstance(image, str): image_path = image # 实际图像 image_array = self.imread_unicode(image_path, cv2.IMREAD_UNCHANGED) # 读取 RGB 图像,转成numpy数组 else: image_array = np.array(image) ocr = PaddleOCR(use_angle_cls=True, lang="ch") result = ocr.ocr(image_array, cls=True) res = [] print("图片中的文本内容:") for block in result: for line in block: text = line[1][0] # 提取文本内容 print(text) res.append(line[1][0]) return res
578