QTableView/QStyledItemDelegate: Mark an *entire* row hovered over with the mouse
-
This seemed so easy, but in execution it has proven difficult so far. I will go into all kinds of detail here, hoping to make myself clear.
Short version: I want an entire row in the table view to be visually "marked" (not selected) when the mouse hovers over it.
Long version:
I have a "QTableView":http://qt-project.org/doc/qt-5.1/qtwidgets/qtableview.html and a "QStyledItemDelegate":http://qt-project.org/doc/qt-5.1/qtwidgets/qstyleditemdelegate.html, both subclassed. I "set the selection behavior":http://qt-project.org/doc/qt-5.1/qtwidgets/qabstractitemview.html#selectionBehavior-prop of the table view to "QAbstractItemView::SelectRows", which makes the selection extend over complete rows. So far, so good.To make things look nicer, I wanted the QModelIndex under the mouse cursor to be slightly highlighted. I tried manipulating "QStyledItemDelegate::paint()":http://qt-project.org/doc/qt-5.1/qtwidgets/qstyleditemdelegate.html#paint to check for the "QStyle::State_MouseOver":http://qt-project.org/doc/qt-5.1/qtwidgets/qstyle.html#StateFlag-enum attribute (which should be set for the "QStyleOptionViewItems state":http://qt-project.org/doc/qt-5.1/qtwidgets/qstyleoption.html#state-var ).
Since the selection behavior is set to select complete rows, this highlighting, however, looks very ugly due to only a single cell being highlighted (not the entire row). I tried circumventing this by dirtily "hacking" the QRect corresponding to the hovered QModelIndex to suit my needs, but the results of that are visually very unpleasing.It seems that the desired behavior can't be truly achieved by fiddling with the item delegate. I may just have to manipulate which cells need redrawing, and for that I have to work with the QTableView.
The table view has a couple of highly private functions (void QTableViewPrivate::drawAndClipSpans(), void QTableViewPrivate::drawCell() ) which seem to be where I should intervene. But these are not part of the public API, so I don't have access to them. I could rewrite void QTableView::paintEvent, but without being able to call the private functions, I don't see a way to actually achieve anything by that.I wanted to lay out everything I have tried so far, in hopes of somebody having a brilliant idea without having to find out what I already found out. Or maybe there is some very simple solution I am overlooking. In any case, I would be grateful for any help. :)
-
After getting quite annoyed with QTableView::paintEvent due to it claiming to be a function I could reimplement however I need (which is not the case since the original makes heavy use of QTableViewPrivate), I went back to the QStyledItemDelegate to try another idea. This is what I came up with:
@void EnhancedItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//...
bool hovered = false;
if(opt.state & QStyle::State_MouseOver)
hovered = true;
else
{
QAbstractItemView *t = qobject_cast<QAbstractItemView *>(this->parent());
if(t)
{
QModelIndex hover = t->indexAt(t->viewport()->mapFromGlobal(QCursor::pos()));
if(hover.row() == index.row())
{
hovered = true;
t->update(hover);//this is important, otherwise the result is very messy
}
}
}//... if(hovered) { QBrush background = opt.palette.highlight(); QColor backColor = opt.palette.highlight().color(); backColor.setAlpha(100); background.setColor(backColor); painter->fillRect(opt.rect, background); painter->setPen(opt.palette.text().color()); }
}@
The order of coloring the background and drawing the other things is important (things may be overlayed with the background, if done wrong), but this seems to be a pretty solid solution overall. At least I have not found any drawbacks yet. Have to conduct some more testing, though. -
Hi,
Your solution doesn't sound bad, you might however simplify things by creating a copy of the options and update state with QStyle::State_MouseOver, you can then let the base implementation do the painting
-
I need full control over the paint-function anyway, and I can't completely follow your thoughts. Are you talking about a fake MouseOver state I should trigger? The ItemDelegate only ever draws a single cell, as far as I know. And I wasn't able to manipulate the calling of that drawing, since it is done in non-reimplementable QTableView-functions.
The solution wasn't too good, by the way. I had to make some small adjustments to it. It's still roughly the same, though.
-
No, I was thinking: just add the QStyle::State_MouseOver flag to the option.state flags when needed.
-
Aren't you just painting a focus rectangle over the each item of the row ?
-
Sorry, my bad, I've mixed hovered and selected. But since you are painting the hovered state like the selected, the technic I proposed earlier (using an updated copy of the option) is still usable, you would only need to add the QStyle::State_Selected and then call QAbstractItemDelegate::paint with this new option. It should handle the drawing for you properly
-
But at which point am I supposed to set that option? "Copying" it would be meaningless inside the item delegates paint() function, since that one only draws a single cell, and I have to explicitly check for the hovered-state anyway.
If you are talking about manipulating the option before calling QStyledItemDelegate::paint() in my custom implementation, that's not possible. As I said: I completely rewrote that function, without any calls to the base class being made.
-
Ok, it wasn't clear that all the painting was custom. Then no need for my idea.
I just thought about something that may be of interest, what about QRubberBand ?
-
That sounds interesting indeed. I have never used a QRubberBand before - what would happen to the text in the tableview? Would it still be readable?
When doing this in the item delegate, I can paint the background of the text before writing the text, so the hover-marking is cleanly realized (but it still has some minor flaws). An overlayed widget might wash out the text.
-
IIRC, the QRubberBand is translucent so you would only have to handle its size and position. You could do it directly in a custom view and wouldn't need your special delegate