如何为两个QListWidget使用单个QScrollBar实现同步滚动
嘿,这个需求我之前做类似对比界面的时候刚好实现过,给你分享两种靠谱的方案,都满足你必须包含两个QListWidget的要求~
方案1:使用单个QScrollBar同步两个ListWidget
这正是你提到的思路,核心是隐藏两个ListWidget自带的滚动条,用一个共用的滚动条来控制两者,同时还要处理双向同步(拖动滚动条/滚动ListWidget内容都能同步)。
实现步骤:
- 先隐藏两个ListWidget的垂直滚动条,避免和共用滚动条冲突
- 创建一个独立的QScrollBar,把它的
valueChanged信号绑定到两个ListWidget的垂直滚动条的setValue方法,这样拖动共用滚动条时两个列表会一起滚动 - 监听两个ListWidget的垂直滚动条的
rangeChanged信号,同步更新共用滚动条的范围(保证滚动范围和列表内容匹配) - 最后还要处理反向同步:当用户用鼠标滚轮滚动某个ListWidget时,把滚动值同步到共用滚动条和另一个ListWidget,这里要注意用
blockSignals()避免循环触发信号
方案2:直接同步两个ListWidget的滚动(无需额外滚动条)
如果不需要单独的滚动条,也可以直接把两个ListWidget的滚动信号互相绑定,同样要处理循环触发的问题。
实现步骤:
- 把第一个ListWidget的垂直滚动条的
valueChanged信号绑定到第二个ListWidget的垂直滚动条的setValue方法 - 反过来再绑定一次,同时用
blockSignals()防止循环触发
完整GUI示例代码
下面是用PyQt5写的完整可运行代码,你可以直接测试两种方案:
import sys from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout, QListWidget, QScrollBar, QVBoxLayout) from PyQt5.QtCore import Qt class SyncScrollWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("双ListWidget同步滚动") self.resize(600, 400) # 创建两个ListWidget并添加测试数据 self.list1 = QListWidget() self.list2 = QListWidget() for i in range(50): self.list1.addItem(f"列表1 条目 {i+1}") self.list2.addItem(f"列表2 条目 {i+1}") # 方案1:使用共用滚动条(默认启用) self.use_shared_scrollbar() # 如果要测试方案2,注释上面的use_shared_scrollbar(),取消下面的注释 # self.sync_list_directly() self.show() def use_shared_scrollbar(self): # 隐藏自带滚动条 self.list1.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.list2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 创建共用滚动条 self.shared_scroll = QScrollBar(Qt.Vertical) # 同步滚动范围 def update_scroll_range(min_val, max_val): self.shared_scroll.setRange(min_val, max_val) self.list1.verticalScrollBar().rangeChanged.connect(update_scroll_range) self.list2.verticalScrollBar().rangeChanged.connect(update_scroll_range) # 共用滚动条控制两个列表 self.shared_scroll.valueChanged.connect(self.list1.verticalScrollBar().setValue) self.shared_scroll.valueChanged.connect(self.list2.verticalScrollBar().setValue) # 列表滚动时同步到共用滚动条 def sync_to_shared(value): self.shared_scroll.blockSignals(True) self.shared_scroll.setValue(value) self.shared_scroll.blockSignals(False) self.list1.verticalScrollBar().valueChanged.connect(sync_to_shared) self.list2.verticalScrollBar().valueChanged.connect(sync_to_shared) # 布局 h_layout = QHBoxLayout() h_layout.addWidget(self.list1) h_layout.addWidget(self.list2) main_layout = QVBoxLayout() main_layout.addLayout(h_layout) main_layout.addWidget(self.shared_scroll, alignment=Qt.AlignRight) self.setLayout(main_layout) def sync_list_directly(self): # 直接同步两个列表的滚动,无需额外滚动条 def sync_list1_to_list2(value): self.list2.verticalScrollBar().blockSignals(True) self.list2.verticalScrollBar().setValue(value) self.list2.verticalScrollBar().blockSignals(False) def sync_list2_to_list1(value): self.list1.verticalScrollBar().blockSignals(True) self.list1.verticalScrollBar().setValue(value) self.list1.verticalScrollBar().blockSignals(False) self.list1.verticalScrollBar().valueChanged.connect(sync_list1_to_list2) self.list2.verticalScrollBar().valueChanged.connect(sync_list2_to_list1) # 布局 h_layout = QHBoxLayout() h_layout.addWidget(self.list1) h_layout.addWidget(self.list2) self.setLayout(h_layout) if __name__ == "__main__": app = QApplication(sys.argv) window = SyncScrollWidget() sys.exit(app.exec_())
代码说明:
- 默认启用的是方案1(共用滚动条),如果要测试方案2,只需要注释掉
self.use_shared_scrollbar(),取消self.sync_list_directly()的注释即可 blockSignals(True)的作用是暂时阻止控件发送信号,避免出现“List1滚动触发List2滚动,List2滚动又触发List1滚动”的无限循环- 测试数据用了50条条目,方便看到滚动效果
内容的提问来源于stack exchange,提问作者Pommesfreak




