帮看一下写的pyside6桌面程序,在点击加载配置按钮后界面卡死无响应的问题,下面我把代码完整给你 main.py from PySide6.QtWidgets import QApplication from auto_fill_window import Auto_Fill_Barcode import sys def main(): app = QApplication(sys.argv) #QApplication 管理GUI应用程序生命周期 window = Auto_Fill_Barcode() #创建窗口 window.show() #使窗口可见 sys.exit(app.exec()) if __name__=="__main__": main() ui.py from PySide6 import QtWidgets from getBarcode_ui import Ui_Form from logger import Logger from PySide6.QtWidgets import QFileDialog from config_load import config_load #从.py中导入要先from from controller import Controller class Auto_Fill_Barcode(QtWidgets.QMainWindow,Ui_Form): def __init__(self): super().__init__() self.setupUi(self) self.log_text = self.plainTextEdit self.config = None self.controller = None self.flag = 'idle' self.logger = Logger(text_edit=self.log_text) self.update_from_ui("等待加载配置文件","black") self.update_ui_state('idle') #事件绑定 self.pushButton.clicked.connect(self.load_config_file) self.pushButton_2.clicked.connect(self.start_stop) def update_from_ui(self,str,color): self.label_13.setText(f"{str}") self.label_13.setStyleSheet(f"color: {color};") def ini_controller(self): self.controller = Controller(self.config) self.controller.log_send.connect(self.log_send) self.controller.finished.connect(self.handle_finished) def handle_finished(self): self.logger.info("正在停止程序,线程资源已释放...") self.update_from_ui("等待启动","black") self.update_ui_state('idle') self.flag = 'idle' def log_send(self,log): self.logger.info(log) def start_stop(self): if not self.config: self.logger.info("请先加载配置文件") return if self.flag=='idle': self.controller.start() self.logger.info("程序启动...") self.update_from_ui("运行中","green") self.update_ui_state('running') self.flag = 'running' else: self.controller.stop() self.logger.info("正在停止程序...") def update_ui_state(self,state:str): self.pushButton_2.setText('启动' if state != 'running' else '停止') def load_config_file(self): file_path,_ = QFileDialog.getOpenFileName(self, "选择配置文件", "", "配置文件 (*.ini)") if not file_path: return try: self.config = config_load(file_path) self.lineEdit.setText(file_path) self.label_2.setText(f"{self.config['TimeSettings']['WAIT_MEDIUM']}") self.label_6.setText(f"{self.config['TimeSettings']['WAIT_LONG']}") self.label_12.setText(f"{self.config['TimeSettings']['BARCODE_ALERT_COUNT']}") self.logger.info("配置文件加载成功") self.update_from_ui("等待启动","black") #初始化controller self.ini_controller() except Exception as e: self.logger.error(f"加载配置失败:{e}") '''def closeEvent(self,event): try: if self.controller is not None and self.controller.is_running(): self.controller.stop() event.accept() except Exception as e: self.logger.info(f"{e}")''' controller.py import random import time from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QTextEdit, QLabel, QGroupBox, QHBoxLayout) from PySide6.QtCore import QThread, Signal, QObject, Qt, QTimer from pywinauto import Application from pywinauto.findwindows import ElementNotFoundError, find_elements from pywinauto.timings import TimeoutError, wait_until from pywinauto.base_wrapper import ElementNotEnabled class Controller(QObject): log_send = Signal(str) finished = Signal() def __init__(self,config): super().__init__() self.config = config self.running = True self.WAIT_SHORT = self.config['TimeSettings']['WAIT_SHORT'] self.WAIT_MEDIUM = self.config['TimeSettings']['WAIT_MEDIUM'] self.WAIT_LONG = self.config['TimeSettings']['WAIT_LONG'] self.WAIT_PERFORMANCE = self.config['TimeSettings']['WAIT_PERFORMANCE'] self.WAIT_AFTER_FILL = self.config['TimeSettings']['WAIT_AFTER_FILL'] self.BUTTON_RETRY_TIMES = self.config['TimeSettings']['BUTTON_RETRY_TIMES'] self.BARCODE_ALERT_COUNT = self.config['TimeSettings']['BARCODE_ALERT_COUNT'] def start(self): TARGET_WINDOW_TITLE = "请扫描或输入条码" while self.running: self.log_send.emit("nihao") time.sleep(3) '''try: # 性能检查 cpu_percent = psutil.cpu_percent(interval=0.1) mem_percent = psutil.virtual_memory().percent if cpu_percent > 80 or mem_percent > 70: time.sleep(1) continue # 查找窗口 windows = find_elements(visible_only=True) found_window = None for win in windows: if win.name == TARGET_WINDOW_TITLE: found_window = win break if not found_window: time.sleep(3) continue handle = found_window.handle # 连接窗口 app = Application(backend="uia").connect(handle=handle) dialog = app.window(handle=handle) # 使用标签定位两个输入框 edit1, edit2 = self.find_edit_boxes_by_labels(dialog) if not edit1 or not edit2: self.logger.warning("无法定位输入框,跳过本次循环") time.sleep(3) continue # 查找确定按钮 confirm_btn = dialog.child_window(title="确定", control_type="Button") # 第一次检查输入框是否为空 val1 = edit1.get_value().strip() val2 = edit2.get_value().strip() if val1 or val2: self.logger.debug("检测到条码已录入,跳过填充") time.sleep(3) continue self.logger.info("检测到条码框为空,等待2秒后再次确认") time.sleep(2) # 再次检查窗口是否存在 if not self.is_window_valid(handle): self.logger.info("窗口已关闭,跳过填充") continue # 再次检查输入框是否为空 val1 = edit1.get_value().strip() val2 = edit2.get_value().strip() if val1 or val2: self.logger.debug("检测到条码已录入,跳过填充") continue # 开始填充 barcode1 = self.generate_simulated_barcode() barcode2 = self.generate_simulated_barcode() edit1.set_text(barcode1) edit2.set_text(barcode2) # 等待按钮可用 for _ in range(5): if confirm_btn.is_enabled(): break time.sleep(0.5) confirm_btn.click() self.logger.info(f"已自动填充条码: {barcode1}, {barcode2}") time.sleep(0.5) # 给窗口关闭一点时间 except (ElementNotFoundError, TimeoutError) as e: self.logger.debug(f"查找控件失败: {str(e)}") time.sleep(3) except ElementNotEnabled as e: self.logger.warning(f"控件不可操作: {str(e)}") time.sleep(1) except Exception as e: self.logger.error(f"发生异常: {str(e)}", exc_info=True) time.sleep(3) self.finished.emit()''' self.finished.emit() def stop(self): self.running = False def generate_simulated_barcode(self): prefix = "AUTO_" timestamp = int(time.time()) % 100000 random_part = random.randint(1000, 9999) return f"{prefix}{timestamp}{random_part}" def is_window_valid(self, handle): try: app = Application(backend="uia").connect(handle=handle) window = app.window(handle=handle) window.exists() return True except: return False def find_edit_boxes_by_labels(self, dialog): try: # 查找两个 Static 标签 label1 = dialog.child_window(title="子板1", control_type="Text") label2 = dialog.child_window(title="子板2", control_type="Text") if not label1 or not label2: self.logger.warning("找不到标签控件,跳过填充") return None, None # 获取下一个兄弟控件(即 Edit 输入框) edit1 = label1.parent().children(control_type="Edit")[0] edit2 = label2.parent().children(control_type="Edit")[1] return edit1, edit2 except Exception as e: self.logger.debug(f"通过标签查找输入框失败: {str(e)}") return None, None 当我将这几行写上时就会出现这样的情况from pywinauto import Application from pywinauto.findwindows import ElementNotFoundError, find_elements from pywinauto.timings import TimeoutError, wait_until from pywinauto.base_wrapper import ElementNotEnabled
时间: 2025-08-02 07:09:44 浏览: 12
<think>问题分析:
根据描述,当在controller.py中导入pywinauto相关模块时,程序在点击“加载配置”按钮后界面会卡死无响应。而在注释掉这些导入后,程序可以正常运行(通过日志输出"nihao"可以证明)。
可能的原因:
1. pywinauto的导入或初始化可能涉及一些耗时的操作,导致主线程阻塞。
2. 在Controller的start方法中,虽然使用了循环,但是是在主线程中运行的,这会导致主线程被长时间占用,从而界面无响应。
从代码中可以看到,Controller的start方法中有一个while循环,这个循环是在主线程中运行的。当点击“加载配置”按钮后,会调用ini_controller方法初始化Controller,然后点击“启动”按钮会调用Controller的start方法。而start方法中的循环会一直运行(每隔3秒打印一次"nihao"),直到self.running变为False。由于这个循环是在主线程(GUI线程)中运行的,它会阻塞事件循环,导致界面无响应。
解决方案:
应该将Controller中的循环放在一个单独的线程中运行,避免阻塞主线程。在PySide6中,可以使用QThread来实现。
修改步骤:
1. 创建一个继承自QThread的线程类,将循环逻辑移到该线程的run方法中。
2. 在Controller中创建并管理这个线程。
修改后的controller.py代码:
注意:由于在Controller的start方法中使用了while循环,我们将其移动到新线程的run方法中。同时,需要确保线程安全地停止线程。
具体修改如下:
修改后的controller.py:
```python
import random
import time
from PySide6.QtCore import QThread, Signal, QObject, Qt
# 注释掉pywinauto的导入,因为目前没有使用,但如果你需要使用,可以保留
# from pywinauto import Application
# from pywinauto.findwindows import ElementNotFoundError, find_elements
# from pywinauto.timings import TimeoutError, wait_until
# from pywinauto.base_wrapper import ElementNotEnabled
class WorkerThread(QThread):
# 自定义信号,用于在循环中发送日志
log_signal = Signal(str)
finished_signal = Signal()
def __init__(self, config):
super().__init__()
self.config = config
self.running = True
def run(self):
# 将原来的循环逻辑移动到这里
self.running = True
while self.running:
self.log_signal.emit("nihao")
time.sleep(3)
# 这里可以放置原来的代码(注释部分)
self.finished_signal.emit()
def stop(self):
self.running = False
class Controller(QObject):
log_send = Signal(str)
finished = Signal()
def __init__(self, config):
super().__init__()
self.config = config
self.worker_thread = None
def start(self):
# 创建并启动工作线程
self.worker_thread = WorkerThread(self.config)
# 连接工作线程的信号到Controller的信号,再传递到UI
self.worker_thread.log_signal.connect(self.log_send)
self.worker_thread.finished_signal.connect(self.on_finished)
self.worker_thread.start()
def stop(self):
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
# 等待线程结束(非阻塞方式,通过信号通知)
# 注意:不要使用wait(),否则会阻塞主线程
def on_finished(self):
# 当工作线程结束时,发送finished信号
self.finished.emit()
```
修改说明:
1. 创建了一个WorkerThread类,继承自QThread。在run方法中执行循环逻辑。
2. 在Controller中,不再直接运行循环,而是启动工作线程。
3. 通过信号传递日志和完成事件。
这样,循环逻辑将在单独的线程中运行,不会阻塞主线程,从而避免界面卡死。
另外,在UI部分的Auto_Fill_Barcode类中,当点击“停止”按钮时,会调用Controller的stop方法,该方法会停止工作线程(通过设置标志位)。
注意:在WorkerThread的run方法中,使用time.sleep(3)会阻塞工作线程,但不会阻塞主线程(GUI线程)。因此,界面可以保持响应。
但是,如果后续需要使用pywinauto操作窗口,请注意:
- pywinauto的操作可能需要在主线程中执行(因为窗口操作通常与线程相关),但这不是绝对的。在pywinauto的文档中,它可以在多线程环境中使用,但需要确保每个线程使用独立的Application对象。
- 如果遇到多线程问题,可以考虑将pywinauto的操作封装成函数,然后通过信号和槽机制在主线程中执行(使用QMetaObject.invokeMethod或直接通过信号触发主线程执行)。
现在,由于我们只是导入了pywinauto但未使用,所以不会产生问题。当需要使用pywinauto时,再根据实际情况调整。
请尝试按照上述修改代码,并测试是否解决界面卡死问题。
注意:在WorkerThread的run方法中,我们使用了time.sleep,这会导致线程休眠。在GUI程序中,如果工作线程需要更新界面,一定要通过信号槽机制,因为Qt规定只有主线程才能更新UI。
另外,在停止线程时,我们只是设置了一个标志位,然后等待线程自然退出(在循环条件中检查标志位)。这种方式是安全的。
修改后的完整controller.py代码(未使用pywinauto部分)已经给出,如果需要使用pywinauto,请将注释去掉,并注意将操作放在工作线程中(或者根据pywinauto的要求调整)。
由于问题描述中只是导入pywinauto就导致卡死,实际上在修改前,即使没有使用pywinauto的代码,循环也是在主线程中运行的,所以卡死是循环导致的。现在将循环移到线程中,应该可以解决问题。
最后,修改后的ui.py和main.py不需要改动。
如果问题解决,可以再考虑将pywinauto的代码取消注释,并放在工作线程的循环中。注意,如果pywinauto的操作本身很耗时,同样会阻塞工作线程(但不会阻塞主线程,所以界面不会卡死)。不过,如果工作线程中执行的操作非常耗时,可能会影响整个程序的性能,因此需要合理设置等待时间。
请测试修改后的代码,观察问题是否解决。
注意:在Controller的stop方法中,我们只是设置了标志位,并没有等待线程结束。如果需要确保线程完全停止,可以在Controller的stop方法中等待线程结束(但注意不要在主线程中等待,可以使用信号或重写关闭事件)。目前,通过finished_signal信号,当线程结束时,会触发Controller的on_finished,然后发送finished信号,UI中会更新状态。
由于在UI的handle_finished方法中已经更新了状态,所以不需要额外操作。
如果测试通过,则问题解决。
如果仍然有问题,请检查日志输出和线程状态。
相关修改后的代码:
由于代码较长,这里只提供了controller.py的修改。其他文件不变。
请按照上述修改controller.py,然后运行程序测试。
如果问题依旧,可能需要进一步分析。
但是,根据描述,问题很可能是由于主线程中的循环阻塞导致。修改后循环在工作线程中运行,应该可以解决问题。
另外,关于pywinauto的导入:在修改后的代码中,即使导入pywinauto也不会导致卡死,因为循环已经不在主线程了。
所以,现在可以取消注释,使用pywinauto的功能。但是,请注意,在WorkerThread的run方法中,使用pywinauto操作窗口时,需要确保窗口操作是线程安全的。根据pywinauto的文档,它可以在多线程中使用,但每个线程应该有自己的Application对象。
因此,在WorkerThread的循环中,每次连接窗口时都创建新的Application对象,这样是安全的。
以下是在工作线程中使用pywinauto的示例(在WorkerThread的run方法中,将注释部分替换):
```python
def run(self):
# 将原来的循环逻辑移动到这里
self.running = True
TARGET_WINDOW_TITLE = "请扫描或输入条码"
while self.running:
# 模拟日志输出
self.log_signal.emit("正在查找目标窗口...")
try:
# 性能检查(如果需要)
# 这里省略性能检查,因为原代码中已经注释掉了
# 查找窗口
from pywinauto.findwindows import find_elements
windows = find_elements(visible_only=True)
found_window = None
for win in windows:
if win.name == TARGET_WINDOW_TITLE:
found_window = win
break
if not found_window:
time.sleep(3)
continue
handle = found_window.handle
# 连接窗口
from pywinauto import Application
app = Application(backend="uia").connect(handle=handle)
dialog = app.window(handle=handle)
# 使用标签定位两个输入框
edit1, edit2 = self.find_edit_boxes_by_labels(dialog)
if not edit1 or not edit2:
self.log_signal.emit("无法定位输入框,跳过本次循环")
time.sleep(3)
continue
# 查找确定按钮
confirm_btn = dialog.child_window(title="确定", control_type="Button")
# 第一次检查输入框是否为空
val1 = edit1.get_value().strip()
val2 = edit2.get_value().strip()
if val1 or val2:
self.log_signal.emit("检测到条码已录入,跳过填充")
time.sleep(3)
continue
self.log_signal.emit("检测到条码框为空,等待2秒后再次确认")
time.sleep(2)
# 再次检查窗口是否存在
if not self.is_window_valid(handle):
self.log_signal.emit("窗口已关闭,跳过填充")
continue
# 再次检查输入框是否为空
val1 = edit1.get_value().strip()
val2 = edit2.get_value().strip()
if val1 or val2:
self.log_signal.emit("检测到条码已录入,跳过填充")
continue
# 开始填充
barcode1 = self.generate_simulated_barcode()
barcode2 = self.generate_simulated_barcode()
edit1.set_text(barcode1)
edit2.set_text(barcode2)
# 等待按钮可用
for _ in range(5):
if confirm_btn.is_enabled():
break
time.sleep(0.5)
confirm_btn.click()
self.log_signal.emit(f"已自动填充条码: {barcode1}, {barcode2}")
time.sleep(0.5) # 给窗口关闭一点时间
except Exception as e:
self.log_signal.emit(f"发生异常: {str(e)}")
time.sleep(3)
self.finished_signal.emit()
```
同时,在WorkerThread类中添加以下方法(从原来的Controller中复制):
```python
def generate_simulated_barcode(self):
prefix = "AUTO_"
timestamp = int(time.time()) % 100000
random_part = random.randint(1000, 9999)
return f"{prefix}{timestamp}{random_part}"
def is_window_valid(self, handle):
try:
from pywinauto import Application
app = Application(backend="uia").connect(handle=handle)
window = app.window(handle=handle)
window.exists()
return True
except:
return False
def find_edit_boxes_by_labels(self, dialog):
try:
# 查找两个 Static 标签
label1 = dialog.child_window(title="子板1", control_type="Text")
label2 = dialog.child_window(title="子板2", control_type="Text")
if not label1 or not label2:
self.log_signal.emit("找不到标签控件,跳过填充")
return None, None
# 获取下一个兄弟控件(即 Edit 输入框)
# 注意:这里假设输入框是标签的下一个兄弟控件,实际可能需要调整
# 原代码:edit1 = label1.parent().children(control_type="Edit")[0]
# 原代码:edit2 = label2.parent().children(control_type="Edit")[1]
# 修改为:先找到标签的父容器,然后查找该容器内的所有Edit控件,然后按顺序取
# 或者根据位置关系,这里需要根据实际窗口调整
# 由于原代码中使用了索引0和1,这里保持不变
edit1 = label1.parent().children(control_type="Edit")[0]
edit2 = label2.parent().children(control_type="Edit")[1]
return edit1, edit2
except Exception as e:
self.log_signal.emit(f"通过标签查找输入框失败: {str(e)}")
return None, None
```
注意:由于现在这些方法在WorkerThread中,所以需要将self.logger改为self.log_signal.emit来输出日志。
另外,异常捕获使用了最通用的Exception,以便于调试。
这样,整个操作都在工作线程中完成,不会阻塞主线程。
请根据实际需求调整代码。
由于问题主要是界面卡死,通过将循环移至线程中应该可以解决。如果还有问题,请再检查。
最后,请注意线程退出时的资源释放,在stop方法中设置标志位,线程会在下一次循环条件检查时退出。
如果循环中某些操作(如pywinauto的某些操作)是阻塞且不可中断的,可能需要额外的措施。但通常time.sleep是可以被中断的(通过标志位,在sleep后检查标志位)。
在循环中,每次循环都会检查self.running,所以当调用stop方法后,循环会在下一次检查时退出。
希望以上修改能解决问题。</think>根据您提供的代码,问题主要出在`controller.py`中的线程处理方式。当您点击"加载配置"按钮时,界面会卡死是因为`Controller.start()`方法中的循环在主线程中运行,阻塞了GUI事件循环。
### 问题原因分析:
1. **主线程阻塞**:`Controller.start()`中的`while self.running`循环在主线程中运行,阻塞了GUI事件处理
2. **未使用QThread**:虽然`Controller`继承自`QObject`,但没有使用`QThread`在后台运行循环
3. **时间消耗**:`time.sleep(3)`会阻塞整个线程,包括GUI更新
### 解决方案:
需要将控制器逻辑移动到单独的线程中。以下是修改后的代码:
#### 修改后的 controller.py
```python
import random
import time
from PySide6.QtCore import QThread, Signal, QObject, QMutex
class WorkerThread(QThread):
log_signal = Signal(str)
finished_signal = Signal()
def __init__(self, config):
super().__init__()
self.config = config
self.mutex = QMutex()
self._running = True
def run(self):
while self.is_running():
self.log_signal.emit("nihao")
time.sleep(3)
self.finished_signal.emit()
def stop(self):
self.mutex.lock()
self._running = False
self.mutex.unlock()
def is_running(self):
self.mutex.lock()
running = self._running
self.mutex.unlock()
return running
class Controller(QObject):
log_send = Signal(str)
finished = Signal()
def __init__(self, config):
super().__init__()
self.config = config
self.worker_thread = None
def start(self):
if self.worker_thread and self.worker_thread.isRunning():
return
self.worker_thread = WorkerThread(self.config)
self.worker_thread.log_signal.connect(self.log_send)
self.worker_thread.finished_signal.connect(self.on_finished)
self.worker_thread.start()
def stop(self):
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
self.worker_thread.quit()
self.worker_thread.wait()
def on_finished(self):
self.finished.emit()
```
### 代码解释:
1. **WorkerThread类**:
- 继承自`QThread`,在后台运行循环
- 使用`QMutex`确保线程安全地访问`_running`标志
- `run()`方法包含主循环逻辑
- `stop()`方法安全地停止线程
2. **Controller类修改**:
- 不再直接包含循环逻辑
- 创建并管理`WorkerThread`实例
- 通过信号连接将日志和完成事件传递给UI
3. **线程安全机制**:
- 使用`QMutex`保护共享变量
- `stop()`方法确保安全停止线程
- `quit()`和`wait()`确保线程完全退出
### 其他需要注意的修改:
在`ui.py`中,确保在窗口关闭时停止线程:
```python
def closeEvent(self, event):
try:
if self.controller is not None:
self.controller.stop()
except Exception as e:
self.logger.error(f"关闭错误: {e}")
finally:
event.accept()
```
### 为什么这样修改能解决问题:
1. 将耗时的循环操作移动到单独的线程中,避免阻塞主GUI线程
2. 使用Qt的线程管理机制,确保线程安全
3. 通过信号/槽机制实现线程间通信,避免直接访问UI组件
4. 提供安全的线程停止机制
### 关于pywinauto导入的问题:
导入pywinauto本身不会导致卡死,但如果您在循环中使用pywinauto操作窗口,需要确保:
1. 这些操作也在工作线程中执行
2. 不要在工作线程中直接更新UI(通过信号/槽机制更新)
3. 处理pywinauto可能抛出的异常
阅读全文
相关推荐



















