Qt用户自定义布局的数据看板实现

引言

多数情况下布局都是固定的,用户能做的只是通过按钮隐藏\显示控件。多数情况下上述简单操作可以满足用户需求,但在作为数据看板展示时,预先提供的子模块可能会远远超出显示屏所能展示的数量,这时候需要用户的参数,允许用户挑选出自己关系的模块并自定义布局进行展示。
在这里插入图片描述

以下代码的核心设计思想就是保证看板的可拓展性以及对界面的高复用度(其他界面类能够不进行修改就在看板中使用)

工厂类(PiecesFactory)

看板中有各种各样的子模块(诸如实时天气变化图、出入口人流密度趋势图等等),为维护方便需要通过枚举值区分,通过不同枚举构造出不同的模块,同时各个模块所占的行列比例不同,也需要保存对应模块所占的行数、列数。

1.通过类名构造类

switch (type) {
case PWT_1:
	return new Label1(parent);
case PWT_2:
	return new Label2(parent);
default:
	return new Label3(parent);
}

简单工厂如上(switch case)就可以实现,但为了后期维护方便(改动的地方集中在同一处),采用了通过类名去构造类的方式实现,如下:

class WidgetFactory
{
public:
    template<typename T>
    static void registerClass()
    {
        constructors().insert(T::staticMetaObject.className(), &constructorHelper<T>);
    }

    static QWidget* createObject(const QByteArray& className, QWidget* parent = nullptr)
    {
        Constructor constructor = constructors().value(className);
        if (constructor == nullptr)
            return nullptr;
        return (*constructor)(parent);
    }

private:
    typedef QWidget* (*Constructor)(QWidget* parent);

    template<typename T>
    static QWidget* constructorHelper(QWidget* parent)
    {
        return new T(parent);
    }

    static QHash<QByteArray, Constructor>& constructors()
    {
        static QHash<QByteArray, Constructor> instance;
        return instance;
    }
};

使用createObject(通过类名构造类)前需要先进行注册registerClass,通过该模板函数将该类型的构造函数保存的容器(static QHash<QByteArray, Constructor> instance)中,如下:

WidgetFactory::registerClass<Label1>();

2.PiecesFactory内容

class PiecesFactory : public QObject
{
    Q_OBJECT

public:
    enum PiecesWidgetType{
        PWT_START = 0,
        PWT_1 = PWT_START,
        PWT_2,
        PWT_3,
        PWT_4,
        PWT_5,
        PWT_END,
    };

    struct PiecesInfo{
        QString className;
        QString displayName;
        int rowCount;// 所占行数
        int columnCount;// 所占列数

        PiecesInfo(){
            rowCount = 0;
            columnCount = 0;
        }
    };

    static PiecesFactory* instance();
    static QMap<int,QPoint> layoutInfoByStr(QString str);
    static QString strByLayoutInfo(QMap<int,QPoint> infoMap);

    QWidget* piecesWidget(int type, QWidget* parent = nullptr);
    PiecesInfo piecesInfo(int type);

    QString className(int type);
    QString displayName(int type);
    int columnSpan(int type);
    int rowSpan(int type);

private:
    explicit PiecesFactory();
    static PiecesFactory* m_obj;
    void initMap();

private:
    QMap<int, PiecesInfo> m_infoMap;
};

class Label1 : public QLabel
{
    Q_OBJECT

public:
    explicit Label1(QWidget *parent = nullptr);
};

class Label2 : public QLabel
{
    Q_OBJECT

public:
    explicit Label2(QWidget *parent = nullptr);
    ~Label2();
};

class Label3 : public QLabel
{
    Q_OBJECT

public:
    explicit Label3(QWidget *parent = nullptr);
};

Label1 、Label2、Label3背景颜色不同只是为了展示,也就是替代不同的模块,可以忽略。

该工厂为单例,方便数据读取减少内存开销。数据保存在容器(QMap<int, PiecesInfo> m_infoMap)中,PiecesInfo有类名(用于构造类)、所占行数、所占列数以及模块显示的名称。函数(QWidget* piecesWidget(int type, QWidget* parent = nullptr))通过枚举返回对应的模块类。

PiecesFactory* PiecesFactory::m_obj = nullptr;
PiecesFactory::PiecesFactory()
    : QObject(nullptr)
{
    initMap();
}

