PDA

View Full Version : How to do auto show/hide a widget in Qt



rawfool
26th May 2013, 15:29
I'm trying to create a widget that has auto show/hide property and the widget has 3 buttons in QVBoxLayout. I need to show the widget the same way Ubuntu unity's menubar is displayed. I need to create that kinda animation. When the cursor is moved to a specified area of the QMainWindow, the widget has to be displayed with some transition time. And when the cursor moves out of the specified area, the widget should be hidden.
Can someone kindly help me out to get started.

Should I use QPropertyAnimation or something related to Qt Graphics ?

Thank you.

wysota
27th May 2013, 06:51
Yes, you can use QPropertyAnimation.

rawfool
27th May 2013, 06:55
I tried QGraphicsView & QGraphicsSCene, but I not working as I expected. I'm calling the widget to show after 3 seconds using a singleshot timer. But I see the widget is flashing and disappearing.
Below is the way how I implemented see how it comes out.


#include "ccentralwidget.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QTimer>

CMainWidget::CMainWidget(QWidget *parent) :
QWidget(parent)
{
this->setStyleSheet("background-color: lightGray;");
this->setFixedSize(650, 450);
m_widget = new CMenuWidget();

QTimer::singleShot(3000, this, SLOT(showMenuWidget()));
}

CMainWidget::~CMainWidget()
{
delete m_widget;
}

void CMainWidget::showMenuWidget()
{
QGraphicsScene scene;
scene.addWidget(m_widget);
scene.setSceneRect(0, 30, 60, 250);

QGraphicsView view(&scene);
view.show();

}

---------------------------------------------------------------------------------------------

And I tried it using QPropertyAnimation also, but it's giving some error/warning messages output.
I'm calling the below slot in singleshot timer the same way as shown in the above code.

void CCentralWidget::showMenuWidget()
{
QPropertyAnimation *animation = new QPropertyAnimation(m_widget, "MyMenu");
animation->setDuration(2000);
animation->setStartValue(QRect(0, 0, 100, 30));
animation->setEndValue(QRect(250, 250, 100, 30));

animation->start();
}


//Output Message:
QPropertyAnimation: you're trying to animate a non-existing property MyMenu of your QObject

I need to show/hide the widget the same way how the left app menu widget is shown/hidden on mouse move to the left area in Ubuntu unity and this kind of graphics can be seen a text editor called Scribes.
So for now I'm trying to achieve the same transition on singleshot timer, later I call it in movemove event.
Kindly help me. Thank you.

wysota
27th May 2013, 08:52
You are creating the view on the stack so it gets destroyed when the stack unwinds when the flow returns from the function. By the way I don't see the point of using a graphics scene (and view) only to add a widget to it. You can use the widget directly.

Santosh Reddy
27th May 2013, 10:48
Here is one example, hope it helps


#include <QtGui>
#include <QtWidgets>
#include <QApplication>

class Widget : public QWidget
{
public:
explicit Widget(QWidget * parent = 0)
: QWidget(parent)
, mAnimation(new QPropertyAnimation(this, "geometry"))
{
parent->setMouseTracking(true);
parent->installEventFilter(this);
mAnimation->setDuration(250);
}

protected:
bool eventFilter(QObject * object, QEvent * event)
{
if((parent() == object) and (event->type() == QEvent::MouseMove))
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
if(mouseEvent->pos().x() < 10)
{
if(isHidden() and (mAnimation->state() != mAnimation->Running))
{
mAnimation->setStartValue(QRect(0, 0, 0, sizeHint().height()));
mAnimation->setEndValue(QRect(0, 0, sizeHint().width(), sizeHint().height()));
disconnect(mAnimation, SIGNAL(finished()), this, SLOT(hide()));
mAnimation->start();
show();
}
}
else if(mAnimation->state() != mAnimation->Running)
{
if(!isHidden())
{
mAnimation->setEndValue(QRect(0, 0, 0, sizeHint().height()));
mAnimation->setStartValue(QRect(0, 0, sizeHint().width(), sizeHint().height()));
connect(mAnimation, SIGNAL(finished()), this, SLOT(hide()));
mAnimation->start();
}
}
}
return QWidget::eventFilter(object, event);
}

private:
QPropertyAnimation * mAnimation;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QLabel label("Main Widget");
label.showMaximized();

Widget widget(&label);
QVBoxLayout * layout = new QVBoxLayout(&widget);

for(int i = 0 ; i < 10; i++)
layout->addWidget(new QPushButton(QString("PushButton %1").arg(100 + i)));

return app.exec();
}

rawfool
27th May 2013, 14:03
Thank you very much Santosh Reddy. Your code was very helpful in understanding QAnimationProperty. After understanding your code, I implemented it, but a bit different in approach.
Instead of adding event filter in menu_widget(child widget), I did it in Parent widget. And everything worked fine.
The transition is sharp and final thing now I'm looking for is some smooth transition. And if I give more value in setDuration(), it's affecting in hiding, but not in showing the widget. However for the setDuration(), even after the specified duration, the transition is sharp, whereas smooth would be more eye-pleasing.
I tried using mAnimation->setEasingCurve(QEasingCurve::InOutElastic) and other QEasingCurve properties, but couldn't achieve the smoothness in transition.

But again, thank you for your kind help.

