PyQt5中出现单次qt.qpa.xcb: BadWindow错误,求排查方案
问题
我正在开发一款调查板程序,该程序使用数据库并展示地理数据,具备多层安全机制,调查案例可设为“公开”或多个安全层级之一。用户可匿名访问,也可注册获得对应权限等级。
测试了以下场景:匿名打开案例后登录(案例应保持激活)、打开案例、切换激活案例、登出(需关闭特权激活案例)等。
首次以非预期方式取消活动案例关闭操作时,出现错误:当用户通过下拉列表取消选择活动案例,程序弹出QMessageBox询问是否关闭,若选“否”,会通过阻塞QComboBox信号、设置currentText再恢复信号的方式重新选中当前案例(避免触发重复激活逻辑),此时控制台输出qt.qpa.xcb: QXcbConnection: XCB error: 3 (BadWindow)。
开发环境
- PyQt 5.15.6
- Python 3.10.12
- Linux Mint 21.3 Virginia 64位(已移除Cinnamon桌面,改用KDE)
典型测试控制台输出
Active Case Title = Ó Duibhir Clan of Pallasgrean and it's uid = 1 User, xxx, Logged in Active Case Title = Cunningham Clan of County Louth and it's uid = 2 Case remains open {added comment: this is from choosing the "No" button of the qMessagebox} <b>qt.qpa.xcb: QXcbConnection: XCB error: 3 (BadWindow), sequence: 2465, resource id: 27269098, major code: 40 (TranslateCoords), minor code: 0</b> Active Case Title = Ó Duibhir Clan of Pallasgrean and it's uid = 1 Active Case Title = Ó Duibhir Clan of Tipperary and it's uid = 3 Case closed Active Case Title = Ó Duibhir Clan of Pallasgrean and it's uid = 1 Case remains open User, xxx, Logged in Active Case Title = Cunningham Clan of County Louth and it's uid = 2 Active Case Title = Ó Duibhir Clan of Pallasgrean and it's uid = 1 Case closed User, xxx, logged out Active Case Title = Ó Duibhir Clan of Pallasgrean and it's uid = 1 User, xxx, Logged in Case remains open Case closed Active Case Title = Ó Duibhir Clan of Tipperary and it's uid = 3 Active Case Title = Cunningham Clan of County Louth and it's uid = 2 Case remains open
相关代码片段
def on_cmbCase_current_changed(): #print("Entered on_cmbCase_current_changed()") if ( self.cmbCase.currentText() != 'Select a case' ): #FIXME: Need "check if isDirty and handling" code here self.hasCase = True self.ActiveCase = self.cmbCase.currentText() self.caseId = self.cmbCase.currentData() self.actionClose.setEnabled(True) print(f"Active Case Title = {self.ActiveCase} and it's uid = {self.caseId}") else: if self.hasCase: qm = QMessageBox(QMessageBox.Question,"Close Active Investigation?","Close the Active Investigation?",QMessageBox.Yes | QMessageBox.No, self) ok = qm.exec() if (ok == qm.Yes): #FIXME: Need to handle case isDirty condition self.hasCase = False self.ActiveCase = None self.caseId = 0 self.actionClose.setEnabled(False) print("Case closed") else: self.cmbCase.blockSignals(True) self.cmbCase.setCurrentText(self.ActiveCase) self.cmbCase.blockSignals(False) print("Case remains open") else: print("No case is open, nothing to do.") ... def ResetControls(): self.cmbCase.blockSignals(True) self.cmbCase.clear() self.cmbCase.addItem('Select a case', 0) self.caselist = pDB.GetCaseList(myConfig.creds, self.userid) if self.caselist is not None: for row in self.caselist: self.cmbCase.addItem(row[0],row[1]) #Try and preserve currently selected Case is there is one if self.hasCase: if (self.ActiveCase == row[0]): self.cmbCase.setCurrentText(row[0]) self.cmbCase.blockSignals(False) ... def Login(): #print(f'Self = {self.objectName()}') if not self.LoggedIn: dlgLogin = qtl.cQLogin(self) ... userid = dlgLogin.GetUserID() if ok: self.actionLog_In.setEnabled(False) self.actionLogout.setEnabled(True) self.LoggedIn = True ... print(f"User, {user}, Logged in") self.userName = user self.userid = userid ResetControls() else: print(f"Login for user, {user}, failed.")
请问我遗漏了什么或处理不当的地方?
分析与解决方案
问题根源
这个XCB错误是因为QMessageBox的阻塞特性导致窗口资源状态不一致:
- 用户选择下拉列表的“Select a case”触发
currentChanged信号,程序弹出QMessageBox并调用exec()进入阻塞状态。 - 此时QComboBox的下拉窗口已关闭,但系统窗口资源的状态还未完全同步。
- 直接在阻塞的消息循环中修改QComboBox的选中项,会触发XCB尝试访问已无效的窗口资源,从而抛出BadWindow错误。
修复方案
1. 异步恢复选中项
用QTimer.singleShot将恢复选中项的操作延迟到QMessageBox关闭、事件循环恢复后执行,避免窗口资源冲突:
else: def restore_selection(): self.cmbCase.blockSignals(True) self.cmbCase.setCurrentText(self.ActiveCase) self.cmbCase.blockSignals(False) QTimer.singleShot(0, restore_selection) print("Case remains open")
QTimer.singleShot(0,...)会把操作放入Qt事件队列,等当前阻塞的消息循环结束后再执行,确保窗口资源状态正常。
2. 验证ActiveCase有效性
在ResetControls中重新加载案例列表后,要确认self.ActiveCase仍在选项中,避免setCurrentText找不到对应项引发隐式错误:
def ResetControls(): self.cmbCase.blockSignals(True) self.cmbCase.clear() self.cmbCase.addItem('Select a case', 0) self.caselist = pDB.GetCaseList(myConfig.creds, self.userid) case_exists = False if self.caselist is not None: for row in self.caselist: self.cmbCase.addItem(row[0],row[1]) if self.hasCase and self.ActiveCase == row[0]: self.cmbCase.setCurrentText(row[0]) case_exists = True # 若ActiveCase不在新列表中,重置状态 if self.hasCase and not case_exists: self.hasCase = False self.ActiveCase = None self.caseId = 0 self.actionClose.setEnabled(False) self.cmbCase.blockSignals(False)
3. 版本升级(可选)
PyQt 5.15.6存在部分XCB相关的窗口管理bug,后续版本已修复,若允许可升级到PyQt 5.15.9或更高版本。
内容的提问来源于stack exchange,提问作者CapnJack
相关产品推荐
相关产品推荐