PiecesFactory* PiecesFactory::instance()
{
    if(m_obj == NULL){
        m_obj = new PiecesFactory;
    }
    return m_obj;
}

QMap<int, QPoint> PiecesFactory::layoutInfoByStr(QString str)
{
    QMap<int, QPoint> tmpMap;
    QStringList infoList = str.split(";");
    foreach(auto itemStr, infoList){
        QStringList itemList = itemStr.split(",");
        if(itemList.count() != 3)
            continue;
        tmpMap[itemList.at(0).toInt()] = QPoint(itemList.at(1).toInt(), itemList.at(2).toInt());
    }

    return tmpMap;
}

QString PiecesFactory::strByLayoutInfo(QMap<int, QPoint> infoMap)
{
    QString str;
    for(auto iter = infoMap.begin(); iter != infoMap.end(); iter++){
        str += (QString::number(iter.key()) + "," + QString::number(iter.value().x()) + "," + QString::number(iter.value().y()) +";");
    }

    return str;
}

QWidget* PiecesFactory::piecesWidget(int type, QWidget *parent)
{
    return WidgetFactory::createObject(className(type).toLocal8Bit(), parent);
}

PiecesFactory::PiecesInfo PiecesFactory::piecesInfo(int type)
{
    return m_infoMap.value(type, PiecesInfo());
}

QString PiecesFactory::className(int type)
{
    return piecesInfo(type).className;
}

QString PiecesFactory::displayName(int type)
{
    return piecesInfo(type).displayName;
}

int PiecesFactory::columnSpan(int type)
{
    return piecesInfo(type).rowCount;
}

int PiecesFactory::rowSpan(int type)
{
    return piecesInfo(type).columnCount;
}

void PiecesFactory::initMap()
{
    if(!m_infoMap.isEmpty())
        return;

    m_infoMap[PWT_1].className = "Label1";
    m_infoMap[PWT_1].displayName = "AAAAAAAA";
    m_infoMap[PWT_1].rowCount = 1;
    m_infoMap[PWT_1].columnCount = 1;
    WidgetFactory::registerClass<Label1>();

    m_infoMap[PWT_2].className = "Label2";
    m_infoMap[PWT_2].displayName = "BBBBBBBB";
    m_infoMap[PWT_2].rowCount = 1;
    m_infoMap[PWT_2].columnCount = 3;
    WidgetFactory::registerClass<Label2>();

    m_infoMap[PWT_3].className = "Label3";
    m_infoMap[PWT_3].displayName = "CCCCCCCC";
    m_infoMap[PWT_3].rowCount = 2;
    m_infoMap[PWT_3].columnCount = 2;
    WidgetFactory::registerClass<Label3>();

    m_infoMap[PWT_4].className = "Label3";
    m_infoMap[PWT_4].displayName = "DDDDDDDD";
    m_infoMap[PWT_4].rowCount = 3;
    m_infoMap[PWT_4].columnCount = 2;
    WidgetFactory::registerClass<Label3>();

    m_infoMap[PWT_5].className = "Label3";
    m_infoMap[PWT_5].displayName = "EEEEEEEE";
    m_infoMap[PWT_5].rowCount = 1;
    m_infoMap[PWT_5].columnCount = 1;
    WidgetFactory::registerClass<Label3>();
}

Label1::Label1(QWidget *parent)
    : QLabel(parent)
{
    setText("AAAAA");

    QPalette palette;
    palette.setColor(QPalette::Background, QColor(Qt::red));
    setAutoFillBackground(true);
    setPalette(palette);
}

Label2::Label2(QWidget *parent)
    : QLabel(parent)
{
    setText("BBBBB");

    QPalette palette;
    palette.setColor(QPalette::Background, QColor(Qt::blue));
    setAutoFillBackground(true);
    setPalette(palette);
}

Label2::~Label2()
{

}

Label3::Label3(QWidget *parent)
    : QLabel(parent)
{
    setText("CCCCC");

    QPalette palette;
    palette.setColor(QPalette::Background, QColor(Qt::yellow));
    setAutoFillBackground(true);
    setPalette(palette);
}

