如何突出显示在折叠的 Qtoolbutton 下的 Qlistwidget 中的项目
How to highlight an item present in Qlistwidget which is under a collapsed Qtoolbutton
基于 by eyllanesc 的精彩回答,我已将 QListWidget 添加到可折叠框中。
下面有一个文本框,用户可以在其中提供搜索字符串,我的目的是在 QListWidget(如果存在)中突出显示该项目。
当前代码工作正常,即如果 QToolButton 已经展开,它将突出显示文本并将其滚动到顶部,但它只会打开并突出显示该项目,但如果尚未展开则不会将其滚动到顶部。 (所以用户现在不知道他是否找到了该项目,因为他看不到它突出显示。)
奇怪的是,如果我再次按回车键,它会滚动到顶部。
我尝试了各种方法,例如使 QlistWidget 处于活动状态、处于焦点状态等,但没有帮助。
请告诉我我遗漏了什么,这样我就不需要按两次 Enter 以防 QToolButton 尚未展开。
编辑:按照建议从代码中删除动画部分。
import time
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QListWidget, QLabel, QApplication, QVBoxLayout, \
QWidget, QSizePolicy, QToolButton, QScrollArea, QFrame, QDockWidget, QLineEdit, QHBoxLayout, QAbstractItemView
collapsed_list = ['What', 'should', 'be', 'done', 'to', 'fix', 'this', 'issue?', 'I', 'am', 'confused']
class CollapsibleDemo(QWidget):
def __init__(self, title="", parent=None):
super(CollapsibleDemo, self).__init__(parent)
self.toggle_button = QToolButton(
text=title, checkable=True, checked=False
)
self.toggle_button.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Fixed
)
self.toggle_button.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon
)
self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.content_area = QScrollArea(
maximumHeight=0, minimumHeight=0
)
self.content_area.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Fixed
)
self.content_area.setFrameShape(QFrame.NoFrame)
lay = QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(10, 10, 1, 1)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
@QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
if not checked:
self.content_area.setMaximumHeight(self.content_height + self.collapsed_height)
else:
self.content_area.setMaximumHeight(0)
def setContentLayout(self, layout):
self.content_area.setLayout(layout)
self.collapsed_height = (
self.sizeHint().height() - self.content_area.maximumHeight()
)
self.content_height = layout.sizeHint().height()
def set_text(self, title):
self.toggle_button.setText(title)
class Try(QMainWindow):
def __init__(self, ):
super().__init__()
self.width = 800
self.height = 800
self.init_ui()
def init_ui(self):
self.resize(self.width, self.height)
self.create_background()
self.add_collapsed_list_box()
self.add_find_text_box()
self.vlay.addStretch()
self.find_text_box.setFocus()
def create_background(self):
dock = QDockWidget()
self.setCentralWidget(dock)
dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
scroll = QScrollArea()
dock.setWidget(scroll)
content = QWidget()
scroll.setWidget(content)
scroll.setWidgetResizable(True)
self.vlay = QVBoxLayout(content)
self.vlay.setSpacing(10)
def add_collapsed_list_box(self):
self.box = CollapsibleDemo(f"Whats inside here!")
self.vlay.addWidget(self.box)
lay_diff = QVBoxLayout()
self.qlist = QListWidget()
self.qlist.addItems(collapsed_list)
lay_diff.addWidget(self.qlist)
self.box.setContentLayout(lay_diff)
def add_find_text_box(self):
self.find_text_box = QLineEdit("am")
find_label = QLabel(" Search text: ")
enter_label = QLabel(" (Press Enter)")
hlayout = QHBoxLayout()
hlayout.addWidget(find_label)
hlayout.addWidget(self.find_text_box)
hlayout.addWidget(enter_label)
hlayout.addStretch(3)
self.vlay.addLayout(hlayout)
self.find_text_box.returnPressed.connect(self.find_selected)
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
if not self.box.toggle_button.isChecked():
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop)
if __name__ == "__main__":
app = QApplication([])
window = Try()
window.show()
app.exec()
有两个问题:
- 动画是异步的:当它们启动时,控制权立即返回,因此在
animation.start()
之后调用的任何函数都会立即执行;这意味着此时动画的值仍然是 start 值;
- 项目视图(和一般的滚动区域)需要一些时间才能正确更新它们的滚动条;即使框立即折叠(没有动画),
scrollToItem
也 不会 工作,因为滚动区域尚未布置其内容;
为了解决问题,解决方法是delay the scrollToItem
call using a singleShot
QTimer.
如果正在使用动画,则必须等待动画结束,因为滚动条会随框大小一起更新。
一个可能的解决方案是创建一个延迟 scrollToItem
的本地函数,并且如果框折叠,则仅在通过连接到动画 finished
信号完成动画时调用它,然后断开连接(这 非常 重要!)。
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
collapsed = not self.box.toggle_button.isChecked()
if collapsed:
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
def scrollTo():
QtCore.QTimer.singleShot(1,
lambda: self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop))
if collapsed:
# disconnect the function!!!
self.box.toggle_animation.finished.disconnect(scrollTo)
if collapsed:
self.box.toggle_animation.finished.connect(scrollTo)
else:
scrollTo()
如果你对动画不感兴趣,那就更简单了:
class CollapsibleDemo(QWidget):
def __init__(self, title="", parent=None):
# ...
# remove all the animations in here
@QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
if checked:
self.setFixedHeight(self.collapsed_height)
self.content_area.setFixedHeight(0)
else:
self.setFixedHeight(self.expanded_height)
self.content_area.setFixedHeight(self.content_height)
def setContentLayout(self, layout):
self.content_area.setLayout(layout)
self.collapsed_height = self.sizeHint().height() - self.content_area.maximumHeight()
self.content_height = layout.sizeHint().height()
self.expanded_height = self.collapsed_height + self.content_height
def set_text(self, title):
self.toggle_button.setText(title)
class Try(QMainWindow):
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
if not self.box.toggle_button.isChecked():
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
QtCore.QTimer.singleShot(1, lambda:
self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop))
如评论中所述,动画持续时间至少为 50-100 毫秒才有意义;使用太短的持续时间会使动画无用(计算机无法显示效果并且我们的眼睛无论如何也无法看到它)并且只会使事情复杂化(您必须等待动画结束)。
基于
下面有一个文本框,用户可以在其中提供搜索字符串,我的目的是在 QListWidget(如果存在)中突出显示该项目。
当前代码工作正常,即如果 QToolButton 已经展开,它将突出显示文本并将其滚动到顶部,但它只会打开并突出显示该项目,但如果尚未展开则不会将其滚动到顶部。 (所以用户现在不知道他是否找到了该项目,因为他看不到它突出显示。) 奇怪的是,如果我再次按回车键,它会滚动到顶部。
我尝试了各种方法,例如使 QlistWidget 处于活动状态、处于焦点状态等,但没有帮助。
请告诉我我遗漏了什么,这样我就不需要按两次 Enter 以防 QToolButton 尚未展开。
编辑:按照建议从代码中删除动画部分。
import time
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QListWidget, QLabel, QApplication, QVBoxLayout, \
QWidget, QSizePolicy, QToolButton, QScrollArea, QFrame, QDockWidget, QLineEdit, QHBoxLayout, QAbstractItemView
collapsed_list = ['What', 'should', 'be', 'done', 'to', 'fix', 'this', 'issue?', 'I', 'am', 'confused']
class CollapsibleDemo(QWidget):
def __init__(self, title="", parent=None):
super(CollapsibleDemo, self).__init__(parent)
self.toggle_button = QToolButton(
text=title, checkable=True, checked=False
)
self.toggle_button.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Fixed
)
self.toggle_button.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon
)
self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.content_area = QScrollArea(
maximumHeight=0, minimumHeight=0
)
self.content_area.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Fixed
)
self.content_area.setFrameShape(QFrame.NoFrame)
lay = QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(10, 10, 1, 1)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
@QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
if not checked:
self.content_area.setMaximumHeight(self.content_height + self.collapsed_height)
else:
self.content_area.setMaximumHeight(0)
def setContentLayout(self, layout):
self.content_area.setLayout(layout)
self.collapsed_height = (
self.sizeHint().height() - self.content_area.maximumHeight()
)
self.content_height = layout.sizeHint().height()
def set_text(self, title):
self.toggle_button.setText(title)
class Try(QMainWindow):
def __init__(self, ):
super().__init__()
self.width = 800
self.height = 800
self.init_ui()
def init_ui(self):
self.resize(self.width, self.height)
self.create_background()
self.add_collapsed_list_box()
self.add_find_text_box()
self.vlay.addStretch()
self.find_text_box.setFocus()
def create_background(self):
dock = QDockWidget()
self.setCentralWidget(dock)
dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
scroll = QScrollArea()
dock.setWidget(scroll)
content = QWidget()
scroll.setWidget(content)
scroll.setWidgetResizable(True)
self.vlay = QVBoxLayout(content)
self.vlay.setSpacing(10)
def add_collapsed_list_box(self):
self.box = CollapsibleDemo(f"Whats inside here!")
self.vlay.addWidget(self.box)
lay_diff = QVBoxLayout()
self.qlist = QListWidget()
self.qlist.addItems(collapsed_list)
lay_diff.addWidget(self.qlist)
self.box.setContentLayout(lay_diff)
def add_find_text_box(self):
self.find_text_box = QLineEdit("am")
find_label = QLabel(" Search text: ")
enter_label = QLabel(" (Press Enter)")
hlayout = QHBoxLayout()
hlayout.addWidget(find_label)
hlayout.addWidget(self.find_text_box)
hlayout.addWidget(enter_label)
hlayout.addStretch(3)
self.vlay.addLayout(hlayout)
self.find_text_box.returnPressed.connect(self.find_selected)
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
if not self.box.toggle_button.isChecked():
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop)
if __name__ == "__main__":
app = QApplication([])
window = Try()
window.show()
app.exec()
有两个问题:
- 动画是异步的:当它们启动时,控制权立即返回,因此在
animation.start()
之后调用的任何函数都会立即执行;这意味着此时动画的值仍然是 start 值; - 项目视图(和一般的滚动区域)需要一些时间才能正确更新它们的滚动条;即使框立即折叠(没有动画),
scrollToItem
也 不会 工作,因为滚动区域尚未布置其内容;
为了解决问题,解决方法是delay the scrollToItem
call using a singleShot
QTimer.
如果正在使用动画,则必须等待动画结束,因为滚动条会随框大小一起更新。
一个可能的解决方案是创建一个延迟 scrollToItem
的本地函数,并且如果框折叠,则仅在通过连接到动画 finished
信号完成动画时调用它,然后断开连接(这 非常 重要!)。
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
collapsed = not self.box.toggle_button.isChecked()
if collapsed:
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
def scrollTo():
QtCore.QTimer.singleShot(1,
lambda: self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop))
if collapsed:
# disconnect the function!!!
self.box.toggle_animation.finished.disconnect(scrollTo)
if collapsed:
self.box.toggle_animation.finished.connect(scrollTo)
else:
scrollTo()
如果你对动画不感兴趣,那就更简单了:
class CollapsibleDemo(QWidget):
def __init__(self, title="", parent=None):
# ...
# remove all the animations in here
@QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
if checked:
self.setFixedHeight(self.collapsed_height)
self.content_area.setFixedHeight(0)
else:
self.setFixedHeight(self.expanded_height)
self.content_area.setFixedHeight(self.content_height)
def setContentLayout(self, layout):
self.content_area.setLayout(layout)
self.collapsed_height = self.sizeHint().height() - self.content_area.maximumHeight()
self.content_height = layout.sizeHint().height()
self.expanded_height = self.collapsed_height + self.content_height
def set_text(self, title):
self.toggle_button.setText(title)
class Try(QMainWindow):
def find_selected(self):
user_text = self.find_text_box.text()
if user_text in collapsed_list:
if not self.box.toggle_button.isChecked():
self.box.on_pressed()
self.box.toggle_button.setChecked(True)
item = self.qlist.findItems(user_text, QtCore.Qt.MatchRegExp)[0]
item.setSelected(True)
QtCore.QTimer.singleShot(1, lambda:
self.qlist.scrollToItem(item, QAbstractItemView.PositionAtTop))
如评论中所述,动画持续时间至少为 50-100 毫秒才有意义;使用太短的持续时间会使动画无用(计算机无法显示效果并且我们的眼睛无论如何也无法看到它)并且只会使事情复杂化(您必须等待动画结束)。