PDA

View Full Version : Hover and Highlight QTable and QTree Items



VireX
11th April 2007, 07:54
I was curious, is Qt Stylesheets the only way to get the hover: property?

I wanted to somehow highlight like a whole row in a TableWidget when you hover your mouse over a row. I also wanted to highlight the QTreeWidgetItem when someone hovers their mouse over it, so the background color changes on that item. I figured it out using QPalette for Highlight by clicking, but not my mouseover...

Any ideas? Thanks.

aamer4yu
11th April 2007, 08:14
U can use itemAt() function of QTableWidget to get the table widget item under the mouse... then set its background brush....
same u can do with qtreewidgetitems i guess...

and yah... capture the mouse movements on mouseMoveEvent() ....
you have to set setMouseTracking(true) to receive the mouse move events....

hope this will help you.. :)

VireX
10th May 2007, 05:16
It kind of helps, I like your itemAt idea, I could do like
itemAt(QCursor::pos()); but then I would need to put this in a loop and multithread right? Where else could I put this? How could I code this so it works?

I think perhaps the best way is to use mouseMoveEvent() and subclass TableWidget and TreeWidget.

VireX
11th May 2007, 08:39
So subclassing is only way I take it? I couldn't figure out the itemAt thing.

jpn
11th May 2007, 08:42
Enable mouse tracking on the viewport and catch mouse move events either by subclassing and reimplementing the event handler or alternatively with an event filter. The latter approach does not require subclassing.

VireX
11th May 2007, 09:04
Where can I learn about "event filters". The subclassing seems time consuming :O.

jpn
11th May 2007, 09:05
There's an example in QObject::installEventFilter() docs.

VireX
11th May 2007, 21:22
Ok I got the code, I'm at the part where I do
event->type() == QEvent::MouseMove){
so in here, do I just somehow get an instance of my original class of QTreeWidget and just do:
QTreeWidgetItem* bla = Widget->itemAt(QCursor::pos());
bla->setBackground(0, QBrush(QColor(255,0,0)));

Is that how I am suppose to do it? Or wait do I cast the QObject as a QTreeWidget perhaps?

jpn
11th May 2007, 23:05
The position passed within the event might be a bit more reliable:


// QTreeWidgetItem* prev;

if (event->type() == QEvent::MouseMove)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QTreeWidgetItem* current = ui.treeWidget->itemAt(mouseEvent->pos());
if (current && current != prev)
{
if (prev)
prev->setBackground(0, palette().base()); // reset previous
current->setBackground(0, Qt::red); // highlight current
prev = current;
}
}

VireX
12th May 2007, 08:10
I think that's exactly what I was looking for thank you. I think I was close.

VireX
12th May 2007, 08:49
I made a Filter class,
I used Q_OBJECT.
I used the obj and event as you said.
I even fixed your code, but it seems it works but very slowly. Like I have to whirl my mouse all over USerList and outside of UserList in order to get it to work properly. I mean it works, it even deletes old highlights, but it doesn't highlight the new spot when i move my mouse.

jpn
12th May 2007, 08:54
Have you enabled mouse tracking on the viewport (http://doc.trolltech.com/4.2/qabstractscrollarea.html#viewport) and also installed the event filter on the viewport (http://doc.trolltech.com/4.2/qabstractscrollarea.html#viewport)?


treeWidget->viewport()->setMouseTracking(true);
treeWidget->viewport()->installEventFilter(this);


Edit: is the tree massive, containing huge amount of items in a complex hierarchy? :)

wysota
12th May 2007, 09:45
Just stumbled upon this thread, so I decided to include my own 5 cents...

There is another possibility - you can use a custom delegate and reimplement editorEvent() and act upon one (or more) of QEvent::HoverEnter, QEvent::HoverLeave or QEvent::HoverMove. If I remember correctly you have to enable mouse tracking for these events to be delivered, although I'm not sure of that.

VireX
12th May 2007, 13:15
wysota, I don't think I wanna delve into making something too complex.
jpn, I was doing:
TreeWidg->setMouseTracking(true);
TreeWidg->installEventFilter(mhf); // mhf = new MouseHoverFilter;

When I changed it to:
TreeWidg->viewport()->setMouseTracking(true);
TreeWidg->viewport()->installEventFilter(mhf); // mhf = new MouseHoverFilter;
Now If I hover over treewidget it crashes the program.

jpn
12th May 2007, 13:26
Now If I hover over treewidget it crashes the program.
Improper cast? Insufficient checks? ...see the attached example.

VireX
12th May 2007, 13:36
No I did exactly what you posted, except that I didn't subclass my treeWidget.
I made a class called MouseHoverFilter which has the event filter. Then I installed the event filter and setmousetracking on the TreeWidget. It only crashes if i add viewport().

VireX
12th May 2007, 13:39
Heres the exact code I used, just try it:

In my main file:
MouseHoverFilter* mhf;
MyList->setMouseTracking(true);
mhf = new MouseHoverFilter;
MyList->installEventFilter(mhf);

Filters.h :

class MouseHoverFilter : public QObject {
Q_OBJECT
public:
MouseHoverFilter(QObject* parent);
QTreeWidgetItem* prev;
protected:
bool eventFilter(QObject *obj, QEvent *event);
};