如上为PiecesFactory的实现,为看板引入新模块只需要在PiecesFactory::initMap()内补充对应模块即可。

控件选择列表(PiecesList)

class PiecesList : public QListWidget
{
    Q_OBJECT

public:
    explicit PiecesList(QList<int> typeList, QWidget *parent = nullptr);
    bool addPiece(int type);
    bool removePiece(int type);

protected:
    void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
    void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE;
    void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE;
    void startDrag(Qt::DropActions supportedActions) Q_DECL_OVERRIDE;

private:
    QList<int> m_typeList;
};

class PiecesThumbnailWidget : public QWidget
{
    Q_OBJECT

public:
    explicit PiecesThumbnailWidget(int piecesType, QWidget *parent = nullptr);
    static QPixmap thumbnail(int piecesType);

protected:
    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;

private:
    int m_piecesType;
    int m_pieceWidth;
    int m_pirecHeight;
};

PiecesList 为左侧列表框,展示支持在看板中显示且未选中的模块。基类为QListWidget,实现拖拽事件以及拖放事件。

PiecesThumbnailWidget未缩略图,拖拽模块时展示,增加界面友好度。通过paintEvent完成绘制。

PiecesList::PiecesList(QList<int> typeList, QWidget *parent)
    : QListWidget(parent)
    , m_typeList(typeList)
{
    setDragEnabled(true);
    setViewMode(QListView::IconMode);
    setSpacing(10);
    setAcceptDrops(true);
    setDropIndicatorShown(true);

    foreach(auto val, typeList){
        addPiece(val);
    }
}

void PiecesList::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat("text/piece-type")){
        event->accept();
    }
    else{
        event->ignore();
    }
}

void PiecesList::dragMoveEvent(QDragMoveEvent *event)
{
    if (event->mimeData()->hasFormat("text/piece-type")) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else {
        event->ignore();
    }
}

void PiecesList::dropEvent(QDropEvent *event)
{
    bool insertable = false;
    if (event->mimeData()->hasFormat("text/piece-type")) {
        QByteArray pieceData = event->mimeData()->data("text/piece-type");
        QDataStream dataStream(&pieceData, QIODevice::ReadOnly);
        int type;
        dataStream >> type;

        insertable = addPiece(type);
    }

    if(insertable){
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else{
       event->ignore();
    }
}

bool PiecesList::addPiece(int type)
{
    if(!m_typeList.contains(type))
        return false;

    QListWidgetItem *pieceItem = new QListWidgetItem(this);
    //pieceItem->setIcon(QIcon(pixmap));
    pieceItem->setSizeHint(QSize(80,40));
    pieceItem->setData(Qt::UserRole, type);
    pieceItem->setText(PiecesFactory::instance()->displayName(type));
    pieceItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);

    return true;
}

bool PiecesList::removePiece(int type)
{
    if(!m_typeList.contains(type))
        return false;

    for (int i=count()-1; i>=0; i--) {
        auto tmpItem = item(i);
        if(tmpItem->data(Qt::UserRole) == type)
            delete takeItem(row(tmpItem));
    }

    return true;
}

void PiecesList::startDrag(Qt::DropActions /*supportedActions*/)
{
    QListWidgetItem *tmpItem = currentItem();

    QByteArray itemData;
    QDataStream dataStream(&itemData, QIODevice::WriteOnly);
    int type = tmpItem->data(Qt::UserRole).toInt();

    dataStream << type;

    QMimeData *mimeData = new QMimeData;
    mimeData->setData("text/piece-type", itemData);

    QDrag *drag = new QDrag(this);
    drag->setMimeData(mimeData);

    //QPixmap pixmap = QPixmap::grabWidget(viewport(), visualItemRect(currentItem()));
    QPixmap pixmap = PiecesThumbnailWidget::thumbnail(type);
    drag->setHotSpot(QPoint(pixmap.width()/2, pixmap.height()/2));
    drag->setPixmap(pixmap);

    if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
        delete takeItem(row(tmpItem));
}

///
PiecesThumbnailWidget::PiecesThumbnailWidget(int piecesType, QWidget *parent)
    : QWidget(parent)
    , m_piecesType(piecesType)
    , m_pieceWidth(30)
    , m_pirecHeight(30)
{
    setAttribute(Qt::WA_TranslucentBackground);
    setFixedSize(m_pieceWidth * PiecesFactory::instance()->columnSpan(m_piecesType), m_pirecHeight * PiecesFactory::instance()->rowSpan(m_piecesType));
}

