检测软件自己跑完了,还得让外部定位软件动起来。
uiautomation、控件查找、缓存机制、配置管理——一个不少。
一、业务场景
检测系统需要与外部定位软件(如显微镜控制软件)联动:
-
自动填入检测到的圆心坐标
-
发送坐标到外部软件
-
触发定位操作(显微镜移动到目标位置)
一句话:检测完了,告诉机器去哪儿看。
二、uiautomation简介
Windows平台上操作原生控件的库。
2.1 安装
bash
pip install uiautomation
2.2 核心能力
-
按
AutomationId查找控件 -
按
Name查找控件 -
控件缓存(避免重复查找)
三、ExternalController类
3.1 类结构
python
class ExternalController:
def __init__(self):
# 控件标识(支持AutomationId和Name两种方式)
self.x_edit_automation_id = ''
self.y_edit_automation_id = ''
self.locate_button_automation_id = ''
self.x_edit_name = ''
self.y_edit_name = ''
self.locate_button_name = ''
# 缓存机制
self.cached_window = None
self.cached_x_edit = None
self.cached_y_edit = None
self.cached_locate_button = None
self.cache_time = 0
self.cache_timeout = 600 # 10分钟
def set_controls(self, x_edit_automation_id='', y_edit_automation_id='',
locate_button_automation_id='', x_edit_name='',
y_edit_name='', locate_button_name=''):
self.x_edit_automation_id = x_edit_automation_id
self.y_edit_automation_id = y_edit_automation_id
self.locate_button_automation_id = locate_button_automation_id
self.x_edit_name = x_edit_name
self.y_edit_name = y_edit_name
self.locate_button_name = locate_button_name
self.clear_cache()
3.2 核心方法
| 方法 | 功能 |
|---|---|
set_controls() | 配置控件标识 |
set_xy(x, y) | 填入坐标并触发定位 |
clear_cache() | 清除控件缓存 |
save_config() | 保存配置到QSettings |
四、控件查找(双重机制)
python
def _find_control(self, automation_id, name, control_type, parent):
# 方式一:按AutomationId查找
try:
if automation_id:
control = parent.Control(searchDepth=1, AutomationId=automation_id)
if control.Exists(0.2):
return control
except:
pass
# 方式二:按Name查找
try:
if name:
control = parent.Control(searchDepth=1, Name=name, ControlType=control_type)
if control.Exists(0.2):
return control
except:
pass
return None
两种方式任选一种配置即可:
| 控件 | AutomationId方式 | Name方式 |
|---|---|---|
| X输入框 | x_edit_automation_id | x_edit_name |
| Y输入框 | y_edit_automation_id | y_edit_name |
| 定位按钮 | locate_button_automation_id | locate_button_name |
五、定位执行
python
def set_xy(self, x, y):
try:
x_edit = self._get_x_edit()
y_edit = self._get_y_edit()
locate_button = self._get_locate_button()
if not x_edit or not y_edit or not locate_button:
return False, '无法找到目标控件'
# 填入坐标(保留两位小数)
x_edit.SetValue(f'{x:.2f}')
y_edit.SetValue(f'{y:.2f}')
# 点击定位按钮
locate_button.Click()
return True, '定位成功'
except Exception as e:
return False, str(e)
六、缓存机制
6.1 缓存有效性
python
def _is_cache_valid(self):
if time.time() - self.cache_time < self.cache_timeout:
return True
return False
def _update_cache_time(self):
self.cache_time = time.time()
6.2 为什么需要缓存
| 优势 | 说明 |
|---|---|
| 性能提升 | 避免每次调用都重新查找控件 |
| 响应更快 | 直接使用缓存的控件引用 |
| 资源节省 | 减少系统调用开销 |
6.3 控件获取(带缓存)
python
def _get_x_edit(self):
if self._is_cache_valid():
return self.cached_x_edit
window = self._get_window()
if not window:
return None
self.cached_x_edit = self._find_control(
self.x_edit_automation_id,
self.x_edit_name,
uiautomation.ControlType.Edit,
window
)
self._update_cache_time()
return self.cached_x_edit
七、配置管理(QSettings)
7.1 保存配置
python
def save_config(self):
settings = QSettings('BurrDetection', 'ExternalControl')
settings.setValue('x_edit_automation_id', self.x_edit_automation_id)
settings.setValue('y_edit_automation_id', self.y_edit_automation_id)
settings.setValue('locate_button_automation_id', self.locate_button_automation_id)
settings.setValue('x_edit_name', self.x_edit_name)
settings.setValue('y_edit_name', self.y_edit_name)
settings.setValue('locate_button_name', self.locate_button_name)
settings.sync()
7.2 加载配置
python
def load_config(self):
settings = QSettings('BurrDetection', 'ExternalControl')
self.x_edit_automation_id = settings.value('x_edit_automation_id', '', type=str)
self.y_edit_automation_id = settings.value('y_edit_automation_id', '', type=str)
self.locate_button_automation_id = settings.value('locate_button_automation_id', '', type=str)
self.x_edit_name = settings.value('x_edit_name', '', type=str)
self.y_edit_name = settings.value('y_edit_name', '', type=str)
self.locate_button_name = settings.value('locate_button_name', '', type=str)
八、主窗口集成
8.1 发送定位按钮
python
def send_xy_and_locate(self):
self.btn_send_xyz.setEnabled(False)
# 读取坐标
try:
x = float(self.edit_x.text())
y = float(self.edit_y.text())
except ValueError:
QMessageBox.warning(self, '警告', '请输入有效的XY坐标')
self.btn_send_xyz.setEnabled(True)
return
# 执行定位
success, msg = self.external_controller.set_xy(x, y)
self.btn_send_xyz.setEnabled(True)
if not success:
QMessageBox.warning(self, '错误', msg)
return
# 更新表格状态
if self.current_image_index >= 0:
status_item = self.table.item(self.current_image_index, 1)
if status_item and status_item.text() == '已检测':
sent_item = QTableWidgetItem('已发送')
sent_item.setBackground(Qt.GlobalColor.yellow)
self.table.setItem(self.current_image_index, 1, sent_item)
九、错误处理
9.1 常见错误及原因
| 错误 | 原因 | 解决方法 |
|---|---|---|
| 控件未找到 | 外部软件未启动 | 先启动外部软件 |
| 控件未找到 | AutomationId/Name配置错误 | 用uiautomation工具检查 |
| 权限不足 | uiautomation需管理员权限 | 以管理员身份运行 |
| 控件失效 | 外部软件界面更新 | 清除缓存重新查找 |
9.2 异常捕获
所有外部操作都包在try-except中,避免因外部软件异常导致主程序崩溃。
python
try:
# uiautomation操作
except Exception as e:
return False, str(e)
十、踩坑记录
-
管理员权限:uiautomation操作外部进程控件,必须以管理员身份运行
-
控件标识获取:用
uiautomation自带的Inspect工具查看控件的AutomationId和Name -
缓存失效:外部软件重启后,缓存的控件引用会失效,要清除缓存
-
超时设置:
Exists(0.2)设置0.2秒超时,避免长时间等待 -
控件查找深度:
searchDepth=1只查直接子控件,避免查找到同名控件
下篇预告
下一篇写批量处理与多线程:QThread、信号槽、任务队列、进度反馈。
如果对外部控制有不同思路,评论区聊。
233

被折叠的 条评论
为什么被折叠?



