QTreeView item's editor position problem? - qt

I have a QTreeView widget in which I have set indentation to 0 with QTreeView::setIndentation(0), but when editing an item, the editor still appears at the indentation level of the default indented behaviour:
I have tried changing the editor position with a QStyledItemDelegate::updateEditorGeometry's method, but it seems that there is a limit to where I can move the editor horizontally, as I cannot move it to a negative offset, past the x == 0 position, like for example QRect(-50, 0, 100, 30).
Any ideas would be greatly appreciated, thanks.
P.S.:
Here is an attempt of a minimal-reproducible-example, but this has another bug: the editor widget does not display when the geometry of the widget in drawRow is changed! The editor works when typing and pressing enter, it's just not displayed! I'll try to figure out what's the difference between my example in the image above and this code.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sip
class MyTreeWidget(QTreeWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setIndentation(0)
self.setMouseTracking(True)
self.setUniformRowHeights(True)
self.setExpandsOnDoubleClick(False)
self.setColumnCount(1)
self.setHeaderLabels(["Items"])
self.setHeaderHidden(True)
self.header().setVisible(False)
self.header().setStretchLastSection(False)
self.header().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
self.add_items()
self.itemClicked.connect(self.click)
def click(self, item, column):
item.setFlags(
Qt.ItemFlag.ItemIsEditable |
Qt.ItemFlag.ItemIsEnabled |
Qt.ItemFlag.ItemIsSelectable
)
index = self.indexFromItem(item, 0)
self.edit(index)
def drawRow(self, painter, option, index):
model = self.model()
row = index.row()
column = index.column()
item = self.itemFromIndex(index)
widget = self.itemWidget(item, 0)
if widget is not None:
geo = QRect(widget.geometry())
geo.setX(geo.x() + 50)
widget.setGeometry(geo) # <- This line causes the editor's horizontal offset
super().drawRow(painter, option, index)
def add_items(self):
items = [
'Cookie dough',
'Hummus',
'Spaghetti',
'Dal makhani',
'Chocolate whipped cream'
]
parent = None
for item in items:
new_item = QTreeWidgetItem(None)
if parent is not None:
new_item = QTreeWidgetItem(parent)
else:
self.addTopLevelItem(new_item)
new_item.setText(0, item)
new_item.setExpanded(True)
parent = new_item
def edit_last_item(*args):
new_item.setFlags(
Qt.ItemFlag.ItemIsEditable |
Qt.ItemFlag.ItemIsEnabled |
Qt.ItemFlag.ItemIsSelectable
)
index = self.indexFromItem(new_item, 0)
self.edit(index)
print("Editing")
QTimer.singleShot(1000, edit_last_item)
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.setLayout(layout)
layout.addWidget(MyTreeWidget())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

In trying to create a minimal reproducible example as #Parisa.H.R mentioned in the comment, I discovered that in the drawRow method in the QTreeView/QTreeWidget there is some code that adjusts the geometry of item widgets, like so (Python/PyQt example):
class MyTreeView(QTreeView):
def drawRow(self, painter, option, index):
model = self.model()
row = index.row()
column = index.column()
item = self.itemFromIndex(index)
widget = self.itemWidget(item, 0)
geo = ... # <- Code that calculates new geometry
widget.setGeometry(geo) # <- This line causes the editor's horizontal offset
super().drawRow(painter, option, index)
which is what causes the editor widget to be moved horizontally to the right.
Thanks #Parisa.H.R

Related

QTableView header word wrap

I'm trying to set the horizontal and vertical headers in QTableView to word wrap but without any success.
I want to set all columns to be the same width (including the vertical header), and those columns that have multiline text to word wrap. If word is wider than the column it should elide right.
I've managed to set the elide using QTableView -> horizontalHeader() -> setTextElideMode(Qt::ElideRight), but I can't do the same for word wrap since QHeaderView doesn't have setWordWrap method. So event if text is multiline it will just elide. Setting the word wrap on the table view doesn't do anything. The table cells contain only small numbers so the issue is only with the headers, and I want to avoid using '/n' since the headers are set dynamically. Is there maybe some other setting I've changed that's not allowing word wrap to function?
I was able to consolidate the two approaches above (c++, Qt 5.12) with a pretty nice result. (no hideheaders on the model)
Override QHeaderView::sectionSizeFromContents() such that size accounts for text wrapping
QSize MyHeaderView::sectionSizeFromContents(int logicalIndex) const
{
const QString text = this->model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString();
const int maxWidth = this->sectionSize(logicalIndex);
const int maxHeight = 5000; // arbitrarily large
const auto alignment = defaultAlignment();
const QFontMetrics metrics(this->fontMetrics());
const QRect rect = metrics.boundingRect(QRect(0, 0, maxWidth, maxHeight), alignment, text);
const QSize textMarginBuffer(2, 2); // buffer space around text preventing clipping
return rect.size() + textMarginBuffer;
}
Set the default alignment to have word wrap (optionally, center)
tableview->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter | (Qt::Alignment)Qt::TextWordWrap);
I've managed to find the solution using subclassing of QHeaderView and reimplementing sectionSizeFromContents and paintSection methods in it. Here's the demo in PyQt5 (tested with Python 3.5.2 and Qt 5.6):
import sys
import string
import random
from PyQt5 import QtCore, QtWidgets, QtGui
class HeaderViewWithWordWrap(QtWidgets.QHeaderView):
def __init__(self):
QtWidgets.QHeaderView.__init__(self, QtCore.Qt.Horizontal)
def sectionSizeFromContents(self, logicalIndex):
if self.model():
headerText = self.model().headerData(logicalIndex,
self.orientation(),
QtCore.Qt.DisplayRole)
options = self.viewOptions()
metrics = QtGui.QFontMetrics(options.font)
maxWidth = self.sectionSize(logicalIndex)
rect = metrics.boundingRect(QtCore.QRect(0, 0, maxWidth, 5000),
self.defaultAlignment() |
QtCore.Qt.TextWordWrap |
QtCore.Qt.TextExpandTabs,
headerText, 4)
return rect.size()
else:
return QtWidgets.QHeaderView.sectionSizeFromContents(self, logicalIndex)
def paintSection(self, painter, rect, logicalIndex):
if self.model():
painter.save()
self.model().hideHeaders()
QtWidgets.QHeaderView.paintSection(self, painter, rect, logicalIndex)
self.model().unhideHeaders()
painter.restore()
headerText = self.model().headerData(logicalIndex,
self.orientation(),
QtCore.Qt.DisplayRole)
painter.drawText(QtCore.QRectF(rect), QtCore.Qt.TextWordWrap, headerText)
else:
QtWidgets.QHeaderView.paintSection(self, painter, rect, logicalIndex)
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.model_cols_names = [ "Very-very long name of my first column",
"Very-very long name of my second column",
"Very-very long name of my third column",
"Very-very long name of my fourth column" ]
self.hide_headers_mode = False
self.data = []
for i in range(0, 10):
row_data = []
for j in range(0, len(self.model_cols_names)):
row_data.append(''.join(random.choice(string.ascii_uppercase +
string.digits) for _ in range(6)))
self.data.append(row_data)
def hideHeaders(self):
self.hide_headers_mode = True
def unhideHeaders(self):
self.hide_headers_mode = False
def rowCount(self, parent):
if parent.isValid():
return 0
else:
return len(self.data)
def columnCount(self, parent):
return len(self.model_cols_names)
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
row = index.row()
if row < 0 or row >= len(self.data):
return None
column = index.column()
if column < 0 or column >= len(self.model_cols_names):
return None
return self.data[row][column]
def headerData(self, section, orientation, role):
if role != QtCore.Qt.DisplayRole:
return None
if orientation != QtCore.Qt.Horizontal:
return None
if section < 0 or section >= len(self.model_cols_names):
return None
if self.hide_headers_mode == True:
return None
else:
return self.model_cols_names[section]
class MainForm(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.model = Model()
self.view = QtWidgets.QTableView()
self.view.setModel(self.model)
self.view.setHorizontalHeader(HeaderViewWithWordWrap())
self.setCentralWidget(self.view)
def main():
app = QtWidgets.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
An open Qt issue from 2010 on this suggests that this may not be easily possible.
However, according to the only comment from 2015, there is a simple workaround for this very issue which goes like this:
myTable->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter | (Qt::Alignment)Qt::TextWordWrap);
I just tested with Qt 5.12 and fortunately found it still working.
In python:
myTableView.horizontalHeader().setDefaultAlignment(Qt.AlignCenter | Qt.Alignment(Qt.TextWordWrap))