My Code (Implemented similarly based on your's in the previous post):

// This is mainWidget, where in I'll show the menu
#include "cmainwidget.h"

CMainWidget::CMainWidget(QWidget *parent)
: QWidget(parent)
{
this->setMouseTracking(true);
this->setFixedSize(650, 400);
menuWidget = new CMenuWidget(this);
menuWidget->hide();
mAnimation = new QPropertyAnimation(menuWidget, "geometry");
mAnimation->setDuration(250); // Increasing this isn't helping in smooth transition, instead it's affecting hide when the mouse is > 80 in "else if" in mouseMoveEvent
mAnimation->setEasingCurve(QEasingCurve::InOutElastic);
}

CMainWidget::~CMainWidget()
{
delete mAnimation;
delete menuWidget;
}

void CMainWidget::mouseMoveEvent(QMouseEvent *mmEvent)
{
if(mmEvent->pos().x() <= 80)
{
qDebug("Mouse X-Position - %d", mmEvent->pos().x());
mAnimation->setStartValue(QRect(0, 40, 0, 40));
mAnimation->setEndValue(QRect(0, 40, menuWidget->sizeHint().width(), 40));
disconnect(mAnimation, SIGNAL(finished()), this, SLOT(hideMenuWid()));
mAnimation->start();
menuWidget->show();
}
else if(mmEvent->pos().x() > 80)
{
qDebug("Mouse X-Position - %d", mmEvent->pos().x());
mAnimation->setEndValue(QRect(0, 40, 0, 40));
mAnimation->setStartValue(QRect(0, 40, menuWidget->sizeHint().width(), 40));
connect(mAnimation, SIGNAL(finished()), this, SLOT(hideMenuWid()));
mAnimation->start();
}
}

void CMainWidget::hideMenuWid()
{
menuWidget->hide();
}

Santosh Reddy
27th May 2013, 14:18
Try using QEasingCurve::InOutQuint, and you can use different setDuration() while showing and while hiding(), this will ensure a slow smotth show, and quick hide.

rawfool
29th May 2013, 12:07
Now a small hiccup, after adding this auto-hide menu, if there is any widget that's overlapping the menu widget's region, then it's not responding and also the menu widget is shown under it.
So if there is any other widget, and when the menu widget is shown, it's moving under visible widget.
Is there anything like bring-to-front, etc, for bringing the menu widget to front. I tried setFocus(); but didn't solve the issue.

How do I bring the menu widget to top-most visible area and mouse hover on the other widgets in that page should be relative ?

I tried, but didn't work as expected:

menuWidget->setWindowFlags(Qt::WindowStaysOnTopHint); // menu widget is child of mainwidget(central Widget of main window) and is not in layout
menuWidget->raise();
frmWidget->setWindowFlags(Qt::WindowStaysOnBottomHint);

EDIT: I'm able to bring it on top by adding the menubar at last. But the mouse move on the bottom widget isn't giving response to this to hide the menu widget, when out of the specified region.

Thank you.

Santosh Reddy
29th May 2013, 17:17
EDIT: I'm able to bring it on top by adding the menubar at last. But the mouse move on the bottom widget isn't giving response to this to hide the menu widget, when out of the specified region.
Then hide() the menu widget when the menu widget reveices QEevnt::Leave event, which indicated that mouse has left the menu widget

rawfool
30th May 2013, 09:24
This is how I did that, but now the menu widget is not hiding,


bool eventFilter(QObject * object, QEvent * event)
{
if((parent() == object) & (event->type() == QEvent::MouseMove))
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
if(mouseEvent->pos().x() < 80)
{
if(isHidden() && (mAnimation->state() != mAnimation->Running))
{
mAnimation->setStartValue(QRect(-3, 80, 0, 250));
mAnimation->setEndValue(QRect(0, 80, 70, 250));
disconnect(mAnimation, SIGNAL(finished()), this, SLOT(hide()));
mAnimation->start();
show();
}
}
else if((mouseEvent->type() == QEvent::Leave) && (mAnimation->state() != mAnimation->Running))
{
qDebug("Inside QEvent::Leave"); // This message is not getting printed
if(!isHidden())
{
mAnimation->setEndValue(QRect(-3, 80, 0, 250));
mAnimation->setStartValue(QRect(0, 80, 70, 250));
connect(mAnimation, SIGNAL(finished()), this, SLOT(hide()));
mAnimation->start();
}
}
}
return QWidget::eventFilter(object, event);
}
I think my else if is wrong. Kindly help me correct this. Thank you

Santosh Reddy
30th May 2013, 10:08
QEvent::MouseMove event is received for parent widget, and QEvent::Leave should be received for self widget, so move the else if out of if(parent() ==...., and also make suere to install the event filter on self. (this->installEventFilter(this);

rawfool
30th May 2013, 10:29
Thanks a lot. All the issues related to this are solved for now. :)

rawfool
3rd June 2013, 06:35
Just a small note, as this may help someone who do this. After doing this -
QEvent::Leave should be received for self widget .. it hides the widget.
A small problem persisted and it was - if there's a layout and widgets in the mouse-hover area, then the trigger didn't reach the menu widget. I solved it by making the layout and widgets as children to the same parent to which menu widget was and enabled mouse tracking true for the widget(s) on the way and this solved my problem.
Thank you.