QPixmap PiecesThumbnailWidget::thumbnail(int piecesType)
{
    PiecesThumbnailWidget tmpWidget(piecesType);
    return QPixmap::grabWidget(&tmpWidget, tmpWidget.rect());
}

void PiecesThumbnailWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    // 绘制背景边框
    painter.save();
    painter.setPen(QPen(QBrush(QColor(74, 75, 77)), 1, Qt::DashLine));
    painter.drawRoundedRect(event->rect().marginsAdded(QMargins(-2,-2,-2,-2)), 4, 4);
    painter.restore();

    // 绘制九宫格
    painter.save();
    QColor tmpColor(255, 204, 0);
    tmpColor.setAlphaF(0.8);
    int rowCount = PiecesFactory::instance()->columnSpan(m_piecesType);
    int columnCount = PiecesFactory::instance()->rowSpan(m_piecesType);
    for (int i=0; i<rowCount; i++) {
        for (int j=0; j<columnCount; j++) {
            painter.setBrush(tmpColor);
            painter.setPen(Qt::NoPen);
            painter.drawRoundedRect(QRect(m_pieceWidth * i, m_pirecHeight * j, m_pieceWidth, m_pirecHeight).marginsAdded(QMargins(-4,-4,-4,-4)), 4, 4);
        }
    }
    painter.restore();
}

布局预览(PiecesPreviewWidget)

class PiecesPreviewWidget : public QWidget
{
    Q_OBJECT

public:
    explicit PiecesPreviewWidget(int rowCount, int columnCount, QWidget *parent = nullptr);

    QMap<int,QPoint> layoutInfo();
    bool setLayoutInfo(QMap<int,QPoint> infoMap);// 初始化完成才能使用, QPoint为左上角行列号
    void clear();

signals:
    void sig_removeItem(int type);// 手动删除控件而不是通过拖拽
    void sig_initFinished();

protected:
    void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
    void dragLeaveEvent(QDragLeaveEvent *event) Q_DECL_OVERRIDE;
    void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE;
    void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE;
    void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
    void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;// 调整控件固定长宽, 避免长宽不能被行列数整除的情况
    bool eventFilter(QObject* obj, QEvent* ev) Q_DECL_OVERRIDE;

private:
    QList<QRect> findPieces(const QRect &pieceRect);
    const QRect dragSquare(const QPoint &position, int rowCount, int columCount);// 拖拽控件范围
    const QRect usedSquare(int type);
    const QRect btnSquare(int type);
    bool isUsedRect(QRect rect);

    int parseType(QByteArray date);
    int grabType(const QPoint &position);// 获取当前位置下的控件类型

    QRect rectByPos(QPoint pos);// 通过行列号获得矩形
    QPoint posByRect(QRect tmpRect);

private:
    QList<QRect> m_pieceRects;
    QList<QRect> m_highlightedRects;
    QMap<int, QList<QRect>> m_usedRects;

    int m_columnCount;
    int m_rowCount;
    int m_pieceWidth;
    int m_pieceHeight;
    int m_btnSize;
    QRect m_btnHoverRect;
};
PiecesPreviewWidget::PiecesPreviewWidget(int rowCount, int columnCount, QWidget *parent)
    : QWidget(parent)
    , m_columnCount(columnCount)
    , m_rowCount(rowCount)
    , m_pieceWidth(0)
    , m_pieceHeight(0)
    , m_btnSize(20)
{
    setAcceptDrops(true);
    setAttribute(Qt::WA_Hover, true);
    installEventFilter(this);
}

QMap<int, QPoint> PiecesPreviewWidget::layoutInfo()
{
    QMap<int, QPoint> typeMap;
    for (auto iter = m_usedRects.begin(); iter != m_usedRects.end(); iter++) {
        for(int i=0; i<iter.value().count(); i++){
            typeMap[iter.key()] = posByRect(usedSquare(iter.key()));
        }
    }
    return typeMap;
}