Implementing a delegate for wordwrap in a QTreeView (Qt/PySide/PyQt)?

I have a tree view with a custom delegate to which I am trying to add word wrap functionality. The word wrapping is working fine, but the sizeHint() seems to not work, so when the text wraps, the relevant row does not expand to include it.
I thought I was taking care of it in sizeHint() by returning document.size().height().
def sizeHint(self, option, index):
text = index.model().data(index)
document = QtGui.QTextDocument()
document.setHtml(text)
document.setTextWidth(option.rect.width())
return QtCore.QSize(document.idealWidth(), document.size().height())
However, when I print out document.size().height() it is the same for every item.
Also, even if I manually set the height (say, to 75) just to check that things will look reasonable, the tree looks like a goldfish got shot by a bazooka (that is, it's a mess):
As you can see, the text in each row is not aligned properly in the tree.
Similar posts
Similar issues have come up before, but no solutions to my problem (people usually say to reimplement sizeHint(), and that's what I am trying):
QTreeWidget set height of each row depending on content
QTreeView custom row height of individual rows
http://www.qtcentre.org/threads/1289-QT4-QTreeView-and-rows-with-multiple-lines
SSCCE
import sys
from PySide import QtGui, QtCore
class SimpleTree(QtGui.QTreeView):
def __init__(self, parent = None):
QtGui.QTreeView.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setGeometry(500,200, 400, 300)
self.setUniformRowHeights(False) #optimize: but for word wrap, we don't want this!
print "uniform heights in tree?", self.uniformRowHeights()
self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Task', 'Description'])
self.setModel(self.model)
self.rootItem = self.model.invisibleRootItem()
item0 = [QtGui.QStandardItem('Sneeze'), QtGui.QStandardItem('You have been blocked up')]
item00 = [QtGui.QStandardItem('Tickle nose, this is a very long entry. Row should resize.'), QtGui.QStandardItem('Key first step')]
item1 = [QtGui.QStandardItem('<b>Get a job</b>'), QtGui.QStandardItem('Do not blow it')]
self.rootItem.appendRow(item0)
item0[0].appendRow(item00)
self.rootItem.appendRow(item1)
self.setColumnWidth(0,150)
self.expandAll()
self.setWordWrap(True)
self.setItemDelegate(ItemWordWrap(self))
class ItemWordWrap(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
QtGui.QStyledItemDelegate.__init__(self, parent)
self.parent = parent
def paint(self, painter, option, index):
text = index.model().data(index)
document = QtGui.QTextDocument() # #print "dir(document)", dir(document)
document.setHtml(text)
document.setTextWidth(option.rect.width()) #keeps text from spilling over into adjacent rect
painter.save()
painter.translate(option.rect.x(), option.rect.y())
document.drawContents(painter) #draw the document with the painter
painter.restore()
def sizeHint(self, option, index):
#Size should depend on number of lines wrapped
text = index.model().data(index)
document = QtGui.QTextDocument()
document.setHtml(text)
document.setTextWidth(option.rect.width())
return QtCore.QSize(document.idealWidth() + 10, document.size().height())
def main():
app = QtGui.QApplication(sys.argv)
myTree = SimpleTree()
myTree.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The issue seems to stem from the fact that the value for option.rect.width() passed into QStyledItemDelegate.sizeHint() is -1. This is obviously bogus!
I've solved this by storing the width in the model from within the paint() method and accessing this from sizeHint().
So in your paint() method add the line:
index.model().setData(index, option.rect.width(), QtCore.Qt.UserRole+1)
and in your sizeHint() method, replace document.setTextWidth(option.rect.width()) with:
width = index.model().data(index, QtCore.Qt.UserRole+1)
if not width:
width = 20
document.setTextWidth(width)

Is there a way to overlay multiple items on a parent widget (PySide/Qt)

I have a main parent widget, and I want several layouts on top of the parent widget.
Initializing a layout with a parent widget will place the layout on top of the parent widget. I like this and would like to do it multiple times (left, top, bottom, and right sides) for the same parent widget.
I used a QGridLayout with different sub layouts, but this caused the layouts to resize and forced them to be small. Whatever Overlay is added last should be on top of the other items.
Below is a very simple example of what I want.
import sys
from PySide import QtGui, QtCore
class Overlay(QtGui.QBoxLayout):
"""Overlay widgets on a parent widget."""
def __init__(self, parent=None, location="left"):
super().__init__(QtGui.QBoxLayout.TopToBottom, parent)
if location == "left" or location == "right":
self.setDirection(QtGui.QBoxLayout.TopToBottom)
if location == "right":
self.setAlignment(QtCore.Qt.AlignRight)
elif location == "top" or location == "bottom":
self.setDirection(QtGui.QBoxLayout.LeftToRight)
if location == "bottom":
self.setAlignment(QtCore.Qt.AlignBottom)
self.css = "QWidget {background-color: lightskyblue; color: white}"
# end Constructor
def addWidget(self, widget):
super().addWidget(widget)
widget.setStyleSheet(self.css)
# end addWidget
# end class Overlay
def main():
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
window.show()
widg = QtGui.QTreeView()
window.setCentralWidget(widg)
left = Overlay(widg, "left")
left.addWidget(QtGui.QLabel("HELLO"))
left.addWidget(QtGui.QLabel("WORLD!"))
top = Overlay(widg, "top")
top.addWidget(QtGui.QLabel("Hello"))
top.addWidget(QtGui.QLabel("World!"))
right = Overlay(location="right")
right.setParent(widg)
right.addWidget(QtGui.QLabel("hello"))
right.addWidget(QtGui.QLabel("world!"))
return app.exec_()
# end main
if __name__ == '__main__':
sys.exit(main())
Is there anyway to have multiple layouts with the same parent? If not is there some way to create a dummy widget that will move with the parent widget and have the Overlays use multiple dummy widgets as their parent?
also
layout = QtGui.QBoxLayout(QtGui.QBoxLayout.TopToBottom, parent_widget)
does not do the same thing as
layout = QtGui.QBoxLayout(QtGui.QBoxLayout.TopToBottom)
layout.setParent(parent_widget)
What does the Initialization do with the parent that is different?
I solved this by creating my own master custom layout. OverlayCenter has the main widget as it's parent and you just add the other layouts to this layout.
import sys
from PySide import QtGui, QtCore
class OverlayCenter(QtGui.QLayout):
"""Layout for managing overlays."""
def __init__(self, parent):
super().__init__(parent)
# Properties
self.setContentsMargins(0, 0, 0, 0)
self.items = []
# end Constructor
def addLayout(self, layout):
"""Add a new layout to overlay on top of the other layouts and widgets."""
self.addChildLayout(layout)
self.addItem(layout)
# end addLayout
def __del__(self):
"""Destructor for garbage collection."""
item = self.takeAt(0)
while item:
item = self.takeAt(0)
# end Destructor
def addItem(self, item):
"""Add an item (widget/layout) to the list."""
self.items.append(item)
# end addItem
def count(self):
"""Return the number of items."""
return len(self.items)
# end Count
def itemAt(self, index):
"""Return the item at the given index."""
if index >= 0 and index < len(self.items):
return self.items[index]
return None
# end itemAt
def takeAt(self, index):
"""Remove and return the item at the given index."""
if index >= 0 and index < len(self.items):
return self.items.pop(index)
return None
# end takeAt
def setGeometry(self, rect):
"""Set the main geometry and the item geometry."""
super().setGeometry(rect)
for item in self.items:
item.setGeometry(rect)
# end setGeometry
# end class OverlayCenter
class Overlay(QtGui.QBoxLayout):
"""Overlay widgets on a parent widget."""
def __init__(self, location="left", parent=None):
super().__init__(QtGui.QBoxLayout.TopToBottom, parent)
if location == "left":
self.setDirection(QtGui.QBoxLayout.TopToBottom)
self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
elif location == "right":
self.setDirection(QtGui.QBoxLayout.TopToBottom)
self.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
elif location == "top":
self.setDirection(QtGui.QBoxLayout.LeftToRight)
self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter)
elif location == "bottom":
self.setDirection(QtGui.QBoxLayout.LeftToRight)
self.setAlignment(QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter)
self.css = "QWidget {background-color: lightskyblue; color: white}"
# end Constructor
def addWidget(self, widget):
super().addWidget(widget)
widget.setStyleSheet(self.css)
# end addWidget
# end class Overlay
def main():
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
window.show()
widg = QtGui.QTreeView()
window.setCentralWidget(widg)
left = Overlay("left")
lhlbl = QtGui.QLabel("Hello")
lwlbl = QtGui.QLabel("World!")
lhlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
lwlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
left.addWidget(lhlbl)
left.addWidget(lwlbl)
top = Overlay("top")
lhlbl = QtGui.QLabel("HELLO")
lwlbl = QtGui.QLabel("WORLD!")
lhlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
lwlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
top.addWidget(lhlbl)
top.addWidget(lwlbl)
right = Overlay("right")
lhlbl = QtGui.QLabel("hellO")
lwlbl = QtGui.QLabel("worlD!")
lhlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
lwlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
right.addWidget(lhlbl)
right.addWidget(lwlbl)
bottom = Overlay("bottom")
lhlbl = QtGui.QLabel("hello")
lwlbl = QtGui.QLabel("world!")
lhlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
lwlbl.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
bottom.addWidget(lhlbl)
bottom.addWidget(lwlbl)
center = OverlayCenter(widg)
center.addLayout(left)
center.addLayout(top)
center.addLayout(right)
center.addLayout(bottom)
return app.exec_()
# end main
if __name__ == '__main__':
sys.exit(main())

pyqt qt4 QTableView how to disable sorting for certain columns?

So I have a QTableView and I only want to let column sorting on column 1 but not column2.
Naturally I tried to installEventFilter on QHeaderView or QTableView, but MouseButtonPress event is not being passed unless you installEventFilter on QApplication
Now if when eventFilter is called, the target object is always the top level widget although event.pos() is actually relative to the header or tablecell depending on where you click.
So we cannot use QHeaderView.rect().contains(event.pos()) to find out if the user clicks on the header because you get false positive when you click on the top edge of the very first table cell.
You can still however calculate this using globalPos but then your eventFilter's logic has to change when you change layout or add more widgets above the tableview.
I believe it is a bug that event.pos() returns the relative pos even the object argument always refer to the same top level widget.
A more logical API would be that there is a event.target() method to return the target where it calculates the relative pos.
But I don't see a target() method or a way to find the target in this event filter.
Maybe I'm missing something?
# -*- coding: utf-8 -*-
# pyqt windows 4.10.3
# python 2.7.5 32 bits
from PyQt4.QtCore import *
from PyQt4.QtGui import *
app = None
tableHeader = None
class MyModel(QAbstractTableModel):
def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
return 2
def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
return 2
def data(self, modelIndex, role=None):
if modelIndex.isValid():
row = modelIndex.row()
col = modelIndex.column()
if role == Qt.DisplayRole:
return "%02d,%02d" % (row, col)
def flags(self, index):
if index.isValid():
return Qt.ItemIsEnabled
def headerData(self, section, Qt_Orientation, role=None):
if role == Qt.DisplayRole and Qt_Orientation == Qt.Horizontal:
return "Column " + str(section+1)
class MyEventFilter(QObject):
def eventFilter(self, object, event):
if event.type() == QEvent.MouseButtonPress:
# object is always app/top level widget
print 'MouseButtonPress target :' + repr(object)
# even though event.pos() gives pos relative to the header when you click on header,
# and pos relative to table cells when you click on table cell
print repr(event.pos())
# however we can get the mouse's global position
print repr(event.globalPos())
# given the top level widget's geometry
print repr(app.activeWindow().geometry())
# and the table header's left, top and height
print repr(tableHeader.rect())
# we can find out whether mouse click is targeted at the header
print repr(event.globalPos().y() - app.activeWindow().geometry().y())
# BUT WHAT IF THE LAYOUT CHANGE OR WE ADD MORE WIDGETS ABOVE THE TABLEVIEW?
# WE HAVE TO ADJUST THE CALCULATION ABOVE!
return False
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = QMainWindow()
t = QTableView()
tableHeader = t.horizontalHeader()
t.setModel(MyModel())
w.setCentralWidget(t)
ef = MyEventFilter()
# installing in QMainWindow or QTableView won't catch MouseButtonPress
# https://qt-project.org/forums/viewthread/9347
#w.installEventFilter(ef)
#t.installEventFilter(ef)
app.installEventFilter(ef)
w.show()
sys.exit(app.exec_())
There's a much easier solution: reimplement the sort method of the model, and only permit sorting for the appropriate column.
Also, as an added refinement, use the sortIndicatorChanged signal of the header to restore the current sort indicator when appropriate.
Here's a demo script:
from PyQt4 import QtGui, QtCore
class TableModel(QtGui.QStandardItemModel):
_sort_order = QtCore.Qt.AscendingOrder
def sortOrder(self):
return self._sort_order
def sort(self, column, order):
if column == 0:
self._sort_order = order
QtGui.QStandardItemModel.sort(self, column, order)
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = QtGui.QTableView(self)
model = TableModel(rows, columns, self.table)
for row in range(rows):
for column in range(columns):
item = QtGui.QStandardItem('(%d, %d)' % (row, column))
item.setTextAlignment(QtCore.Qt.AlignCenter)
model.setItem(row, column, item)
self.table.setModel(model)
self.table.setSortingEnabled(True)
self.table.horizontalHeader().sortIndicatorChanged.connect(
self.handleSortIndicatorChanged)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
def handleSortIndicatorChanged(self, index, order):
if index != 0:
self.table.horizontalHeader().setSortIndicator(
0, self.table.model().sortOrder())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(5, 5)
window.show()
window.setGeometry(600, 300, 600, 250)
sys.exit(app.exec_())

Qt/PyQt: QGraphicsItem vs. QGraphicsWidget geometry, position, mouse interaction

I am converting a larger program of QGraphicsItems to QGraphicsWidgets (let's call them item and widget for typing sake). Mouse hover fails now because the position and/or rect of the widgets are not the same as the old items. I've boiled down to a simple case with a view, scene, an item and a widget. The blue item renders at 100x50 pix, and hoverEnterEvent occurs as expected. However, the red widget is rendered at half intended width. I can fix this if I reimplement the pure virtual function boundingRect for the widget but the hover event is still only triggered atop the 50x50 left half. What pos/rect/geometry methods do I need to use/override to get the widget to interact properly with the mouse just like the item? Thanks. Here's my sample code
#!/usr/local/bin/python
import os, sys
from PyQt4.Qt import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyView(QGraphicsView):
def __init__(self):
QGraphicsView.__init__(self)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.scene = QGraphicsScene(self)
self.item = GraphicsItem('item', 100, 50)
self.item.moveBy(50, 50)
self.scene.addItem(self.item)
self.widget = GraphicsWidget('widget', 100, 50)
self.scene.addItem(self.widget)
self.setScene(self.scene)
class GraphicsItem(QGraphicsItem):
def __init__(self, name, width, height):
QGraphicsItem.__init__(self)
self.setAcceptHoverEvents(True)
self.name = name
self.__width = width
self.__height = height
def boundingRect(self):
return QRectF(0, 0, self.__width, self.__height)
def hoverEnterEvent(self, event):
self.__printGeometryDetails()
def paint(self, painter, option, widget):
bgRect = self.boundingRect()
painter.drawRects(bgRect)
painter.fillRect(bgRect, QColor('blue'))
def __printGeometryDetails(self):
print self.name
print ' pos (%.0f, %0.0f)' % (self.pos().x(), self.pos().y())
print ' boundingRect (%.0f, %0.0f, %.0f, %0.0f)' % (self.boundingRect().x(), self.boundingRect().y(), self.boundingRect().width(), self.boundingRect().height())
class GraphicsWidget(QGraphicsWidget):
def __init__(self, name, width, height):
QGraphicsWidget.__init__(self)
self.setAcceptHoverEvents(True)
self.name = name
self.__width = width
self.__height = height
def boundingRect(self):
return QRectF(0, 0, self.__width, self.__height)
def hoverEnterEvent(self, event):
self.__printGeometryDetails()
def paint(self, painter, option, widget):
bgRect = self.boundingRect()
painter.drawRects(bgRect)
painter.fillRect(bgRect, QColor('red'))
def __printGeometryDetails(self):
print self.name
print ' pos (%.0f, %0.0f)' % (self.pos().x(), self.pos().y())
print ' boundingRect (%.0f, %0.0f, %.0f, %0.0f)' % (self.boundingRect().x(), self.boundingRect().y(), self.boundingRect().width(), self.boundingRect().height())
print ' geometry (%.0f, %0.0f, %.0f, %0.0f)' % (self.geometry().x(), self.geometry().y(), self.geometry().width(), self.geometry().height())
print ' rect (%.0f, %0.0f, %.0f, %0.0f)' % (self.rect().x(), self.rect().y(), self.rect().width(), self.rect().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
view = MyView()
view.setGeometry(600, 100, 400, 370)
view.show()
sys.exit(app.exec_())
It does seem to work correctly if you use self.resize(width, height) instead of redefining boundingRect.

Resources