Problem in Restoring Floating Toolbar for Qmainwindow

Problem in restoring floating toolbar for QMainWindow

OK, a workaround is to reset the window flags on the toolbar when it is first shown and is floating. I tracked this down by seeing what happens once a toolbar is dropped after being dragged (but not plugged into main window). (It calls setWindowState() and all that does in this situation is hide the toolbar, call updateWindowFlags(), and show it again).

This could be handled from the QMainWindow::showEvent() or from the eventFilter installed onto the QToolBar. I think it's simpler from the former.

UPDATE: This problem actually happens whenever the toolbar is first shown even if not at app startup, e.g. from the toggle view menu by the user once app starts. I updated the code below to fix that issue as well. And see notes below about another issue with minimizing the main window.

I added this to the MainWindow class from the MCVE:

  protected:
void showEvent(QShowEvent *e) override {
QMainWindow::showEvent(e);
#ifdef Q_OS_LINUX
if (lToolbar->isFloating()
// remove the next condition and the toolsbar will get hidden the 2nd time main window is minimized.
&& lToolbar->windowFlags().testFlag(Qt::X11BypassWindowManagerHint)
) {
const bool vis = !lToolbar->isHidden();
qDebug() << lToolbar->isFloating() << vis << lToolbar->windowFlags();
lToolbar->hide();
lToolbar->setWindowFlag(Qt::X11BypassWindowManagerHint, false);
if (vis)
lToolbar->show();
#endif
}

QToolBar* lToolbar; // Use this in MainWindow constructor to save the instance pointer.

I also noticed another issue with the initially-floating toolbar. When the main window is minimized, the toolbar doesn't get hidden but stays where it was on the screen. Regardless of what is in the toolbar (eg. no combo box, just QActions). This workaround could also sort-of address that issue (see code comment), but only the 2nd time the window is minimized. Needs a better workaround for the first minimize.

Can others confirm this? Potentially a larger issue than the editable combo and I'd be surprised if no one noticed before.

I guess this should be filed as a Qt bug either way.

UPDATE2: This version also fixes the minimize issue. I guess something happens after the QMainWindow::showEvent() that changes how the toolbar behaves. Which explains why the above workaround works only after the 1st minimize. So scheduling the toolbar "fix" for later works around that also.

class MainWindow : public QMainWindow
{
...
#ifdef Q_OS_LINUX
protected:
void showEvent(QShowEvent *e) override
{
QMainWindow::showEvent(e);
if (lToolbar->isFloating() && lToolbar->windowFlags().testFlag(Qt::X11BypassWindowManagerHint) ) {
// QMainWindow::show() after QMainWindow::restoreState() will break the minimizing again so we should delay calling adjustToolbar().
QMetaObject::invokeMethod(this, "adjustToolbar", Qt::QueuedConnection);
// If we're sure restoreState() is only called after show() then adjustToolbar() could be called here directly instead.
//adjustToolbar();
}
}

private slots:
void adjustToolbar() const
{
const bool vis = !lToolbar->isHidden();
qDebug() << lToolbar->isFloating() << vis << lToolbar->windowFlags();
lToolbar->hide();
lToolbar->setWindowFlag(Qt::X11BypassWindowManagerHint, false);
if (vis)
lToolbar->show();
}
#endif

private:
QToolBar* lToolbar;
};

ADDED: A QToolBar subclass which applies the workaround on its own, nothing special needed in the QMainWindow. The minimize fix still only works when the adjustToolbar() function is queued or if restoreState() is only called after show() (see code comments).

class ToolBar : public QToolBar
{
Q_OBJECT
public:
using QToolBar::QToolBar;

#ifdef Q_OS_LINUX
protected:
void showEvent(QShowEvent *e) override
{
QToolBar::showEvent(e);
if (isFloating() && windowFlags().testFlag(Qt::X11BypassWindowManagerHint) ) {
// QMainWindow::show() after QMainWindow::restoreState() will break the minimizing again so we should delay calling adjustToolbar().
QMetaObject::invokeMethod(this, "adjustToolbar", Qt::QueuedConnection);
// If we're sure restoreState() is only called after show() then adjustToolbar() could be called here directly instead.
//adjustToolbar();
}
}

private slots:
void adjustToolbar()
{
const bool vis = !isHidden();
hide();
setWindowFlag(Qt::X11BypassWindowManagerHint, false);
if (vis)
show();
}
#endif
};

UPDATE3: The minimizing issue also exists with floating QDockWidget if the QMainWindow state is restored before it is shown. In fact with "older" Qt versions the floating widget doesn't show up at all (doesn't with <= 5.9.5 but does with >= 5.12.4, don't have anything in between to try ATM). So the proper approach is to show() the main window first and then restoreState(). Unfortunately this doesn't seem to work for QToolBar.

UPDATE4: Filed as QTBUG-78293

QToolbar in QMainWindow - signal toolbar about its position change

I found that the QToolBar::topLevelChanged(bool) signal helps me solve the problem. Whenever the user wants to change the toolbar area of a toolbar he/she has to click on the toolbar, drag it to that area and then drop it. Based on the Qt documentation:

This signal is emitted when the floating property changes. The
topLevel parameter is true if the toolbar is now floating; otherwise
it is false.

So whenever the user is dragging the toolbar it is obviously also floating. Once the toolbar is docked it is not floating. Detecting this change in the floating property of the toolbar can be used to adjust its contents.

Here is the result if anyone's interested:

Top placement (indicator pointing downwards)

Sample Image

Bottom placement (indicator pointing upwards)

Sample Image

Left placement (indicator pointing to the right)

Sample Image

Right placement (indicator pointing to the left)

Sample Image

PyQt 4: Get Position of Toolbar

The QMainWindow class already has APIs for this: i.e. saveState and restoreState. These can be used to save and restore the state of all the toolbars and dock-widgets in your application.

To use them, you first need to make sure that all your toolbars and dock-widgets are given a unique object-name when they are created:

class Example(QtGui.QMainWindow):
...
def initUI(self):
...
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setObjectName('foobar')

Then you can override closeEvent to save the state:

    class Example(QtGui.QMainWindow):
...
def closeEvent(self, event):
with open('/tmp/test.conf', 'wb') as stream:
stream.write(self.saveState().data())

(NB: I've just used a temporary file here for testing, but it would obviously be much better to use something like QSettings in your real application).

Finally, you can restore the state that was saved previously:

class Example(QtGui.QMainWindow):    
def __init__(self, parent=None):
...
self.initUI()

try:
with open('/tmp/test.conf', 'rb') as stream:
self.restoreState(QtCore.QByteArray(stream.read()))
except IOError:
pass

Can you add a toolbar to QDialog?

If you don't need the built-in drag and drop feature of QMainWindow's toolbars, you can simply add a QToolBar to any layout, including QDialog's layout(). See the DigviJay Patil's answer below for details, which is definitely cleaner conceptually.

Otherwise, please read on.


  1. It is not directly possible to add a QToolBar to a QDialog in the QMainWindow::addToolBar() sense, because QDialog inherits only QWidget and not QMainWindow, as you noted (hence do not have the method addToolBar())

  2. You can't make a QMainWindow modal, but you can insert a QMainWindow in a QDialog this way:

Code:

MyDialog::MyDialog() :
QDialog()
{
QMainWindow * mainWindow = new QMainWindow(); // or your own class
// inheriting QMainWindow

QToolBar * myToolBar = new QToolBar();
mainWindow->addToolBar(myToolBar);

QHBoxLayout * layout = new QHBoxLayout();
layout->addWidget(mainWindow);
setLayout(layout);
}

Indeed, a QMainWindow doesn't necessarily have to be a top-level widget, and you can even insert several QMainWindows as children of a single widget (may not be the wisest choice though, as the user would probably be confused with the separate sets of menu bars, toolbars, dock widgets, etc.).

QDockWidget does not resize properly when interacting with GUI commands of QMainWindow

@Emanuele, The post you saw it is mainly for sub-classing the QDockWidget as a child and therefore that solution had to be implemented manually. I think that if you look at this alternative solution you will find it useful.

Try to modify on your constructor adding resizeDocks({dock}, {100}, Qt::Horizontal); as in the post so to have:

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mNewText = new QPlainTextEdit;
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
mDockWidget_A->installEventFilter(new QDockResizeEventFilter(mNewText,dynamic_cast<QFluidGridLayout*>(mNewText->layout())));
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);

resizeDocks({mDockWidget_A}, {100}, Qt::Horizontal);

}


Related Topics



Leave a reply



Submit