Issue
I created a QComboBox
and set a custom view for it:
self.list_view = QListView()
self.list_view.setWordWrap(True)
self.list_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.settings = QComboBox()
self.settings.setFixedHeight(28)
self.settings.setView(self.list_view)
How can I set a specific word wrapping mode for a QListView
or a QComboBox
?
In the documentation I found a QTextOption
class that has a .setWrapMode(wrapmode: PySide6.QtGui.QTextOption.WrapMode)
method, but didn't find a way to set a QTextOption
for a QListView
/QComboBox
like a QListView
for a QComboBox
.
Solution
The only way to achieve so while maintaining the model data is to implement a custom item delegate that uses QTextDocument for both sizeHint()
and paint()
.
The combo must also be a subclass that overrides showPopup()
, which is required to update the default (assumed) width of the view and compute correct sizes. This step is fundamental, as before showing itself, the doesn't know yet if the scroll bars will be visible or not (which could depend on the maxVisibleItems()
property but also the screen height).
Note that this approach is not always perfect: if the items have very long text and the combo is very narrow, the scroll bar might be shown even if the model has fewer items than the maxVisibleItems
. As a small workaround, I ensured that the painter always clips to the contents of the option. The only alternative solution is to compute the size hint of the first ten items, based on the current combo width, then check if the sum of the heights of those hints exceeds the current screen and eventually change again the reference width before actually showing the popup.
from random import choice
from PyQt5 import QtGui, QtWidgets
letters = 'abcdefhgijklmnopqrstuvwxyz0123456789' * 2 + ' '
class WrapDelegate(QtWidgets.QStyledItemDelegate):
referenceWidth = 0
wrapMode = QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere
def sizeHint(self, opt, index):
doc = QtGui.QTextDocument(index.data())
textOption = QtGui.QTextOption()
textOption.setWrapMode(self.wrapMode)
doc.setDefaultTextOption(textOption)
doc.setTextWidth(self.referenceWidth)
return doc.size().toSize()
def paint(self, qp, opt, index):
self.initStyleOption(opt, index)
style = opt.widget.style()
opt.text = ''
style.drawControl(style.CE_ItemViewItem, opt, qp, opt.widget)
doc = QtGui.QTextDocument(index.data())
textOption = QtGui.QTextOption()
textOption.setWrapMode(textOption.WrapAtWordBoundaryOrAnywhere)
doc.setDefaultTextOption(textOption)
doc.setTextWidth(opt.rect.width())
qp.save()
qp.translate(opt.rect.topLeft())
qp.setClipRect(0, 0, opt.rect.width(), opt.rect.height())
doc.drawContents(qp)
qp.restore()
class ComboWrap(QtWidgets.QComboBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.delegate = WrapDelegate(self.view())
self.view().setItemDelegate(self.delegate)
self.view().setResizeMode(QtWidgets.QListView.Adjust)
def showPopup(self):
container = self.view().parent()
l, _, r, _ = container.layout().getContentsMargins()
margin = l + r + container.frameWidth() * 2
if self.model().rowCount() > self.maxVisibleItems():
margin += self.view().verticalScrollBar().sizeHint().width()
self.delegate.referenceWidth = self.width() - margin
super().showPopup()
app = QtWidgets.QApplication([])
combo = ComboWrap()
combo.addItems([''.join(choice(letters) for i in range(100)) for i in range(20)])
combo.show()
app.exec()
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.