I have an application where fixed-size child widgets need to be added programatically to a dock widget at run time based on user input. I want to add these widgets to a dock
The problem is, nothing in the code above actually causes the QMainWindowLayout to recalculate itself. That function is buried within the QMainWindowLayout private class, but can be stimulated by adding and removing a dummy QDockWidget, which causes the layout to invalidate and recalcualte the dock widget positions
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
The only problem with this is that if you dig into the QT source code, you'll see that adding a dock widget causes the dock separator to be released, which causes unintuitive and choppy behavior as the user tries to resize the dock, and the mouse unexpectedly 'lets go'.
void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
QDockWidget *dockwidget,
Qt::Orientation orientation)
{
addChildWidget(dockwidget);
// If we are currently moving a separator, then we need to abort the move, since each
// time we move the mouse layoutState is replaced by savedState modified by the move.
if (!movingSeparator.isEmpty())
endSeparatorMove(movingSeparatorPos);
layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation);
emit dockwidget->dockLocationChanged(area);
invalidate();
}
That can be corrected by moving the cursor back onto the separator and simulating a mouse press, basically undoing the endSeparatorMove
callafter the docks have been repositioned. It's important to post the event, rather than send it, so thatit occurs after the resize event. The code for doing so looks like:
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent =
new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
Where 2 is a magic number that accounts for the group box border.
Put that all together, and here is the event filter than gives the desired behavior:
#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include <QCoreApplication>
#include <QMouseEvent>
#include "QFluidGridLayout.h"
class QDockResizeEventFilter : public QObject
{
public:
friend QMainWindow;
friend QLayoutPrivate;
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = 0)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height());
if (dock->size().width() != fixedSize.width())
{
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
// cause mainWindow dock layout recalculation
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
// adding dock widgets causes the separator move event to end
// restart it by synthesizing a mouse press event
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
// ...
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h__