欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Qt实战--主窗口布局

程序员文章站 2022-05-30 15:22:02
...

QMainWindow

MainWindow类我们一般选择直接继承自QMainWindow,因为QMainWindow已经向我们提供了一个常用的应用程序主窗口布局,包括QMenuBar菜单栏、QToolBar工具栏、QStatusBar状态栏、QDockWidget可停靠控件、以及需要自己定制的CentralWidget*控件。这大大节省了我们布局主窗口的时间。

Qt实战--主窗口布局

构造函数

我们一般在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组成,实现效果如下

Qt实战--主窗口布局
Qt实战--主窗口布局

HVideoWidget的具体接口和实现下节见