gpt4 book ai didi

c++ - 创建一个根据内容调整大小的 QDockWidget

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:02:12 27 4
gpt4 key购买 nike

我有一个应用程序,需要在运行时根据用户输入以编程方式将固定大小的子窗口小部件添加到停靠窗口小部件。我想将这些小部件添加到 Qt::RightDockArea 上的停靠栏,从上到下直到空间用完,然后创建一个新列并重复(基本上只是流程布局示例的相反 here ,我调用 fluidGridLayout)

我可以让停靠栏小部件使用事件过滤器适本地调整自身大小,但调整后的停靠栏的几何形状不会改变,并且一些小部件绘制在主窗口之外。有趣的是,调整主窗口的大小,或者 float 和取消 float 停靠栏会导致它“弹出”回到正确的位置(但是我还没有找到一种方法来以编程方式复制它)

我不能使用任何内置的 QT 布局,因为在我的真实程序中使用小部件,它们最终也会被绘制出屏幕。

有什么方法可以让扩展坞在调整大小时将其左上角坐标更新到正确的位置?

我认为这可能会引起普遍兴趣,因为在 QT 中获得停靠小部件的直观布局管理行为可能是人类已知的最困难的事情。

视觉示例:

复制此代码的示例如下。

  1. 使用按钮向程序添加 4 个小部件

Step 1

  1. 调整绿色底部停靠栏的大小,直到只显示两个小部件。请注意,剩下的 3 个小部件被绘制在主窗口之外,但是停靠栏的大小合适,您再也看不到关闭按钮就是明证

Step 2

  1. 取消停靠蓝色停靠小部件。请注意它会调整到合适的大小。

Step 3

  1. 将蓝色停靠点重新停靠到右侧停靠区域。请注意,它现在似乎表现正常。

Step 4

  1. 现在将绿色停靠栏调整到最小尺寸。请注意,停靠栏现在位于 GUI 的中间。卧槽,这怎么可能??

Step 5

代码

下面我给出了从屏幕截图中复制 GUI 的代码。

主要.cpp:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

return a.exec();
}

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};

#endif // MAINWINDOW_H

主窗口.cpp

#include "mainwindow.h"
#include "QFluidGridLayout.h"
#include "QDockResizeEventFilter.h"
#include <QDockWidget>
#include <QGroupBox>
#include <QPushButton>
#include <QWidget>
#include <QDial>

class QTestWidget : public QGroupBox
{
public:
QTestWidget() : QGroupBox()
{
setFixedSize(50,50);
setStyleSheet("background-color: red;");

QDial* dial = new QDial;
dial->setFixedSize(40,40);
QLayout* testLayout = new QVBoxLayout;
testLayout->addWidget(dial);
//testLayout->setSizeConstraint(QLayout::SetMaximumSize);

setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setLayout(testLayout);
}

QSize sizeHint()
{
return minimumSize();
}

QDial* dial;
};

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{


QDockWidget* rightDock = new QDockWidget();
QDockWidget* bottomDock = new QDockWidget();

QGroupBox* central = new QGroupBox();
QGroupBox* widgetHolder = new QGroupBox();
QGroupBox* placeHolder = new QGroupBox();

placeHolder->setStyleSheet("background-color: green;");
placeHolder->setMinimumHeight(50);

widgetHolder->setStyleSheet("background-color: blue;");
widgetHolder->setMinimumWidth(50);
widgetHolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
widgetHolder->setLayout(new QFluidGridLayout);
widgetHolder->layout()->addWidget(new QTestWidget);

QPushButton* addWidgetButton = new QPushButton("Add another widget");
connect(addWidgetButton, &QPushButton::pressed, [=]()
{
widgetHolder->layout()->addWidget(new QTestWidget);
});

central->setLayout(new QVBoxLayout());
central->layout()->addWidget(addWidgetButton);
rightDock->setWidget(widgetHolder);
rightDock->installEventFilter(new QDockResizeEventFilter(widgetHolder,dynamic_cast<QFluidGridLayout*>(widgetHolder->layout())));
bottomDock->setWidget(placeHolder);

this->addDockWidget(Qt::RightDockWidgetArea, rightDock);
this->addDockWidget(Qt::BottomDockWidgetArea, bottomDock);

this->setCentralWidget(central);
central->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
this->setMinimumSize(500,500);
}

};

