QPushButton geometry issue



  • I'm creating my own click effect, rather than the default emphasized by the border. It simply fades into a white rect over top of the QPushButton. You can see it in the upper-left in this image:
    http://i.imgur.com/buqYXyn.png

    As you can tell, it doesn't fully cover the button as I was hoping it would and I'm not sure why.

    EDIT: Here is the working code folks. The gentleman in the comments was less than helpful in providing a meaningful solution, but getting a grips with the workings of Qt isn't terribly difficult provided the excellent documentation. Perhaps this will assist others when coming across a similar situation.

    pushbutton.h:

    #ifndef PUSHBUTTON
    #define PUSHBUTTON
    
    #include <QtCore>
    #include <QPushButton>
    #include <QPainter>
    
    class PushButton : public QPushButton
    {
        Q_OBJECT
    
    public:
        explicit PushButton(QString text, QWidget *parent = 0);
    
    protected:
        virtual void paintEvent(QPaintEvent *);
    
    public slots:
        void OnClick();
        void OnTick();
    
    private:
        enum States
        {
            NONE = 0,
            RESET,
            DRAW
        } state = NONE;
    
        QTimer *timer;
    
        float delta = -1.0f;
    };
    
    #endif
    

    pushbutton.cpp:

    #include "pushbutton.h"
    
    PushButton::PushButton(QString text, QWidget *parent) : QPushButton(text, parent)
    {
        timer = new QTimer(this);
    
        QObject::connect(this, SIGNAL(clicked(bool)), this, SLOT(OnClick()));
        QObject::connect(timer, SIGNAL(timeout()), this, SLOT(OnTick()));
    }
    
    void PushButton::paintEvent(QPaintEvent *e)
    {
        QPushButton::paintEvent(e);
    
        if(state == States::NONE)
        {
            return;
        }
    
        QPainter painter(this);
    
        if(state == States::RESET)
        {
            state = States::NONE;
    
            painter.eraseRect(rect());
    
            update();
        }
        else
        {
            painter.fillRect(rect(), QColor(200, 200, 200, 200 * (delta < 1.0f ? delta : 2.0f - delta)));
        }
    }
    
    void PushButton::OnClick()
    {
        state = States::DRAW;
    
        delta = 0.0f;
    
        timer->start(20);
    }
    
    void PushButton::OnTick()
    {
        delta += 0.2f;
    
        if(delta > 2.0f)
        {
            state = States::RESET;
    
            timer->stop();
        }
    
        update();
    }
    
    

    Regards,
    Tannz0rz


  • Moderators

    Hi, welcome to devnet.

    Ok, it's gonna be hard to explain but I'll try...
    There are a couple of fundamental mistakes here:

    ButtonClickEffect inherits from QWidget. This is wrong to start with. It's an effect applied to a widget. It shouldn't be a widget itself.

    Then you cast QWidget* parent to QPushButton with a C cast...ugh, don't do that. If I pass a QLineEdit as a parent this code will crash and burn.

    Then there's a paint event. It's a paint event of the effect widget, not the button widget, so passing button->geometry() to fillRect is wrong, as its a geometry that possibly (and in this case does) span outside the clip rectangle of the widget being painted.

    You're giving your ButtonClickEffect widget a parent, but it doesn't place it in any layout or anything. So it will just have some default size (what you see in the picture). It will not resize to the size of it's parent on its own, and you can't paint outside of the rectangle of that widget (like you try to do).

    This whole approach is not really good. Just install an event filter on the button and hijack its paint event there to do your custom drawing.



  • @Chris-Kawa Thanks. How do you force a paint update in that case? It needs to be updated within the timer.


  • Moderators

    You can call either update() or more directly repaint() on it in response to timer event and then do the painting in the event filter..



  • @Chris-Kawa I've tried the following, but the results are unsavory. It flashes white over top of the button and thereafter the text becomes hidden until the next paintEvent call. The update seems to work, just that it doesn't render as it ought to.

    Header: http://pastebin.com/cW72y2ns
    Source: http://pastebin.com/H7vQgEy6

    Initialization:

    pushButton->installEventFilter(new ButtonClickEffect(pushButton));
    

  • Moderators

    You're discarding the button default paint event so yes, you will not see any text (or a button) with that code.
    What is the result you're trying to get?



  • @Chris-Kawa A quick flash of white overlaid on the button. As in my fillRect, I set the alpha to

    200 * (delta < 1.0f ? delta : 2.0f - delta)
    

    So it scales up to 1 from 0 and down to 0 from 1.


  • Moderators

    Well yes, but you're painting that color with alpha over whatever widget is under the button, since the event filter is called before paint event of the button and you return true from it so the button's paint event is never actually called. To see the button painted underneath you need to actually call the paint event of the button manually before you overpaint it with your color.

    Unrelated comments:

    You seem to have a very bad habit of just casting types with a C cast to whatever you think you need at the moment. I'm referring to the QWidget *widget = (QWidget *)object; cast in the eventFilter. If I used any non-widget QObject derived type with your class it would compile successfully and crash at runtime. There's a isWidgetType() method on a QObject that you can test with before casting. Also please don't use C style casts. It's just plain evil and you will cause bugs with it. Either to yourself or other people that use your classes. There's a lot safer qobject_cast for QObject derived types that will return nullptr that you can test for if the type can't be cast.

    Painting in response to a timer with interval of 10ms makes no sense on almost any of user's machines. The typical display is ticking at 60Hz (i.e. little over 16ms) so anything lower than that is just giving Qt more work in filtering out the update requests you are flooding it with. Displays with a higher refresh rate are still very rare and even on them there's not gonna be a noticeable difference if you stick to 60Hz animation.

    Using == operator on a floating point numbers is on borderline of being a bug. You should practically never do that unless you're actually testing floating number stability, which is like super rare. Use non-strict comparisons (< and >) or just stick to fixed point integers for steps like that.


Log in to reply
 

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