PyQt5中QListWidget上移项需手动repaint?求优化方案
QListWidget移动项后选中状态不自动刷新的问题解决
我碰到过类似的情况,这其实是Qt的视图更新机制在item移动后的一个小同步问题。让我给你拆解一下原因和更优雅的解决办法:
问题原因
当你用takeItem移除项再用insertItem插入新位置时,QListWidget内部的选中状态跟踪和视图渲染之间出现了短暂的不同步。虽然你用setCurrentItem指定了选中项,但控件并没有自动触发选中状态的UI重绘——这就是为什么必须手动调用repaint才能看到蓝色选中条带的原因。
无需repaint的解决方案
有两种更符合Qt设计思路的方法,完全可以去掉手动重绘的操作:
方法1:用setCurrentRow替代setCurrentItem
这是最推荐的方式。当你把项插入到row-1位置后,直接通过行号来设置选中状态,而不是直接操作item对象。这样QListWidget会直接定位到目标行,并自动触发视图更新,选中条带会正常显示:
修改你的__up槽函数:
@pyqtSlot() def __up(self): """Move the item up by one row.""" row = self.__fruit_list.currentRow() if row >= 1: # Remove the currently selected item. item = self.__fruit_list.takeItem(row) self.__fruit_list.insertItem(row - 1, item) # 用行号设置选中,替代setCurrentItem self.__fruit_list.setCurrentRow(row - 1) # 删掉self.__fruit_list.repaint()
方法2:用update()替代repaint()
如果坚持要用setCurrentItem,可以把repaint()换成update()。update()会让Qt在事件循环空闲时安排重绘,比强制立即重绘的repaint()更符合Qt的事件处理机制,性能也更好:
@pyqtSlot() def __up(self): """Move the item up by one row.""" row = self.__fruit_list.currentRow() if row >= 1: item = self.__fruit_list.takeItem(row) self.__fruit_list.insertItem(row - 1, item) self.__fruit_list.setCurrentItem(item) # 用update替代repaint self.__fruit_list.update()
为什么setCurrentItem会失效?
简单来说,当你把item从列表中移除再插入时,QListWidget内部对这个item的位置关联暂时没有同步,setCurrentItem虽然标记了item为选中状态,但控件没有意识到这个item已经在新位置上,所以没有触发对应的UI更新。而setCurrentRow直接操作行号,控件会直接处理该行的选中渲染,避免了这个同步问题。
下面是修改后的完整可运行代码:
"""Demonstrates QListWidget and adding and modifying values using a dialog.""" # Importing the standard python libraries. import sys # Importing the PyQt5 libraries. from PyQt5.QtWidgets import QApplication, QAbstractItemView, QMessageBox from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout from PyQt5.QtWidgets import QListWidget, QPushButton from PyQt5.QtCore import pyqtSlot class FruitListApp(QMainWindow): """Fruit List Management Application class.""" def __init__(self, parent=None): """Initialize the Fruit List Application.""" super().__init__(parent) # Setup the widgets. self.__fruit_list = QListWidget() self.__fruit_list.setSelectionMode(QAbstractItemView.SingleSelection) self.__button_up = QPushButton("&Up") # Layout the widgets. button_box = QWidget() button_box_layout = QVBoxLayout() button_box_layout.addWidget(self.__button_up) button_box.setLayout(button_box_layout) central_widget = QWidget() layout = QHBoxLayout() layout.addWidget(self.__fruit_list) layout.addWidget(button_box) central_widget.setLayout(layout) # Setup the main application window. self.setCentralWidget(central_widget) self.setWindowTitle("Edit Fruit List") # Setup signals and slots. self.__button_up.clicked.connect(self.__up) def add_items(self, item_list): """Add items provided as a list to the fruit list.""" # Item is a list. if isinstance(item_list, list): if len(item_list) > 0: # Add items to the list after determining the previous count # of items in the list. previous_count = self.__fruit_list.count() self.__fruit_list.addItems(item_list) # Set the first item added from a list as the current # selection. self.__fruit_list.setCurrentRow(previous_count) else: QMessageBox.warning(self, "Error", "The fruit names list " "provided is empty.") return else: QMessageBox.warning(self, "Error", "The fruit names are not " "provided as a list.") return @pyqtSlot() def __up(self): """Move the item up by one row.""" row = self.__fruit_list.currentRow() if row >= 1: # Remove the currently selected item. item = self.__fruit_list.takeItem(row) self.__fruit_list.insertItem(row - 1, item) self.__fruit_list.setCurrentRow(row - 1) if __name__ == "__main__": def main(): """Main method to test the application.""" # The fruit list. fruit = ["Banana", "Apple", "Elderberry", "Clementine", "Fig", "Guava", "Mango", "Honeydew Melon", "Date", "Watermelon", "Tangerine", "Ugli Fruit", "Juniperberry", "Kiwi", "Lemon", "Nectarine", "Plum", "Raspberry", "Strawberry", "Orange"] # Instantiate the application. app = QApplication(sys.argv) # Instantiate the Fruit List Main Window. main_window = FruitListApp() # Add the fruit list. main_window.add_items(fruit) # Display the main application window. main_window.show() # Start the event loop. app.exec_() # Start the application. main()
内容的提问来源于stack exchange,提问作者Sanket Rajgarhia
相关产品推荐
相关产品推荐

