1. 程式人生 > >Qt實戰--主窗口布局

Qt實戰--主窗口布局

QMainWindow

MainWindow類我們一般選擇直接繼承自QMainWindow,因為QMainWindow已經向我們提供了一個常用的應用程式主窗口布局,包括QMenuBar選單欄、QToolBar工具欄、QStatusBar狀態列、QDockWidget可停靠控制元件、以及需要自己定製的CentralWidget中央控制元件。這大大節省了我們佈局主視窗的時間。

QMainWindow

建構函式

我們一般在QWidget派生類的建構函式中構造介面,並建立固定的訊號與槽連線,形式如下:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // member variable initialization here
    // ...
initUI(); initConnect(); }

initUI

在主視窗initUI中我們需要設定視窗大小、設定視窗圖示、初始化選單欄、工具欄、狀態列、還有不可缺少的一步是設定中央控制元件

void MainWindow::initUI(){
    setWindowIcon(QIcon(":/image/icon.png"));
    setBaseSize(1200, 800);

    initMenu();

    center = new CentralWidget;
    setCentralWidget(center);

    statusBar()
->
showMessage(tr("Ready")); }

initMenu

void MainWindow::initMenu(){
    // Media
    QMenu *mediaMenu = menuBar()->addMenu(tr("&Media"));
    QToolBar *mediaToolbar = addToolBar(tr("&Media"));
    toolbars.push_back(mediaToolbar);

    QAction* actOpenFile = new QAction(QIcon(":/image/file.png"
), tr(" Open File")); actOpenFile->setShortcut(QKeySequence("Ctrl+F")); connect(actOpenFile, &QAction::triggered, this, [=](){ onOpenMedia(MEDIA_TYPE_FILE); }); mediaMenu->addAction(actOpenFile); mediaToolbar->addAction(actOpenFile); QAction* actOpenNetwork = new QAction(QIcon(":/image/network.png"), tr(" Open Network")); actOpenNetwork->setShortcut(QKeySequence("Ctrl+N")); connect(actOpenNetwork, &QAction::triggered, this, [=](){ onOpenMedia(MEDIA_TYPE_NETWORK); }); mediaMenu->addAction(actOpenNetwork); mediaToolbar->addAction(actOpenNetwork); QAction* actOpenCapture = new QAction(QIcon(":/image/capture.png"), tr(" Open Capture")); actOpenCapture->setShortcut(QKeySequence("Ctrl+C")); connect(actOpenCapture, &QAction::triggered, this, [=](){ onOpenMedia(MEDIA_TYPE_CAPTURE); }); mediaMenu->addAction(actOpenCapture); mediaToolbar->addAction(actOpenCapture); // ... // Help QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(tr(" &About"), this, SLOT(about())); }

initMenu中通過menuBar()->addMenu為選單欄新增選單,addToolBar新增對應的工具欄,然後通過new QAction來建立一個動作,QMenu::addActionQToolbar::addAction新增QAction

CentralWidget

我們設計的中央區域顯示,初步是顯示一個媒體播放列表HMediaList(以H開頭的都是我們自定義類,以和Qt中Q開頭的類區別開)和一個多畫面網格HMultiView,使用QSplitter作為可伸縮分隔板。

void CentralWidget::initUI(){
    ml = new HMediaList;
    mv = new HMultiView;

    QSplitter *split = new QSplitter(Qt::Horizontal);
    split->addWidget(ml);
    split->addWidget(mv);

    ml->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    ml->setMinimumWidth(300);
    mv->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    mv->setMinimumWidth(700);
    split->setStretchFactor(0, MEDIA_LIST_FACTOR);
    split->setStretchFactor(1, MULTI_VIEW_FACTOR);

    QHBoxLayout *hbox = genHBoxLayout();
    hbox->addWidget(split);
    setLayout(hbox);

    ml->setVisible(MEDIA_LIST_VISIBLE);
}

HMultiView

多畫面網格,我們的需求是:

  • 自由切換row*col風格(選單欄選擇風格)
  • 擴充套件(全屏)某個單元格(滑鼠雙擊)
  • 交換兩個單元格位置(滑鼠左鍵拖動)
  • 合併單元格(滑鼠右鍵拖動畫合併區域)
  • 根據id獲取單元格
  • 根據位置獲取單元格
  • 獲取空閒狀態的單元格
  • 儲存當前佈局,以便退出程式後,下次進來恢復佈局
struct HWndInfo{
    int id;
    QRect rc;
    bool visible;
};

struct HSaveLayout{
    HLayout layout;
    QVector<HWndInfo> views;
};

class HMultiView : public QWidget
{
    Q_OBJECT
public:
    enum Action{
        STRETCH,
        EXCHANGE,
        MERGE,
    };
    explicit HMultiView(QWidget *parent = nullptr);

    HVideoWidget* getPlayerByID(int playerid);
    HVideoWidget* getPlayerByPos(QPoint pt);
    HVideoWidget* getIdlePlayer();

signals:

public slots:
    void setLayout(int row, int col);
    void mergeCells(int lt, int rb);
    void exchangeCells(HVideoWidget* player1, HVideoWidget* player2);
    void stretch(QWidget* wdg);
    void saveLayout();
    void restoreLayout();

    void play(HMedia& media);

protected:
    void initUI();
    void initConnect();

    void relayout();
    virtual void resizeEvent(QResizeEvent* e);
    virtual void mousePressEvent(QMouseEvent *e);
    virtual void mouseReleaseEvent(QMouseEvent *e);
    virtual void mouseMoveEvent(QMouseEvent *e);
    virtual void mouseDoubleClickEvent(QMouseEvent *e);

public:
    HLayout layout;
    QVector<QWidget*> views;
    QLabel *labRect;
    QLabel *labDrag;

    HSaveLayout save_layout;

    QPoint ptMousePress;
    Action action;
};

根據需求我們定義出標頭檔案,過載滑鼠按下、移動、釋放、雙擊事件,resize時也需要重新佈局

為了完成合並單元格的需求,我們定義了一個邏輯類HLayout,這個類記錄了每個單元格所佔行和列

#ifndef HLAYOUT_H
#define HLAYOUT_H

#include <map>

class HLayoutCell{
public:
    HLayoutCell(){r1=r2=c1=c2=0;}
    HLayoutCell(int r1, int r2, int c1, int c2){
        this->r1 = r1;
        this->r2 = r2;
        this->c1 = c1;
        this->c2 = c2;
    }

    int getRowspan() {return r2 - r1 + 1;}
    int getColspan() {return c2 - c1 + 1;}
    int getNums() {return getRowspan() * getColspan();}

    bool contain(HLayoutCell cell){
        if (cell.r1 >= r1 && cell.r2 <= r2 &&
                cell.c1 >= c1 && cell.c2 <= c2)
            return true;
        return false;
    }

    int r1,r2,c1,c2;
};

class HLayout
{
public:
    explicit HLayout();

    void init(int row, int col);
    bool getLayoutCell(int id, HLayoutCell& rst);
    HLayoutCell merge(int lt, int rb);

public:
    int row;
    int col;
    int num;
    std::map<int, HLayoutCell> m_mapCells; // id => HLayoutCell
};

#endif // HLAYOUT_H
#include "hlayout.h"
#include "hdef.h"

HLayout::HLayout()
{

}

void HLayout::init(int row, int col){
    this->row = row;
    this->col = col;
    num = row * col;
    m_mapCells.clear();
    for (int r = 1; r <= row; ++r){
        for (int c = 1; c <= col; ++c){
            int id = (r-1) * col + c;
            m_mapCells[id] = HLayoutCell(r,r,c,c);
        }
    }
}

bool HLayout::getLayoutCell(int id, HLayoutCell& rst){
    if (m_mapCells.find(id) != m_mapCells.end()){
        rst = m_mapCells[id];
        return true;
    }
    return false;
}

HLayoutCell HLayout::merge(int lt, int rb){
    HLayoutCell cell_lt,cell_rb;
    if (getLayoutCell(lt, cell_lt) && getLayoutCell(rb, cell_rb)){
        int r1 = MIN(cell_lt.r1, cell_rb.r1);
        int r2 = MAX(cell_lt.r2, cell_rb.r2);
        int c1 = MIN(cell_lt.c1, cell_rb.c1);
        int c2 = MAX(cell_lt.c2, cell_rb.c2);

        HLayoutCell cell(r1, r2, c1, c2);
        std::map<int, HLayoutCell>::iterator iter = m_mapCells.begin();
        while (iter != m_mapCells.end()){
            if (cell.contain(iter->second)){
                iter = m_mapCells.erase(iter);
            }else
                ++iter;
        }
        m_mapCells[lt] = cell;

        return cell;
    }
}

具體實現細節請專研原始碼,我就不細說了。

HVideoWidget

每個單元格都是一個視訊控制元件HVideoWidgetHVideoWidgetHVideoTitlebarHVideoToolbarHVideoWnd組成,實現效果如下

HVideoWidget
HVideoWidget

HVideoWidget的具體介面和實現下節見