MouseHoverFilter::MouseHoverFilter(QObject* parent = 0) : QObject(parent) {
prev = 0;
}

bool MouseHoverFilter::eventFilter(QObject *obj, QEvent *event){
if(event->type() == QEvent::MouseMove){
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QTreeWidget* qtw = static_cast<QTreeWidget*>(obj);
QTreeWidgetItem* current = qtw->itemAt(mouseEvent->pos());
if(current && current != prev && current->childCount() == 0){
if(prev)prev->setBackground(0, qtw->palette().base());
if(prev)prev->setBackground(1, qtw->palette().base());
if(prev)prev->setBackground(2, qtw->palette().base());
current->setBackground(0, QColor(193, 235, 255));
current->setBackground(1, QColor(193, 235, 255));
current->setBackground(2, QColor(193, 235, 255));
prev = current;
}
return true;
} else {
QObject::eventFilter(obj, event);
}
return false;
}
#include "Filters.moc"

wysota
12th May 2007, 14:18
wysota, I don't think I wanna delve into making something too complex.

Doesn't seem very complex...

jpn
12th May 2007, 14:56
This is the improper cast:

QTreeWidget* qtw = static_cast<QTreeWidget*>(obj);
The receiver object is the viewport widget, not the tree widget.

VireX
12th May 2007, 14:59
I've tried setting a QTreeWidget inside the Mouse class and delivering the original QTreeWidget to the Mouse Class constructor, but that made no difference, so that isn't the problem, it works this way too.

VireX
12th May 2007, 15:12
Here's my Filters.h

jpn
12th May 2007, 15:49
Here's my Filters.h
Works for me. Does this still crash for you? Could you paste backtrace from debugger?

VireX
12th May 2007, 16:17
No it doesn't crash after I removed your code of "bla->viewport()->setMouseTracking" back to the correct form: "bla->setMouseTracking()".

Thats not the problem, it's slow, I could take a video, and what you will see is, me spinning and making my mouse go crazy on the treewidget list, and after several seconds it finally highlights one item, and when my mouse is not even on the list, it's still highlighted. Like as if, the treewidget picks randomly when it wants to highlight something. Then I spin my mouse all over the screen again and again, then finally some other random TreeItem highlights itself.

wysota
12th May 2007, 16:27
Why don't you try my solution? :)

jpn
12th May 2007, 16:40
No it doesn't crash after I removed your code of "bla->viewport()->setMouseTracking" back to the correct form: "bla->setMouseTracking()".
I'm sorry to say but it's not correct at all. It's the viewport widget which receives mouse move events.



Thats not the problem, it's slow, I could take a video, and what you will see is, me spinning and making my mouse go crazy on the treewidget list, and after several seconds it finally highlights one item, and when my mouse is not even on the list, it's still highlighted. Like as if, the treewidget picks randomly when it wants to highlight something. Then I spin my mouse all over the screen again and again, then finally some other random TreeItem highlights itself.
That's most likely because you're not receiving those mouse move events but some other event causes an update every now and then, depending on if you enter and leave the widget or change the focus or something like that. Is the compilable example in post #15 (http://www.qtcentre.org/forum/p-hover-and-highlight-qtable-and-qtree-items-post36917/postcount15.html) that "slow"? Or did you even bother checking out out?

VireX
12th May 2007, 17:01
1. Well doing bla->viewport()-> causes crash, that's 100% for sure, therefore it is not correct.

2. Maybe so, but of course why would I bother checking it out when I have the same code you showed me earlier in a different style?

@wysota: Your solution works great. Still trying to find out what went wrong with jpn's solution.

jpn
12th May 2007, 17:07
1. Well doing bla->viewport()-> causes crash, that's 100% for sure, therefore it is not correct.
Again, the example works for fine. You must have a quirk of some kind in your code.


Use a debugger
examine the backtrace
find a reason why does it crash



2. Maybe so, but of course why would I bother checking it out when I have the same code you showed me earlier in a different style?
Because it would convince you that the approach works after all.

VireX
12th May 2007, 17:10
Both your solutions work fine, in their isolated situations.
I just don't understand why it doesn't work with my MouseFilter class which is almost EXACTLY like jpn's program.

Why would Qt put setMouseTracking and installEventFilter in the QAbstracts if it was suppose to be used inside a class with viewport().

When I do TreeWidget->viewport()->setMouseTracking(true); it crashes, that's the SOLE reason. I think jpn doesn't realize i'm not talking about his program.

wysota
12th May 2007, 17:16
"Almost" sometimes makes a big difference. I have to agree with JP - you have to install the filter on the viewport and not the list itself. Event filters don't propagate - if the list installs a filter on its viewport and you install a fillter on the list, you won't get events for the viewport. You're having obvious errors in your code which JP already pointed out. You'll be receiving mouse move events only for that parts of the view which are not occupied by other widgets (viewport and scrollbars) and I doubt that's what you want.

VireX
12th May 2007, 17:22
Maybe so.

One question why doesnt using QPalette::Background or Window make the background become red? I tried different color roles in your code.

