How do I properly implement a "minimize to tray" function in Qt?
I tried the following code inside QMainWindow::changeEvent(QEvent *e), but the window simply minimizes to the taskbar and the client area appears blank white when restored.
if (Preferences::instance().minimizeToTray())
{
e->ignore();
this->setVisible(false);
}
Attempting to ignore the event doesn't seem to do anything, either.
Apparently a small delay is needed to process other events (perhaps someone will post the exact details?). Here's what I ended up doing, which works perfectly:
void MainWindow::changeEvent(QEvent* e)
{
switch (e->type())
{
case QEvent::LanguageChange:
this->ui->retranslateUi(this);
break;
case QEvent::WindowStateChange:
{
if (this->windowState() & Qt::WindowMinimized)
{
if (Preferences::instance().minimizeToTray())
{
QTimer::singleShot(250, this, SLOT(hide()));
}
}
break;
}
default:
break;
}
QMainWindow::changeEvent(e);
}
In addition to what Jake Petroules said, it appears that simply doing:
QTimer::singleShot(0, this, SLOT(hide()));
is enough. From http://qt-project.org/doc/qt-4.8/qtimer.html#details :
As a special case, a QTimer with a timeout of 0 will time out as soon as all the events in the window system's event queue have been processed.
This way you don't have the problem of selecting an appropriate delay value...
void main_window::create_tray_icon()
{
m_tray_icon = new QSystemTrayIcon(QIcon(":/icon.png"), this);
connect( m_tray_icon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(on_show_hide(QSystemTrayIcon::ActivationReason)) );
QAction *quit_action = new QAction( "Exit", m_tray_icon );
connect( quit_action, SIGNAL(triggered()), this, SLOT(on_exit()) );
QAction *hide_action = new QAction( "Show/Hide", m_tray_icon );
connect( hide_action, SIGNAL(triggered()), this, SLOT(on_show_hide()) );
QMenu *tray_icon_menu = new QMenu;
tray_icon_menu->addAction( hide_action );
tray_icon_menu->addAction( quit_action );
m_tray_icon->setContextMenu( tray_icon_menu );
m_tray_icon->show();
}
void main_window::on_show_hide( QSystemTrayIcon::ActivationReason reason )
{
if( reason )
{
if( reason != QSystemTrayIcon::DoubleClick )
return;
}
if( isVisible() )
{
hide();
}
else
{
show();
raise();
setFocus();
}
}
That's how I realize a "minimize to tray". You can now minimize either by double clicking on the icon, or by right-clicking and selecting "Show/Hide" in the menu.
I have found that the showMinimized() slot works without a QTimer delay, so you can use code like:
mw->show();
if ( qApp->arguments().contains( "--startHidden" ) )
mw->showMinimized();
in your main() to show a main window and immediately iconize it, when desired.
Related
Trying to show a tool tip on mouse press event and make it popped up for some time. For now it shows only if a mouse button is pressed.
void ::mousePressEvent(QMouseEvent* e)
{
if (!m_isLinkingAvailable)
{
QToolTip::showText(e->screenPos().toPoint(),
tr("Symbol Linking\navailable only for Price"), this);
}
}
According to the Qt Docs, looks like there's an alternate method for this function:
void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
You should be able to specify a time for how long to display the tooltip.
EDIT:
Okay so seems like this method doesn't work as expected with a mousePress event, so here's an alternative using a QTimer:
Add these to your class:
MyConstructor(...params...)
, m_tooltipTimer(new QTimer(this)) // don't forget this line
{
connect(m_tooltipTimer, SIGNAL(timeout()), this, SLOT(updateTooltip()));
setAcceptedMouseButtons(Qt::AllButtons);
}
...
public slots:
void mousePressEvent(QMouseEvent *event) override;
void updateTooltip();
...
private:
QPoint m_tooltipPos;
qint64 m_tooltipTimerStart;
QTimer *m_tooltipTimer;
And then implement these in your .cpp
void ::mousePressEvent(QMouseEvent *event) {
m_tooltipTimer->start(200); // 5x per second, automatically resets timer if already started
m_tooltipTimerStart = QDateTime::currentMSecsSinceEpoch();
m_tooltipPos = event->globalPos();
event->accept();
}
void ::updateTooltip() {
auto howLongShown = QDateTime::currentMSecsSinceEpoch() - m_tooltipTimerStart; // startTime here is the moment of first showing of the tooltip
qDebug() << howLongShown;
if (howLongShown < 1000) { // 1 sec
QToolTip::showText(m_tooltipPos, tr("Test Tooltip")); // Replace this with your own
} else {
QToolTip::hideText();
m_tooltipTimer->stop();
}
}
Thanks to #Ian Burns's answer I have manged to create own approach:
void ::mousePressEvent(QMouseEvent*)
{
QTimer::singleShot(200, [this]()
{
QToolTip::showText(mapToGlobal({}),
tr("Symbol Linking\navailable only for Price"), this);
});
}
Somehow if I show a tooltip inside mousePressEvent method it disappears immediately after I unpress the mouse button. QTimer delays the pop up call and it stays popped for reasonable time.
I am using C++/Qt 5.5 and in my software, I want the Qt::Key_Space to trigger the opening of a menu, and the same button, to trigger the closing of the same menu. The problem is that if I implement it as:
this->button = new QPushButton();
this->button->setShortcut( QKeySequence( Qt::Key_Space ) );
connect(this->button, &QPushButton::clicked, this, &MenuClass::startMenuButtonClicked);
...
this->in_menu_button = new QPushButton();
this->in_menu_button->setShortcut( QKeySequence( Qt::Key_Space ) );
connect(this->in_menu_button, &QPushButton::clicked, this, &InTheMenuClass::startMenuButtonClicked);
If i do it like this, only one way to the menu works. Do you have any ideas on how to solve this issue?
You need to create only one button and assign the shortcut to it. Then depending on the state of the menu (open/close) perform an opposite action in the button's handler.
I solved it using a state machine. I created an enumaration with 2 states and made a static instance of it.
public:
enum STATE {MAIN_WINDOW, MENU};
static STATE state;
Then, i initialized it by default to have the MAIN_WINDOW state.
MyClass::STATE MyClass::state = STATE::MAIN_WINDOW;
Then, I connected both slots of the shortcuts for both buttons to call the same slot which checks and sets the current state.
QShortcut * shortcut = new QShortcut( QKeySequence( Qt::Key_Space ), back_to_main );
connect( shortcut, &QShortcut::activated, this, &MyClass::checkState);
QShortcut * shortcut = new QShortcut( QKeySequence( Qt::Key_Space ), go_to_menu );
connect( shortcut, &QShortcut::activated, this, &MyClass::checkState);
void MyClass::checkState()
{
if (MyClass::state == MyClass::MAIN_WINDOW )
{
MyClass::goToMenu();
MyClass::state = MyClass::STATE::MENU;
}
else if ( MyClass::state == MyClass::MENU )
{
MyClass::goBackToMain();
MyClass::state = MyClass::STATE::MAIN_APP;
}
}
*MyClass can be any class.
Extraneous Information:
I am attempting to build an application using Qt. This application features a QMdiArea and a child-window. My child-window will have a menu which can be integrated into the QMdiArea or segregated and attached to the child itself. Though, this is a bit more detail than needed...
Problem:
I would like my child-widget to have a menu with a shortcut, "CTRL+W." But, because I am using a QMdiArea, the shortcut is already used causing:
QAction::eventFilter: Ambiguous shortcut overload: Ctrl+W
How can I get rid of this shortcut and claim it within my child widget instead?
Update:
Here is what I've tried with no luck:
class MDI : public QMdiArea
{
Q_OBJECT
private:
bool event(QEvent *tEvent)
{
if (tEvent->type() == QEvent::KeyPress)
{
QKeyEvent* ke = static_cast<QKeyEvent*>(tEvent);
if (ke->key()== Qt::Key_W && ke->modifiers() & Qt::ControlModifier)
emit KeyCW();
return true;
}
return QMdiArea::event(tEvent);
}
public:
signals:
void KeyCW();
};
This works if I do something as simple as change Qt::Key_W to Qt::Key_L. The key-combo is received and event is thrown. With W, it just never happens. I've also tried moving event to QMainWindow as well as an eventFilter in the subwindow to QMdiArea. It seems that it is a little overly complicated to do something as simple as remove default key-handlers from within QMdiArea.
You can disable this shortcut like this:
for( QAction *action : subWindow->systemMenu()->actions() ) {
if( action->shortcut() == QKeySequence( QKeySequence::Close ) ) {
action->setShortcut( QKeySequence() );
break;
}
}
You could get rid of the pre-defined close action of the QMdiSubWindow altogether by using Qt::CustomizeWindowHint as additional flag when adding the subwindow.
QMdiSubWindow *subWindow2 = mdiArea.addSubWindow(internalWidget2,
Qt::Widget | Qt::CustomizeWindowHint |
Qt::WindowMinMaxButtonsHint);
Subclass QMdiArea and reimplement keyPressEvent(). That should work.
void keyPressEvent(QKeyEvent* event){
if(event->key() == Qt::Key_W and event->modifiers() & Qt::ControlModifier){
// handle it
}else{
return QMdiArea::keyPressEvent(event);
}
}
You could also use event filters. I don't enough about your class hierarchy, but I hope you get the idea.
bool CustomMdiArea::eventFilter(QObject *object, QEvent *event){
if(object == yourChildWindow && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_W and keyEvent->modifiers() & Qt::ControlModifier) {
//handle it
return true;
}else{
return false;
}
}
return false;
}
From what I can tell, what I am looking for is not possible short of writing my own MDIArea.
The shortcut is set in QMdiSubWindowPrivate::createSystemMenu() during
the construction of a QMdiSubWindow, I doubt that you can remove it
without having to patch Qt libs.
Hopefully at some point someone will disprove this or QT will make changes. Meanwhile, it looks like we will all need to stay away from these pre-assigned shortcuts.
I was able to work around this by setting the shortcut context for my close action. By setting it to Qt::WidgetShortcut, I no longer get the ambiguous shortcut overload. Here is how I'm setting up my close action now:
closeAction = new QAction(tr("&Close"), this);
closeAction->setShortcut(Qt::CTRL|Qt::Key_W);
closeAction->setShortcutContext(Qt::WidgetShortcut);
connect(closeAction, SIGNAL(triggered()), mdiArea, SLOT(closeActiveSubWindow()));
I open QDialog window from QMainWindow. Now when I press the QDialog window
its not always closing in the first press - I need to press few times (3-4) to close it .
I have closeEvent slot that has simple event->accept(); inside it.
This is how I call the QDialog from the main window:
void MyManager::DialogContainerOpen(type t)
{
if(pMyDialogContainer == NULL)
{
pMyDialogContainer = new MyDialogContainer();
}
int returnVal = QDialog::Rejected;
if(!m_bContainer)
{
m_bContainer = true;
int returnVal = pMyDialogContainer->exec();
if(returnVal != QDialog::Accepted ) {
m_bContainer = false;
}
}
}
This is the first problem.
The second problem is how do i set the QDialog windows NOT to be allays on top? (I don’t want it to block the parent window.
UPDATE
well i found out that the function from the MainWindow that showing the contexMenu
and inside it has the connect single/slot is keeps to invoke so i just used the disconnect
i dont know if its the best sulotion but its working.
now i juat have the final problem .
here is the code i hope its ok
void MainWindowContainer::ShowContextMenu(const QPoint& pos) // this is a slot
{
QModelIndex modelIndx;
QPoint globalPos = ui.treeView_mainwindow->mapToGlobal(pos);
bool b1 = connect(OpenAction, SIGNAL(triggered()),m_SignalMapper, SLOT(map()) );
m_SignalMapper->setMapping(OpenAction,voidID);
bool b2 = connect(m_SignalMapper, SIGNAL(mapped(QString)), this, SLOT(OpenWin(QString)));
QAction* selectedItem = ContextMenu.exec(globalPos);
}
void MainWindowContainer::OpenWin(QString gid)
{
//disconnect(sender0, SIGNAL(overflow()),receiver1, SLOT(handleMathError()));
disconnect(m_SignalMapper, SIGNAL(mapped(QString)),this, SLOT(OpenWin(QString)));
disconnect(OpenAction,SIGNAL(triggered()),m_SignalMapper, SLOT(map()));
....
....
}
For your second question, the term you are looking for is modal vs modeless dialogs. The QDialog documentation tells exactly how you create non-modal dialogs:
Modeless dialogs are displayed using show(), which returns control to the caller immediately.
i.e. don't use exec() as that will make a modal dialog (which blocks the parent).
You should not connect the same signal/slot more than once unless you want the action run multiple times. All you need to do is to connect the QAction's signal to the slot once. This is usually done in the constructor (or a dedicated function called from the constructor) where you create the action.
I have a Qt main window that will pop up a dialog box that has an OK and Cancel button. This dialog has a simple spinner that asks a user for a number that should be returned to the main window when OK or Cancel is pressed (for cancel, it will just send back -1).
I thought about using code in a signal in mainWindow.cpp like so:
void mainWindow::slot_openNumberDlg(){
// Create a new dialog
numberDlg dlg( this );
// Show it and wait for Ok or Cancel
if( dlg.exec() == QDialog::Accepted ){
return;
}
}
But how would I return the value of the spinner in the dialog back to main window if it is destroyed when the button is pressed?
I also thought about an alternative solution where I would initialize the dialog in mainWindow's constructor, but it isn't shown until a signal activated a slot in mainWindow.cpp like so:
// Create as pointer in main.h:
numberDlg *dlg;
// Initialize pointer and setup listener in constructor (main.cpp):
mainWindow::mainWindow(){
dlg = new numberDlg();
QObject::connect( dlg, SIGNAL( sig_retVal(int) ), ui->widget, SLOT( slot_showNum(int) ) );
}
// Make a function to handle show/close:
void mainWindow::slot_numberOpenClose(bool trigger){
if(trigger){
dlg->show();
} else {
dlg->close();
}
}
The above code would be able to send the number from the dialog's spinner to mainWindow using a signal/slot, but it won't lock out mainWindow. I do not want the user to continue using mainWindow until the dialog has been accepted or rejected.
Any ideas on how I can lock the main window and send a number back to main on dialog close?
What you should do is create a function in your dialog class that returns the value you want, and use it like so from your main window:
void mainWindow::slot_openNumberDlg(){
// Create a new dialog
numberDlg dlg( this );
// Show it and wait for Ok or Cancel
if( dlg.exec() == QDialog::Accepted ){
m_theValue = dlg.myValue(); // <--
return;
}
} // dlg goes out of scope
dlg won't be destroyed until it goes out of scope where I placed the comment, so your method will work fine.
use dlg.setWindowModality(Qt::WindowModal); :
void mainWindow::slot_openNumberDlg(){
numberDlg dlg( this );
dlg.setWindowModality(Qt::WindowModal);//blocks input to its parent window
if( dlg.exec() == QDialog::Accepted )
{
dlg.setResult(true);//mask
return;
}
else if(dlg.exec() == QDialog::Rejected)
{
dlg.setResult(false);//mask
return;
}
}
if(dlg.getResult() == true)//value usable only when accepted
{
m_theValue = dlg.myValue();
}
If you want more accurate result, override the closeEvent() method of QWidget to handle the exit button of your dialog. (or you could set default value as false)
void numberDlg::closeEvent(QCloseEvent *event)
{
setResult(false);
close();
}