highlight QGraphicsItem



  • how can i highlight a custom QGraphicsItem on hover? the item is a cross - X, drawn by two lines.

    i know about setGraphicsEffect(new QGraphicsColorizeEffect()) but that's not what i want. i want there to be outlines (borders) around the cross on hover.

    i guess i need to sublcass QGraphicsEffect and override its draw(), but how?


  • Qt Champions 2017

    Hi
    What about just subclass QGraphicsItem and paint it yourself

    class SimpleItem : public QGraphicsItem
    {
    public:
           void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                   QWidget *widget)
        {
        draw the lines / with or without highlight
        }
    };
    


  • @mrjj
    there i draw without the highlight. it's okay.
    for hoverEnterEvent i need to draw WITH highlight


  • Lifetime Qt Champion

    Hi,

    You can trigger a repaint in the hoverEnterEvent.



  • @SGaist
    yes, but how should i draw a "border" around the line?


  • Lifetime Qt Champion

    Paint a rectangle that is slightly bigger that the geometry of your widget.



  • @user4592357 the same draw function needs to know when it needs to draw the highlight or just the line... how you do that depends on details of your implementation which you have not shared with us yet. I am sure you can figure some way to do it though :-).



  • @kenchan
    it is in Python, sorry. my tries are in vain:

    class Item(QGraphicsItem):
    
        def __init__(self, x, y):
            super(ErrorItem, self).__init__()
            self.__x = x
            self.__y = y
    
            self.__hovered = False
    
            self.setAcceptHoverEvents(True)
    
        def paint(self, painter, option, widget):
            pen = QPen(Qt.red)
            pen.setWidth(4)
            painter.setPen(pen)
    
            painter.drawLine(QPoint(0, 0), QPoint(100, 100))
            painter.drawLine(QPoint(0, 100), QPoint(100, 0))
    
            if self.__hovered:
                # what should go here?
                pen.setColor(Qt.white)
                pen.setWidth(8)
                self.__hovered = False
                # painter.drawLine(QPoint(-50, -50), QPoint(103, 103))
    
        def boundingRect(self):
            return QRectF(0, 0, 100, 100)
    
        def hoverEnterEvent(self, event):
            # self.setGraphicsEffect(QGraphicsColorizeEffect())
            self.__hovered = True
            
        def hoverLeaveEvent(self, event):
            self.setGraphicsEffect(None)
    


  • @user4592357 Sorry I don't develop Qt with Python...
    Maybe you could put the line drawing bits after the if statement and make that just change the colour and the thickness?
    Since you can draw the line normally then that should work as before, right?
    The next issue will be if your hovered flag is getting changed when the event is fired. Can you debug that to check it?
    Finally i guess you will want to force the scene to redraw, if that is the problem you are seeing.
    I would have thought that the scene would be redrawn when the hover events happen?
    I believe the default implementation of hoveredEnterEvent() function just calls update(). Maybe you should call update() or something?
    I think you should reset the hovered flag to false in the hoverLeaveEvent so it only happens when you are actually hovering over it :-)



  • @kenchan
    drawing the line thicker isn't a problem, but i don't know how to draw a red line (normal) + the highlight (outline) around it which is white



  • @user4592357
    if you can draw it one way you can draw it the other way right?
    So what exactly is the problem now then, the events are not working? the events work but your draw functions is not getting called when expect?



  • @kenchan
    as you can see in code, i set a boolean in hover event and in paint() check - if it's hovered draw outline, but it doesn't. i also tried to call update() inside hoverEnterEvent - still no luck



  • @user4592357
    What don't you understand about how to draw it?
    If you want an outline around a line the easiest way is to draw a thin line on top of a thicker line using the same two points.
    You draw the thick one first then you draw the thin one on top. If you want to make absolutely sure that the thin one is on top you can set the z value of it to be above the thick one.



  • @user4592357
    And does you hovered flag actually get set?



  • @user4592357
    Are you adding the pen thickness to your bounding rect?
    Also, I think you must call prepareGeometyChange() if you change the shape or size of the bounding box etc.
    You usually call this in a setting function that results in a change in shape. In your case you probably want to do it in your event function.



  • @kenchan
    z value of what? QLine?



  • @user4592357
    Ah, sorry that is a property of your graphics item :-) no, I think just the draw order should work for the painter.



  • @user4592357
    So, did you have any luck with it yet?



  • @kenchan
    no this is what i get:

    no hover:
    alt text

    after hover:
    alt text

    code:

        def paint(self, painter, option, widget):
            pen = QPen(Qt.red)
            if self.__hovered:
                # what should go here?
                self.__hovered = False
                pen.setColor(Qt.white)
                pen.setWidth(8)
                painter.setPen(pen)
                painter.drawLine(QPoint(0, 0), QPoint(100, 100))
                painter.drawLine(QPoint(0, 100), QPoint(100, 0))
    
            pen.setWidth(4)
            painter.setPen(pen)
    
            line = QLine(QPoint(0, 0), QPoint(100, 100))
            painter.drawLine(line)
            painter.drawLine(QPoint(0, 100), QPoint(100, 0))
    
        def boundingRect(self):
            return QRectF(0, 0, 108, 108)
    
        def hoverEnterEvent(self, event):
            # self.setGraphicsEffect(QGraphicsColorizeEffect())
            self.__hovered = True
            self.update()
            self.prepareGeometryChange()
    
        def hoverLeaveEvent(self, event):
            self.setGraphicsEffect(None)
    


  • @user4592357
    Hmm so that red thing is your thin line version?



  • @kenchan
    yes



  • @user4592357
    so, it is drawing it but in the wrong place?



  • @user4592357
    you are still not reseting the hover flag in hoverleave so it is always on, right?



  • @kenchan
    i reset it after drawing the white line.
    it is drawing above red line instead of around it



  • @user4592357
    Well I am sorry to say that i just made a test using the same ideas in C++ and it works just fine for me and I don't have to call update :-).
    So are we seeing a Python issue here or is it your paint function ?



  • @kenchan
    can you please share the code? maybe i am missing something?



  • @user4592357
    It is in an app for testing my QGraphicsItems but I can share the code for the subclass.
    As a matter of interest what settings do you use for the QGraphicsScene and View? maybe the drawing is being influenced by those settings?



  • @user4592357
    Looking at your paint code, I think it is wrong. I think you should separate the line drawing into two if sections.

    One draws both lines when hovering and the other draws just one line when not. That is how my code is. I tried it your way and I only saw the the thick lines.



  • @user4592357

    Here is my code, i hope it helps.

    EDIT: added the code for the shape() member function;
    Using a path stroker with the same line thickness as the thin line.
    Also, made the highlight line 50% transparent.

    The header

    class TestItem : public QGraphicsItem
    {
    public:
        bool hovering;
    public:
        explicit TestItem(QGraphicsItem * parent = 0);
    
        void setHovering(bool flag);
        QRectF boundingRect() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
       QPainterPath shape() const;
    
        virtual void hoverEnterEvent(QGraphicsSceneHoverEvent * event);
        virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent * event);
    
    };
    

    The code

    TestItem::TestItem(QGraphicsItem * parent) : QGraphicsItem(parent)
    {
        setAcceptHoverEvents(true);
        hovering = false;
    }
    
    void TestItem::setHovering(bool flag)
    {
        hovering = flag;
    
    }
    
    QRectF TestItem::boundingRect() const
    {
        if(hovering)
            return QRectF(-4, -4, 106, 106);
        else
            return QRectF(-2,-2, 102, 102);
    }
    
    void TestItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        QPen pen1(QBrush(QColor(0,255,0,128)),8,Qt::SolidLine,Qt::RoundCap);
        QPen pen2(QBrush(QColor(255,0,0)),2,Qt::SolidLine,Qt::RoundCap);
    
        if(hovering)
        {
            painter->setPen(pen1);
            painter->drawLine(0.0,0.0,100.0,100.0);
            painter->drawLine(0.0,100.0,100.0,00.0);
    
            painter->setPen(pen2);
            painter->drawLine(0.0,0.0,100.0,100.0);
            painter->drawLine(0.0,100.0,100.0,00.0);
        }
        else
        {
            painter->setPen(pen2);
            painter->drawLine(0.0,0.0,100.0,100.0);
            painter->drawLine(0.0,100.0,100.0,00.0);
        }
    }
    
    QPainterPath TestItem::shape() const
    {
        QPainterPath path;
        path.moveTo(0,0);
        path.lineTo(100,100);
        path.moveTo(0,100);
        path.lineTo(100,0);
        QPen pen(QBrush(QColor(255,0,0)),2,Qt::SolidLine,Qt::RoundCap);
        QPainterPathStroker stroker(pen);
        return stroker.createStroke(path);
    }
    
    void TestItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
    {
        prepareGeometryChange();
        setHovering(true);
    }
    
    void TestItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
    {
        prepareGeometryChange();
        setHovering(false);
    }
    


  • @user4592357
    I edited the code a bit to make it thicker gave it round caps and made the bounding rect a tad bigger. It was leaving crud behind after it was removed :-)
    looks nicer now.



  • @kenchan
    thnaks.

    i was missing the call to prepareGeometryChange() in hoverLeaveEvent

    by the way i noticed that when i hover over the bottom (in picture) item, it "goes up". when i hover over the top item, it "goes down".

    this happens only after first time hovering. after that they don't move.

    what is the reason?



  • @user4592357
    that is probably the scene or the view adjusting the scaling for the added items. That is why I asked about the settings you are applying to those before doing the drawing.



  • @user4592357
    Please mark the thread as solved if that is the case.



  • @kenchan
    the only setting i have is applied to the view:

    setRenderHint(QPainter.Antialiasing)
    setRenderHint(QPainter.TextAntialiasing)
    

    removing them doesn't solve the problem, however.



  • @user4592357
    No they wouldn't, i was thinking more in terms of...

    setViewportUpdateMode()
    setResizeAnchor()
    setDragMode()
    setHorizontalScrollBarPolicy()
    setSceneRect()
    

    and the like on the QGraphicsView. These things can affect how the scene behaves. It is a good idea to check how they affect yours.



  • @kenchan
    setSceneRect() seems to do the job. but how do i tell it to use all the available area as scene rect? because i might have dockable widgets enabled in which case i need the scene rect to be smaller (i hope i could explain it)



  • @user4592357
    Yes well that is the tricky bit. the sceneRect is the part of the scene you want to fit into the viewport rectangle.
    make it bigger in scene space and it looks smaller in the viewport and so on. You can make it scale automatically to the items in the scene or do it manually.



  • @kenchan
    this did the trick ():

    view->setSceneRect(scene()->sceneRect())
    

    and my last question. as you could see in the above pictures, the crosses' bounding rects may overlap. in that case, when, for example cursor is in bottom cross's bounding rect, and i try to touch the lower right part of above cross, the highlight for the above cross isn't displayed.

    is this issue solvable?



  • @user4592357
    Ok, now things might get a bit more complex. The selection is currently using the bounding box so when they overlap you need to do more work if you want to distinguish exactly which cross was hit. In this case the zValue of the items is making the one you drew last get the hit first? You can set the zValue when this is important for picking but I don't think it helps in this case :-).
    Please read the documentation about the QGraphicsItem (the c++ docs I guess). If you implement the shape() method you can use a QPainterPath to return a more accurate shape, in your case lines with a thickness. You need to make it look at the thin version of the line because that is what the user will be seeing and trying to pick. You can use contains() and collidesWithPath() etc. to get a more precise picking experience :-). You will probably want to use these different testing functions in the event before you decide that the cursor is actually hovering over your shape. This kind of testing can get slow for complex shapes and when you have many shapes to test, but the bounding box test filters out things pretty quickly.
    In the last resort you can implement you own geometric testing algorithms but you probably don't need to go that far, do you?

    I hope this all helps :-)



  • @kenchan
    something like this?

        def shape():
            path = QPainterPath(QPointF(x(), y()))
            path.setFillRule(Qt.WindingFill)
            update()
            path.lineTo(QPointF(100, 100))
            return path
    

    this doesn't work though. i don't know how to return shape for two lines.

    EDIT: what about using two QGraphicsLineItems? how would i return the shape() of the total then?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.