2014-10-09 186 views
4

我有一個應用程序需要在運行時基於用戶輸入以編程方式將固定大小的子小部件添加到Dock小部件。我希望將這些小部件從上到下添加到Qt :: RightDockArea中的一個擴展塢上,直到它耗盡空間,然後創建一個新列並重復(基本上與流佈局示例here相反,我稱之爲fluidGridLayout)創建一個調整其內容大小的QDockWidget

我可以使用事件過濾器來正確調整自身的大小,但調整大小的碼頭的幾何體不會改變,並且某些小部件會在主窗口之外繪製。有趣的是,調整主窗口的大小,或者浮動和解開碼頭會導致它「彈出」回正確的位置(但我還沒有能夠找到一種方法來複制這個程序)但是我不能使用

任何內置的QT佈局,因爲在我的真實程序中使用小部件時,它們最終也會脫離屏幕。

有沒有什麼方法可以讓碼頭在調整大小後將其左上角的座標更新到正確的位置?

我認爲這可能是普遍感興趣的,因爲在QT中爲碼頭小部件獲取直觀的佈局管理行爲可能是人們所知道的最困難的事情。

VISUAL EXMAPLE:

用於複製此代碼的代碼示例如下。

  1. 使用按鈕添加4只小部件到程序

Step 1

  • 調整綠色底部塢直到僅示出兩個窗口小部件。請注意,剩餘的3個部件越來越畫主窗口之外,但碼頭是正確的大小,用事實證明,你不能看到關閉按鈕了
  • Step 2

  • 解開藍色碼頭小部件。注意它適合它的大小。
  • Step 3

  • 重新停靠碼頭藍色到右錨接區域。注意它現在看起來行爲正常。
  • Step 4

  • 現在調整綠色塢到它的最小尺寸。注意碼頭現在在GUI的中間。 WTf,這怎麼可能?
  • Step 5

    THE CODE

    下面我請代碼從截圖複製GUI。

    main.cpp中:

    #include "mainwindow.h" 
    #include <QApplication> 
    
    int main(int argc, char *argv[]) 
    { 
        QApplication a(argc, argv); 
        MainWindow w; 
        w.show(); 
    
        return a.exec(); 
    } 
    

    mainwindow.h

    #ifndef MAINWINDOW_H 
    #define MAINWINDOW_H 
    
    #include <QMainWindow> 
    
    class MainWindow : public QMainWindow 
    { 
        Q_OBJECT 
    
    public: 
        MainWindow(QWidget *parent = 0); 
        ~MainWindow(); 
    }; 
    
    #endif // MAINWINDOW_H 
    

    mainwindow.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__ 
    

    回答

    2

    的問題是,沒有在上面的代碼實際上導致QMainWindowLayout重新計算本身。這個功能是埋在QMainWindowLayout私有類中,但可以通過添加和去除僞QDockWidget,這將導致佈局無效和recalcualte碼頭窗口小部件的位置上刺激

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

    與此唯一的問題是,如果你深入研究QT源代碼,您會發現添加一個dock小部件會導致dock分隔符被釋放,這會導致用戶試圖調整dock的大小時不直觀和不連貫的行爲,並且鼠標意外地'放手'。

    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 callafter碼頭已被定位來校正。發佈該事件非常重要,而不是發送它,以便在發生resize事件之後發生。這樣做的代碼如下所示:

    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是佔組框邊框的幻數。

    把那都在一起,這裏是事件過濾器比得到期望的行爲:

    更正事件過濾

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