在Pyside6使用过程中,如果使用到了QTableWidget中的setCellWidget方法进行添加表格中添加按钮或其他控件的操作时,正常情况下是没有任何问题的,但在特定情况下发生了奇怪的、莫名的程序卡死,这个可能的情况如下:
- 子进程管道发送数据回主进程
- 主进程通过单例发送了一个通知给QTableWidget控件
- QTableWidget控件进行clear()方法,清空
- QTableWidget重新赋值行数,并在循环中使用setCellWidget进行按钮添加
程序卡死发生时,并没有出现任何报错,且通过cellWidget()获取widget时亦为None。
如果你也遇到了以上的情况,请按本文内容进行解决。
一、发生问题的伪代码
self.ui.table_widget.clear()
self.ui.table_widget.setHorizontalHeaderLabels(['标题', '姓名', '操作'])
self.ui.table_widget.setRowCount(3)
for i in range(3):
data = data_list[i]
item = QTableWidgetItem(data.get('title'))
self.ui.table_widget.setItem(i, 1, item)
item = QTableWidgetItem(data.get('name'))
self.ui.table_widget.setItem(i, 2, item)
self.ui.table_widget.setCellWidget(i, 3, QPushButton('编辑'))
二、解决方案
如果发生上面的问题,解决方案即是当QTableWidget的每一行创建后就不要进行clear()操作,然后当更新数据时,仅通过setRowCount()的行数,然后把以前的item进行复用即可:
self.ui.table_widget.setHorizontalHeaderLabels(['标题', '姓名', '操作'])
self.ui.table_widget.setRowCount(3)
for i in range(3):
data = data_list[i]
if self.ui.table_widget.item(i, 0) is None:
item = QTableWidgetItem(data.get('title'))
self.ui.table_widget.setItem(i, 0, item)
else:
item = self.ui.table_widget.item(i, 0)
item.setText(data.get('title'))
if self.ui.table_widget.item(i, 1) is None:
item = QTableWidgetItem(data.get('name'))
self.ui.table_widget.setItem(i, 1, item)
else:
item = self.ui.table_widget.item(i, 1)
item.setText(data.get('name'))
if self.ui.table_widget.cellWidget(i, 3) is None:
self.ui.table_widget.setCellWidget(i, 3, QPushButton('编辑'))
三、终极方案
其实发生原因的根本在于子进程返回主进程时需要线程去消息队列的获取,所以会发生调用子线程的方法,造成主线程的UI崩溃,而python自带的threading在保活情况下并没有子线程返回主线程的方法,所以我们需要更换子线程的方法。
我们利用QThread来进行子线程返回主线程,通过信号发送就可以完整实现子线程返回主线程:
class CallbackThread(QThread):
callback = Signal(dict)
def __init__(self, conn=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.event = threading.Event()
self.conn = conn
def run(self):
# 子进程的数据发送到主进程
while not self.event.is_set():
try:
res = self.conn.recv()
except:
break
self.callback.emit(res)
print('子进程获取数据线程关闭')
def quit(self):
self.event.set()
由于我们使用的QThread,所以在使用单例时必须使用QObject的对象:
class SocketManager(QObject):
def __init__():
receive_thread = CallbackThread(conn=main_conn)
receive_thread.callback.connect(self.main_thread_callback)
receive_thread.start()
def main_thread_callback(self, res):
pass
四、总结
虽然复用控件会造成一定的内存消耗,但可以避免很多不明确的问题发生,所以明确什么时候复用什么时候保活,不然会出现奇怪的问题。