bool PiecesPreviewWidget::setLayoutInfo(QMap<int, QPoint> infoMap)
{
    for (auto iter=infoMap.begin(); iter != infoMap.end(); iter++) {
        int type = iter.key();
        if(m_usedRects.contains(type))
            continue;

        bool isFaild = false;
        for(int i=0; i<PiecesFactory::instance()->columnSpan(type); i++){
            for(int j=0; j<PiecesFactory::instance()->rowSpan(type); j++){
                QRect tmpRect = rectByPos(iter.value() + QPoint(j,i));
                if(tmpRect.width() == 0 || isUsedRect(tmpRect)){
                    isFaild = true;
                }
                m_usedRects[type] << tmpRect;
            }
        }

        if(isFaild)
            m_usedRects.remove(type);
    }

    update();
    return true;
}

void PiecesPreviewWidget::clear()
{
    m_usedRects.clear();
    m_highlightedRects.clear();
    update();
}

void PiecesPreviewWidget::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat("text/piece-type")){
        event->accept();
    }
    else{
        event->ignore();
    }
}

void PiecesPreviewWidget::dragLeaveEvent(QDragLeaveEvent *)
{
    m_highlightedRects.clear();
    update();
}

void PiecesPreviewWidget::dragMoveEvent(QDragMoveEvent *event)
{
    m_highlightedRects.clear();

    if (event->mimeData()->hasFormat("text/piece-type")){
        int type = parseType(event->mimeData()->data("text/piece-type"));
        m_highlightedRects = findPieces(dragSquare(event->pos(), PiecesFactory::instance()->columnSpan(type), PiecesFactory::instance()->rowSpan(type)));
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else {
        event->ignore();
    }

    update();
}

void PiecesPreviewWidget::dropEvent(QDropEvent *event)
{
    bool insertable = false;
    if (event->mimeData()->hasFormat("text/piece-type")){
        int type = parseType(event->mimeData()->data("text/piece-type"));
        int rowCount = PiecesFactory::instance()->columnSpan(type);
        int columnCount = PiecesFactory::instance()->rowSpan(type);
        auto tmpPieces = findPieces(dragSquare(event->pos(), rowCount, columnCount));

        bool isUsed = false;
        foreach(auto tmpRect, tmpPieces){
            if(isUsedRect(tmpRect)){
                isUsed = true;
                break;
            }
        }

        if(!isUsed && tmpPieces.count() == rowCount * columnCount && !m_usedRects.contains(type)){
            m_usedRects[type] = tmpPieces;
            insertable = true;
        }
    }

    if(insertable){
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else{
       event->ignore();
    }

    m_highlightedRects.clear();
    update();
}

void PiecesPreviewWidget::mousePressEvent(QMouseEvent *event)
{
    int type = grabType(event->pos());

    if (type == -1)
        return;

    auto btnRect = btnSquare(type);
    auto tmpList = m_usedRects[type];
    m_usedRects.remove(type);
    update();

    if(btnRect.contains(event->pos())){
        emit sig_removeItem(type);// 不通过拖拽删除
    }
    else{
        QByteArray itemData;
        QDataStream dataStream(&itemData, QIODevice::WriteOnly);
        dataStream << type;

        QMimeData *mimeData = new QMimeData;
        mimeData->setData("text/piece-type", itemData);

        QDrag *drag = new QDrag(this);
        drag->setMimeData(mimeData);

        QPixmap pixmap = PiecesThumbnailWidget::thumbnail(type);
        drag->setHotSpot(QPoint(pixmap.width()/2, pixmap.height()/2));
        drag->setPixmap(pixmap);

        if (!(drag->exec(Qt::MoveAction) == Qt::MoveAction)) {
            m_usedRects[type] = tmpList;
            update();
        }
    }
}

void PiecesPreviewWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(event->rect(), Qt::white);

    // 绘制九宫格
    painter.save();
    foreach(auto tmpRect, m_pieceRects){
        QColor color(74, 75, 77);
        if(isUsedRect(tmpRect) && m_highlightedRects.contains(tmpRect)){
            color = QColor(255, 60, 60);
        }
        else if(isUsedRect(tmpRect)){
            color = QColor(255, 204, 0);
        }
        else if(m_highlightedRects.contains(tmpRect)){
            color = QColor(0, 195, 133);
        }

        painter.setBrush(color);
        painter.setPen(Qt::NoPen);
        painter.drawRoundedRect(tmpRect.marginsAdded(QMargins(-10,-10,-10,-10)), 10, 10);
    }
    painter.restore();

    for (auto iter = m_usedRects.begin(); iter != m_usedRects.end(); iter++) {
        QRect tmpRect = usedSquare(iter.key());

        // 绘制边框
        painter.save();
        painter.setPen(QPen(QBrush(QColor(74, 75, 77)), 2, Qt::DashLine));
        painter.drawRoundedRect(tmpRect.marginsAdded(QMargins(-5,-5,-5,-5)), 10, 10);
        painter.restore();

        // 绘制文字
        painter.save();
        painter.setPen(Qt::white);
        painter.drawText(tmpRect.marginsAdded(QMargins(-20,-20,-20,-20)), Qt::AlignLeft | Qt::AlignTop, PiecesFactory::instance()->displayName(iter.key()));
        painter.restore();

        // 绘制按钮
        painter.save();
        QRect btnRect = btnSquare(iter.key());
        QColor btnColor = btnRect.contains(this->mapFromGlobal(QCursor::pos())) ? QColor(255, 60, 60) : QColor(255, 100, 100);
        painter.setPen(Qt::NoPen);
        painter.setBrush(btnColor);
        painter.drawEllipse(btnRect);
        painter.setPen(Qt::white);
        painter.drawText(btnRect, Qt::AlignCenter, "X");
        painter.restore();
    }

}

void PiecesPreviewWidget::resizeEvent(QResizeEvent *)
{
    m_pieceWidth = width() / m_columnCount;
    m_pieceHeight = height() / m_rowCount;
    setFixedSize(m_pieceWidth * m_columnCount, m_pieceHeight * m_rowCount);

    for (int i=0; i<m_rowCount; i++) {
        for (int j=0; j<m_columnCount; j++) {
            m_pieceRects.append(QRect(j*m_pieceWidth, i*m_pieceHeight, m_pieceWidth, m_pieceHeight));
        }
    }

    emit sig_initFinished();
}

bool PiecesPreviewWidget::eventFilter(QObject *obj, QEvent *ev)
{
    if (obj == this) {
        if (QEvent::HoverMove == ev->type()) {
            QRect tmpRect;

            for (auto iter = m_usedRects.begin(); iter != m_usedRects.end(); iter++) {
                if(btnSquare(iter.key()).contains(this->mapFromGlobal(QCursor::pos()))){
                    tmpRect = btnSquare(iter.key());
                    break;
                }
            }

            if(tmpRect != m_btnHoverRect){
                if(tmpRect.width() > 0)
                    update(tmpRect);
                if(m_btnHoverRect.width() > 0)
                    update(m_btnHoverRect);
                m_btnHoverRect = tmpRect;
            }
        }
    }

    return QWidget::eventFilter(obj, ev);
}

QList<QRect> PiecesPreviewWidget::findPieces(const QRect &pieceRect)
{
    QList<QRect> rectList;
    foreach(auto tmpRect, m_pieceRects){
        if(pieceRect.contains(tmpRect))
            rectList.append(tmpRect);
    }

    return rectList;
}

const QRect PiecesPreviewWidget::dragSquare(const QPoint &position, int rowCount, int columCount)
{
    int tmpWidth = rowCount*m_pieceWidth;
    int tmpHeight = columCount*m_pieceHeight;
    QRect tmpRect(position.x() - tmpWidth/2, position.y() - tmpHeight/2, tmpWidth, tmpHeight);

    return tmpRect.marginsAdded(QMargins(m_pieceWidth/2, m_pieceHeight/2, m_pieceWidth/2, m_pieceHeight/2));
}

const QRect PiecesPreviewWidget::usedSquare(int type)
{
    if(!m_usedRects.contains(type) && m_usedRects[type].isEmpty())
        return QRect();

    QRect tmpRect = m_usedRects[type].at(0);
    for (int i=1; i<m_usedRects[type].count(); i++) {
        tmpRect = tmpRect.united(m_usedRects[type].at(i));
    }

    return tmpRect;
}

const QRect PiecesPreviewWidget::btnSquare(int type)
{
    QRect tmpRect = usedSquare(type);
    if(tmpRect.width() == 0){
        return QRect();
    }
    else{
        return QRect(tmpRect.right() - m_btnSize, tmpRect.top(), m_btnSize, m_btnSize);
    }
}

bool PiecesPreviewWidget::isUsedRect(QRect rect)
{
    for (auto iter = m_usedRects.begin(); iter != m_usedRects.end(); iter++) {
        if(iter.value().contains(rect))
            return true;
    }
    return false;
}

int PiecesPreviewWidget::parseType(QByteArray date)
{
    QDataStream dataStream(&date, QIODevice::ReadOnly);
    int type;
    dataStream >> type;
    return type;
}

int PiecesPreviewWidget::grabType(const QPoint &position)
{
    for (auto iter = m_usedRects.begin(); iter != m_usedRects.end(); iter++) {
        if(usedSquare(iter.key()).contains(position))
            return iter.key();
    }
    return -1;
}

QRect PiecesPreviewWidget::rectByPos(QPoint pos)
{
    int index = pos.x() * m_columnCount + pos.y();
    if(index >= 0 && index < m_pieceRects.count()){
        return m_pieceRects.at(pos.x()*m_columnCount + pos.y());
    }
    else{
        return QRect();
    }
}

QPoint PiecesPreviewWidget::posByRect(QRect tmpRect)
{
    if(QRect(0, 0, m_columnCount*m_pieceWidth, m_rowCount*m_pieceHeight).contains(tmpRect.topLeft())){
        return QPoint(tmpRect.topLeft().y() / m_pieceHeight, tmpRect.topLeft().x() / m_pieceWidth);
    }
    else{
        return QPoint(-1,-1);
    }
}

PiecesPreviewWidget通过paintEvent绘制布局方块,灰色为空置方块,黄色为已占用方块,绿色为可拖入方块,红色为不可拖入方块。再就是实现拖拽以及拖入事件。

该类的难点就在于拖入模块时需要确认周边方格是否闲置,判断该模块是否能够插入当前布局中。

const QRect PiecesPreviewWidget::dragSquare(const QPoint &position, int rowCount, int columCount)
{
    int tmpWidth = rowCount*m_pieceWidth;
    int tmpHeight = columCount*m_pieceHeight;
    QRect tmpRect(position.x() - tmpWidth/2, position.y() - tmpHeight/2, tmpWidth, tmpHeight);

    return tmpRect.marginsAdded(QMargins(m_pieceWidth/2, m_pieceHeight/2, m_pieceWidth/2, m_pieceHeight/2));
}

通过dragSquare以当前点为中心按照所占行列数以及预览方块的长宽,生成模块实际占用的范围,再将范围增加半个方块大小,保证无论鼠标如何移动一定能够覆盖方块。

QList<QRect> PiecesPreviewWidget::findPieces(const QRect &pieceRect)
{
    QList<QRect> rectList;
    foreach(auto tmpRect, m_pieceRects){
        if(pieceRect.contains(tmpRect))
            rectList.append(tmpRect);
    }

    return rectList;
}

获得模块活动范围后,再通过findPieces找着到rect下覆盖的方块,判断这些方块是否为已使用方块(m_usedRects),以此判断是否能够插入当前布局

实际布局(BoardLayoutWidget)

class BoardLayoutWidget : public QWidget
{
    Q_OBJECT

public:
    explicit BoardLayoutWidget(QWidget *parent = nullptr);
    void initWidgets(QMap<int,QPoint> infoMap);
    void initWidgets(QString str);

private:
    QGridLayout* m_layout;
};

///
BoardLayoutWidget::BoardLayoutWidget(QWidget *parent)
    : QWidget(parent)
{
    m_layout = new QGridLayout(this);
}

void BoardLayoutWidget::initWidgets(QMap<int, QPoint> infoMap)
{
    QLayoutItem *child;
     while ((child = m_layout->takeAt(0)) != 0) {
         delete child->widget();
         delete child;
     }

    for (auto iter=infoMap.begin(); iter != infoMap.end(); iter++) {
        auto type = iter.key();
        m_layout->addWidget(PiecesFactory::instance()->piecesWidget(type),
                            iter.value().x(),
                            iter.value().y(),
                            PiecesFactory::instance()->rowSpan(type),
                            PiecesFactory::instance()->columnSpan(type));
    }
}

void BoardLayoutWidget::initWidgets(QString str)
{
    initWidgets(PiecesFactory::layoutInfoByStr(str));
}

通过布局信息依次插入栅格布局汇总,布局信息保存为infoMap,key值为模块的枚举,value为左上角的行列号。工厂中也提供将map转换为str的函数,str的格式为:“枚举,左上角行号,左上角列号;”。

示例

上述动画中的实际代码

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_background = new QWidget(this);
    setCentralWidget(m_background);

    QList<int> typeList;
    typeList << PiecesFactory::PWT_1 << PiecesFactory::PWT_2 << PiecesFactory::PWT_3;
    m_piecesList = new PiecesList(typeList, this);
    m_piecesList->setFixedWidth(220);
    m_piecesList->setFixedHeight(150);

    QList<int> typeList2;
    typeList2 << PiecesFactory::PWT_4 << PiecesFactory::PWT_5;
    m_piecesList2 = new PiecesList(typeList2, this);
    m_piecesList2->setFixedWidth(220);
    m_piecesList2->setFixedHeight(150);

    m_previewWidget = new PiecesPreviewWidget(4, 4, this);
    m_previewWidget->setFixedSize(540,540);
    connect(m_previewWidget, &PiecesPreviewWidget::sig_removeItem, m_piecesList, &PiecesList::addPiece);
    connect(m_previewWidget, &PiecesPreviewWidget::sig_removeItem, m_piecesList2, &PiecesList::addPiece);

    m_boardWidget = new BoardLayoutWidget(this);
    m_boardWidget->setFixedSize(540,540);

    QString tmpStr("1,0,1;2,0,2;4,0,0;");
    connect(m_previewWidget, &PiecesPreviewWidget::sig_initFinished, this, [=]{
        m_previewWidget->setLayoutInfo(PiecesFactory::layoutInfoByStr(tmpStr));
    });

    auto map = PiecesFactory::layoutInfoByStr(tmpStr);
    for (auto iter=map.begin(); iter != map.end(); iter++) {
        m_piecesList->removePiece(iter.key());
        m_piecesList2->removePiece(iter.key());
    }

    auto lineEdit = new QPlainTextEdit(this);
    lineEdit->setFixedWidth(220);
    lineEdit->setFixedHeight(100);

    auto setBtn = new QPushButton("set", this);
    connect(setBtn, &QPushButton::clicked, this, [=]{
        m_previewWidget->setLayoutInfo(PiecesFactory::layoutInfoByStr(lineEdit->toPlainText()));
    });

    auto getBtn = new QPushButton("get", this);
    connect(getBtn, &QPushButton::clicked, this, [=]{
        lineEdit->setPlainText(PiecesFactory::strByLayoutInfo(m_previewWidget->layoutInfo()));
    });

    auto refreshBtn = new QPushButton("refresh", this);
    connect(refreshBtn, &QPushButton::clicked, this, [=]{
        m_boardWidget->initWidgets(m_previewWidget->layoutInfo());
    });

    auto label1 = new QLabel("list1", this);
    label1->adjustSize();
    auto label2 = new QLabel("list2", this);
    label2->adjustSize();
    auto label3 = new QLabel("layoutInfo", this);
    label3->adjustSize();

    auto btnLayout = new QHBoxLayout;
    btnLayout->addWidget(setBtn);
    btnLayout->addWidget(getBtn);

    auto listLayout = new QVBoxLayout;
    listLayout->addWidget(label1);
    listLayout->addWidget(m_piecesList);
    listLayout->addWidget(label2);
    listLayout->addWidget(m_piecesList2);
    listLayout->addWidget(label3);
    listLayout->addWidget(lineEdit);
    listLayout->addLayout(btnLayout);
    listLayout->addWidget(refreshBtn);

    auto mainLayout = new QHBoxLayout(m_background);
    mainLayout->addLayout(listLayout);
    mainLayout->addWidget(m_previewWidget);
    mainLayout->addWidget(m_boardWidget);
}

有两个模块选择列表m_piecesList 和m_piecesList 2,refreshBtn 用于调整预览后刷新真实控件布局。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arui丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值