Also your code highlights each item in each column. Is there a way to highlight background of the whole row? (I have selection mode in selectRow).

wysota
12th May 2007, 18:13
No, you have to do it through editorEvent as I suggested earlier. But it's simpler to adjust the selection using an event filter. Here is a combined approach:


bool editorEvent ( QEvent * event, QAbstractItemModel * model,
const QStyleOptionViewItem & option, const QModelIndex & index ){
if(event->type()==QEvent::MouseMove){
// view is the parent
QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
if(view){
view->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
return true;
}
}
return QItemDelegate::editorEvent(event, model, option, index);
}
bool eventFilter(QObject *o, QEvent *e){
// view is the parent
QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
if(view && e->type()==QEvent::Leave){
view->selectionModel()->clearSelection();
}
return QItemDelegate::eventFilter(o, e);
}

VireX
15th May 2007, 01:12
I added that to delegate class but it made no difference.

wysota
15th May 2007, 10:10
Did you remember about enabling mouse tracking?

VireX
15th May 2007, 20:13
Here's my code, and mouse tracking does not change anything (I put mousetracking on list List->setMouseTracking(true); I even tried List->viewport()->setMouseTracking(true); nothing works):


class Delegate : public QItemDelegate {
public:
Delegate(QObject *parent=0) : QItemDelegate(parent){ }
protected:
void drawDisplay ( QPainter * painter, const QStyleOptionViewItem & option, const QRect &rect, const QString &text ) const{
QStyleOptionViewItem opt = option;
if(option.state & QStyle::State_MouseOver){
opt.palette.setColor(QPalette::Base, QColor(0,255,0));
opt.palette.setColor(QPalette::Text, Qt::red);
//opt.font.setBold(true);
}
QItemDelegate::drawDisplay(painter, opt, rect, text);
}
bool editorEvent ( QEvent * event, QAbstractItemModel * model,
const QStyleOptionViewItem & option, const QModelIndex & index ){
if(event->type()==QEvent::MouseMove){
// view is the parent
QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
if(view){
view->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
return true;
}
}
return QItemDelegate::editorEvent(event, model, option, index);
}
bool eventFilter(QObject *o, QEvent *e){
// view is the parent
QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
if(view && e->type()==QEvent::Leave){
view->selectionModel()->clearSelection();
}
return QItemDelegate::eventFilter(o, e);
}
};
#include "Filters.moc"

wysota
15th May 2007, 23:09
"view is the parent" - is it true for you? Did you set the view as the parent of the delegate? Because if you didn't then it doesn't have a chance to work :) And remember to install the event filter on the viewport of the view, otherwise selection won't be cleared when you leave the view.

VireX
16th May 2007, 04:30
Why would I install the eventfilter, we dont use that for this. We dont use installEventFilter for this function. It would be easier if you showed me the exact code you used in like your main.cpp.

I did:
UserList->viewport()->setMouseTracking(true);
UserList->setItemDelegate(new Delegate(UserList->viewport()));
UserList->viewport()->setAttribute(Qt::WA_Hover, true);

Which is all you told me to do.

jpn
16th May 2007, 07:22
I think wysota meant more or less something like this:


Delegate* delegate = new Delegate(UserList); // view as parent, not viewport
UserList->viewport()->installEventFilter(delegate); // install event filter on the viewport
UserList->setItemDelegate(delegate);

VireX
16th May 2007, 09:09
Well all that did is take away the red bold hover on mouse over. Didn't fix the problem.

wysota
16th May 2007, 09:55
Why would I install the eventfilter, we dont use that for this. We dont use installEventFilter for this function.
So why do you paste me the code that uses it? :eek:

It would be easier if you showed me the exact code you used in like your main.cpp.
My main.cpp doesn't exist for a few days anymore, so this is not an option.


I did:
UserList->viewport()->setMouseTracking(true);
UserList->setItemDelegate(new Delegate(UserList->viewport()));
UserList->viewport()->setAttribute(Qt::WA_Hover, true);

Which is all you told me to do.
No, I didn't. Hover attribute shouldn't be necessary and the viewport shouldn't be the parent of the delegate. Please verify that the if block in the editorEvent gets entered at all.

Here, I implemented the thing again.

VireX
18th May 2007, 06:37
Nice it worked finally, I guess since I was using your old stuff and new stuff together it was conflicting and not showing up. And perhaps wrong parents etc.

Anyway I'm still wondering, how come selectionMode being SelectRow, yet the hover only highlights EACH column. Is it possible to code a way to highlight the WHOLE ROW, instead of a single column of the row?

wysota
18th May 2007, 09:28
If you change the code that does the selection then yes. I only selected a single index.

VireX
18th May 2007, 22:55
/*Hmm I almost have it working but I have to use
view->selectionModel()->select(index.sibling(index.row(), 0), QItemSelectionModel::Select);
view->selectionModel()->select(index.sibling(index.row(), 1), QItemSelectionModel::Select);
view->selectionModel()->select(index.sibling(index.row(), 2), QItemSelectionModel::Select);*/
Nvm, I think I got it to work now. Thanks for your help :D :D.