QFluidGirdLayout.h

#ifndef QFluidGridLayout_h__
#define QFluidGridLayout_h__

#include <QLayout>
#include <QGridLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>

class QFluidGridLayout : public QLayout
{
public:

enum Direction { LeftToRight, TopToBottom};

QFluidGridLayout(QWidget *parent = 0)
: QLayout(parent)
{
setContentsMargins(8,8,8,8);
setSizeConstraint(QLayout::SetMinAndMaxSize);
}

~QFluidGridLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}

void addItem(QLayoutItem *item)
{
itemList.append(item);
}


Qt::Orientations expandingDirections() const
{
return 0;
}


bool hasHeightForWidth() const
{
return false;
}

int heightForWidth(int width) const
{
int height = doLayout(QRect(0, 0, width, 0), true, true);
return height;
}

bool hasWidthForHeight() const
{
return true;
}

int widthForHeight(int height) const
{
int width = doLayout(QRect(0, 0, 0, height), true, false);
return width;
}

int count() const
{
return itemList.size();
}

QLayoutItem *itemAt(int index) const
{
return itemList.value(index);
}

QSize minimumSize() const
{
QSize size;
QLayoutItem *item;
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());

size += QSize(2*margin(), 2*margin());
return size;
}

void setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
doLayout(rect);
}

QSize sizeHint() const
{
return minimumSize();
}

QLayoutItem *takeAt(int index)
{
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return 0;
}


private:

int doLayout(const QRect &rect, bool testOnly = false, bool width = false) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
int lineWidth = 0;

QLayoutItem* item;
foreach(item,itemList)
{
QWidget* widget = item->widget();

if (y + item->sizeHint().height() > effectiveRect.bottom() && lineWidth > 0)
{
y = effectiveRect.y();
x += lineWidth + right;
lineWidth = 0;
}

if (!testOnly)
{
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}


y += item->sizeHint().height() + top;

lineHeight = qMax(lineHeight, item->sizeHint().height());
lineWidth = qMax(lineWidth, item->sizeHint().width());
}

if (width)
{
return y + lineHeight - rect.y() + bottom;
}
else
{
return x + lineWidth - rect.x() + right;
}
}

QList<QLayoutItem *> itemList;
Direction dir;
};

#endif // QFluidGridLayout_h__

QDockResizeEventFilter.h

#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__

#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>

#include "QFluidGridLayout.h"

class QDockResizeEventFilter : public QObject
{
public:

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 = static_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->resize(fixedSize);
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
mainWindow->repaint();
//dock->setGeometry(mainWindow->rect().right()-fixedSize.width(),dock->geometry().y(),fixedSize.width(), fixedSize.height());
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
m_dockChild->resize(m_layout->sizeHint().width(), m_dockChild->height());
}

}

return false;

}

private:

QWidget* m_dockChild;
QFluidGridLayout* m_layout;

};

#endif // QDockResizeEventFilter_h__

最佳答案

问题是,上面的代码实际上没有导致 QMainWindowLayout 重新计算自身。该函数隐藏在 QMainWindowLayout 私有(private)类中,但可以通过添加和删除虚拟 QDockWidget 来激发,这会导致布局无效并重新计算停靠小部件位置

QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);

唯一的问题是,如果您深入研究 QT 源代码,您会发现添加停靠栏小部件会导致停靠栏分隔符被释放,这会在用户尝试调整停靠栏大小时导致不直观和断断续续的行为,鼠标意外地“放手”了。

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();
}

这可以通过将光标移回分隔符并模拟鼠标按下来纠正,基本上是在重新定位停靠点后撤消 endSeparatorMove 调用。重要的是发布事件而不是发送事件,以便它在调整大小事件之后发生。这样做的代码如下所示:

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);

其中 2 是占组框边框的魔数(Magic Number)。

将所有这些放在一起,这里是提供所需行为的事件过滤器:

更正事件过滤器

#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__

关于c++ - 创建一个根据内容调整大小的 QDockWidget,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26286646/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com